fix(host): a host with no host.env streams a screen, not a test pattern
android / android (push) Canceled after 0s
apple / swift (push) Canceled after 37s
apple / screenshots (push) Canceled after 0s
arch / build-publish (push) Canceled after 0s
ci / rust (push) Canceled after 40s
ci / rust-arm64 (push) Canceled after 37s
ci / web (push) Canceled after 38s
ci / docs-site (push) Canceled after 36s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 15s
deb / build-publish (push) Canceled after 0s
deb / build-publish-host (push) Canceled after 46s
deb / build-publish-client-arm64 (push) Canceled after 41s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 11s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 11s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 0s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 8s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 25s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m19s
docker / builders-arm64cross (push) Successful in 16s
docker / deploy-docs (push) Successful in 38s
windows-host / package (push) Successful in 17m58s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 34s

`PUNKTFUNK_VIDEO_SOURCE` had no default, and unset fell through to the synthetic
test pattern. That was invisible while the systemd unit made host.env mandatory —
you could not start the host without the file, and every copy of the file sets
`virtual`. Now that the unit treats host.env as optional (020306b5), unset is
reachable, and a packaged Linux install with no host.env would serve a test
pattern to Moonlight clients.

Default it to `virtual`, the flagship per-client virtual output — which is what
every setter in the tree already says: packaging/bazzite/host.env,
packaging/kde/host.env, both host.env.example files, the host.env the Windows
installer generates, and scripts/bench/gpu-stream.sh. Nothing sets `synthetic`
and nothing relied on unset. `synthetic` stays reachable by naming it, since any
unrecognised value lands in that arm.

Only one of the three readers changes behaviour: stream.rs's
`== Some("virtual")` now matches when the variable is absent. gamestream/mod.rs
already folded None and Some("virtual") into the same `_` arm for the HDR
capability probe, and rtsp.rs only tests for `portal`.

The native punktfunk/1 plane never read this — it always builds a virtual
display — so this only ever affected GameStream/Moonlight sessions.

