Add setup tray entry, wizard progress/log, config add/remove, stale-wine check

- tray: uninstalled launchers now show a "Setup…" entry that spawns
  the setup wizard as a child process.
- setup.rs: download shows a progress bar (bytes / total), and
  umu-run stdout+stderr stream into a scrollable log pane. A 250 ms
  tick subscription pulls updates from the shared state.
- config add-launcher / remove-launcher CLI, with sensible defaults
  for prefix_dir, gameid, and process_pattern derived from name/exe.
- diagnose: flag stale wineserver processes when no launcher is
  running, suggesting `umutray kill`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-04-17 12:46:47 -07:00
parent 14eccf4ef0
commit 22fa1efabf
8 changed files with 339 additions and 20 deletions
+69
View File
@@ -61,6 +61,21 @@ fn default_proton_version() -> String {
"GE-Proton".into()
}
fn regex_escape(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 4);
for c in s.chars() {
if matches!(
c,
'.' | '*' | '?' | '+' | '(' | ')' | '[' | ']'
| '{' | '}' | '|' | '\\' | '^' | '$'
) {
out.push('\\');
}
out.push(c);
}
out
}
/// The six launchers umutray ships out of the box. `exe_path`, `gameid`,
/// and `process_pattern` are best-effort defaults for typical installs —
/// users can adjust per-launcher via `umutray config edit`.
@@ -231,6 +246,60 @@ impl Config {
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn add_launcher(
&mut self,
name: String,
display: Option<String>,
exe_path: PathBuf,
prefix_dir: Option<PathBuf>,
gameid: Option<String>,
process_pattern: Option<String>,
installer_url: Option<String>,
) -> Result<()> {
if self.launchers.iter().any(|l| l.name == name) {
anyhow::bail!("launcher '{name}' already exists");
}
let display = display.unwrap_or_else(|| name.clone());
let prefix_dir = prefix_dir
.unwrap_or_else(|| home_dir().join("Games").join(&name));
let gameid = gameid.unwrap_or_else(|| format!("umu-{name}"));
let process_pattern = process_pattern.unwrap_or_else(|| {
exe_path
.file_name()
.and_then(|s| s.to_str())
.map(regex_escape)
.unwrap_or_else(|| name.clone())
});
self.launchers.push(Launcher {
name: name.clone(),
display,
prefix_dir,
exe_path,
gameid,
process_pattern,
installer_url,
proton_version: None,
});
self.save()?;
println!("\x1b[1;32m✓\x1b[0m Added launcher '{name}'.");
Ok(())
}
pub fn remove_launcher(&mut self, name: &str) -> Result<()> {
let before = self.launchers.len();
self.launchers.retain(|l| l.name != name);
if self.launchers.len() == before {
anyhow::bail!("no launcher named '{name}'");
}
self.save()?;
println!(
"\x1b[1;32m✓\x1b[0m Removed '{name}'. \
The Wine prefix on disk was left untouched."
);
Ok(())
}
/// Update global fields non-interactively, then save.
/// Use `config edit` for per-launcher changes.
pub fn set_globals(