0.31.0's idled takeover left nothing watching for "Switch to Desktop" — Nobara lost the switch, and both distro families leaked an idled Game Mode #358

Merged
enricobuehler merged 2 commits from worktree-nobara-gamemode-switch into main 2026-08-20 17:51:49 +00:00
Owner

Field report 2026-08-20: "switching to KDE from game mode is broken on Nobara" on 0.31.0 — the thing #342 had just fixed for Bazzite.

Root cause: a gate that quietly became unreachable code

c2f5e91b replaced the display-manager stop with the idled autologin takeover, and deleted the two lines the old DM stop carried — record_session_select_baseline() and *STOPPED_DM = Some(dm). 38a0f54b then removed every remaining writer of that static.

create_managed_session still gated the in-stream "Switch to Desktop" handler on it, so honor_session_select_switch became dead code on every box. cargo check cannot see this: the static is still read, just never written.

Why Bazzite did not notice — the two families switch by completely different mechanisms

Measured on both boxes today:

Bazzite .41 Nobara .136
os-session-select thin wrapper → steamosctl switch-to-desktop-mode (D-Bus) writes the sentinel, then pkexecs itself and seds /etc/plasmalogin.conf
~/.config/steamos-session-select 🛑 never written — verified absent before and after a completed switch written unconditionally; mtime advanced at the exact second of the switch
launcher tail (/usr/bin/gamescope-session-plus) ends after unset-environment 🛑 exec pkexec "$0"systemctl restart plasmalogin — this is Nobara's switch
is_steam_htpc_platform() → session watcher matches bazziteON, follows the switch itself 🛑 ID=nobaraOFF

Bazzite is covered by the watcher and writes no sentinel. Nobara is covered by the sentinel and has no watcher. Fixing or breaking one says nothing about the other — which is exactly how this shipped.

On Nobara the switch therefore went entirely unhandled: the capture loss it causes drives a rebuild, and the rebuild relaunches game mode over the booting desktop. The "thrown back in" report of 2026-07-24, reopened.

Second bug, and this one affects Bazzite too

The takeover replaces the box's game-mode ExecStart with a sleep. The disconnect restore sweeps that drop-in — but a switch is not a disconnect. Measured on Bazzite .41: after a completed switch to KDE the unit still carried ExecStart=/usr/bin/sleep infinity, so the user's next "Return to Gaming Mode" starts a unit that does nothing at all — on glass, indistinguishable from broken hardware.

That is the same barred way back release_autologin_mask already exists to prevent for the mask this drop-in replaced, so the hand-back goes there (covering the watcher-driven distros) and in honor_session_select_switch (covering the sentinel-driven ones). There are exactly two ways a box leaves our takeover mid-stream; both now owe it the same hand-back.

The change

  • gate on takeover_idled() (reads IDLE_DROPIN_ARMED) instead of the retired STOPPED_DM;
  • record_session_select_baseline() where the idle drop-in is installed — the arming moment;
  • both hand-back paths remove_idle_dropin();
  • STOPPED_DM documented as what it has become: adoption-only state for a takeover stranded by a pre-0.31.0 host. Its DM restore + os-session-select desktop replay moves to replay_switch_under_restored_dm, reached only for such an adopted takeover — a 0.31.0 takeover leaves the DM up precisely so the OS does that work itself, and on the VM it does.

One file, +192/−50.

Verification

On the Nobara VM (123 on home-node-3), against live systemd — a temporary #[ignore] test driving the real path, stop_autologin_sessions() → real steamos-session-select plasmacreate_managed_session():

[onbox] steamos-session-select plasma -> ExitStatus(unix_wait_status(0))
[onbox] create_managed_session -> the user switched the box to the desktop session — the box's
        own game mode is handed back; re-detection follows the desktop compositor as it comes up
[onbox] sessions after the switch: 72=KDE
test result: ok. 1 passed

…the drop-in gone, and the box on glass in KDE.

Negative control — backed out to the 0.31.0 shape on the same box and re-ran:

panicked at gamescope.rs:6290: the switch did not advance ~/.config/steamos-session-select

which is the blindness itself: the sentinel did advance, 0.31.0 just had no baseline to see it against.

Bazzite non-regression is provable, not probabilistic. session_select_mtime() is None there permanently, so session_select_requested() can never be true and the gate can never fire. Confirmed empirically on .41, and confirmed the switch still reaches Desktop=KDE under an idled takeover with the DM alive.

Gates: cargo fmt --all --check clean · scripts/xcheck.sh linux clippy (--all-targets -D warnings) clean · 247 pf-vdisplay tests green natively on Linux · the committed guard the_mask_comes_off_only_when_the_box_takes_itself_back, extended to cover the idle drop-in, passes against a live user manager and fails without the hand-back.

Not exercised: a live streaming client. There is no headless client (the CLI shells out to an SDL3+Vulkan renderer), so the on-box test drives the same host functions a client connect and rebuild drive, but the video pipeline itself was not in the loop.

