feat(gamescope/hdr): HDR is attempted by default and every install path ships the patched gamescope
ci / web (push) Successful in 1m9s
ci / docs-site (push) Successful in 1m14s
ci / rust-arm64 (push) Successful in 3m28s
deb / build-publish-client-arm64 (push) Successful in 2m28s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 10s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 9s
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 8s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 12s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 29s
android / android (push) Successful in 5m47s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m16s
docker / builders-arm64cross (push) Successful in 9s
deb / build-publish (push) Successful in 6m5s
docker / deploy-docs (push) Successful in 35s
deb / build-publish-host (push) Successful in 6m35s
ci / rust (push) Successful in 6m38s
arch / build-publish (push) Successful in 9m13s
apple / swift (push) Failing after 13m37s
apple / screenshots (push) Skipped
windows-host / package (push) Successful in 17m4s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 20s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m17s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 39s

Field report (RX 5700 / SteamOS): 10-bit HEVC "unsupported" — but the GPU was
never the blocker (RADV probed Main10 fine on that box). The session stayed SDR
because stock gamescope offers no 10-bit PQ capture formats and nothing shipped
our pipewire-hdr build outside the Bazzite sysext, while PUNKTFUNK_GAMESCOPE_HDR
additionally defaulted off everywhere. Close all three gaps:

- PUNKTFUNK_GAMESCOPE_HDR now defaults ON (explicit-off grammar, the flip the
  config comment planned post-canary). Safe by construction: the +pfhdr banner
  probe, managed-spawn term, and client 10-bit cap keep stock-gamescope boxes on
  the exact 8-bit path. The nix module's gamescopeHdr (default true now) only
  controls whether the patched binary is on PATH; it no longer sets the env.
- SteamOS: new scripts/steamdeck/build-gamescope.sh builds the HDR gamescope in
  the same pf2 trixie distrobox (apt dep list validated by a full build in a
  debian:trixie container), installs ~/.local/bin/punktfunk-gamescope, and
  maintains PUNKTFUNK_GAMESCOPE_BIN in host.env ONLY while the binary passes its
  on-glass --version/+pfhdr check — a stale absolute override would break
  session spawning, not just HDR. Wired into install.sh (§3b), update.sh
  (retrofit), and rebuild-check.sh (ldd-probes it like the host binary).
- Arch: build-sysext.sh gains a --gamescope fold-in (banner-verified by
  execution, host-image-only), and punktfunk-host optdepends the companion.
  Deliberately NO CI-published host sysext: per the 2026-07-22 packaging
  verdict a prebuilt SteamOS host breaks on A/B soname bumps (and /var is
  per-partition-set), so arch.yml records that instead — SteamOS hosts use the
  distrobox flow above.

