Repository navigation
fix(linux): Play from a desktop launcher no longer has systemd stop the game - #2540
Conversation
…he game A desktop that starts each launcher as a systemd user service stops that service, and the game still in its control group, when the Builder window closes after Play. Start the game in a scope of its own with systemd-run --user --scope when the Builder runs in a .service and a trial run works; otherwise start it as before. Fixes NotRexed#2529 Assisted-by: Claude Code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Tested on COSMIC (Pop!_OS 24.04, systemd 255, NVIDIA 595.104.02, RTX 3070 Ti). Before this PR, Play from the launcher run as a user service ( With this PR's Caveat: this ran the PR's builder from source, not a packaged AppImage built from it. |
|
Thanks for the fix, and @toniosim for the analysis. I tested the merged change on Arch (Hyprland, systemd 262.1) with a transient user service shaped like the issue's launcher service and a stand-in for the game: on its base the child was killed 90.1 s after Play; with the fix it was still alive in its own scope after 96 s. So it fixes the reported case here. Two cases are left, both shown by injection (not an estimate of how often they happen):
#2643 is a small follow-up for both: Play waits (at most two seconds, usually no time at all) until the real runner has left the launcher's control group before the window closes, and starts the game the plain way exactly once if that does not happen. Ten ordinary launches behave as before on both. Not tested: a real desktop's Play click, GNOME, KDE or COSMIC, and a real log-out. |
Wait for the real runner to leave the launcher's cgroup before Play closes the Builder. Gate the command on permission from the parent so a timed-out runner cannot start another game after plain fallback. Keep the existing scope choice and record a failed hand-off in startup diagnostics. Cover failed, delayed, timed-out and immediately ready runners with an injected clock, plus the real diagnostics fallback notice. Refs NotRexed#2529 Refs NotRexed#2540 Assisted-by: GPT-6 Astra Co-Authored-By: GPT-6 Astra <noreply@openai.com>
….2.0 Kept: this fork's low-end VRAM defaults, the handheld texture limits (with upstream's small-VRAM mips rule), the Steam fixes, the native file choosers and the name. Taken from upstream: its Linux sleep timer (NotRexed#2637, this fork's handheld rule), the RADV mesh default (NotRexed#2362), the systemd scope for Play (NotRexed#2540). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C7fdTPXbwDRohew9qanvbp
Upstream NotRexed#2529, NotRexed#2540 and NotRexed#2643 describe the scope hand-off; players should know the window waits for it and that the fallback cannot bypass the desktop's service lifetime.
On Linux desktops that start each launcher as a systemd service (KDE/SteamOS, GNOME, COSMIC), pressing Play in the launcher window starts the game and then systemd kills it about 90 seconds later, which players see as a silent crash.
Fixes #2529
What changed
builder/linux/hookshot_builder.py: newown_scope(). When the Builder is running inside a.servicecgroup andsystemd-runis available, Play starts the game throughsystemd-run --user --scope --collect --quiet --, so it lives in a scope of its own and the launcher's service can stop without it. A trialsystemd-run ... trueruns first; if the user manager can't be reached (or there is no systemd, or the launcher is in a scope or a terminal), Play starts the game exactly as before.tests/test_linux_play_scope.py(new, game-free, runs on Linux and Windows): when the scope is used and when it isn't (scope / cgroup v1 / no/proc/ nosystemd-run/ failing trial / timeout), and thatplay()puts the prefix in front of both the diagnostics script and the direct-exe fallback.tests/test_linux_play_diagnostics.py: stubsown_scopein the namespace it buildsplay()in.The game not exiting on SIGTERM in a windowed run (also noted in the issue) is left for a separate PR.
How it was tested
python -I -B tests/run_all.py play builderon Windows: all pass (the Linux-only checks skip there).systemd-run --user --unit=... python3 launcher.py, the reporter's setup) that starts a long-lived child withstart_new_session=Trueand exits:app.slice/run-p<pid>-i<n>.scope.Assisted-by: Claude Code
🤖 Generated with Claude Code