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

Detect AppImages that crash immediately after spawning (within 1.5s) by
capturing stderr and using try_wait(). Show a full AlertDialog with a
plain-text explanation, scrollable error output, and a copy-to-clipboard
button. Covers Qt plugin errors, missing libraries, segfaults, permission
issues, and display connection failures.

Move launch operations to background threads in both the detail view and
context menu to avoid blocking the UI during the 1.5s crash detection
window.

Suppress all 57 compiler warnings across future-use modules (backup,
notification, report, watcher) and individual unused fields/variants in
other core modules.
This commit is contained in:
lashman
2026-02-27 20:23:10 +02:00
parent c9c9c0341b
commit d493516efa
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()