⚠ Pre-existing Nobara noise, unchanged by this PR: because its launcher restarts plasmalogin when the unit stops, our takeover causes one DM restart + relogin per connect. It converges (unit active (running) on the sleep, DM active, no loop), but for ~20 s afterwards os-session-select picks an empty session_launcher — so any on-box test must let the box settle first.

Field report 2026-08-20: **"switching to KDE from game mode is broken on Nobara"** on 0.31.0 — the thing #342 had just fixed for Bazzite. ## Root cause: a gate that quietly became unreachable code `c2f5e91b` replaced the display-manager stop with the idled autologin takeover, and deleted the two lines the old DM stop carried — `record_session_select_baseline()` and `*STOPPED_DM = Some(dm)`. `38a0f54b` then removed every remaining writer of that static. `create_managed_session` still gated the in-stream "Switch to Desktop" handler on it, so **`honor_session_select_switch` became dead code on every box**. `cargo check` cannot see this: the static is still read, just never written. ## Why Bazzite did not notice — the two families switch by completely different mechanisms Measured on both boxes today: | | **Bazzite `.41`** | **Nobara `.136`** | |---|---|---| | `os-session-select` | thin wrapper → `steamosctl switch-to-desktop-mode` (D-Bus) | writes the sentinel, then `pkexec`s itself and seds `/etc/plasmalogin.conf` | | `~/.config/steamos-session-select` | 🛑 **never written** — verified absent before *and* after a completed switch | ✅ written unconditionally; mtime advanced at the exact second of the switch | | launcher tail (`/usr/bin/gamescope-session-plus`) | ends after `unset-environment` | 🛑 `exec pkexec "$0"` → **`systemctl restart plasmalogin`** — this *is* Nobara's switch | | `is_steam_htpc_platform()` → session watcher | ✅ matches `bazzite` ⇒ **ON**, follows the switch itself | 🛑 `ID=nobara` ⇒ **OFF** | Bazzite is covered by the **watcher** and writes no sentinel. Nobara is covered by the **sentinel** and has no watcher. Fixing or breaking one says nothing about the other — which is exactly how this shipped. On Nobara the switch therefore went entirely unhandled: the capture loss it causes drives a rebuild, and the rebuild relaunches game mode over the booting desktop. The "thrown back in" report of 2026-07-24, reopened. ## Second bug, and this one affects Bazzite too The takeover replaces the box's game-mode `ExecStart` with a sleep. The disconnect restore sweeps that drop-in — **but a switch is not a disconnect.** Measured on Bazzite `.41`: after a *completed* switch to KDE the unit still carried `ExecStart=/usr/bin/sleep infinity`, so the user's next "Return to Gaming Mode" starts a unit that does nothing at all — on glass, indistinguishable from broken hardware. That is the same barred way back `release_autologin_mask` already exists to prevent for the mask this drop-in replaced, so the hand-back goes there (covering the watcher-driven distros) **and** in `honor_session_select_switch` (covering the sentinel-driven ones). There are exactly two ways a box leaves our takeover mid-stream; both now owe it the same hand-back. ## The change - gate on `takeover_idled()` (reads `IDLE_DROPIN_ARMED`) instead of the retired `STOPPED_DM`; - `record_session_select_baseline()` where the idle drop-in is installed — the arming moment; - **both** hand-back paths `remove_idle_dropin()`; - `STOPPED_DM` documented as what it has become: **adoption-only** state for a takeover stranded by a pre-0.31.0 host. Its DM restore + `os-session-select desktop` replay moves to `replay_switch_under_restored_dm`, reached only for such an adopted takeover — a 0.31.0 takeover leaves the DM up precisely so the OS does that work itself, and on the VM it does. One file, +192/−50. ## Verification **On the Nobara VM (123 on home-node-3), against live systemd** — a temporary `#[ignore]` test driving the real path, `stop_autologin_sessions()` → real `steamos-session-select plasma` → `create_managed_session()`: ``` [onbox] steamos-session-select plasma -> ExitStatus(unix_wait_status(0)) [onbox] create_managed_session -> the user switched the box to the desktop session — the box's own game mode is handed back; re-detection follows the desktop compositor as it comes up [onbox] sessions after the switch: 72=KDE test result: ok. 1 passed ``` …the drop-in gone, and the box on glass in KDE. **Negative control** — backed out to the 0.31.0 shape on the same box and re-ran: ``` panicked at gamescope.rs:6290: the switch did not advance ~/.config/steamos-session-select ``` which is the blindness itself: the sentinel *did* advance, 0.31.0 just had no baseline to see it against. **Bazzite non-regression is provable, not probabilistic.** `session_select_mtime()` is `None` there permanently, so `session_select_requested()` can never be true and the gate can never fire. Confirmed empirically on `.41`, and confirmed the switch still reaches `Desktop=KDE` under an idled takeover with the DM alive. **Gates:** `cargo fmt --all --check` clean · `scripts/xcheck.sh linux clippy` (`--all-targets -D warnings`) clean · **247 pf-vdisplay tests** green natively on Linux · the committed guard `the_mask_comes_off_only_when_the_box_takes_itself_back`, extended to cover the idle drop-in, passes against a live user manager and **fails without the hand-back**. ⚠ **Not exercised:** a live streaming client. There is no headless client (the CLI shells out to an SDL3+Vulkan renderer), so the on-box test drives the same host functions a client connect and rebuild drive, but the video pipeline itself was not in the loop. ⚠ Pre-existing Nobara noise, unchanged by this PR: because its launcher restarts plasmalogin when the unit stops, our takeover causes one DM restart + relogin per connect. It converges (unit `active (running)` on the sleep, DM active, no loop), but for ~20 s afterwards `os-session-select` picks an empty `session_launcher` — so any on-box test must let the box settle first.
enricobuehler added 2 commits 2026-08-20 17:49:30 +00:00
The managed takeover stopped stopping the display manager and started idling the
box's autologin session instead (c2f5e91b). That commit also deleted the two lines
the old DM stop carried — `record_session_select_baseline()` and
`STOPPED_DM = Some(dm)` — and 38a0f54b then removed every remaining writer of that
static. The in-stream switch gate in `create_managed_session` still reads it, so
`honor_session_select_switch` became unreachable code on every box.