host.env.example's header no longer claims this key is the one non-optional
setting; the file is now overrides only, and the host runs without it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 19:31:36 +02:00
co-authored by Claude Opus 5
parent fd3c54bd43
commit 3594bc029e
3 changed files with 15 additions and 7 deletions
+8 -2
View File
@@ -101,7 +101,9 @@ pub struct HostConfig {
pub chacha20: bool,
/// `PUNKTFUNK_PERF` — per-stage timing instrumentation.
pub perf: bool,
/// `PUNKTFUNK_VIDEO_SOURCE` — GameStream video source select (`virtual` / `portal` / unset → synthetic).
/// `PUNKTFUNK_VIDEO_SOURCE` — GameStream video source select. `virtual` (the default — a
/// per-client virtual output at the client's own mode) / `portal` (capture an existing
/// monitor); anything else, including the literal `synthetic`, gets the test pattern.
pub video_source: Option<String>,
/// `PUNKTFUNK_CAPTURE_MONITOR` — pin capture at a NAMED physical monitor (`DP-1`, `HDMI-A-2`),
/// instead of creating a virtual display or taking whichever head the portal hands back. The
@@ -247,7 +249,11 @@ impl HostConfig {
perf: flag("PUNKTFUNK_PERF"),
// Default ON while the interval-stutter field program runs (see the field doc).
stall_probes: env_on("PUNKTFUNK_STALL_PROBES").unwrap_or(true),
video_source: val("PUNKTFUNK_VIDEO_SOURCE"),
// Defaults to `virtual` — the flagship per-client virtual output. It used to be unset,
// which fell through to the synthetic test pattern: fine for a dev box that always has
// a host.env, wrong for a packaged install, whose unit no longer requires that file at
// all. `synthetic` is still reachable by naming it (any unrecognised value lands there).
video_source: val("PUNKTFUNK_VIDEO_SOURCE").or_else(|| Some("virtual".to_string())),
// Trimmed + emptied-to-None: `PUNKTFUNK_CAPTURE_MONITOR=` in a host.env means "not
// set", not "match the monitor named empty string".
capture_monitor: val("PUNKTFUNK_CAPTURE_MONITOR")
+1 -1
View File
@@ -59,7 +59,7 @@ redundant or stale.
| Setting | Values | Meaning |
|---|---|---|
| `PUNKTFUNK_COMPOSITOR` | `kwin` · `mutter` · `gamescope` · `wlroots` · `hyprland` (aliases: `kde`/`plasma`, `gnome`, `sway`/`wlr`) | Which backend creates the virtual display. `wlroots` is sway/River; `hyprland` is its own backend. **Leave unset.** Setting it **pins** the backend and turns session-following **off** — per connect *and* mid-stream, so a Desktop ↔ Gaming switch kills the stream instead of being followed. For CI/tests and dedicated single-session appliances only. |
| `PUNKTFUNK_VIDEO_SOURCE` | `virtual` · `portal` | **GameStream/Moonlight sessions only** — it has no effect on the native `punktfunk/1` plane. `virtual` creates a per-client display at the client's exact mode (the normal choice); `portal` captures an existing monitor instead, and is what the GNOME 50+ HDR monitor mirror needs — see [HDR](/docs/hdr#linux--gnome). To stream a physical monitor to a Punktfunk app, use `PUNKTFUNK_CAPTURE_MONITOR` below, or the console's **Streamed screen**. |
| `PUNKTFUNK_VIDEO_SOURCE` | `virtual` (default) · `portal` | **GameStream/Moonlight sessions only** — it has no effect on the native `punktfunk/1` plane. `virtual` creates a per-client display at the client's exact mode (the normal choice); `portal` captures an existing monitor instead, and is what the GNOME 50+ HDR monitor mirror needs — see [HDR](/docs/hdr#linux--gnome). To stream a physical monitor to a Punktfunk app, use `PUNKTFUNK_CAPTURE_MONITOR` below, or the console's **Streamed screen**. |
| `PUNKTFUNK_CAPTURE_MONITOR` | a connector name (`HDMI-A-1`, `DP-2`, …) | Stream a **physical** monitor this host already has instead of creating a virtual display — see [Streamed screen](/docs/virtual-displays#stream-a-real-monitor-instead). List the names with `punktfunk-host list-monitors`. Setting it here **outranks the web console's** choice, so an appliance stays aimed where its operator pointed it; leave it unset to steer from the console. A name that matches no monitor fails the session loudly rather than streaming a different screen. Linux only. |
| `PUNKTFUNK_ZEROCOPY` | `1` · `0` *(default on)* | GPU zero-copy capture→encode (dmabuf → CUDA → NVENC, or D3D11 on Windows). **On by default** — no need to set it; it falls back to a CPU path automatically. Set `0` to force the CPU path. One exception: Windows **Intel/QSV** keeps the CPU path by default until zero-copy is validated on Intel hardware — set `1` to try it there. |
| `PUNKTFUNK_INPUT_BACKEND` | `libei` · `kwin` · `gamescope` · `wlr` | How input is injected. `kwin` (KWin fake-input) for KDE — direct injection with no portal approval dialog, so it also works on a headless KDE box; `libei` (the RemoteDesktop portal) for GNOME; `gamescope` for Bazzite/gamescope; `wlr` for Sway/wlroots **and Hyprland**. Auto-detected with the compositor; a value that isn't one of the four is ignored and detection runs anyway. |
+6 -4
View File
@@ -3,7 +3,8 @@
# YOU BARELY NEED THIS FILE. The host AUTO-DETECTS the live session per connect — which compositor
# is running (KWin / Mutter / sway / Hyprland / gamescope), its Wayland socket, session bus, and the
# matching input backend — and FOLLOWS the box when it switches between a desktop and Steam Gaming
# Mode, even mid-stream. Everything below except PUNKTFUNK_VIDEO_SOURCE is an optional override.
# Mode, even mid-stream. EVERYTHING below is an optional override — the host runs with sane
# defaults if this file does not exist at all (its systemd unit no longer requires it).
#
# Two rules that save debugging sessions:
# * Keys are CASE-SENSITIVE. `punktfunk_gamescope_attach=1` sets nothing — use the exact
@@ -13,9 +14,10 @@
# Mode mid-stream then kills the stream instead of being followed), and stale session vars
# point detection at dead sockets. Those knobs are for CI and dedicated appliances (below).
# Video source: `virtual` creates a per-client virtual output at the client's exact
# resolution+refresh (the flagship mode); `portal` captures an existing monitor.
PUNKTFUNK_VIDEO_SOURCE=virtual
# Video source (GameStream/Moonlight sessions only): `virtual` creates a per-client virtual
# output at the client's exact resolution+refresh (the flagship mode, and the default);
# `portal` captures an existing monitor.
#PUNKTFUNK_VIDEO_SOURCE=virtual
# GPU zero-copy capture (dmabuf → CUDA → NVENC / VAAPI / Vulkan) is ON by default and falls back to
# CPU automatically. No need to set it. Set to 0 only to force the CPU path.