From aeb84a3d684409fbe8ce2e8d355507328bbb7261 Mon Sep 17 00:00:00 2001 From: Gavin Nugent Date: Sun, 28 Jun 2026 22:29:50 +0100 Subject: [PATCH] Support SteamOS Game Mode: global logging via MANGOHUD_CONFIG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous override targeted gamescope-session-plus only. On stock SteamOS (/usr/lib/steamos/gamescope-session) that override is never sourced and the session forces MANGOHUD_CONFIGFILE=/no_display, so no logs were produced. Detect the session flavor and, on SteamOS, set MANGOHUD_CONFIG in environment.d — it overrides the forced config file and enables logging globally with no per-game launch options. Verified with a vkcube precedence test (412-row CSV) and end-to-end in a live SteamOS 3.9 Game Mode session (DREDGE, 1039-row mangoapp CSV). Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 4 + LICENSE | 21 ++++ README.md | 180 ++++++++++++++++++++++++++++++++++ mangohud-logger.sh | 233 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 438 insertions(+) create mode 100644 .gitignore create mode 100755 LICENSE create mode 100755 README.md create mode 100755 mangohud-logger.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6dcbbea --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# never commit a nested clone or local logs +mangohud-logger/ +mango-logs/ +*.csv diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..5acd172 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Gavin Nugent + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100755 index 0000000..2a66b80 --- /dev/null +++ b/README.md @@ -0,0 +1,180 @@ +# mangohud-logger + +One-touch toggle for MangoHud CSV logging on Linux gaming-launcher systems +(SteamOS Game Mode, DeckShift, super-alt-S, plain `gamescope-session-plus`, or +any setup that already uses MangoHud). + +Run the script — it switches logging on if it's off, off if it's on. CSV +samples land in `~/Downloads/mango-logs/`. That's the entire interface. + +## Why this exists + +Getting MangoHud to *actually* write logs from a Steam-via-gamescope game +turns out to be a multi-step setup, and every step has a non-obvious gotcha. +Worse, the *right* setup differs depending on which gamescope session manager +your distro ships. This script detects that and handles it so you don't have to. + +## What it changes + +The script writes marker-tagged files (so it can clean them up on disable, and +won't trample anything you wrote by hand): + +1. **`~/.config/MangoHud/MangoHud.conf`** — appends a marker-bracketed block + with `output_folder`, `autostart_log=1`, `log_duration=0` (= until you + quit), `log_interval=100`, `toggle_logging=Shift_L+F2`, and a + `no_display=0` override. (Also serves as the on/off state marker.) + +2. **`~/.config/environment.d/95-mangohud-logger.conf`** — contains: + - `MANGOHUD=1` so the Vulkan loader auto-injects MangoHud into every game. + - `MANGOHUD_CONFIG=`. This is the important + one for SteamOS Game Mode — see [Game Mode](#steamos-game-mode) below. + + Requires a logout/login to take effect (systemd-user only reads + `environment.d/*.conf` at session start). + +3. **`~/.config/gamescope-session-plus/sessions.d/steam`** — *only written on + the `gamescope-session-plus` session flavor* (see flavor detection below). + A user-level override that unsets the system's `MANGOHUD_CONFIGFILE` and + turns off `STEAM_USE_MANGOAPP`. Not written on stock SteamOS. + +When you toggle it off, the script removes everything it wrote. + +## Session-flavor detection + +Game Mode is launched differently on different distros, and the fix differs +too. On enable, the script detects which one you have (`detect_gs_flavor`): + +| Flavor | Detected by | What the script does | +|--------|-------------|----------------------| +| `plus` | `/usr/share/gamescope-session-plus/sessions.d/steam` exists | Writes the `sessions.d/steam` override (file #3). | +| `steamos` | `/usr/lib/steamos/gamescope-session` exists | Skips file #3; relies on `MANGOHUD_CONFIG` (file #2). See below. | +| `none` | neither | Desktop-only; `MANGOHUD=1` is enough for Vulkan games. | + +## SteamOS Game Mode + +Stock SteamOS (e.g. 3.x "holo") launches Game Mode via +`/usr/lib/steamos/gamescope-session`, **not** `gamescope-session-plus`. That +session script unconditionally does: + +```bash +export STEAM_USE_MANGOAPP=1 +export MANGOHUD_CONFIGFILE="$tmpdir/mangohud.config" # written with just "no_display" +``` + +and it **sources no user override file** — so the old `sessions.d/steam` trick +does nothing here, and you can't override `STEAM_USE_MANGOAPP` from +`environment.d` (the session exports it afterwards). + +**How logging actually works here:** the on-screen overlay in Game Mode is +**mangoapp** (a compositor overlay), and mangoapp *is* MangoHud — it can write +CSV logs itself. Steam launches mangoapp with your systemd user environment, so +it inherits the `MANGOHUD_CONFIG` from file #2. `MANGOHUD_CONFIG` takes +precedence over the session's `no_display` `MANGOHUD_CONFIGFILE`, so mangoapp +picks up `autostart_log`/`output_folder` and logs every game — **no per-game +launch options needed.** + +Notes: +- Logs are named **`mangoapp_.csv`** (the logger is mangoapp, not the + in-game layer), so a file spans a Game Mode session rather than one-per-game. +- **You must fully restart into Game Mode once after enabling** (log out/in or + reboot) so mangoapp inherits `MANGOHUD_CONFIG`. If you enable while already in + Game Mode, that session won't have it and nothing logs. +- Verified on SteamOS 3.9 with MangoHud 0.8.3-rc1: a Proton game (DREDGE) in + Game Mode produced a 1039-row `mangoapp_*.csv` of real fps/frametime data. + +**Per-game fallback** (if you don't want to restart the session, or want the +in-game Vulkan layer instead of mangoapp): set this in a game's +*Steam → Properties → Launch Options*: + +``` +MANGOHUD_CONFIGFILE=$HOME/.config/MangoHud/MangoHud.conf mangohud %command% +``` + +## Gotchas it works around + +- **`no_display` blocks `autostart_log`.** MangoHud's `autostart_log` rides on + the render hook that `no_display` disables, so the log never starts. The + script's block ends with `no_display=0`. Tradeoff: while logging is on, the + HUD is visible during games. + +- **The system session forces a `no_display` config file.** Both session + flavors export `MANGOHUD_CONFIGFILE` pointing at a temp file containing only + `no_display`, which would suppress your real config. On `plus` the script + unsets that var; on `steamos` the `MANGOHUD_CONFIG` env var overrides it. + +- **Steam uses `mangoapp` instead of the in-game Vulkan layer in Game Mode.** + `STEAM_USE_MANGOAPP=1`. On `steamos` you can't turn this off globally — so the + script leans into it and lets mangoapp do the logging (see above). On `plus` + the override sets `STEAM_USE_MANGOAPP=0` to use the in-game layer. + +- **`log_duration=0` means "log forever".** Empirically `0` = log until you quit + or hit the toggle key. + +- **`MANGOHUD=1` only injects the Vulkan layer.** OpenGL-only games still need + `mangohud %command%` in Steam launch options or an `LD_PRELOAD` wrapper. + +## Install + +Requires `mangohud` (and `lib32-mangohud` for 32-bit games). On Arch / SteamOS: + +```bash +sudo pacman -S mangohud lib32-mangohud +``` + +Then: + +```bash +git clone https://github.com/28allday/mangohud-logger-steamos.git +cd mangohud-logger-steamos +chmod +x mangohud-logger.sh +./mangohud-logger.sh +``` + +Or copy the script anywhere on `$PATH`: + +```bash +install -Dm755 mangohud-logger.sh ~/.local/bin/mangohud-logger +mangohud-logger +``` + +## Usage + +```bash +./mangohud-logger.sh +``` + +No flags, no subcommands. First run enables; second run disables. After the +first enable, **log out and back in** (Game Mode users: fully restart into Game +Mode) so the env vars become effective. You only need to do that once. + +In-game keys (set by the script's MangoHud block): + +| Key | Action | +|---|---| +| `Shift+F2` | Toggle logging on/off mid-game | +| Your existing `toggle_hud` key | Hide the HUD overlay (logging keeps running) | + +CSV files appear in `~/Downloads/mango-logs/`. Drop them into + for graphs, or open in a spreadsheet. + +## Compatibility + +- **SteamOS Game Mode** (`/usr/lib/steamos/gamescope-session`) — logs via + mangoapp; see [Game Mode](#steamos-game-mode). +- **DeckShift** ([28allday/DeckShift](https://github.com/28allday/DeckShift)) + — gamescope-session-plus based. +- **super-alt-S** ([28allday/super-alt-S-cachy-deckmode](https://github.com/28allday/super-alt-S-cachy-deckmode)) + — KDE-Plasma gaming mode on CachyOS. +- **Plain `gamescope-session-plus`** on any Arch-based distro. +- **Steam without gamescope** — works too; the gamescope override becomes a no-op. +- **Non-systemd distros** — the `environment.d` file won't be read; set + `MANGOHUD=1` (and `MANGOHUD_CONFIG`) via your distro's env-var mechanism. + +## Honours XDG + +Log folder is `$XDG_DOWNLOAD_DIR/mango-logs` (resolved via `xdg-user-dir`), +config dir is `$XDG_CONFIG_HOME` — works on non-English locales. + +## License + +MIT diff --git a/mangohud-logger.sh b/mangohud-logger.sh new file mode 100755 index 0000000..c79afc0 --- /dev/null +++ b/mangohud-logger.sh @@ -0,0 +1,233 @@ +#!/usr/bin/env bash +# +# mangohud-logger — toggle MangoHud CSV logging on/off +# +# Just run it: if logging is off it gets switched on, if it's already on +# it gets switched off. +# +# What it manages on enable: +# 1. ~/.config/MangoHud/MangoHud.conf +# → appends a marker block with output_folder, autostart_log, etc. +# → keeps your existing keys; overrides `no_display` while logging +# (autostart_log rides on the render hook that no_display disables). +# 2. ~/.config/environment.d/95-mangohud-logger.conf +# → contains MANGOHUD=1 so the Vulkan layer auto-loads into every +# game (gamescope sessions, Steam, native Vulkan apps). +# Picked up by systemd-user at next session login. +# +# Logs land in the user's Downloads folder under "mango-logs" +# (honours XDG_DOWNLOAD_DIR from ~/.config/user-dirs.dirs, else ~/Downloads). + +set -euo pipefail + +readonly MARKER_BEGIN="# >>> mangohud-logger BEGIN >>>" +readonly MARKER_END="# <<< mangohud-logger END <<<" + +CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/MangoHud" +CONFIG_FILE="$CONFIG_DIR/MangoHud.conf" + +ENV_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/environment.d" +ENV_FILE="$ENV_DIR/95-mangohud-logger.conf" + +GS_SESSION_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/gamescope-session-plus/sessions.d" +GS_SESSION_FILE="$GS_SESSION_DIR/steam" +GS_OWNED_MARKER="# Written by mangohud-logger." + +# Detect which gamescope (Game Mode) session manager is in use — this decides +# whether our env override is actually sourced at session start: +# plus → gamescope-session-plus sources ~/.config/gamescope-session-plus/ +# sessions.d/* after its own steam script, so our override works. +# steamos → /usr/lib/steamos/gamescope-session hard-exports +# STEAM_USE_MANGOAPP=1 and MANGOHUD_CONFIGFILE=/no_display, and +# sources NO user file. A global override is therefore impossible; +# logging requires per-game Steam launch options instead. +# none → no gamescope session found (desktop-only machine). +detect_gs_flavor() { + if [[ -e /usr/share/gamescope-session-plus/sessions.d/steam ]]; then + printf 'plus\n' + elif [[ -e /usr/lib/steamos/gamescope-session ]]; then + printf 'steamos\n' + else + printf 'none\n' + fi +} +GS_FLAVOR="$(detect_gs_flavor)" + +# The per-game launch-option string that makes logging work on the steamos +# flavor (repoints MANGOHUD_CONFIGFILE back at our config so autostart_log fires). +LAUNCH_OPT="MANGOHUD_CONFIGFILE=$CONFIG_FILE mangohud %command%" + +# Resolve Downloads via xdg-user-dir if available, else the user-dirs.dirs file, +# else fall back to ~/Downloads. This makes the script work on non-English locales. +if command -v xdg-user-dir >/dev/null 2>&1; then + DOWNLOADS_DIR="$(xdg-user-dir DOWNLOAD)" +elif [[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" ]]; then + # shellcheck disable=SC1090,SC1091 + source "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" + DOWNLOADS_DIR="${XDG_DOWNLOAD_DIR:-$HOME/Downloads}" +else + DOWNLOADS_DIR="$HOME/Downloads" +fi +LOG_DIR="$DOWNLOADS_DIR/mango-logs" + +err() { printf '\033[31merror:\033[0m %s\n' "$*" >&2; } +info() { printf '\033[36m::\033[0m %s\n' "$*"; } +ok() { printf '\033[32m✓\033[0m %s\n' "$*"; } +warn() { printf '\033[33m!\033[0m %s\n' "$*"; } + +if ! command -v mangohud >/dev/null 2>&1; then + err "mangohud is not installed — install it first (e.g. 'sudo pacman -S mangohud lib32-mangohud')." + exit 1 +fi + +mkdir -p "$CONFIG_DIR" +[[ -f "$CONFIG_FILE" ]] || : > "$CONFIG_FILE" + +if grep -qF "$MARKER_BEGIN" "$CONFIG_FILE"; then + # Currently enabled → strip the block and remove the env file. + tmp=$(mktemp) + awk -v b="$MARKER_BEGIN" -v e="$MARKER_END" ' + $0 == b { skip = 1; next } + $0 == e { skip = 0; next } + !skip { print } + ' "$CONFIG_FILE" > "$tmp" + # Trim trailing blank lines the block may have left behind. + sed -i -e :a -e '/^\s*$/{$d;N;ba' -e '}' "$tmp" + mv "$tmp" "$CONFIG_FILE" + + env_removed=0 + if [[ -f "$ENV_FILE" ]]; then + rm -f "$ENV_FILE" + env_removed=1 + fi + + # Only remove the gamescope session override if WE wrote it. + gs_removed=0 + if [[ -f "$GS_SESSION_FILE" ]] && head -1 "$GS_SESSION_FILE" | grep -qF "$GS_OWNED_MARKER"; then + rm -f "$GS_SESSION_FILE" + gs_removed=1 + fi + + ok "MangoHud logging disabled" + info "existing logs kept in: $LOG_DIR" + if (( env_removed )); then + info "removed: $ENV_FILE" + info "MANGOHUD env var stays set in the *current* session until logout" + fi + if (( gs_removed )); then + info "removed: $GS_SESSION_FILE" + fi +else + # Currently disabled → append the config block + write the env file. + # Check for pre-existing manual log keys *before* we write, so the warning is accurate. + manual_log_keys=0 + if grep -qE '^\s*(output_folder|autostart_log|log_duration|log_interval|toggle_logging)\s*=' "$CONFIG_FILE"; then + manual_log_keys=1 + fi + + mkdir -p "$LOG_DIR" + { + [[ -s "$CONFIG_FILE" ]] && printf '\n' + printf '%s\n' "$MARKER_BEGIN" + printf '# Added by mangohud-logger — remove this block to disable logging.\n' + printf 'output_folder=%s\n' "$LOG_DIR" + printf 'autostart_log=1\n' + printf 'log_duration=0\n' + printf 'log_interval=100\n' + printf 'toggle_logging=Shift_L+F2\n' + # Override any earlier `no_display` — MangoHud's autostart_log rides on the + # render hook, which `no_display` disables. Without this, logs never start. + printf 'no_display=0\n' + printf '%s\n' "$MARKER_END" + } >> "$CONFIG_FILE" + + # Inline config string mirroring the MangoHud.conf block. MANGOHUD_CONFIG takes + # precedence over MANGOHUD_CONFIGFILE, so this is what makes logging work GLOBALLY + # in SteamOS Game Mode — that session forces MANGOHUD_CONFIGFILE=/no_display, + # but it never sets MANGOHUD_CONFIG, so ours (from environment.d) wins for every game. + # NOTE: relies on output_folder having no commas/spaces (true for the default path). + MH_INLINE="output_folder=$LOG_DIR,autostart_log=1,log_duration=0,log_interval=100,toggle_logging=Shift_L+F2,no_display=0" + + mkdir -p "$ENV_DIR" + cat > "$ENV_FILE" </no_display, which would otherwise suppress our config and +# stop logging. This inline config wins over it, so autostart_log/output_folder take +# effect for every game with NO per-game launch options. Verified: MANGOHUD_CONFIG +# beats a no_display configfile (vkcube CSV log produced). +MANGOHUD_CONFIG=$MH_INLINE +EOF + + # Game Mode handling depends on which session manager is installed (see + # detect_gs_flavor). Only gamescope-session-plus sources our override file; + # the steamos flavor needs per-game launch options instead. + gs_action="" + if [[ "$GS_FLAVOR" == "plus" ]]; then + # gamescope-session-plus override — only write if file is absent or already + # ours, so we never trample a user-written override. + if [[ ! -f "$GS_SESSION_FILE" ]] || head -1 "$GS_SESSION_FILE" | grep -qF "$GS_OWNED_MARKER"; then + mkdir -p "$GS_SESSION_DIR" + cat > "$GS_SESSION_FILE" </no_display and STEAM_USE_MANGOAPP=1 with no user + # hook — so a global override is impossible. Remove any stale override we + # wrote on a previous (plus-flavor) run so it doesn't mislead. + if [[ -f "$GS_SESSION_FILE" ]] && head -1 "$GS_SESSION_FILE" | grep -qF "$GS_OWNED_MARKER"; then + rm -f "$GS_SESSION_FILE" + fi + gs_action="n/a — using MANGOHUD_CONFIG (global, no override file needed)" + else + gs_action="no gamescope session detected (desktop only)" + fi + + if (( manual_log_keys )); then + warn "pre-existing log-related keys found in config — they may override ours." + fi + + ok "MangoHud logging enabled" + info "log folder: $LOG_DIR" + info "toggle key: Shift+F2 (during a game)" + info "config file: $CONFIG_FILE" + info "env file: $ENV_FILE (MANGOHUD=1)" + info "session type: $GS_FLAVOR (gamescope override: $gs_action)" + info "note: while logging is on, the HUD becomes visible during games" + info " (autostart_log needs the render hook that no_display disables)" + + if [[ "$GS_FLAVOR" == "steamos" ]]; then + # SteamOS Game Mode forces MANGOHUD_CONFIGFILE=/no_display, but our + # MANGOHUD_CONFIG (set above in environment.d) overrides it for every game — + # so logging IS global here, no per-game launch options needed. + info "→ logging is GLOBAL via MANGOHUD_CONFIG — no per-game launch options needed." + warn "log out and back into Game Mode (or reboot) so environment.d takes effect." + info "while logging, the in-game MangoHud overlay will be visible (autostart_log" + info "needs the render hook that no_display disables) — that's expected." + info "fallback for a single game (e.g. if you don't want to restart the session):" + printf ' \033[1m%s\033[0m\n' "$LAUNCH_OPT" + else + warn "log out and back in (or restart your gamescope session) so the env var takes effect." + fi +fi