fix(console): make a failed Steam session diagnosable, add shortcut tooling

Autologin into the gamescope Steam session does not take on the real
hardware; the television lands on ReGreet instead. The config side is
correct -- initial_session is present in the generated greetd.toml and
services.greetd.restart evaluates to false -- so the likely cause is the
session starting and then dying, which greetd answers by falling through
to default_session.

greetd hands a session the VT for its standard streams, so whatever the
session prints on its way down is erased as soon as the greeter repaints.
Run the launcher under systemd-cat so the reason survives in the journal
under the steam-session identifier.

Steam's non-Steam-game Browse dialog and its library-folder picker both
go through the XDG portal FileChooser interface and do not open here
(ValveSoftware/steam-for-linux#9447, #10415). Add steamtinkerlaunch,
which writes shortcuts.vdf without the dialog, and document
library_folder_add as the equivalent route for the library folder.

Docs also catch up with the host's actual state: hardware-configuration
.nix is a real scan now, standalone pcsx2 is installed, and the untested
claims become the list of open problems.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Emma Thorpe
2026-09-01 10:04:26 +01:00
co-authored by Claude Opus 5
parent db0867ecc7
commit f35cc58bbb
2 changed files with 116 additions and 38 deletions
+100 -32
View File
@@ -9,17 +9,12 @@ A 4th-generation Core i7 (Haswell) on a UEFI board with an NVIDIA GeForce GTX
is driven from the sofa with a Bluetooth controller; keyboard and mouse are
supported but secondary.
## Not installed yet
## Disk
`hardware-configuration.nix` in this host directory is a **placeholder**, not a
hardware scan. It exists so the flake evaluates in CI and assumes the install
labels its partitions `nixos` (root, ext4) and `BOOT` (ESP, vfat). Replace the
whole file with the output of `nixos-generate-config` run on the machine and
commit that. If the labels do not match, the boot fails on a missing device
rather than touching the wrong disk.
Partition the disk GPT with an ESP (vfat, 512 MB is comfortable). Nothing else
in the host config depends on the disk layout.
`hardware-configuration.nix` is a real `nixos-generate-config` scan from the
installed machine: GPT with a vfat ESP at `/boot` and an ext4 root, both
addressed by UUID, and no swap device. Nothing else in the host config depends
on the disk layout.
## Bootloader
@@ -88,7 +83,7 @@ to ReGreet, where the ordinary Sway session is available for everything else.
`services.greetd.restart` defaults to `false` once `initial_session` is set,
which is what stops a logout looping straight back into autologin.
Two details worth knowing:
Three details worth knowing:
- The launcher is referenced as `/run/current-system/sw/bin/steam-gamescope`.
The steam module builds that script privately and only adds it to
@@ -96,17 +91,73 @@ Two details worth knowing:
- `programs.gamescope.capSysNice = true` installs gamescope as a setcap wrapper
in `/run/wrappers/bin` instead of the system profile. That path precedes the
system profile on `PATH`, so `steam-gamescope` still resolves it.
- greetd gives the session the VT for its standard streams, so anything the
Steam session prints as it dies is erased the moment the greeter repaints.
`initial_session` therefore runs the launcher under `systemd-cat`, and the
output is readable after the fact with `journalctl -b -t steam-session`.
### When the television lands on ReGreet instead of Steam
greetd starts `initial_session` **once per boot** — the check is the presence of
`/run/greetd.run` — and if the session process starts and then exits, greetd
falls through to `default_session`, i.e. ReGreet. A gamescope that dies on
startup is therefore indistinguishable from autologin never having run, unless
the logs are read:
```sh
journalctl -b -t steam-session # what the session itself said
journalctl -b -u greetd # whether greetd started it at all
ls -l /run/greetd.run # exists => greetd has already had its one go
```
If greetd never even reaches the session, it prints `unable to start greeter`
and **exits** — there is no greeter to fall back to and the screen stays blank.
Recover over SSH (`modules/ssh.nix`), or hold space at power-on and boot the
previous generation.
To reproduce a session failure by hand, switch to a free VT and run
`steam-gamescope` there. Running it from inside Sway is not the same test:
gamescope picks its nested Wayland backend when `WAYLAND_DISPLAY` is set, and it
is the DRM backend — the one the boot session uses — that is in question.
The greeter is **Dvorak**, like every other host here (`modules/sway.nix` forces
`XKB_DEFAULT_VARIANT=dvorak` on cage). Only relevant if someone else has to type
a password.
### Adding RetroArch and Clone Hero to Big Picture
### Adding RetroArch, PCSX2 and Clone Hero to Big Picture
Both are installed system-wide but are not Steam titles. Add each once, from a
Sway session, via Steam's **Games → Add a Non-Steam Game**; they then appear in
Big Picture and inherit Steam Input, so the controller works in them without
further configuration.
All three are installed system-wide but are not Steam titles. Added as Steam
shortcuts they appear in Big Picture and inherit Steam Input, so the controller
works in them without further configuration.
Steam's own **Games → Add a Non-Steam Game** is the documented route and is also
the one that tends not to work. Both that dialog's **Browse** button and the
library-folder picker below go through the XDG desktop portal `FileChooser`
interface; when the portal is unreachable the client silently falls back to a
legacy chooser that is blank or inert — Valve
[#9447](https://github.com/ValveSoftware/steam-for-linux/issues/9447) and
[#10415](https://github.com/ValveSoftware/steam-for-linux/issues/10415), neither
fixed. Check the portal side before blaming the client:
```sh
busctl --user introspect org.freedesktop.portal.Desktop \
/org/freedesktop/portal/desktop | grep FileChooser
```
`modules/sway.nix` enables `xdg-desktop-portal-gtk`, which is the backend that
implements `FileChooser` (`xdg-desktop-portal-wlr` implements only screenshot
and screencast), and the generated `sway-portals.conf` routes everything but
those two to `gtk`. If the interface is missing from the introspection above,
the backend is not running and that is the fault to chase.
Independent of the dialog, `steamtinkerlaunch` writes `shortcuts.vdf` directly:
```sh
# Steam must be closed -- it rewrites shortcuts.vdf on exit.
steamtinkerlaunch addnonsteamgame -an="RetroArch" -ep="$(command -v retroarch)"
steamtinkerlaunch addnonsteamgame -an="PCSX2" -ep="$(command -v pcsx2-qt)"
steamtinkerlaunch addnonsteamgame -an="Clone Hero" -ep="$(command -v clonehero)"
```
## Emulation — RetroArch
@@ -167,13 +218,12 @@ system directory on this host:
Be honest about the two heaviest cores. `dolphin` and `pcsx2` are libretro ports
of emulators whose upstream effort goes into their **standalone** builds; the
cores lag on compatibility and are the first place to look when a GameCube, Wii
or PS2 title misbehaves. If a game does not cooperate, add the standalone
emulators to `environment.systemPackages` in `gaming.nix`:
or PS2 title misbehaves.
```nix
pkgs.dolphin-emu # GameCube / Wii
pkgs.pcsx2 # PlayStation 2
```
That has already happened for PS2: the LRPS2 core was not good enough in
practice, so **standalone `pkgs.pcsx2` is installed** in `gaming.nix` alongside
the core and is what PS2 titles should be run through. `pkgs.dolphin-emu` is not
installed; add it the same way if GameCube or Wii turns out to need it.
Both are controller-driven and can be added to Big Picture the same way as
RetroArch. The hardware is not the limit here — a GTX 1070 and a Haswell i7 run
@@ -226,7 +276,19 @@ RetroArch is pointed at these paths declaratively. The other two have to be told
once, in their own UIs:
- **Steam** — Settings → Storage → the `+` control → add `/srv/games/steam` as a
library folder. Games installed there survive a reinstall of the OS.
library folder. Games installed there survive a reinstall of the OS. That
picker is the broken one described under
[Adding RetroArch, PCSX2 and Clone Hero to Big Picture](#adding-retroarch-pcsx2-and-clone-hero-to-big-picture);
when it comes up blank, use Steam's own console instead — open Steam with
`steam -console`, pick the **CONSOLE** tab and run:
```
library_folder_add /srv/games/steam
```
The directory must already exist and be writable by the user Steam runs as,
which `systemd.tmpfiles` guarantees. The command does not create it.
- **Clone Hero** — set the song library path to `/srv/games/clonehero/songs` from
its settings screen. Clone Hero keeps its own config in `~/.clonehero`.
@@ -311,17 +373,23 @@ wired 360 pads — as well as `mt76x2u`, and pulls in proprietary dongle firmwar
Not worth the side effects unless that dongle is actually in use, and if it ever
is, the 360 pads have to be re-tested.
## Untested claims
## Open problems
This host has not been built or booted yet. Two things are worth watching on
first boot:
The machine is installed and Sway works. Still outstanding:
- **gamescope on the proprietary NVIDIA driver.** `gaming.nix` sets
`GBM_BACKEND=nvidia-drm` and `__GLX_VENDOR_LIBRARY_NAME=nvidia` for the
session, which is the standard fix, but the combination has a history of
needing tweaks. If the session dies at startup, switch the greeter back to
interactive by commenting out `services.greetd.settings.initial_session`, log
into Sway, and read `journalctl --user -b`.
- **Autologin into Steam does not happen** — the television comes up on ReGreet.
Not yet root-caused; the config side is correct (`initial_session` is present
in the generated `greetd.toml` and `services.greetd.restart` evaluates to
`false`), so the suspicion is that gamescope starts and dies on the
proprietary NVIDIA driver, which greetd treats as a finished session and
answers by starting the greeter. `gaming.nix` sets `GBM_BACKEND=nvidia-drm`
and `__GLX_VENDOR_LIBRARY_NAME=nvidia`, the standard fix, but the combination
has a history of needing more. Read `journalctl -b -t steam-session` first —
see [When the television lands on ReGreet instead of Steam](#when-the-television-lands-on-regreet-instead-of-steam).
- **Steam's file pickers do not open** — both the non-Steam-game **Browse**
dialog and the library-folder picker. Upstream client bugs with usable
workarounds; see the two sections above.
- **Boot into Big Picture has not been exercised**, since autologin has not run.
- **Television resolution and refresh.** gamescope takes the output's native
mode by default. Add `gamescopeSession.args = [ "-W" "3840" "-H" "2160" "-r"
"60" ]` if a specific mode is wanted.
+16 -6
View File
@@ -61,6 +61,14 @@ let
"clonehero/backgrounds"
];
# greetd gives a session the VT for stdio, so a failed Steam session leaves no
# evidence once the greeter takes the VT back. Log to the journal instead:
# `journalctl -b -t steam-session`.
steamSession = pkgs.writeShellScript "steam-session" ''
exec ${pkgs.systemd}/bin/systemd-cat --identifier=steam-session \
/run/current-system/sw/bin/steam-gamescope
'';
# RetroArch and the cores this machine is expected to run. The wrapper already
# points RetroArch at the packaged assets, core info and joypad autoconfig
# profiles; `settings` here is merged on top of those.
@@ -178,13 +186,11 @@ in
# ask for it; Steam's Proton builds and most native titles do.
programs.gamemode.enable = true;
# Autologin into the Steam session. The launcher is not exposed as a package
# by the steam module -- it is built inside it and added to
# environment.systemPackages -- so reference it through the system profile.
# greetd's `restart` option defaults to false once initial_session is set,
# which is what stops a logout from looping straight back into autologin.
# Autologin into the Steam session. greetd's `restart` option defaults to
# false once initial_session is set, which is what stops a logout from looping
# straight back into autologin.
services.greetd.settings.initial_session = {
command = "/run/current-system/sw/bin/steam-gamescope";
command = "${steamSession}";
user = tvUser;
};
@@ -242,6 +248,10 @@ in
pkgs.clonehero
pkgs.pcsx2
pkgs.dualsensectl # DualSense LED/battery/mic control from the shell
# Writes shortcuts.vdf from the shell. Steam's own "Add a Non-Steam Game"
# dialog depends on a working portal file picker and frequently does not
# open; see docs/hosts/console.md.
pkgs.steamtinkerlaunch
pkgs.mangohud # FPS/frametime overlay; use `mangohud %command%` in Steam
pkgs.vulkan-tools # vulkaninfo, for checking the 32/64-bit ICDs Proton needs
];