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

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 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)