refactor: apply CLAUDE.md code quality improvements and add packaging

- Add #![forbid(unsafe_code)] to main.rs (issue #3)
- Replace raw ANSI escape codes with owo-colors crate (issue #2)
- Replace manual HOME path construction with dirs::home_dir() (issue #5)
- Ship umutray.service as a static file; service::install() substitutes
  the binary path at install time instead of generating the unit at runtime
- Add packaging/PKGBUILD following Arch Rust package guidelines
- Add CLAUDE.md tracking refactor tasks
- setup.rs: clean up downloaded temp files on abort/back, save launcher
  to config only after successful install, auto-start download when a
  preset has an installer_url
- util.rs: add pick_folder() using zenity/kdialog subprocesses (no rfd)
- config.rs: populate installer_url for all 6 built-in presets with
  official download URLs
- Document the Option<Option<Vec<String>>> gamescope pattern at main.rs:307

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-04-18 19:28:10 -07:00
parent f2f584febf
commit 9b7e474e80
11 changed files with 233 additions and 57 deletions
+27 -16
View File
@@ -1,5 +1,6 @@
use anyhow::{Context, Result};
use directories::ProjectDirs;
use owo_colors::OwoColorize;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
@@ -86,9 +87,7 @@ pub struct Config {
}
fn home_dir() -> PathBuf {
std::env::var("HOME")
.map(PathBuf::from)
.expect("$HOME is not set; cannot determine default paths")
dirs::home_dir().expect("Cannot determine home directory")
}
fn default_compat_dir() -> PathBuf {
@@ -128,7 +127,9 @@ pub fn presets() -> Vec<Launcher> {
),
gameid: "umu-battlenet".into(),
process_pattern: r"Battle\.net".into(),
installer_url: None,
installer_url: Some(
"https://www.battle.net/download/getInstallerForGame?os=win&gameProgram=BATTLENET_APP&version=Live".into(),
),
proton_version: None,
games: vec![],
},
@@ -141,7 +142,9 @@ pub fn presets() -> Vec<Launcher> {
),
gameid: "umu-eaapp".into(),
process_pattern: r"EADesktop\.exe".into(),
installer_url: None,
installer_url: Some(
"https://origin-a.akamaihd.net/EA-Desktop-Client-Download/installer-releases/EAappInstaller.exe".into(),
),
proton_version: None,
games: vec![],
},
@@ -154,7 +157,9 @@ pub fn presets() -> Vec<Launcher> {
),
gameid: "umu-epicgameslauncher".into(),
process_pattern: r"EpicGamesLauncher\.exe".into(),
installer_url: None,
installer_url: Some(
"https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/installer/download/EpicGamesLauncherInstaller.msi".into(),
),
proton_version: None,
games: vec![],
},
@@ -167,7 +172,9 @@ pub fn presets() -> Vec<Launcher> {
),
gameid: "umu-uplay".into(),
process_pattern: r"UbisoftConnect\.exe|upc\.exe".into(),
installer_url: None,
installer_url: Some(
"https://ubistatic3-a.akamaihd.net/orbit/launcher_installer/UbisoftConnectInstaller.exe".into(),
),
proton_version: None,
games: vec![],
},
@@ -180,7 +187,9 @@ pub fn presets() -> Vec<Launcher> {
),
gameid: "umu-gog".into(),
process_pattern: r"GalaxyClient\.exe".into(),
installer_url: None,
installer_url: Some(
"https://webinstallers.gog-statics.com/download/GOG_Galaxy_2.0.exe".into(),
),
proton_version: None,
games: vec![],
},
@@ -193,7 +202,9 @@ pub fn presets() -> Vec<Launcher> {
),
gameid: "umu-rockstar".into(),
process_pattern: r"Rockstar Games.*Launcher\.exe".into(),
installer_url: None,
installer_url: Some(
"https://gamedownloads.rockstargames.com/public/installer/Rockstar-Games-Launcher.exe".into(),
),
proton_version: None,
games: vec![],
},
@@ -320,7 +331,7 @@ impl Config {
games: vec![],
});
self.save()?;
println!("\x1b[1;32m✓\x1b[0m Added launcher '{name}'.");
println!("{} Added launcher '{name}'.", "".green().bold());
Ok(())
}
@@ -354,7 +365,7 @@ impl Config {
gamescope,
});
self.save()?;
println!("\x1b[1;32m✓\x1b[0m Added game '{name}' under launcher '{launcher}'.");
println!("{} Added game '{name}' under launcher '{launcher}'.", "".green().bold());
Ok(())
}
@@ -370,7 +381,7 @@ impl Config {
anyhow::bail!("launcher '{launcher}' has no game named '{name}'");
}
self.save()?;
println!("\x1b[1;32m✓\x1b[0m Removed game '{name}' from '{launcher}'.");
println!("{} Removed game '{name}' from '{launcher}'.", "".green().bold());
Ok(())
}
@@ -408,7 +419,7 @@ impl Config {
g.gamescope = v;
}
self.save()?;
println!("\x1b[1;32m✓\x1b[0m Updated flags for '{launcher}/{name}'.");
println!("{} Updated flags for '{launcher}/{name}'.", "".green().bold());
Ok(())
}
@@ -420,8 +431,8 @@ impl Config {
}
self.save()?;
println!(
"\x1b[1;32m✓\x1b[0m Removed '{name}'. \
The Wine prefix on disk was left untouched."
"{} Removed '{name}'. The Wine prefix on disk was left untouched.",
"".green().bold()
);
Ok(())
}
@@ -443,7 +454,7 @@ impl Config {
self.proton_compat_dir = d;
}
self.save()?;
println!("\x1b[1;32m✓\x1b[0m Config saved.");
println!("{} Config saved.", "".green().bold());
println!();
self.show()
}