Bazzite never noticed, for two reasons measured on `.41`: its `os-session-select`
is a thin wrapper over `steamosctl` D-Bus calls and writes NO sentinel (verified —
`~/.config/steamos-session-select` does not exist before or after a completed
switch), and `is_steam_htpc_platform()` defaults the mid-stream session watcher ON
for bazzite/steamos, which follows the switch by itself.

Nobara gets neither. `ID=nobara` matches no HTPC default, so no watcher; and its
ChimeraOS-layout `os-session-select` DOES write the sentinel, which was the only
thing the host had to see the switch by. So on Nobara the user's "Switch to
Desktop" went entirely unhandled: the capture loss it causes drives a rebuild, the
rebuild relaunches game mode over the booting desktop, and the stream is thrown
straight back in — the 2026-07-24 field report, reopened by a user 2026-08-20.

Arm the gate on the takeover that actually exists now. `takeover_idled()` reads
IDLE_DROPIN_ARMED, the idle drop-in re-baselines the sentinel when it goes in, and
STOPPED_DM is documented as what it has become: adoption-only state for a takeover
stranded by a pre-0.31.0 host. The hand-back keeps that host's DM restore + switch
replay (now `replay_switch_under_restored_dm`) but runs it only for such an adopted
takeover — a 0.31.0 takeover leaves the DM up precisely so the OS does that work
itself, and on the VM it does: `Updated user selected session to plasma`, then
Plasma.

Both hand-back paths also give the box its own Game Mode back, which neither did.
The takeover replaces that session's ExecStart with a sleep, and a mid-stream
switch is the one exit that leaked it — the disconnect restore sweeps it, but a
switch is not a disconnect. Measured on Bazzite `.41`: after a completed switch to
KDE the unit still carried `ExecStart=/usr/bin/sleep infinity`, so "Return to
Gaming Mode" would have started a unit that does nothing. That is the same barred
way back `release_autologin_mask` already exists to prevent for the mask this
drop-in replaced, so it goes there — covering the watcher-driven distros — and in
`honor_session_select_switch`, covering the sentinel-driven ones.

Verified on the Nobara VM (123 on home-node-3) by driving the real path against
live systemd: takeover -> real `steamos-session-select plasma` -> rebuild. The
rebuild refuses to relaunch game mode, the idle drop-in is gone, and the box
arrives in KDE. Backed out to the 0.31.0 shape on the same box it fails at "the
switch did not advance the sentinel" — the blindness itself. The extended
`the_mask_comes_off_only_when_the_box_takes_itself_back` covers the watcher path
against a live user manager, and fails without the hand-back.
Merge remote-tracking branch 'origin/main' into worktree-nobara-gamemode-switch
ci / bun-nix (pull_request) Successful in 33s
ci / web (pull_request) Successful in 1m12s
ci / docs-site (pull_request) Successful in 1m21s
ci / docs-drift (pull_request) Successful in 48s
ci / rust-arm64 (pull_request) Successful in 1m55s
android / android (pull_request) Successful in 7m59s
ci / rust (pull_request) Successful in 16m58s
fade2f7af3
enricobuehler scheduled this pull request to auto merge when all checks succeed 2026-08-20 17:51:42 +00:00
enricobuehler merged commit 1f6f01cb76 into main 2026-08-20 17:51:49 +00:00
enricobuehler deleted branch worktree-nobara-gamemode-switch 2026-08-20 17:51:59 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#358