Add a day-zero first-run wizard before the desktop tour
First-boot --autostart now walks Welcome, timezone, network hint, updates, and an optional snapper day-zero snapshot in the existing help window, then hands off to the live cheatsheet tour. Existing onboarded users are skipped.
This commit is contained in:
parent
d9012c8e72
commit
af6ba1bd82
9 changed files with 901 additions and 31 deletions
|
|
@ -1,3 +1,4 @@
|
|||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
/// Fire-and-forget: runs `command` via a shell, matching the idiom already
|
||||
|
|
@ -8,6 +9,35 @@ pub fn run(command: &str) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Fire-and-forget argv form — use this when arguments come from the UI
|
||||
/// (timezone names, page ids) so they never pass through a shell.
|
||||
pub fn run_argv(prog: &str, args: &[&str]) {
|
||||
if let Err(e) = Command::new(prog).args(args).spawn() {
|
||||
eprintln!("breadhelp: failed to run `{prog}`: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// `true` when `name` resolves to an executable on `PATH`.
|
||||
pub fn command_exists(name: &str) -> bool {
|
||||
std::env::var_os("PATH")
|
||||
.map(|paths| std::env::split_paths(&paths).any(|dir| is_executable(&dir.join(name))))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn is_executable(path: &Path) -> bool {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::metadata(path)
|
||||
.map(|m| m.is_file() && m.permissions().mode() & 0o111 != 0)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
path.is_file()
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs `command` on a background thread and delivers success/failure back
|
||||
/// onto the GTK main loop via `on_done` — plain `spawn()` only confirms the
|
||||
/// process *launched*, not how it exited, and one-click fixes need to report
|
||||
|
|
@ -26,3 +56,19 @@ pub fn run_reporting(command: &str, on_done: impl Fn(bool) + 'static) {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Same as [`run_reporting`], but without a shell.
|
||||
pub fn run_argv_reporting(prog: &str, args: &[&str], on_done: impl Fn(bool) + 'static) {
|
||||
let prog = prog.to_string();
|
||||
let args: Vec<String> = args.iter().map(|s| (*s).to_string()).collect();
|
||||
let (tx, rx) = async_channel::bounded(1);
|
||||
std::thread::spawn(move || {
|
||||
let status = Command::new(&prog).args(&args).status();
|
||||
let _ = tx.send_blocking(status.map(|s| s.success()).unwrap_or(false));
|
||||
});
|
||||
glib::MainContext::default().spawn_local(async move {
|
||||
if let Ok(success) = rx.recv().await {
|
||||
on_done(success);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue