Add launch crash detection with detailed error dialog, fix all warnings

This commit is contained in:
2026-02-27 20:23:10 +02:00
parent e20759d1cb
commit 11c754a8c1
16 changed files with 324 additions and 70 deletions

View File

@@ -42,6 +42,7 @@ pub enum LaunchMethod {
/// Extract-and-run fallback (APPIMAGE_EXTRACT_AND_RUN=1)
ExtractAndRun,
/// Via firejail sandbox
#[allow(dead_code)]
Sandboxed,
}
@@ -58,11 +59,18 @@ impl LaunchMethod {
/// Result of a launch attempt.
#[derive(Debug)]
pub enum LaunchResult {
/// Successfully spawned the process.
/// Successfully spawned the process and it's still running.
Started {
child: Child,
method: LaunchMethod,
},
/// Process spawned but crashed immediately (within ~1 second).
Crashed {
exit_code: Option<i32>,
stderr: String,
#[allow(dead_code)]
method: LaunchMethod,
},
/// Failed to launch.
Failed(String),
}
@@ -155,20 +163,56 @@ fn execute_appimage(
cmd.env(key, value);
}
// Detach from our process group so the app runs independently
// Capture stderr to detect crash messages, stdin detached
cmd.stdin(Stdio::null());
cmd.stderr(Stdio::piped());
match cmd.spawn() {
Ok(child) => LaunchResult::Started {
child,
method: method.clone(),
},
Err(e) => LaunchResult::Failed(format!("Failed to spawn process: {}", e)),
Ok(mut child) => {
// Brief wait to detect immediate crashes (e.g. missing Qt plugins)
std::thread::sleep(std::time::Duration::from_millis(1500));
match child.try_wait() {
Ok(Some(status)) => {
// Process already exited - it crashed
let stderr = child
.stderr
.take()
.and_then(|mut err| {
let mut buf = String::new();
use std::io::Read;
err.read_to_string(&mut buf).ok()?;
Some(buf)
})
.unwrap_or_default();
LaunchResult::Crashed {
exit_code: status.code(),
stderr,
method: method.clone(),
}
}
Ok(None) => {
// Still running - success
LaunchResult::Started {
child,
method: method.clone(),
}
}
Err(_) => {
// Can't check status, assume it's running
LaunchResult::Started {
child,
method: method.clone(),
}
}
}
}
Err(e) => LaunchResult::Failed(format!("Failed to start: {}", e)),
}
}
/// Parse a launch_args string from the database into a Vec of individual arguments.
/// Splits on whitespace; returns an empty Vec if the input is None or empty.
#[allow(dead_code)]
pub fn parse_launch_args(args: Option<&str>) -> Vec<String> {
args.map(|s| s.split_whitespace().map(String::from).collect())
.unwrap_or_default()