Support SteamOS Game Mode: global logging via MANGOHUD_CONFIG

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=<tmp>/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) <noreply@anthropic.com>
This commit is contained in:
Gavin Nugent 2026-06-28 22:29:50 +01:00
commit aeb84a3d68
4 changed files with 438 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
# never commit a nested clone or local logs
mangohud-logger/
mango-logs/
*.csv

21
LICENSE Executable file
View file

@ -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.

180
README.md Executable file
View file

@ -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=<inline copy of the logging keys>`. 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_<timestamp>.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
<https://flightlessmango.com/> 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

233
mangohud-logger.sh Executable file
View file

@ -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=<tmp>/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=<tmp>/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" <<EOF
$GS_OWNED_MARKER Delete this file (or re-run the script) to remove.
# MANGOHUD=1 makes the Vulkan loader inject the MangoHud layer into every game.
MANGOHUD=1
# MANGOHUD_CONFIG (inline) OVERRIDES MANGOHUD_CONFIGFILE. SteamOS Game Mode forces
# MANGOHUD_CONFIGFILE=<tmp>/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" <<EOF
$GS_OWNED_MARKER
# Sourced AFTER /usr/share/gamescope-session-plus/sessions.d/steam (last wins).
#
# Without this:
# 1. The system steam session script exports MANGOHUD_CONFIGFILE pointing to a
# temp file containing only "no_display", which suppresses our user
# ~/.config/MangoHud/MangoHud.conf and so autostart_log/output_folder
# never take effect. Unsetting the var falls back to the user file.
# 2. STEAM_USE_MANGOAPP=1 makes Steam ask gamescope to use the mangoapp
# compositor overlay instead of the in-game MangoHud Vulkan layer.
# The Vulkan layer is the one that writes CSV logs.
unset MANGOHUD_CONFIGFILE
export STEAM_USE_MANGOAPP=0
export STEAM_MANGOAPP_PRESETS_SUPPORTED=0
EOF
gs_action="written"
else
gs_action="left alone (you have a custom override at $GS_SESSION_FILE)"
fi
elif [[ "$GS_FLAVOR" == "steamos" ]]; then
# This build uses /usr/lib/steamos/gamescope-session, which hard-exports
# MANGOHUD_CONFIGFILE=<tmp>/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=<tmp>/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