Add per-game overlay toggles (gamemode, mangohud, gamescope)
Games live under each Launcher as a Vec<Game>; the launcher itself never picks up overlays, only games launched through `play` do. - config: Game struct with gamemode/mangohud/gamescope fields, plus add_game / remove_game / set_game_flags methods. - launcher::play_game wraps the game command as `gamescope [args] -- gamemoderun umu-run <exe>` (each layer optional) and sets MANGOHUD=1 when enabled. - CLI: `play`, `games`, `config add-game`, `config remove-game`, `config set-game-flags`. - tray: per-game submenus with Play + checkmark toggles for GameMode / MangoHud / Gamescope; toggles persist to disk immediately. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+170
@@ -46,6 +46,21 @@ enum Commands {
|
||||
/// List configured launchers and whether they're installed / running
|
||||
Launchers,
|
||||
|
||||
/// Play a specific game through its launcher's prefix, applying the
|
||||
/// per-game overlay flags (gamemode, mangohud, gamescope).
|
||||
Play {
|
||||
/// Launcher name (e.g. battlenet)
|
||||
launcher: String,
|
||||
/// Game name (e.g. overwatch)
|
||||
game: String,
|
||||
},
|
||||
|
||||
/// List configured games per launcher
|
||||
Games {
|
||||
/// Only show games for this launcher (omit for all)
|
||||
launcher: Option<String>,
|
||||
},
|
||||
|
||||
/// Print setup instructions for a launcher (automated wizard coming soon)
|
||||
Setup {
|
||||
/// Launcher name
|
||||
@@ -132,6 +147,60 @@ enum ConfigAction {
|
||||
/// Short CLI name
|
||||
name: String,
|
||||
},
|
||||
/// Add a game under an existing launcher
|
||||
AddGame {
|
||||
/// Launcher that owns this game
|
||||
launcher: String,
|
||||
|
||||
/// Short CLI name for the game (e.g. "overwatch")
|
||||
name: String,
|
||||
|
||||
/// Game exe path relative to drive_c/
|
||||
#[arg(long, value_name = "PATH")]
|
||||
exe_path: PathBuf,
|
||||
|
||||
/// Display name (defaults to NAME)
|
||||
#[arg(long)]
|
||||
display: Option<String>,
|
||||
|
||||
/// Wrap the game in gamemoderun
|
||||
#[arg(long)]
|
||||
gamemode: bool,
|
||||
|
||||
/// Set MANGOHUD=1 for the game
|
||||
#[arg(long)]
|
||||
mangohud: bool,
|
||||
|
||||
/// Enable gamescope with these args (space-separated, e.g. "-f -W 2560")
|
||||
#[arg(long, value_name = "ARGS")]
|
||||
gamescope: Option<String>,
|
||||
},
|
||||
/// Remove a game from a launcher
|
||||
RemoveGame {
|
||||
launcher: String,
|
||||
name: String,
|
||||
},
|
||||
/// Toggle per-game overlay flags
|
||||
SetGameFlags {
|
||||
launcher: String,
|
||||
name: String,
|
||||
|
||||
/// true / false — wrap in gamemoderun
|
||||
#[arg(long, value_name = "BOOL")]
|
||||
gamemode: Option<bool>,
|
||||
|
||||
/// true / false — set MANGOHUD=1
|
||||
#[arg(long, value_name = "BOOL")]
|
||||
mangohud: Option<bool>,
|
||||
|
||||
/// Enable gamescope with these args (space-separated)
|
||||
#[arg(long, value_name = "ARGS", conflicts_with = "no_gamescope")]
|
||||
gamescope: Option<String>,
|
||||
|
||||
/// Disable gamescope wrapping
|
||||
#[arg(long)]
|
||||
no_gamescope: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
@@ -186,6 +255,40 @@ fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
Commands::Play { launcher: lname, game: gname } => {
|
||||
let l = config.find(&lname).ok_or_else(|| {
|
||||
anyhow::anyhow!("unknown launcher '{lname}' — try `umutray launchers`")
|
||||
})?;
|
||||
let g = l.find_game(&gname).ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"launcher '{lname}' has no game named '{gname}' — try `umutray games {lname}`"
|
||||
)
|
||||
})?;
|
||||
launcher::play_game(&config, l, g)?;
|
||||
}
|
||||
|
||||
Commands::Games { launcher: lname } => {
|
||||
let launchers: Vec<&config::Launcher> = match &lname {
|
||||
Some(n) => vec![config.find(n).ok_or_else(|| {
|
||||
anyhow::anyhow!("unknown launcher '{n}' — try `umutray launchers`")
|
||||
})?],
|
||||
None => config.launchers.iter().collect(),
|
||||
};
|
||||
for l in launchers {
|
||||
if l.games.is_empty() {
|
||||
println!(" {}: (no games)", l.display);
|
||||
continue;
|
||||
}
|
||||
println!(" {}:", l.display);
|
||||
for g in &l.games {
|
||||
let installed = g.full_exe_path(l).exists();
|
||||
let marker = if installed { "\x1b[1;32m✓\x1b[0m" } else { "·" };
|
||||
let flags = format_game_flags(g);
|
||||
println!(" {marker} {:14} {}{}", g.name, g.display, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Commands::Setup { name } => {
|
||||
let l = config.find(&name).ok_or_else(|| {
|
||||
anyhow::anyhow!("unknown launcher '{name}' — try `umutray launchers`")
|
||||
@@ -231,6 +334,55 @@ fn main() -> Result<()> {
|
||||
let mut c = config;
|
||||
c.remove_launcher(&name)?;
|
||||
}
|
||||
ConfigAction::AddGame {
|
||||
launcher,
|
||||
name,
|
||||
exe_path,
|
||||
display,
|
||||
gamemode,
|
||||
mangohud,
|
||||
gamescope,
|
||||
} => {
|
||||
let mut c = config;
|
||||
let gs = gamescope.map(|s| {
|
||||
s.split_whitespace().map(String::from).collect::<Vec<_>>()
|
||||
});
|
||||
c.add_game(
|
||||
&launcher,
|
||||
name,
|
||||
display,
|
||||
exe_path,
|
||||
gamemode,
|
||||
mangohud,
|
||||
gs,
|
||||
)?;
|
||||
}
|
||||
ConfigAction::RemoveGame { launcher, name } => {
|
||||
let mut c = config;
|
||||
c.remove_game(&launcher, &name)?;
|
||||
}
|
||||
ConfigAction::SetGameFlags {
|
||||
launcher,
|
||||
name,
|
||||
gamemode,
|
||||
mangohud,
|
||||
gamescope,
|
||||
no_gamescope,
|
||||
} => {
|
||||
let gs_update = if no_gamescope {
|
||||
Some(None)
|
||||
} else {
|
||||
gamescope.map(|s| {
|
||||
Some(
|
||||
s.split_whitespace()
|
||||
.map(String::from)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
})
|
||||
};
|
||||
let mut c = config;
|
||||
c.set_game_flags(&launcher, &name, gamemode, mangohud, gs_update)?;
|
||||
}
|
||||
},
|
||||
|
||||
Commands::Service { action } => match action {
|
||||
@@ -242,3 +394,21 @@ fn main() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn format_game_flags(g: &config::Game) -> String {
|
||||
let mut tags: Vec<&str> = Vec::new();
|
||||
if g.gamemode {
|
||||
tags.push("gamemode");
|
||||
}
|
||||
if g.mangohud {
|
||||
tags.push("mangohud");
|
||||
}
|
||||
if g.gamescope.is_some() {
|
||||
tags.push("gamescope");
|
||||
}
|
||||
if tags.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" [{}]", tags.join(", "))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user