Initial import
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
# 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.rs`
|
||||
- `src/system/detect.rs`
|
||||
- `src/system/paths.rs`
|
||||
- `src/system/mod.rs`
|
||||
|
||||
---
|
||||
|
||||
## src/system/paths.rs
|
||||
|
||||
```rust
|
||||
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.
|
||||
|
||||
```rust
|
||||
#[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:**
|
||||
1. Try `which mangohud` → path
|
||||
2. Run `mangohud --version 2>&1` → parse version string
|
||||
3. 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`
|
||||
4. Check Flatpak: `flatpak list 2>/dev/null | grep -i mangohud`
|
||||
|
||||
**GPU detection:**
|
||||
1. Read `/sys/class/drm/card*/device/vendor` — `0x1002`=AMD, `0x10de`=NVIDIA, `0x8086`=Intel
|
||||
2. Read `/sys/class/drm/card*/device/device` for device ID
|
||||
3. Cross-reference with `lspci -nn 2>/dev/null` for name
|
||||
|
||||
**Display server detection:**
|
||||
1. Check `$WAYLAND_DISPLAY` — if set → Wayland
|
||||
2. Check `$DISPLAY` — if set → X11 (or XwaylandUnderWayland if also Wayland)
|
||||
3. 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.
|
||||
|
||||
```rust
|
||||
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):
|
||||
1. `$MANGOHUD_CONFIGFILE` → EnvFile (priority 5)
|
||||
2. `$MANGOHUD_CONFIG` → EnvInline (priority 5, wins over EnvFile)
|
||||
3. `{xdg}/MangoHud/MangoHud.conf` → GlobalXdg (priority 2)
|
||||
4. `{xdg}/MangoHud/*.conf` (excluding MangoHud.conf) → PerAppXdg (priority 3)
|
||||
5. 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
|
||||
|
||||
```rust
|
||||
pub mod detect;
|
||||
pub mod paths;
|
||||
|
||||
pub use detect::{SystemInfo, MangoHudInfo, GpuInfo, DisplayServer, AvailableTools};
|
||||
pub use paths::XdgPaths;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ] `cargo test --lib` passes 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 `--version` output
|
||||
- [ ] Display server (Wayland/X11) correctly detected from environment
|
||||
- [ ] All paths resolve via XDG (no hardcoded `/home/username`)
|
||||
- [ ] No `.unwrap()` in production code
|
||||
Reference in New Issue
Block a user