App: a second copy opening, or OpenGL that fails, no longer strands Window Graphics

Two copies opened close together made the second take the first's
launch-pending marker for a crash and switch Window Graphics to OpenGL.
Where OpenGL can't open a window (NVIDIA on X11: "gl not compatible with
provided surface"), every later launch then failed, with no way to reach
the setting.

The marker is now locked while its launch runs, so a locked marker is a
launch still opening, not a crash. And a window that fails to open on
OpenGL puts Window Graphics back to Automatic.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
prekabrekiandClaude Opus 5.5 committed 2026-10-08 15:03:19 +02:00
1 parent fbc4080f53
commit 384c8cc094
2 files changed
+53 -8

No files matched your search

+47 -7
View File
@@ -5,8 +5,11 @@
//! can't be caught, so each launch leaves a marker in the settings folder until its window has
//! drawn a frame. A launch that finds the previous one's marker switches Window Graphics to
//! OpenGL, which those drivers run, and says so once the window is up. `WGPU_BACKEND` (wgpu's
//! own override) still wins over both.
//! own override) still wins over both. The marker is locked while its launch runs, so a second
//! copy opened meanwhile isn't taken for a crash; and a window OpenGL can't open either puts
//! Window Graphics back to Automatic ([`opengl_failed`]) rather than failing every launch.
use std::fs::File;
use std::path::{Path, PathBuf};
use effectcraft_engine::prefs::{PREFS_FILE, Prefs};
@@ -23,6 +26,8 @@ pub struct Launch {
/// Switched to OpenGL because the previous launch never drew: the message to show.
pub notice: Option<String>,
marker: Option<PathBuf>,
/// Holds the marker's lock until the window draws (or the process ends).
lock: Option<File>,
}
impl Launch {
@@ -34,8 +39,10 @@ impl Launch {
let prefs_path = dir.join(PREFS_FILE);
let mut prefs = std::fs::read_to_string(&prefs_path).map(|t| Prefs::from_json(&t)).unwrap_or_default();
let marker = dir.join(MARKER);
// Locked by another launch that is still opening its window: not a crash.
let held = File::open(&marker).is_ok_and(|f| f.try_lock().is_err());
let mut notice = None;
if marker.exists() && prefs.startup.window_graphics == "auto" && cfg!(not(target_os = "macos")) {
if marker.exists() && !held && prefs.startup.window_graphics == "auto" && cfg!(not(target_os = "macos")) {
prefs.startup.window_graphics = "gl".into();
match std::fs::create_dir_all(dir).and_then(|()| std::fs::write(&prefs_path, prefs.to_json())) {
Ok(()) => log::warn!("the last launch never drew its window: switching Window Graphics to OpenGL"),
@@ -49,18 +56,20 @@ impl Launch {
);
}
let backends = (prefs.startup.window_graphics == "gl").then_some(Backends::GL);
let marker = match std::fs::create_dir_all(dir).and_then(|()| std::fs::write(&marker, "")) {
Ok(()) => Some(marker),
let (marker, lock) = match std::fs::create_dir_all(dir).and_then(|()| File::create(&marker)) {
// Where locks aren't available the guard works as before.
Ok(f) => (Some(marker), f.try_lock().is_ok().then_some(f)),
Err(e) => {
log::warn!("launch marker: {e}");
None
(None, None)
}
};
Launch { backends, notice, marker }
Launch { backends, notice, marker, lock }
}
/// The window drew its first frame: the launch made it.
pub fn drawn(&mut self) {
self.lock = None;
if let Some(m) = self.marker.take()
&& let Err(e) = std::fs::remove_file(&m)
{
@@ -69,6 +78,18 @@ impl Launch {
}
}
/// The window couldn't open with OpenGL (an error, not a crash): back to Automatic, and no marker.
pub fn opengl_failed(dir: Option<&Path>) {
let Some(dir) = dir else { return };
let prefs_path = dir.join(PREFS_FILE);
let mut prefs = std::fs::read_to_string(&prefs_path).map(|t| Prefs::from_json(&t)).unwrap_or_default();
prefs.startup.window_graphics = "auto".into();
if let Err(e) = std::fs::write(&prefs_path, prefs.to_json()) {
log::warn!("saving Window Graphics failed: {e}");
}
let _ = std::fs::remove_file(dir.join(MARKER));
}
#[cfg(test)]
mod tests {
use super::*;
@@ -89,7 +110,7 @@ mod tests {
l.drawn();
assert!(!dir.join(MARKER).exists());
// A launch that crashes before drawing (it never calls `drawn`)…
let _crashed = Launch::begin(Some(&dir), false);
drop(Launch::begin(Some(&dir), false));
// …makes the next one use OpenGL and say so, once.
let mut l = Launch::begin(Some(&dir), false);
if cfg!(target_os = "macos") {
@@ -108,4 +129,23 @@ mod tests {
assert!(!dir.join(MARKER).exists());
let _ = std::fs::remove_dir_all(&dir);
}
/// A second copy opened while the first is still opening isn't a crash; OpenGL that can't
/// open a window either goes back to Automatic instead of failing every launch.
#[test]
fn a_second_copy_or_failed_opengl_doesnt_strand_window_graphics() {
let dir = std::env::temp_dir().join(format!("ec-launch-guard-2-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let _first = Launch::begin(Some(&dir), false);
assert_eq!(Launch::begin(Some(&dir), false).backends, None);
assert_eq!(window_graphics(&dir), "auto");
drop(_first);
if cfg!(not(target_os = "macos")) {
assert_eq!(Launch::begin(Some(&dir), false).backends, Some(Backends::GL), "a real crash still switches");
opengl_failed(Some(&dir));
assert_eq!(window_graphics(&dir), "auto");
assert_eq!(Launch::begin(Some(&dir), false).backends, None);
}
let _ = std::fs::remove_dir_all(&dir);
}
}
+6 -1
View File
@@ -211,7 +211,12 @@ fn main() -> eframe::Result {
);
match &result {
Ok(()) => log::info!("the window closed"),
Err(e) => startup_failed(e, !on_gl && cfg!(not(target_os = "macos"))),
Err(e) => {
if on_gl {
launch_guard::opengl_failed(effectcraft_host::config_dir().as_deref());
}
startup_failed(e, !on_gl && cfg!(not(target_os = "macos")));
}
}
result
}