Initial release: gamewrap v1.0.0

Steam-first Linux game launcher wrapper for MangoHud and GameMode.
Manages launch behavior via TOML config with named profiles, per-game
bindings, and full diagnostics. All v1 criteria validated.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-05-31 22:13:20 -04:00
commit 9e2431e58a
28 changed files with 7996 additions and 0 deletions
+147
View File
@@ -0,0 +1,147 @@
use crate::config::{GameLibsMode, Settings};
use crate::error::{AppError, config_error};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SettingKey {
Overlay,
Performance,
SteamHostLibs,
GameLibs,
Verbose,
Gamescope,
GamescopeWidth,
GamescopeHeight,
GamescopeFps,
FpsCap,
Vkbasalt,
Esync,
Fsync,
LargeAddressAware,
PreLaunch,
PostLaunch,
}
impl SettingKey {
pub fn parse(value: &str) -> Result<Self, AppError> {
match value {
"overlay" => Ok(Self::Overlay),
"performance" => Ok(Self::Performance),
"steam-host-libs" | "host-libs" => Ok(Self::SteamHostLibs),
"game-libs" => Ok(Self::GameLibs),
"verbose" => Ok(Self::Verbose),
"gamescope" => Ok(Self::Gamescope),
"gamescope-width" => Ok(Self::GamescopeWidth),
"gamescope-height" => Ok(Self::GamescopeHeight),
"gamescope-fps" => Ok(Self::GamescopeFps),
"fps-cap" => Ok(Self::FpsCap),
"vkbasalt" => Ok(Self::Vkbasalt),
"esync" => Ok(Self::Esync),
"fsync" => Ok(Self::Fsync),
"large-address-aware" | "laa" => Ok(Self::LargeAddressAware),
"pre-launch" => Ok(Self::PreLaunch),
"post-launch" => Ok(Self::PostLaunch),
_ => Err(config_error(
format!("`{value}` is not a known setting."),
"Valid settings: overlay, performance, steam-host-libs, game-libs, verbose, gamescope, gamescope-width, gamescope-height, gamescope-fps, fps-cap, vkbasalt, esync, fsync, large-address-aware, pre-launch, post-launch.",
)),
}
}
}
pub fn set_value(settings: &mut Settings, key: SettingKey, value: &str) -> Result<(), AppError> {
match key {
SettingKey::Overlay => settings.overlay = Some(parse_toggle(value)?),
SettingKey::Performance => settings.performance = Some(parse_toggle(value)?),
SettingKey::SteamHostLibs => settings.steam_host_libs = Some(parse_toggle(value)?),
SettingKey::GameLibs => settings.game_libs = Some(parse_game_libs(value)?),
SettingKey::Verbose => settings.verbose = Some(parse_toggle(value)?),
SettingKey::Gamescope => settings.gamescope = Some(parse_toggle(value)?),
SettingKey::GamescopeWidth => {
settings.gamescope_width = Some(parse_pixel_count(value)?);
}
SettingKey::GamescopeHeight => {
settings.gamescope_height = Some(parse_pixel_count(value)?);
}
SettingKey::GamescopeFps => {
settings.gamescope_fps = Some(parse_gamescope_fps(value)?);
}
SettingKey::FpsCap => {
let n: u32 = value.parse().map_err(|_| {
config_error(
format!("`{value}` is not a valid FPS cap. Use a number like `60` or `120`."),
"Use `gamewrap config set fps-cap 60` for a 60 FPS cap.",
)
})?;
settings.fps_cap = Some(n);
}
SettingKey::Vkbasalt => settings.vkbasalt = Some(parse_toggle(value)?),
SettingKey::Esync => settings.esync = Some(parse_toggle(value)?),
SettingKey::Fsync => settings.fsync = Some(parse_toggle(value)?),
SettingKey::LargeAddressAware => settings.large_address_aware = Some(parse_toggle(value)?),
SettingKey::PreLaunch => settings.pre_launch = Some(value.to_string()),
SettingKey::PostLaunch => settings.post_launch = Some(value.to_string()),
}
Ok(())
}
pub fn reset_value(settings: &mut Settings, key: SettingKey) {
match key {
SettingKey::Overlay => settings.overlay = None,
SettingKey::Performance => settings.performance = None,
SettingKey::SteamHostLibs => settings.steam_host_libs = None,
SettingKey::GameLibs => settings.game_libs = None,
SettingKey::Verbose => settings.verbose = None,
SettingKey::Gamescope => settings.gamescope = None,
SettingKey::GamescopeWidth => settings.gamescope_width = None,
SettingKey::GamescopeHeight => settings.gamescope_height = None,
SettingKey::GamescopeFps => settings.gamescope_fps = None,
SettingKey::FpsCap => settings.fps_cap = None,
SettingKey::Vkbasalt => settings.vkbasalt = None,
SettingKey::Esync => settings.esync = None,
SettingKey::Fsync => settings.fsync = None,
SettingKey::LargeAddressAware => settings.large_address_aware = None,
SettingKey::PreLaunch => settings.pre_launch = None,
SettingKey::PostLaunch => settings.post_launch = None,
}
}
fn parse_toggle(value: &str) -> Result<bool, AppError> {
match value {
"on" => Ok(true),
"off" => Ok(false),
_ => Err(config_error(
format!("`{value}` is not a valid on/off value."),
"Use `on` or `off`.",
)),
}
}
fn parse_game_libs(value: &str) -> Result<GameLibsMode, AppError> {
match value {
"auto" => Ok(GameLibsMode::Auto),
"keep" => Ok(GameLibsMode::Keep),
"gamemode" => Ok(GameLibsMode::Gamemode),
_ => Err(config_error(
format!("`{value}` is not a valid value for game-libs."),
"Use one of: auto, keep, gamemode.",
)),
}
}
fn parse_pixel_count(value: &str) -> Result<u32, AppError> {
value.parse::<u32>().map_err(|_| {
config_error(
format!("`{value}` is not a valid pixel count. Use a number like `1920`."),
"Use a whole-number pixel count for gamescope resolution settings.",
)
})
}
fn parse_gamescope_fps(value: &str) -> Result<u32, AppError> {
value.parse::<u32>().map_err(|_| {
config_error(
format!("`{value}` is not a valid FPS. Use a number like `60`."),
"Use `gamewrap config set gamescope-fps 60` for a 60 FPS gamescope target.",
)
})
}