Field report: "on Bazzite when using gaming mode it is mirroring the main display instead of giving the client its own. When I start the stream the main display goes dark, but after a few seconds it comes back and the stream is a mirror."
It is our own template that does it.
Root cause
packaging/bazzite/host.env set PUNKTFUNK_GAMESCOPE_ATTACH=1, and every install path — rpm, deb, Arch, nix — ships that file as /usr/share/punktfunk/host.env.bazzite with the docs telling people to cp it verbatim. So the recommended Bazzite setup turned the attach override on for everyone.
That override is rung 2 of pick_gamescope_mode, abovededicated_launch at rung 3:
The rung-3 comment describes the operator overrides as a debug/CI escape hatch, which is right — but we were shipping one as a distro default. On a Bazzite box that made both the managed takeover and the dedicated game session unreachable, so a game launched from a client's library could not get a session of its own either, which is the case the dedicated route exists for.
With a physical display connected, attach then takes the physical_display_connected() arm in ensure_box_gamescope_mode and streams the box's own head at the box's own mode — logged as gamescope: box drives a physical display — attaching at its own mode (no re-mode). That is the mirror the reporter saw.
What changed
packaging/bazzite/host.env forces nothing. The per-connect detection answers instead, which on a box shipping gamescope-session-plus is MANAGED. Attach stays available, documented as the opt-in it is, with the mirror and dedicated-session cost stated.
The off-switch actually works now. Both overrides were read var_os(..).is_some(), so PUNKTFUNK_GAMESCOPE_ATTACH=0 meant ATTACH ON — the opposite of what the line says and of every other knob on this host. They now use the shared pf_host_config::env_on grammar (0|false|off|no disable; a bare =1 keeps working). Anyone who "turned attach off" in an older host.env had it on the whole time.
The punktfunk group requirement moved into the template, since managed depends on it to stop the display manager.
Docs that advertised attach as the default updated: packaging/bazzite/README.md plus the bazzite, configuration, gamescope and hdr pages. scripts/host.env.example needed no change — it already had the line commented out and described managed as the default.
⚠ Two things to weigh before merging
1. A trade-off I did not decide. Defaulting Bazzite to managed means a user not in the punktfunk group moves from "works, but mirrors" to the SDDM relogin-loop storm — dm_plan("sddm", live) is mask + stop_dm, and the stop needs the group; without it the takeover proceeds mask-only and warns. The startup preflight detects and announces this, and the template now names the group, but if you'd rather not expose unjoined users to the storm, the alternative is gating the managed default on group membership. Say the word and I'll add it.
2. This only helps fresh installs. An upgrade never rewrites an existing ~/.config/punktfunk/host.env, so every Bazzite box already set up from the old template keeps the pin until the line is deleted by hand. The bazzite and hdr pages now say so, and it should be the first thing checked on any future mirror report.
Not in this PR
The reporter's dark-then-back flicker is a separate bug, already fixed on main by b2c03f19 but not yet in any release tag (0.27.0 and earlier still have it). Before that commit the physical-display guard only wrapped if let Some(node) = find_gamescope_node(), so a momentarily absent node fell through to set-environment SCREEN_WIDTH/HEIGHT plus a unit restart — blanking the panel, then attaching to the restored session. Shipping 0.27.x+ covers it.
Verification
scripts/xcheck.sh linux check — clean
scripts/xcheck.sh linux clippy (--all-targets -- -D warnings) — clean
cargo test -p pf-vdisplay under rust:1.96 — 206 passed / 0 failed
cargo fmt --all --check — clean
Gate proved non-vacuous against a planted compile_error! in routing.rs (caught, then reverted)
Field report: *"on Bazzite when using gaming mode it is mirroring the main display instead of giving the client its own. When I start the stream the main display goes dark, but after a few seconds it comes back and the stream is a mirror."*
It is our own template that does it.
## Root cause
`packaging/bazzite/host.env` set `PUNKTFUNK_GAMESCOPE_ATTACH=1`, and every install path — rpm, deb, Arch, nix — ships that file as `/usr/share/punktfunk/host.env.bazzite` with the docs telling people to `cp` it verbatim. So the *recommended* Bazzite setup turned the attach override on for everyone.
That override is rung 2 of `pick_gamescope_mode`, **above** `dedicated_launch` at rung 3:
```
1 force_managed 2 attach_env||node_env 3 dedicated_launch
4 session_env||managed_infra 5 foreign_gamescope 6 Spawn
```
The rung-3 comment describes the operator overrides as a debug/CI escape hatch, which is right — but we were shipping one as a distro default. On a Bazzite box that made both the managed takeover and the dedicated game session unreachable, so a game launched from a client's library could not get a session of its own either, which is the case the dedicated route exists for.
With a physical display connected, attach then takes the `physical_display_connected()` arm in `ensure_box_gamescope_mode` and streams the box's own head at the box's own mode — logged as `gamescope: box drives a physical display — attaching at its own mode (no re-mode)`. That is the mirror the reporter saw.
## What changed
- **`packaging/bazzite/host.env` forces nothing.** The per-connect detection answers instead, which on a box shipping `gamescope-session-plus` is MANAGED. Attach stays available, documented as the opt-in it is, with the mirror and dedicated-session cost stated.
- **The off-switch actually works now.** Both overrides were read `var_os(..).is_some()`, so `PUNKTFUNK_GAMESCOPE_ATTACH=0` meant ATTACH **ON** — the opposite of what the line says and of every other knob on this host. They now use the shared `pf_host_config::env_on` grammar (`0|false|off|no` disable; a bare `=1` keeps working). Anyone who "turned attach off" in an older `host.env` had it on the whole time.
- **The `punktfunk` group requirement moved into the template**, since managed depends on it to stop the display manager.
- Docs that advertised attach as the default updated: `packaging/bazzite/README.md` plus the `bazzite`, `configuration`, `gamescope` and `hdr` pages. `scripts/host.env.example` needed no change — it already had the line commented out and described managed as the default.
## ⚠ Two things to weigh before merging
**1. A trade-off I did not decide.** Defaulting Bazzite to managed means a user *not* in the `punktfunk` group moves from "works, but mirrors" to the SDDM relogin-loop storm — `dm_plan("sddm", live)` is mask + `stop_dm`, and the stop needs the group; without it the takeover proceeds mask-only and warns. The startup preflight detects and announces this, and the template now names the group, but if you'd rather not expose unjoined users to the storm, the alternative is gating the managed default on group membership. Say the word and I'll add it.
**2. This only helps fresh installs.** An upgrade never rewrites an existing `~/.config/punktfunk/host.env`, so every Bazzite box already set up from the old template keeps the pin until the line is deleted by hand. The bazzite and hdr pages now say so, and it should be the first thing checked on any future mirror report.
## Not in this PR
The reporter's *dark-then-back* flicker is a **separate** bug, already fixed on `main` by `b2c03f19` but not yet in any release tag (0.27.0 and earlier still have it). Before that commit the physical-display guard only wrapped `if let Some(node) = find_gamescope_node()`, so a momentarily absent node fell through to `set-environment SCREEN_WIDTH/HEIGHT` plus a unit restart — blanking the panel, then attaching to the restored session. Shipping 0.27.x+ covers it.
## Verification
- `scripts/xcheck.sh linux check` — clean
- `scripts/xcheck.sh linux clippy` (`--all-targets -- -D warnings`) — clean
- `cargo test -p pf-vdisplay` under `rust:1.96` — **206 passed / 0 failed**
- `cargo fmt --all --check` — clean
- Gate proved **non-vacuous** against a planted `compile_error!` in `routing.rs` (caught, then reverted)
Field report: "on Bazzite when using gaming mode it is mirroring the main display
instead of giving the client its own." It is our own template that does it.
`packaging/bazzite/host.env` set `PUNKTFUNK_GAMESCOPE_ATTACH=1`, and every install
path — rpm, deb, Arch, nix — ships that file as `/usr/share/punktfunk/host.env.bazzite`
with the docs telling people to copy it verbatim. So the recommended Bazzite setup
turned the attach override ON for everyone.
That override is rung 2 of `pick_gamescope_mode`, ABOVE `dedicated_launch` at rung 3.
The rung comment calls the operator overrides a debug/CI escape hatch, which is right —
but we were shipping one as a distro default, so on a Bazzite box the managed takeover
and the dedicated game session were both unreachable. A game launched from a client's
library could not get a session of its own either, which is the case the dedicated
route exists for. With a physical display connected, attach then takes the
`physical_display_connected()` arm and streams the box's own head at the box's own
mode: the mirror the reporter saw.
The template now forces nothing and lets the per-connect detection answer, which on a
box with `gamescope-session-plus` is MANAGED. Attach stays available, documented as the
opt-in it is, with the mirror and the dedicated-session cost stated. Because managed
depends on the `punktfunk` group to stop the display manager, the template now says so
where someone choosing a model will read it, rather than only in the distro guide.
Also fixes the off-switch. Both overrides were read with `var_os(..).is_some()`, so
`PUNKTFUNK_GAMESCOPE_ATTACH=0` meant ATTACH ON — the opposite of what the line says,
and of every other knob on this host. They now use the shared `env_on` grammar, so
`0|false|off|no` disable and a bare `=1` keeps working. Anyone who "turned attach off"
in an older host.env had it on the whole time.
Note an upgrade never rewrites an existing `~/.config/punktfunk/host.env`, so boxes set
up from an older template keep the pin until the line is deleted by hand; the Bazzite
and HDR pages now say that.
Verified: `scripts/xcheck.sh linux` check + clippy `-D warnings` clean, pf-vdisplay
206/0 under rust:1.96, `cargo fmt --all --check` clean. Gate proved non-vacuous against
a planted `compile_error!` in routing.rs.
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: "on Bazzite when using gaming mode it is mirroring the main display instead of giving the client its own. When I start the stream the main display goes dark, but after a few seconds it comes back and the stream is a mirror."
It is our own template that does it.
Root cause
packaging/bazzite/host.envsetPUNKTFUNK_GAMESCOPE_ATTACH=1, and every install path — rpm, deb, Arch, nix — ships that file as/usr/share/punktfunk/host.env.bazzitewith the docs telling people tocpit verbatim. So the recommended Bazzite setup turned the attach override on for everyone.That override is rung 2 of
pick_gamescope_mode, abovededicated_launchat rung 3:The rung-3 comment describes the operator overrides as a debug/CI escape hatch, which is right — but we were shipping one as a distro default. On a Bazzite box that made both the managed takeover and the dedicated game session unreachable, so a game launched from a client's library could not get a session of its own either, which is the case the dedicated route exists for.
With a physical display connected, attach then takes the
physical_display_connected()arm inensure_box_gamescope_modeand streams the box's own head at the box's own mode — logged asgamescope: box drives a physical display — attaching at its own mode (no re-mode). That is the mirror the reporter saw.What changed
packaging/bazzite/host.envforces nothing. The per-connect detection answers instead, which on a box shippinggamescope-session-plusis MANAGED. Attach stays available, documented as the opt-in it is, with the mirror and dedicated-session cost stated.var_os(..).is_some(), soPUNKTFUNK_GAMESCOPE_ATTACH=0meant ATTACH ON — the opposite of what the line says and of every other knob on this host. They now use the sharedpf_host_config::env_ongrammar (0|false|off|nodisable; a bare=1keeps working). Anyone who "turned attach off" in an olderhost.envhad it on the whole time.punktfunkgroup requirement moved into the template, since managed depends on it to stop the display manager.packaging/bazzite/README.mdplus thebazzite,configuration,gamescopeandhdrpages.scripts/host.env.exampleneeded no change — it already had the line commented out and described managed as the default.⚠ Two things to weigh before merging
1. A trade-off I did not decide. Defaulting Bazzite to managed means a user not in the
punktfunkgroup moves from "works, but mirrors" to the SDDM relogin-loop storm —dm_plan("sddm", live)is mask +stop_dm, and the stop needs the group; without it the takeover proceeds mask-only and warns. The startup preflight detects and announces this, and the template now names the group, but if you'd rather not expose unjoined users to the storm, the alternative is gating the managed default on group membership. Say the word and I'll add it.2. This only helps fresh installs. An upgrade never rewrites an existing
~/.config/punktfunk/host.env, so every Bazzite box already set up from the old template keeps the pin until the line is deleted by hand. The bazzite and hdr pages now say so, and it should be the first thing checked on any future mirror report.Not in this PR
The reporter's dark-then-back flicker is a separate bug, already fixed on
mainbyb2c03f19but not yet in any release tag (0.27.0 and earlier still have it). Before that commit the physical-display guard only wrappedif let Some(node) = find_gamescope_node(), so a momentarily absent node fell through toset-environment SCREEN_WIDTH/HEIGHTplus a unit restart — blanking the panel, then attaching to the restored session. Shipping 0.27.x+ covers it.Verification
scripts/xcheck.sh linux check— cleanscripts/xcheck.sh linux clippy(--all-targets -- -D warnings) — cleancargo test -p pf-vdisplayunderrust:1.96— 206 passed / 0 failedcargo fmt --all --check— cleancompile_error!inrouting.rs(caught, then reverted)