Also fixes a latent bug that made build-sysext.sh fail every invocation as
documented: a literal `}` inside `${1:?usage …{host,client}…}` terminates the
expansion early and corrupts $PKG (caught by the new synthetic-package test,
which also covers the fold-in, the unmarked-gamescope refusal, and the
client-image guard).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 13:50:31 +02:00
co-authored by Claude Fable 5
parent 78926be4ac
commit 19392918ff
19 changed files with 290 additions and 60 deletions
+11 -3
View File
@@ -40,9 +40,10 @@ serving HTTPS (HTTP/1.1 over TLS) with the host's identity cert), so its service
| Script | What it does |
|--------|--------------|
| `install.sh` | Idempotent installer: ensure the `pf2` distrobox + toolchain → build host + web + **plugin runner** → write config → tune sysctl + udev + `vhci-hcd` + `input` group and **register it on SteamOS's atomic-update keep list** (sudo) → install + start `punktfunk-host` / `punktfunk-web` systemd **user** services with linger, plus the **rebuild check** below. |
| `update.sh` | Rebuild everything from the current source and restart the services (config + pairings persist). `--pull` does `git pull` first. Also retrofits anything a newer install.sh writes (runner, keep-list registration, rebuild check) onto older installs. |
| `rebuild-check.sh` | The post-OS-update self-heal (run by `punktfunk-rebuild-check.service` before the host at session start): `ldd`-probes the binary — milliseconds when healthy, a full `update.sh` rebuild only when a SteamOS update actually broke its library links. |
| `install.sh` | Idempotent installer: ensure the `pf2` distrobox + toolchain → build host + web + **plugin runner** → write config → build the **HDR gamescope** below → tune sysctl + udev + `vhci-hcd` + `input` group and **register it on SteamOS's atomic-update keep list** (sudo) → install + start `punktfunk-host` / `punktfunk-web` systemd **user** services with linger, plus the **rebuild check** below. |
| `update.sh` | Rebuild everything from the current source and restart the services (config + pairings persist). `--pull` does `git pull` first. Also retrofits anything a newer install.sh writes (runner, HDR gamescope, keep-list registration, rebuild check) onto older installs. |
| `build-gamescope.sh` | Build gamescope + the `pipewire-hdr` patches (`packaging/gamescope`) in the same distrobox and install it as `~/.local/bin/punktfunk-gamescope`, wiring `PUNKTFUNK_GAMESCOPE_BIN` into `host.env` — what lets Game Mode stream **10-bit BT.2020 PQ (HDR)** instead of 8-bit SDR. Best-effort: a failure warns and the host streams SDR. Content-stamped — a no-op unless `packaging/gamescope/` changed or the binary broke. |
| `rebuild-check.sh` | The post-OS-update self-heal (run by `punktfunk-rebuild-check.service` before the host at session start): `ldd`-probes the host binary **and the HDR gamescope** — milliseconds when healthy, a full `update.sh` rebuild only when a SteamOS update actually broke library links. |
```sh
git clone https://git.unom.io/unom/punktfunk ~/punktfunk
@@ -74,6 +75,13 @@ default `pf2`), `PUNKTFUNK_MGMT_PORT` (47990), `PUNKTFUNK_WEB_PORT` (47992).
- **Plugin runner:** the deb's payload laid out user-scoped (read-only `/usr` can't take the
package): wrapper `~/.local/bin/punktfunk-scripting`, pinned `bun` in
`~/.local/lib/punktfunk-scripting/`, bundle in `~/.local/share/punktfunk-scripting/`.
- **HDR gamescope:** `~/.local/bin/punktfunk-gamescope` (gamescope + the `pipewire-hdr` patches,
built in `pf2`, run natively — it does **not** replace the system gamescope; only the sessions
the host spawns use it). `host.env` gains `PUNKTFUNK_GAMESCOPE_BIN=` pointing at it — a line
`build-gamescope.sh` maintains and removes again if the binary ever stops working, because a
stale absolute override would break session spawning, not just HDR. HDR is attempted by
default when present; `PUNKTFUNK_GAMESCOPE_HDR=0` in `host.env` forces SDR. Verify with
`punktfunk-host hdr-probe`.
- **System tuning (sudo):** `/etc/sysctl.d/99-punktfunk-net.conf` (32 MB UDP buffers — the #1
high-bitrate lever), `/etc/udev/rules.d/60-punktfunk.rules` (`uinput`/`uhid` access),
`/etc/modules-load.d/punktfunk.conf` (`vhci-hcd` for the native Deck pad), `$USER` in the `input`
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env bash
# punktfunk — build the HDR-capable `punktfunk-gamescope` for a SteamOS host (called by
# install.sh and update.sh; safe to run by hand).
#
# Stock gamescope offers no 10-bit PQ formats on its PipeWire capture node, so every session on
# the gamescope backend stays 8-bit SDR. This builds gamescope + punktfunk's `pipewire-hdr`
# patches (the ONE canonical recipe, packaging/gamescope/build-punktfunk-gamescope.sh) inside the
# same Debian-trixie distrobox the host is built in, installs it as
# ~/.local/bin/punktfunk-gamescope, and pins the host to it via PUNKTFUNK_GAMESCOPE_BIN in
# host.env. HDR is on by default in the host; with this binary present a 10-bit-capable client
# streams true HDR10 from Game Mode.
#
# BEST-EFFORT BY DESIGN, mirroring the CI packaging paths: streaming works without it (sessions
# stay SDR), so a failure here warns loudly and exits 0 — it must never cost the host install or
# update it runs inside. The one hard rule is honesty at the seams: host.env points at the binary
# ONLY while `--version` proves it runs on SteamOS glass and carries the `+pfhdr` marker; on any
# failure both the binary and the host.env line are removed (a stale absolute override would
# otherwise break gamescope session spawning entirely, which is worse than SDR).
#
# The build clones gamescope fresh each run (submodules included). That costs bandwidth, but it
# only runs when the packaging/gamescope tree changed (pin bump, patch change — tracked by a
# content stamp) or the installed binary stopped working; a no-op re-run is milliseconds.
set -euo pipefail
log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; }
ok() { printf '\033[1;32m ok\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m !!\033[0m %s\n' "$*" >&2; }
SRC="${PUNKTFUNK_SRC:-$HOME/punktfunk}"
BOX="${PUNKTFUNK_BOX:-pf2}"
GS_BIN="$HOME/.local/bin/punktfunk-gamescope"
STAMP="$HOME/.local/share/punktfunk/gamescope.stamp"
HOST_ENV="$HOME/.config/punktfunk/host.env"
PKGDIR="$SRC/packaging/gamescope"
# host.env may only name the binary while it verifiably works (see header). sed -i is fine: the
# file is ours (install.sh §3) and the line is one this script wrote.
unwire() {
[ -f "$HOST_ENV" ] || return 0
if grep -q '^PUNKTFUNK_GAMESCOPE_BIN=' "$HOST_ENV"; then
sed -i '/^# HDR gamescope built by scripts\/steamdeck/d;/^PUNKTFUNK_GAMESCOPE_BIN=/d' "$HOST_ENV"
warn "host.env: removed PUNKTFUNK_GAMESCOPE_BIN (no working punktfunk-gamescope) — sessions stream SDR"
fi
}
wire() {
[ -f "$HOST_ENV" ] || return 0 # install.sh runs this after §3 wrote host.env; by-hand runs may predate it
grep -q '^PUNKTFUNK_GAMESCOPE_BIN=' "$HOST_ENV" && return 0
printf '\n# HDR gamescope built by scripts/steamdeck/build-gamescope.sh (10-bit BT.2020 PQ capture;\n# rebuilt by update.sh; PUNKTFUNK_GAMESCOPE_HDR=0 forces SDR).\nPUNKTFUNK_GAMESCOPE_BIN=%s\n' "$GS_BIN" >> "$HOST_ENV"
ok "host.env: PUNKTFUNK_GAMESCOPE_BIN=$GS_BIN"
}
# The on-glass truth: the binary must RUN ON STEAMOS (not merely in the build container — this is
# the ABI seam the whole distrobox approach exists for) and carry the +pfhdr banner marker.
verifies() { [ -x "$GS_BIN" ] && "$GS_BIN" --version 2>&1 | head -1 | grep -q '+pfhdr'; }
[ -d "$PKGDIR/patches" ] || { warn "no $PKGDIR — skipping the HDR gamescope build"; exit 0; }
# Content stamp over the packaging tree: a pin bump or patch edit re-triggers the build; anything
# else makes this a fast no-op.
want_stamp="$(find "$PKGDIR" -type f | LC_ALL=C sort | xargs sha256sum | sha256sum | cut -d' ' -f1)"
if [ -f "$STAMP" ] && [ "$(cat "$STAMP")" = "$want_stamp" ] && verifies; then
ok "punktfunk-gamescope up to date ($("$GS_BIN" --version 2>&1 | head -1))"
wire
exit 0
fi
log "Building punktfunk-gamescope (HDR 10-bit capture; ~5-10 min, best-effort)"
# gamescope's build deps in the box (trixie names, mirroring packaging/gamescope/PKGBUILD — keep
# the two lists in step). Provisioned here, not in install.sh's main pass, so a dep problem can
# only ever cost this feature. glm/stb come in as meson wraps; wlroots/libliftoff/vkroots/
# libdisplay-info are vendored submodules — none of those need packages.
if ! distrobox enter "$BOX" -- bash -lc '
set -e
export DEBIAN_FRONTEND=noninteractive
sudo apt-get update -qq
sudo apt-get install -y -qq --no-install-recommends \
meson ninja-build cmake glslang-tools \
libwayland-dev wayland-protocols libxkbcommon-dev \
libx11-dev libxcomposite-dev libxdamage-dev libxext-dev libxmu-dev \
libxrender-dev libxres-dev libxtst-dev libxxf86vm-dev libxcursor-dev \
libxcb-errors-dev libxcb-icccm4-dev libxcb-ewmh-dev \
libxcb1-dev libxcb-composite0-dev libxcb-render0-dev libxcb-xfixes0-dev \
libxcb-shm0-dev libxcb-res0-dev libxcb-present-dev libxcb-xinput-dev \
libxcb-randr0-dev libxcb-dri3-dev libxcb-shape0-dev \
libcap-dev libdrm-dev libinput-dev libudev-dev libseat-dev \
libvulkan-dev libglm-dev libpixman-1-dev libeis-dev \
libavif-dev libdecor-0-dev hwdata libluajit-5.1-dev \
libpipewire-0.3-dev libspa-0.2-dev libsdl2-dev \
xwayland liblcms2-dev >/dev/null
' ; then
warn "could not provision gamescope build deps in '$BOX' — sessions stay SDR (re-run update.sh to retry)"
exit 0
fi
if ! distrobox enter "$BOX" -- bash -lc "
set -e
bash '$PKGDIR/build-punktfunk-gamescope.sh' --prefix \"\$HOME/.local\" --no-setcap
"; then
warn "punktfunk-gamescope failed to build — sessions stay SDR (re-run update.sh to retry)"
unwire
exit 0
fi
if verifies; then
# CAP_SYS_NICE is gamescope's scheduling boost — worth it, never required. Filesystem caps
# can't be set from the rootless box, so it happens here on the SteamOS side.
if sudo -n true 2>/dev/null; then
sudo setcap 'CAP_SYS_NICE=eip' "$GS_BIN" 2>/dev/null \
&& ok "setcap CAP_SYS_NICE (frame-pacing boost)" \
|| warn "could not setcap CAP_SYS_NICE (gamescope still works, pacing slightly worse)"
fi
mkdir -p "$(dirname "$STAMP")"
printf '%s' "$want_stamp" > "$STAMP"
wire
ok "punktfunk-gamescope ready: $("$GS_BIN" --version 2>&1 | head -1)"
else
# Built in the box but does not run on SteamOS (an ABI/soname seam) or lacks the marker. A
# broken binary must not linger where PATH or host.env could resolve it.
warn "built binary failed its on-glass check on SteamOS — removing it; sessions stay SDR"
"$GS_BIN" --version 2>&1 | head -3 >&2 || true
rm -f "$GS_BIN" "$STAMP"
unwire
fi
exit 0
+8
View File
@@ -245,6 +245,14 @@ else
[ "$WITH_WEB" = 1 ] && ok "web.env exists (login password unchanged)"
fi
# --- 3b. HDR gamescope (punktfunk-gamescope, best-effort) ------------------
# Stock gamescope offers only 8-bit capture, so Game Mode streams SDR. The punktfunk build adds
# the 10-bit BT.2020 PQ formats, and the host attempts HDR by default the moment it is present
# (PUNKTFUNK_GAMESCOPE_HDR=0 forces SDR). Best-effort by design: on any failure this warns and
# the host streams SDR — see build-gamescope.sh, which also wires PUNKTFUNK_GAMESCOPE_BIN into
# host.env only while the binary provably runs on SteamOS.
PUNKTFUNK_SRC="$SRC" PUNKTFUNK_BOX="$BOX" bash "$SRC/scripts/steamdeck/build-gamescope.sh"
# --- 4. system tuning (needs sudo: UDP buffers + gamepad udev rule + vhci-hcd + input group) --------
log "System tuning (UDP buffers + gamepad rules + vhci-hcd + input group)"
# sudo was acquired up front in preflight (SUDO_OK) so this never stalls behind the long build; a
+18 -3
View File
@@ -18,14 +18,29 @@ export PATH="$HOME/.local/bin:$PATH"
SRC="${PUNKTFUNK_SRC:-$HOME/punktfunk}"
BIN="$SRC/target-steamos/release/punktfunk-host"
NEED=0
if [ ! -x "$BIN" ]; then
echo "punktfunk-host binary missing at $BIN — running a full rebuild" >&2
elif ! ldd "$BIN" 2>/dev/null | grep -q "not found"; then
exit 0 # every library resolves — nothing to do
else
NEED=1
elif ldd "$BIN" 2>/dev/null | grep -q "not found"; then
echo "punktfunk-host no longer loads after a SteamOS update — its missing libraries:" >&2
ldd "$BIN" 2>/dev/null | grep "not found" >&2 || true
echo "rebuilding against the new OS tree (this takes a few minutes; streaming resumes after)" >&2
NEED=1
fi
# The HDR gamescope companion chases OS libraries the same way. Probe it only when host.env pins
# it (build-gamescope.sh wires that line only while the binary works) — a break here would not
# just lose HDR, it would break gamescope session SPAWNING via the stale absolute override, so it
# rebuilds with the same urgency as the host binary.
GS_BIN="$(sed -n 's/^PUNKTFUNK_GAMESCOPE_BIN=//p' "$HOME/.config/punktfunk/host.env" 2>/dev/null | head -1)"
if [ -n "$GS_BIN" ]; then
if [ ! -x "$GS_BIN" ] || ldd "$GS_BIN" 2>/dev/null | grep -q "not found"; then
echo "punktfunk-gamescope no longer loads after a SteamOS update — rebuilding it" >&2
NEED=1
fi
fi
[ "$NEED" = 0 ] && exit 0 # everything resolves — nothing to do (every normal boot)
exec bash "$SRC/scripts/steamdeck/update.sh"
+6
View File
@@ -52,6 +52,12 @@ sed 's|^ExecStart=.*|ExecStart=%h/.local/bin/punktfunk-scripting|' \
systemctl --user daemon-reload
ok "plugin runner rebuilt (opt-in service: systemctl --user enable --now punktfunk-scripting)"
# HDR gamescope (punktfunk-gamescope): rebuild when the packaging tree changed or the installed
# binary stopped working — also RETROFITS it onto older installs that predate it (fast no-op
# otherwise). Best-effort; on failure the host streams SDR (see build-gamescope.sh).
log "HDR gamescope (punktfunk-gamescope)"
PUNKTFUNK_SRC="$SRC" PUNKTFUNK_BOX="$BOX" bash "$SRC/scripts/steamdeck/build-gamescope.sh"
# Retrofit the post-OS-update rebuild check (install.sh §5) onto older installs: probes the host
# binary with ldd at session start and re-runs this script when a SteamOS update broke its links.
if [ ! -f "$HOME/.config/systemd/user/punktfunk-rebuild-check.service" ]; then