-
Notifications
You must be signed in to change notification settings - Fork 0
Steamos Session Launch
The supervisor behind session transitions: lifecycle, watchdog, signals and exit codes.
All runtime values (binary paths, log level, timeouts) are read from /etc/default/steamos_diy.conf via get_ssot_var(), which caches results in-process. If libcore.so is missing at utils.py import time, the process exits with code 127. See Utilities Engine for the full SSoT API.
Execution arguments are generated from a YAML configuration.
The _build_gamescope_args() function constructs the execution string:
-
Flag Injection: Parses the
flagslist from the YAML and appends them to thegamescopecommand. -
Environment Overrides: Injects custom variables from
env_vars(e.g.,STEAM_GAMESCOPE_VRR_SUPPORTED: "1") into the session environment. Configuration guidance (including the MangoHud/--mangoappcaveat) lives in Dynamic Gamescope Mapping. -
Safe Execution: Uses
subprocess.Popenwith argument arrays to prevent shell-injection vulnerabilities.
The session target is persisted to /var/lib/steamos_diy/next_session via write_atomic() (fdatasync + rename). See Utilities Engine for the write protocol.
-
Gaming Mode ➔ Desktop:
session_select.pywritesdesktopto the state file and callssteam -shutdown. The launcher detects the child exit and terminates;systemdrestarts it into Plasma. -
Desktop Mode ➔ Gaming:
session_select.pywritessteamto the state file and callsqdbus6 org.kde.Shutdown /Shutdown logout. The launcher restarts into Gamescope+Steam. -
Session Persistence: The state file is written in three cases: an explicit switch request (via
session_select.py), a successful validation (confirming the running target is stable), or a crash recovery (forcingdesktop). On reboot the system returns to whatever was last written.
Gamescope is launched with two hardcoded flags, followed by the user-defined YAML flags, and finally the Steam process after --:
gs_args = [gs_bin, "-e", "-f"] # hardcoded: embedded + fullscreen
# ... flags from config.yaml appended here ...
gs_args.extend(["--", steam_bin, "-gamepadui", "-steamos3"])-
-e: Runs Gamescope in embedded mode, integrating with the existing DRM/KMS session rather than creating a new display server. -
-f: Forces fullscreen output. -
-gamepadui: Launches Steam in Big Picture / Gamepad UI mode — the controller-friendly interface optimised for TV/couch use. -
-steamos3: Activates SteamOS-specific features, including system update channels and controller mapping.
After Gamescope is spawned, the launcher checks post_start_cmds from config.yaml. If the list is non-empty, a daemon thread is started that sleeps POST_START_DELAY seconds (SSoT, default 2.0s) and then fires each command via spawn_native (detached, start_new_session=True). The delay is shorter than VALIDATION_TIMEOUT, so all commands are executed before the session is declared stable.
This mechanism is designed for runtime calls that require the Gamescope socket to be open (e.g. gamescopectl). Commands are only dispatched for the steam session target — the Plasma desktop session does not trigger this hook.
-
Tags used by this module:
STEAM—POST_START_CMD: <cmd>logged at INFO after each command fires.
The supervisor uses an event-driven mechanism to monitor process health and prevent boot loops.
-
Process monitoring: Uses
proc.wait()instead of a polling loop. If the session exits beforeVALIDATION_TIMEOUTelapses, it is treated as a crash. -
Validation Window: Governed by
VALIDATION_TIMEOUTin the SSoT configuration. Default is5.0s; lower values (e.g.3.0s) suit fast NVMe storage. -
Emergency Recovery: If a crash occurs within the validation window, the supervisor writes
desktopto the state file atomically and terminates the process. Termination sendsSIGTERMand waits up toTERM_TIMEOUTseconds (SSoT, default5); if the process doesn't exit,SIGKILLis sent.
The launcher registers handlers for SIGTERM and SIGINT at startup. The exit code controls whether systemd restarts the service:
| Event | Handler | Exit code | systemd effect |
|---|---|---|---|
systemctl stop / SIGTERM / SIGINT
|
_handle_term — terminates child gracefully, then exits |
0 |
No restart (Restart=on-failure does not trigger on 0) |
| Session ended naturally (switch or crash recovery) |
run() — exits after NOTIFY_DELAY pause |
75 (EX_TEMPFAIL) |
Restart triggered — launcher re-reads next_session and spawns the new target |
NOTIFY_DELAY (SSoT, default 0.4s) is a brief sleep inserted between the TTY transition message and sys.exit(75), giving the user time to read the message before the service restarts.
If you love this project, feel free to join and help me make it better!