Two fixes and one follow-up feature from one Bazzite/KDE + Chromecast field report (0.31.4, BigRog).
1. Monitors cycling off/on, ending dark or at wrong resolutions — on Default and Shared Desktop
The report's log has the tell: an exclusive apply on a triple-monitor desk logged also_disabled=[] — the topology apply found zero enabled physicals to disable, so the
teardown restore had nothing to re-enable.
KWin persists output state per monitor set (kwinoutputconfig.json). Creating our virtual
output changes the set, and a set that ever ran exclusive has "physicals disabled" stored — so KWin
disables the physicals itself, in the ~30 ms between our output's creation and the apply's
enumeration. From there the loop feeds itself:
exclusive captures nothing → teardown restores nothing → the desk ends dark, and KWin's own
recovery improvises modes (the "some or all at different resolutions" the report describes);
under Shared Desktop / Extend, the same stored setup switches off screens those topologies
promise never to touch ("previously never able to stay on, even with shared");
every session boundary flips the monitor set → modesets on every head → the off/on/off/on churn,
which is also what keeps the virtual display renegotiating (and is when its stored 1080p entry
can land on it — the existing actual_dims re-assert handles the create-time case).
The fix, all in the KWin backend (pf-vdisplay):
create() snapshots the enabled physicals before the virtual output exists — the only read
KWin's reaction cannot have polluted.
Exclusive: the snapshot joins the restore list (union_restore), so an output KWin disabled
before we could see it is still re-enabled at teardown.
Extend/Primary: reenable_stranded() undoes KWin's stored-config disable — immediately, and once
more after 2 s for a late apply. One shot each, never a loop.
The group-teardown restore gains a delayed verify that re-asserts after the output reclaim,
under the without-us set — a user-applied config KWin then persists, healing the stored setup
instead of re-fighting it next session. It stands down if a quick reconnect's managed output
already owns the topology.
One-time manual step for an already-stranded box (the reporter's current state): the snapshot
cannot see outputs that were dark before the update. Re-enable the monitors once in System
Settings → Display (or kscreen-doctor output.<name>.enable) while no stream is live; that
writes the healthy without-us setup back and the loop cannot re-form.
2. Chromecast: settings tabs unreachable with just the TV remote
The tab split put section switching on L1/R1, Tab and PgUp/PgDn — a Chromecast remote has none of
those, and D-pad left/right already mean "adjust the focused row". With the pad dropped (as
Chromecast pads do), every tab but the first was unreachable.
Up from the list's top row now steps onto the tab strip instead of recoiling: left/right travel the
ring (wrapping, like the shoulders), Down or A drop back into the rows, B still leaves the screen.
The strip shows the handoff (brighter pill, ‹ › chevrons), the rows rest their focus ring while the
strip holds it, and the hint bar names the D-pad's meaning up there. The gamepad path is untouched.
3. The legend speaks the device in hand
The hint legend only ever spoke gamepad (or desktop-keyboard keycaps), whatever was driving: a TV
remote got Enter/Esc/Tab, and a Switch pad's badges read the Xbox letters — "A Select" over the
button engraved B.
The shell now notes what drove last at every input seam (the Android host loop labels its two menu
paths — discrete events are the remote, the pad sampler is the pad; the desktop overlay's menu
channel is pad by construction; the keyboard path notes itself), and the legend resolves per frame:
a pad speaks its own family — PlayStation shapes, Nintendo letters (both pairs swapped, the
way the pad is engraved), ABXY for the rest;
keys on Android are a TV remote: an OK badge, the ↩ return arrow for back, the section hint
pointing at the D-pad path (▲) from fix 2, and the Y/X hints hidden outright — a remote has
neither, and advertising a button the device cannot press is worse than silence;
keys on the desktop stay keyboard keycaps; an untouched console follows the connected pad, else
the platform's key device.
The controller chip grows a remote mark and says "TV remote" when that is what is driving. A
pointer press changes nothing — a tap says nothing about the buttons in the user's other hand.
Verification
cargo clippy --all-targets -- -D warnings clean for pf-vdisplay, pf-console-ui, and punktfunk-client-android (arm64 Linux container); cargo test -p pf-vdisplay: 256 passed.
New unit tests: union_restore coverage; dpad_alone_reaches_every_tab; the glyph-style matrix
(the_legend_follows_what_drives), Nintendo letter mapping, and remote hint hiding — the
pf-console-ui suites run in ci / rust (the local arm64 image has no SDL3 to link tests).
The screen dump gains 11-home-remote / 11b-settings-remote for the eyeball pass.
Not run: a live KWin session against the stored-config scenario, and the dump render itself
(Linux-only harness).
Two fixes and one follow-up feature from one Bazzite/KDE + Chromecast field report (0.31.4, `BigRog`).
## 1. Monitors cycling off/on, ending dark or at wrong resolutions — on Default *and* Shared Desktop
The report's log has the tell: an exclusive apply on a triple-monitor desk logged
`also_disabled=[]` — the topology apply found **zero enabled physicals to disable**, so the
teardown restore had nothing to re-enable.
KWin persists output state per **monitor set** (`kwinoutputconfig.json`). Creating our virtual
output changes the set, and a set that ever ran exclusive has "physicals disabled" stored — so KWin
disables the physicals **itself**, in the ~30 ms between our output's creation and the apply's
enumeration. From there the loop feeds itself:
- exclusive captures nothing → teardown restores nothing → the desk ends dark, and KWin's own
recovery improvises modes (the "some or all at different resolutions" the report describes);
- under Shared Desktop / Extend, the same stored setup switches off screens those topologies
promise never to touch ("previously never able to stay on, even with shared");
- every session boundary flips the monitor set → modesets on every head → the off/on/off/on churn,
which is also what keeps the virtual display renegotiating (and is when its stored 1080p entry
can land on it — the existing `actual_dims` re-assert handles the create-time case).
The fix, all in the KWin backend (`pf-vdisplay`):
- `create()` snapshots the enabled physicals **before** the virtual output exists — the only read
KWin's reaction cannot have polluted.
- Exclusive: the snapshot joins the restore list (`union_restore`), so an output KWin disabled
before we could see it is still re-enabled at teardown.
- Extend/Primary: `reenable_stranded()` undoes KWin's stored-config disable — immediately, and once
more after 2 s for a late apply. One shot each, never a loop.
- The group-teardown restore gains a delayed verify that re-asserts **after** the output reclaim,
under the without-us set — a user-applied config KWin then persists, healing the stored setup
instead of re-fighting it next session. It stands down if a quick reconnect's managed output
already owns the topology.
**One-time manual step for an already-stranded box** (the reporter's current state): the snapshot
cannot see outputs that were dark before the update. Re-enable the monitors once in System
Settings → Display (or `kscreen-doctor output.<name>.enable`) **while no stream is live**; that
writes the healthy without-us setup back and the loop cannot re-form.
## 2. Chromecast: settings tabs unreachable with just the TV remote
The tab split put section switching on L1/R1, Tab and PgUp/PgDn — a Chromecast remote has none of
those, and D-pad left/right already mean "adjust the focused row". With the pad dropped (as
Chromecast pads do), every tab but the first was unreachable.
Up from the list's top row now steps onto the tab strip instead of recoiling: left/right travel the
ring (wrapping, like the shoulders), Down or A drop back into the rows, B still leaves the screen.
The strip shows the handoff (brighter pill, ‹ › chevrons), the rows rest their focus ring while the
strip holds it, and the hint bar names the D-pad's meaning up there. The gamepad path is untouched.
## 3. The legend speaks the device in hand
The hint legend only ever spoke gamepad (or desktop-keyboard keycaps), whatever was driving: a TV
remote got Enter/Esc/Tab, and a Switch pad's badges read the Xbox letters — "A Select" over the
button engraved B.
The shell now notes what drove last at every input seam (the Android host loop labels its two menu
paths — discrete events are the remote, the pad sampler is the pad; the desktop overlay's menu
channel is pad by construction; the keyboard path notes itself), and the legend resolves per frame:
- a pad speaks its own family — PlayStation shapes, **Nintendo letters** (both pairs swapped, the
way the pad is engraved), ABXY for the rest;
- keys on Android are a **TV remote**: an OK badge, the ↩ return arrow for back, the section hint
pointing at the D-pad path (▲) from fix 2, and the Y/X hints hidden outright — a remote has
neither, and advertising a button the device cannot press is worse than silence;
- keys on the desktop stay keyboard keycaps; an untouched console follows the connected pad, else
the platform's key device.
The controller chip grows a remote mark and says "TV remote" when that is what is driving. A
pointer press changes nothing — a tap says nothing about the buttons in the user's other hand.
## Verification
- `cargo clippy --all-targets -- -D warnings` clean for `pf-vdisplay`, `pf-console-ui`, and
`punktfunk-client-android` (arm64 Linux container); `cargo test -p pf-vdisplay`: 256 passed.
- New unit tests: `union_restore` coverage; `dpad_alone_reaches_every_tab`; the glyph-style matrix
(`the_legend_follows_what_drives`), Nintendo letter mapping, and remote hint hiding — the
pf-console-ui suites run in `ci / rust` (the local arm64 image has no SDL3 to link tests).
- The screen dump gains `11-home-remote` / `11b-settings-remote` for the eyeball pass.
- Not run: a live KWin session against the stored-config scenario, and the dump render itself
(Linux-only harness).
A Bazzite triple-monitor field report: monitors cycling off/on around every
session, sessions ending with some or all monitors dark or at the wrong
resolution, and an exclusive apply logging also_disabled=[] on a desk with
three enabled physicals.
KWin persists output state per MONITOR SET (kwinoutputconfig.json). Creating
our virtual output changes the set, and a set that ever ran exclusive has
'physicals disabled' stored — so KWin disables them ITSELF, in the window
between our output's creation and the topology apply's enumeration. The apply
then finds nothing enabled, captures nothing for restore, and teardown
re-enables nothing: the desk stays dark, and KWin's own recovery improvises
modes. Under Extend/Primary the same stored setup switched screens off that
those topologies promise never to touch.
Three moves, all in the KWin backend:
* create() snapshots the enabled physicals BEFORE the virtual output exists —
the only read KWin's reaction cannot have polluted.
* Exclusive: the snapshot joins the restore list (union_restore) — an output
KWin disabled before we could see it is still re-enabled at teardown.
Extend/Primary: reenable_stranded() puts the stored-config casualties back
on, immediately and once more after a short delay (KWin can apply the
stored setup after our first read). One shot each, never a loop.
* The group-teardown restore gains a delayed verify that re-asserts AFTER our
output is reclaimed — under the without-us set, so KWin persists the heal
into the stored setup instead of re-fighting us next session. It stands
down if a new session's managed output already owns the topology.
A box stranded BEFORE this fix stays stranded once: the snapshot cannot see
outputs that were already dark. Re-enabling them once in System Settings
while no stream is live writes the healthy without-us setup back, and the
loop cannot re-form.
A Chromecast field report: with only the TV remote — the pad had dropped off,
as Chromecast pads do — every settings tab but the first was unreachable. The
split of the one 30-row scroll into tabs put section switching on L1/R1, Tab
and PgUp/PgDn, and a remote has none of those; the D-pad's left/right already
mean 'adjust the focused row'.
Up from the list's top row now steps onto the tab strip instead of recoiling:
left/right travel the ring (wrapping, like the shoulders), Down or A drop
back into the rows of the tab that's showing, B still leaves the screen, and
the shoulders keep working from either focus. The strip shows the handoff —
brighter pill, ‹ › chevrons — the rows rest their focus ring while it holds
focus, and the hint bar names the D-pad's meaning up there. A pointer press
on the rows takes the focus back.
The gamepad path is untouched: nothing changes until an Up is pressed on the
top row, which used to be a boundary recoil.
Follow-up from the same Chromecast field report: the hint legend only ever
spoke gamepad (or desktop-keyboard keycaps), whatever was actually driving.
A TV remote got Enter/Esc/Tab — keys it does not have — and a Switch pad's
badges read the Xbox letters, so 'A Select' sat over the button engraved B.
The shell now notes WHAT drove last at every input seam — the Android host
loop labels its two menu paths (discrete events are the remote, the pad
sampler is the pad), the desktop overlay's menu channel is pad by
construction, and the keyboard path notes itself. Each frame the legend
resolves from that:
* a pad speaks its own family — PlayStation shapes, Nintendo letters (both
pairs swapped, the way the pad is engraved), ABXY for the rest;
* keys on Android are a TV remote: an OK badge for select, the ↩ return
arrow for back, the section hint pointing at the D-pad path (▲) that
reaches the tab strip, and the Y/X hints hidden outright — a remote has
neither, and advertising a button the device cannot press is worse than
silence;
* keys on the desktop stay keyboard keycaps;
* nothing has driven yet: the connected pad's family, else the platform's
key device — so an untouched console reads right on every device.
The controller chip grows a remote mark and says 'TV remote' when that is
what is driving; a pointer press changes nothing, since a tap says nothing
about the buttons in the user's other hand. The screen dump gains the two
remote frames (Home + Settings) for the eyeball pass.
enricobuehler
marked the pull request as ready for review 2026-08-26 14:44:27 +00:00
enricobuehler
scheduled this pull request to auto merge when all checks succeed 2026-08-26 14:44:40 +00:00
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.
Two fixes and one follow-up feature from one Bazzite/KDE + Chromecast field report (0.31.4,
BigRog).1. Monitors cycling off/on, ending dark or at wrong resolutions — on Default and Shared Desktop
The report's log has the tell: an exclusive apply on a triple-monitor desk logged
also_disabled=[]— the topology apply found zero enabled physicals to disable, so theteardown restore had nothing to re-enable.
KWin persists output state per monitor set (
kwinoutputconfig.json). Creating our virtualoutput changes the set, and a set that ever ran exclusive has "physicals disabled" stored — so KWin
disables the physicals itself, in the ~30 ms between our output's creation and the apply's
enumeration. From there the loop feeds itself:
recovery improvises modes (the "some or all at different resolutions" the report describes);
promise never to touch ("previously never able to stay on, even with shared");
which is also what keeps the virtual display renegotiating (and is when its stored 1080p entry
can land on it — the existing
actual_dimsre-assert handles the create-time case).The fix, all in the KWin backend (
pf-vdisplay):create()snapshots the enabled physicals before the virtual output exists — the only readKWin's reaction cannot have polluted.
union_restore), so an output KWin disabledbefore we could see it is still re-enabled at teardown.
reenable_stranded()undoes KWin's stored-config disable — immediately, and oncemore after 2 s for a late apply. One shot each, never a loop.
under the without-us set — a user-applied config KWin then persists, healing the stored setup
instead of re-fighting it next session. It stands down if a quick reconnect's managed output
already owns the topology.
One-time manual step for an already-stranded box (the reporter's current state): the snapshot
cannot see outputs that were dark before the update. Re-enable the monitors once in System
Settings → Display (or
kscreen-doctor output.<name>.enable) while no stream is live; thatwrites the healthy without-us setup back and the loop cannot re-form.
2. Chromecast: settings tabs unreachable with just the TV remote
The tab split put section switching on L1/R1, Tab and PgUp/PgDn — a Chromecast remote has none of
those, and D-pad left/right already mean "adjust the focused row". With the pad dropped (as
Chromecast pads do), every tab but the first was unreachable.
Up from the list's top row now steps onto the tab strip instead of recoiling: left/right travel the
ring (wrapping, like the shoulders), Down or A drop back into the rows, B still leaves the screen.
The strip shows the handoff (brighter pill, ‹ › chevrons), the rows rest their focus ring while the
strip holds it, and the hint bar names the D-pad's meaning up there. The gamepad path is untouched.
3. The legend speaks the device in hand
The hint legend only ever spoke gamepad (or desktop-keyboard keycaps), whatever was driving: a TV
remote got Enter/Esc/Tab, and a Switch pad's badges read the Xbox letters — "A Select" over the
button engraved B.
The shell now notes what drove last at every input seam (the Android host loop labels its two menu
paths — discrete events are the remote, the pad sampler is the pad; the desktop overlay's menu
channel is pad by construction; the keyboard path notes itself), and the legend resolves per frame:
way the pad is engraved), ABXY for the rest;
pointing at the D-pad path (▲) from fix 2, and the Y/X hints hidden outright — a remote has
neither, and advertising a button the device cannot press is worse than silence;
the platform's key device.
The controller chip grows a remote mark and says "TV remote" when that is what is driving. A
pointer press changes nothing — a tap says nothing about the buttons in the user's other hand.
Verification
cargo clippy --all-targets -- -D warningsclean forpf-vdisplay,pf-console-ui, andpunktfunk-client-android(arm64 Linux container);cargo test -p pf-vdisplay: 256 passed.union_restorecoverage;dpad_alone_reaches_every_tab; the glyph-style matrix(
the_legend_follows_what_drives), Nintendo letter mapping, and remote hint hiding — thepf-console-ui suites run in
ci / rust(the local arm64 image has no SDL3 to link tests).11-home-remote/11b-settings-remotefor the eyeball pass.(Linux-only harness).