6.8 KiB
6.8 KiB
Phase 03 — Config Resolver & System Detection
Goal
Implement the config file discovery/priority system and system detection module. No GTK4 code. All modules are pure Rust with unit tests.
Files to implement
src/config/resolver.rssrc/system/detect.rssrc/system/paths.rssrc/system/mod.rs
src/system/paths.rs
use std::path::PathBuf;
use xdg::BaseDirectories;
pub struct XdgPaths {
pub config_home: PathBuf, // $XDG_CONFIG_HOME or ~/.config
pub mangohud_dir: PathBuf, // {config_home}/MangoHud/
pub global_config: PathBuf, // {mangohud_dir}/MangoHud.conf
pub data_home: PathBuf,
}
impl XdgPaths {
pub fn resolve() -> anyhow::Result<Self>
}
/// Return all possible Steam root directories (native + Flatpak variants).
pub fn steam_roots() -> Vec<PathBuf>
/// Return all possible Heroic config directories.
pub fn heroic_config_dirs() -> Vec<PathBuf>
/// Return all possible Lutris config directories.
pub fn lutris_config_dirs() -> Vec<PathBuf>
/// Expand ~ in paths (since std::fs doesn't do this).
pub fn expand_tilde(path: &str) -> PathBuf
src/system/detect.rs
Run at application startup. Returns a SystemInfo struct that is passed to the
rest of the app and refreshed on demand.
#[derive(Debug, Clone)]
pub struct SystemInfo {
pub mangohud: MangoHudInfo,
pub gpu: GpuInfo,
pub display_server: DisplayServer,
pub tools: AvailableTools,
pub integrations: IntegrationAvailability,
}
#[derive(Debug, Clone)]
pub struct MangoHudInfo {
pub installed: bool,
pub version: Option<String>, // parsed from `mangohud --version`
pub lib_path: Option<PathBuf>, // path to libMangoHud.so
pub flatpak: bool,
}
#[derive(Debug, Clone)]
pub struct GpuInfo {
pub vendor: GpuVendor,
pub name: Option<String>,
pub pci_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DisplayServer {
Wayland,
X11,
XwaylandUnderWayland,
Unknown,
}
#[derive(Debug, Clone)]
pub struct AvailableTools {
pub vkcube: Option<PathBuf>,
pub glxgears: Option<PathBuf>,
pub gamemodectl: Option<PathBuf>,
pub gamemoded: Option<PathBuf>,
}
#[derive(Debug, Clone)]
pub struct IntegrationAvailability {
pub steam: bool,
pub steam_flatpak: bool,
pub lutris: bool,
pub lutris_flatpak: bool,
pub heroic: bool,
pub heroic_flatpak: bool,
pub gamemode: bool,
}
/// Run all detection. Runs in a tokio task, sends result back via channel.
pub async fn detect_system() -> anyhow::Result<SystemInfo>
Detection implementation details:
MangoHud detection:
- Try
which mangohud→ path - Run
mangohud --version 2>&1→ parse version string - Check library existence at common paths:
/usr/lib/x86_64-linux-gnu/mangohud/libMangoHud.so/usr/lib/mangohud/libMangoHud.so/usr/local/lib/mangohud/libMangoHud.so~/.local/lib/mangohud/libMangoHud.so
- Check Flatpak:
flatpak list 2>/dev/null | grep -i mangohud
GPU detection:
- Read
/sys/class/drm/card*/device/vendor—0x1002=AMD,0x10de=NVIDIA,0x8086=Intel - Read
/sys/class/drm/card*/device/devicefor device ID - Cross-reference with
lspci -nn 2>/dev/nullfor name
Display server detection:
- Check
$WAYLAND_DISPLAY— if set → Wayland - Check
$DISPLAY— if set → X11 (or XwaylandUnderWayland if also Wayland) - Check
$XDG_SESSION_TYPE
Tool detection: which for each tool via std::process::Command
src/config/resolver.rs
Full implementation of the config layer discovery system.
See docs/config_resolution.md for the complete algorithm.
use crate::config::types::*;
use crate::system::paths::XdgPaths;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct ConfigLayer {
pub path: Option<PathBuf>,
pub source_type: LayerSource,
pub priority: u8,
pub exists: bool,
pub is_editable: bool,
pub config: Option<AnnotatedConfig>,
}
#[derive(Debug, Clone)]
pub enum LayerSource {
CompiledDefault,
GlobalXdg,
PerAppXdg(String),
AppLocal(PathBuf),
EnvFile(PathBuf),
EnvInline(String),
}
/// A conflict: one option set in multiple layers.
#[derive(Debug, Clone)]
pub struct ConfigConflict {
pub key: String,
pub winning_layer_priority: u8,
pub winning_value: ConfigValue,
pub shadowed: Vec<(u8, ConfigValue)>, // (priority, value) of shadowed layers
}
pub struct Resolver;
impl Resolver {
/// Discover all config layers. Reads env vars and filesystem.
pub async fn discover(xdg: &XdgPaths) -> anyhow::Result<Vec<ConfigLayer>>
/// Build conflict map from a resolved layer stack.
/// Returns only options that appear in more than one layer with different values.
pub fn find_conflicts(layers: &[ConfigLayer]) -> Vec<ConfigConflict>
/// Return the effective value for a key (highest priority layer that sets it).
pub fn effective_value(key: &str, layers: &[ConfigLayer]) -> ConfigValue
/// Return a display label for a layer source.
pub fn layer_label(source: &LayerSource) -> String
/// Create a new per-app config at the XDG path.
pub fn create_per_app_config(app_name: &str, xdg: &XdgPaths) -> anyhow::Result<PathBuf>
/// Scan common game directories for app-local MangoHud.conf files.
async fn scan_game_dirs() -> Vec<PathBuf>
}
Discovery order (implement exactly as in docs/config_resolution.md):
$MANGOHUD_CONFIGFILE→ EnvFile (priority 5)$MANGOHUD_CONFIG→ EnvInline (priority 5, wins over EnvFile){xdg}/MangoHud/MangoHud.conf→ GlobalXdg (priority 2){xdg}/MangoHud/*.conf(excluding MangoHud.conf) → PerAppXdg (priority 3)- Steam/game dirs scan → AppLocal (priority 4)
Tests required
- discover: correctly reads $XDG_CONFIG_HOME override
- discover: env var $MANGOHUD_CONFIG parsed as inline layer at priority 5
- find_conflicts: detects option set in both global and per-app configs
- effective_value: returns env value when set, file value otherwise
- create_per_app_config: creates file with correct header comment
src/system/mod.rs
pub mod detect;
pub mod paths;
pub use detect::{SystemInfo, MangoHudInfo, GpuInfo, DisplayServer, AvailableTools};
pub use paths::XdgPaths;
Acceptance Criteria
cargo test --libpasses all new tests- Resolver correctly discovers layers in priority order
- Env var layers detected with correct priority (5 = highest)
- Conflict detection identifies options shadowed by higher-priority layers
- System detection identifies GPU vendor from
/sys/class/drm - MangoHud version extracted from
--versionoutput - Display server (Wayland/X11) correctly detected from environment
- All paths resolve via XDG (no hardcoded
/home/username) - No
.unwrap()in production code