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
enricobuehlermerged 2 commits from worktree-nobara-gamemode-switch into main2026-08-20 17:51:49 +00:00
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
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 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.
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.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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
c2f5e91breplaced 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).38a0f54bthen removed every remaining writer of that static.create_managed_sessionstill gated the in-stream "Switch to Desktop" handler on it, sohonor_session_select_switchbecame dead code on every box.cargo checkcannot 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:
.41.136os-session-selectsteamosctl switch-to-desktop-mode(D-Bus)pkexecs itself and seds/etc/plasmalogin.conf~/.config/steamos-session-select/usr/bin/gamescope-session-plus)unset-environmentexec pkexec "$0"→systemctl restart plasmalogin— this is Nobara's switchis_steam_htpc_platform()→ session watcherbazzite⇒ ON, follows the switch itselfID=nobara⇒ OFFBazzite 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
ExecStartwith 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 carriedExecStart=/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_maskalready exists to prevent for the mask this drop-in replaced, so the hand-back goes there (covering the watcher-driven distros) and inhonor_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
takeover_idled()(readsIDLE_DROPIN_ARMED) instead of the retiredSTOPPED_DM;record_session_select_baseline()where the idle drop-in is installed — the arming moment;remove_idle_dropin();STOPPED_DMdocumented 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 desktopreplay moves toreplay_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()→ realsteamos-session-select plasma→create_managed_session():…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:
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()isNonethere permanently, sosession_select_requested()can never be true and the gate can never fire. Confirmed empirically on.41, and confirmed the switch still reachesDesktop=KDEunder an idled takeover with the DM alive.Gates:
cargo fmt --all --checkclean ·scripts/xcheck.sh linux clippy(--all-targets -D warnings) clean · 247 pf-vdisplay tests green natively on Linux · the committed guardthe_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 afterwardsos-session-selectpicks an emptysession_launcher— so any on-box test must let the box settle first.