Files
mangotune/docs/plan/architecture.md
T
2026-03-30 23:06:06 -04:00

185 lines
6.4 KiB
Markdown

# Architecture — MangoTune
## Rust Edition & MSRV
- Rust edition: **2021**
- Minimum Supported Rust Version: **1.75.0**
- Build target: `x86_64-unknown-linux-gnu` (primary), `aarch64-unknown-linux-gnu` (secondary)
## Cargo.toml Dependencies
```toml
[package]
name = "mangotune"
version = "0.1.0"
edition = "2021"
authors = ["MangoTune Contributors"]
description = "A modern MangoHud configurator for Linux"
license = "GPL-3.0"
repository = "https://github.com/your-org/mangotune"
[[bin]]
name = "mangotune"
path = "src/main.rs"
[dependencies]
# GUI
gtk4 = { version = "0.9", features = ["v4_12"] }
libadwaita = { version = "0.7", features = ["v1_5"] }
glib = "0.20"
gio = "0.20"
# Async runtime (for subprocess management, file watching)
tokio = { version = "1", features = ["rt-multi-thread", "process", "fs", "sync", "time"] }
# Serialization (for GSettings schema, internal state)
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Config file parsing
indexmap = "2" # preserve insertion order in parsed configs
# Error handling
anyhow = "1"
thiserror = "1"
# Filesystem watching (live reload when config changes externally)
notify = "6"
# XDG base directory resolution
xdg = "2"
# Regex (for config line parsing)
regex = "1"
once_cell = "1"
# Process detection (checking if gamemode is running, etc.)
sysinfo = "0.31"
# Logging/tracing
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
[build-dependencies]
# For compiling GSettings schema and other build-time assets
glib-build-tools = "0.20"
[dev-dependencies]
tempfile = "3"
assert_fs = "1"
```
## System Dependencies (must be present at build time)
| Package | Ubuntu/Debian | Fedora/RHEL | Arch |
|--------------------------|---------------------------|---------------------------------|----------------|
| GTK4 dev headers | `libgtk-4-dev` | `gtk4-devel` | `gtk4` |
| libadwaita dev headers | `libadwaita-1-dev` | `libadwaita-devel` | `libadwaita` |
| GLib dev headers | `libglib2.0-dev` | `glib2-devel` | `glib2` |
| pkg-config | `pkg-config` | `pkgconf` | `pkgconf` |
Runtime (optional, detected at launch):
- `mangohud` — the actual overlay
- `vkcube` — from `vulkan-tools` package
- `glxgears` — from `mesa-utils` package
- `gamemoded` — from `gamemode` package
- `gamemodectl` — from `gamemode` package
## Module Dependency Graph
```
main.rs
└── app.rs (GtkApplication)
└── window.rs (AdwApplicationWindow)
├── ui/pages/*.rs ← all pages
│ ├── config/resolver.rs ← discovers config stack
│ ├── config/validator.rs ← validates on every change
│ ├── config/parser.rs ← reads/writes files
│ └── config/schema.rs ← option definitions
├── ui/widgets/*.rs ← reusable widgets
├── system/detect.rs ← run at startup
├── system/paths.rs ← XDG resolution
├── launcher/runner.rs ← test process management
└── integrations/*.rs ← GameMode/Steam/Lutris/Heroic
```
## Data Flow
```
Startup:
system::detect::run()
→ SystemInfo { mangohud_version, gpu_vendor, display_server, available_tools }
config::resolver::discover()
→ Vec<ConfigLayer> { path, source_type, priority, exists }
For each ConfigLayer:
config::parser::read(path)
→ RawConfig { lines: Vec<ConfigLine> }
config::schema::annotate(raw)
→ AnnotatedConfig { options: IndexMap<key, ConfigEntry> }
User edits a field:
ui::widgets::* emits change signal
→ config::validator::check(key, value, &schema)
→ ValidationResult::Ok | ValidationResult::Error(msg) | ValidationResult::Warning(msg)
If Ok: update in-memory AnnotatedConfig
check for dependency side-effects
update cascade_view to show which layer owns the value
enable Save button if no errors anywhere
Save:
config::validator::check_all(&config) ← full pass before any write
→ if any Error: abort, show error summary toast
→ if all Ok: config::parser::write(path, config)
preserving all comment lines unchanged
```
## Threading Model
- **Main thread**: GTK4 event loop only. No blocking calls.
- **Tokio thread pool**: file I/O, subprocess spawning, filesystem watcher.
- Communication: `glib::MainContext::channel()` for sending results back to GTK main thread.
- Never call GTK functions from tokio threads.
## GSettings Schema
Used for persisting app preferences (window size, last-opened config path, theme preference).
NOT used for MangoHud config itself — that is always written directly to .conf files.
Schema ID: `com.mangotune.MangoTune`
Keys:
- `last-config-path` (string)
- `window-width` (int, default 1200)
- `window-height` (int, default 780)
- `active-page` (string, default "performance")
- `show-raw-editor` (bool, default false)
## Error Handling Strategy
- All I/O operations return `anyhow::Result`.
- UI layer converts errors to `AdwToast` notifications (non-blocking).
- Critical startup errors (GTK init failure) use `eprintln!` + `process::exit(1)`.
- Validation errors are `thiserror` enums, displayed inline, never panicked on.
- Never use `.unwrap()` or `.expect()` in production paths. Use `?` or match.
## Config File Format Notes
MangoHud .conf files follow these rules (the parser must handle all of them):
1. Lines starting with `#` are comments — preserve verbatim.
2. Empty lines — preserve verbatim.
3. `key=value` — option with value.
4. `key` alone (no `=`) — boolean flag, presence = enabled.
5. `# key` — commented-out option (disabled).
6. `# key=value` — commented-out option with default value shown.
7. Inline comments after values are NOT standard and should be treated as part of value.
8. Duplicate keys: last occurrence wins (MangoHud behavior).
9. Encoding: UTF-8.
The parser must distinguish between:
- An option that is absent from the file (use MangoHud's compiled default)
- An option explicitly set to 0 or empty (user explicitly disabled)
- An option present as a bare key (user enabled a flag)