6.4 KiB
6.4 KiB
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
[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 overlayvkcube— fromvulkan-toolspackageglxgears— frommesa-utilspackagegamemoded— fromgamemodepackagegamemodectl— fromgamemodepackage
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
AdwToastnotifications (non-blocking). - Critical startup errors (GTK init failure) use
eprintln!+process::exit(1). - Validation errors are
thiserrorenums, 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):
- Lines starting with
#are comments — preserve verbatim. - Empty lines — preserve verbatim.
key=value— option with value.keyalone (no=) — boolean flag, presence = enabled.# key— commented-out option (disabled).# key=value— commented-out option with default value shown.- Inline comments after values are NOT standard and should be treated as part of value.
- Duplicate keys: last occurrence wins (MangoHud behavior).
- 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)