The previous --merge-dupes resolved a mode (option key) defined more than once by keeping the last value and silently discarding the earlier ones, and it only acted when there were duplicate section headers — so a mode repeated inside a single section (e.g. two `wayland =` lines) was flagged by --verify but had no automated fix, even though the error pointed users at --merge-dupes. Now the repair is non-destructive and covers both cases: duplicate [section] blocks are folded into their first occurrence, and a mode defined more than once (across those blocks or within one section) keeps its first occurrence under the original name while every later one is renamed to <mode>-DUPE-000N. Renaming preserves the delimiter, spacing and value verbatim. The report lists each rename so the duplicates can be reconciled by hand. --verify / parse-error hints and the game-launcher dialog now mention duplicate modes too. Also note in the README that, because Steam stores launch options per account, this centralises them in one per-user config so nothing has to be copied between accounts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| bin | ||
| config | ||
| docs | ||
| tests | ||
| .gitignore | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| Makefile | ||
| README.md | ||
game-launcher
A small per-game launch-options manager for Steam on Linux.
Steam only lets you set one launch-option string per game, and editing it for
tweaks (different resolutions, wrappers like gamemoderun/mangohud/gamescope,
Proton env vars, pre/post hooks) is tedious. This project moves all of that into
a single config file and an optional picker dialog, so one Steam launch option —
game-launcher %command% — drives every game.
Steam's launch options are also stored per account, so if you have more than one
account you normally have to re-enter (and keep in sync) the same long, fiddly
strings for every game on each one. Here they live in a single per-user config
file instead: set game-launcher %command% once per game on each account and
all the real options come from ~/.config/launch-options.ini — nothing to copy
between accounts.
Components
| Component | Installed to | Role |
|---|---|---|
launch-options |
/usr/local/bin/launch-options |
Python CLI. Resolves a game alias or Steam AppID to a canonical key and prints the launch command for a named mode, or lists games/modes. |
game-launcher |
/usr/local/bin/game-launcher |
Bash wrapper you set as a game's Steam launch option. Reads $SteamAppId, asks launch-options for the game's modes, shows a yad picker when there's more than one, substitutes %command%, and runs it. |
launch-options.ini |
~/.config/launch-options.ini |
Your data: alias map plus a section of named modes per game. |
Registering a game is built into the CLI: launch-options --add lists your
installed Steam games, lets you pick one, asks for a friendly name, and adds the
alias (name + AppID) plus a starter section to your config.
The scripts install to a system directory (/usr/local/bin) rather than
~/.local/bin because native Steam doesn't reliably put ~/.local/bin on the
PATH it launches games with, so game-launcher %command% wouldn't resolve.
/usr/local/bin is on the standard system PATH that Steam inherits.
launch-options and launch-options.ini are usable on their own (e.g. to print
a command); game-launcher is the Steam integration layer on top.
Install
sudo make install
sudo is needed because the scripts go to /usr/local/bin (a system directory
on Steam's PATH). The config still lands in the invoking user's
~/.config/launch-options.ini — the Makefile detects $SUDO_USER so it never
ends up in /root — and is installed only if you don't already have one
(your existing config is never clobbered).
Override locations with the standard variables:
sudo make install PREFIX=/usr # scripts to /usr/bin
sudo make install BINDIR=/opt/bin
make install-config CONFIGDIR=~/.config # config only, no sudo
Other targets:
make install-config— install just the config (only if absent), no sudo.make install-config-force— (re)install the bundled config, overwriting the existing one.sudo make uninstall— remove the scripts (your config is left in place).make help— list targets and show the resolved install paths.
Requirements
python3(standard library only) forlaunch-options.bashandyadforgame-launcher's picker.- Whatever each mode invokes —
gamemoderun,mangohud,gamescope, etc. - Native Steam (the scripts target
/usr/local/binon itsPATH). Flatpak Steam runs sandboxed and won't see host/usr/local/binwithout extra setup. - Optional:
fzffor a nicer game picker inlaunch-options --add(it falls back to a numbered menu without it).
Usage
-
In Steam, set the game's launch option to:
game-launcher %command%(Run
game-launcherin a terminal with noSteamAppIdset and it prints this line for you.) -
Add the game to
~/.config/launch-options.ini. The quickest way is:launch-options --addIt lists your installed Steam games, lets you pick one, asks for a friendly name, and writes the alias (name + AppID) plus a starter section for you. Then edit that section to define the game's modes (see below).
To add a game by hand, map its Steam AppID to a game key in
[aliases]and give that key a section. The AppID mapping is whatgame-launcherneeds to resolve the game from$SteamAppId; a friendly alias alongside it is optional but usually preferred so you can refer to the game by name. Every lookup goes through[aliases]— a section name is never an implicit alias, so the self-mappingcs2 = cs2is what makeslaunch-options cs2resolve (without it you'd have to use the AppID). For CS2 (AppID730):[aliases] ; cs2 = cs2 is the optional friendly alias; 730 = cs2 maps the AppID (required) cs2 = cs2 730 = cs2 [cs2] default = %command% -
Launch from Steam. If the game has more than one mode, a picker appears; otherwise it launches directly. Each run is logged to
/tmp/game-launcher.log.
You can also query the config directly:
launch-options cs2 # print the 'default' mode's command for cs2
launch-options cs2 fullscreen # print the 'fullscreen' mode's command
launch-options --list # list every game and its modes
launch-options --list cs2 # list modes for one game
launch-options --verify # check the config for problems
launch-options --merge-dupes # fold duplicate sections, rename duplicate modes
Checking and repairing the config
configparser rejects a config with a duplicate [section] header or a mode
(key) defined more than once. launch-options --verify reports such problems
(with line numbers) in plain language instead of a traceback, and also warns
about softer issues like an alias that points to a section that doesn't exist:
$ launch-options --verify
launch-options.ini has 1 problem(s) that stop it from loading:
• line 132: duplicate key 'wayland' in [cs2] (first defined at line 128)
Run `launch-options --merge-dupes` to fold duplicate sections together and rename duplicate modes.
launch-options --merge-dupes repairs both, non-destructively — nothing you
wrote is thrown away:
- Duplicate
[section]blocks are folded into their first occurrence. - A mode defined more than once (across those blocks, or repeated inside a
single section) keeps its first occurrence under the original name; each later
one is renamed to
<mode>-DUPE-000N. The command output tells you exactly which modes were renamed, e.g.[cs2] wayland → wayland-DUPE-0001, so you can reconcile the duplicates and delete the extras.
It writes a .bak backup before rewriting. When game-launcher hits an
unloadable config it runs --verify first and offers this same repair in a
yad dialog, rather than launching the game with none of your options applied.
Config format
~/.config/launch-options.ini is an INI file with three kinds of section:
[aliases], one [<game-key>] section per game, and an optional [vars].
[aliases] maps any string — a short name or a Steam AppID — to a
canonical game key. Mapping the AppID is what lets game-launcher resolve a game
from $SteamAppId automatically.
[aliases]
; 730 is CS2's Steam AppID
cs2 = cs2
730 = cs2
Comments must be on their own line.
configparserhere has inline comments disabled, so a;after a value is kept as literal text (and in a mode command it's a shell command separator) — not a comment.
[<game-key>] sections define named modes. Each value is a command line
containing the %command% placeholder, which is replaced with the actual game
invocation Steam would have run. Env vars, wrappers, pre/post hooks (separated by
;), and trailing game args all go here.
[cs2]
default = SDL_VIDEO_DRIVER=wayland mangohud %command% -w 1920 -h 1440
overlay = SDL_VIDEO_DRIVER=x11 mangohud %command% -w 1920 -h 1440
fullscreen = %command% -w 2560 -h 1440
A game with a single default mode launches without a prompt; multiple modes
trigger the picker. Game keys are matched case-insensitively with whitespace
collapsed. Override the config path with the LAUNCH_OPTS_INI environment
variable.
[vars] (optional) holds reusable fragments, referenced as %name% in any
mode and expanded before launch. Handy for something shared across a game's modes:
[vars]
autoexec = +exec autoexec.cfg
[cs2]
default = mangohud %command% %autoexec% -w 1920 -h 1440
plain = %command% %autoexec% -w 1920 -h 1440
Only names defined in [vars] are expanded — %command% and shell syntax
($VAR, ${VAR}, $(...)) pass through untouched. Vars may reference other
vars. (This is a small custom mechanism, not configparser interpolation, which
would collide with %command% and shell ${...}.)
Security note
game-launcher evals the selected mode string, so the config file is trusted,
executable input. Only put commands in it you'd be willing to run yourself.
