fix(steamdeck/install): set up controller passthrough + surface the required reboot
ci / docs-site (push) Successful in 1m12s
ci / web (push) Successful in 1m13s
apple / swift (push) Successful in 1m18s
decky / build-publish (push) Successful in 21s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 14s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 12s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 51s
ci / bench (push) Successful in 6m35s
apple / screenshots (push) Successful in 6m13s
deb / build-publish (push) Successful in 10m21s
docker / deploy-docs (push) Successful in 34s
deb / build-publish-host (push) Successful in 9m58s
android / android (push) Successful in 17m7s
arch / build-publish (push) Successful in 17m47s
ci / rust (push) Successful in 19m28s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 20m59s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 20m2s

A fresh SteamOS host install failed on-glass two ways: KWin denied Desktop-mode
capture (zkde_screencast_unstable_v1) so every session died in the pipeline build,
and the native Steam Deck pad degraded to an Xbox 360 controller. Both stem from
setup that only a fresh login applies — the host user-service keeps the
pre-install group set and KWin reads its .desktop grant at session start — plus a
missing vhci-hcd autoload the pad's USB/IP transport needs (the deb/rpm/arch
packages ship it; the Deck installer did not).

- install.sh: install punktfunk-modules.conf (+ modprobe vhci-hcd now) for the
  native Deck controller transport, seed the KDE RemoteDesktop grant
  (kde-authorized) for Desktop-mode input, and print a loud "reboot before
  streaming" notice when a relogin is actually required (input group added or the
  .desktop grant first installed).
- update.sh: retrofit the udev rule, vhci-hcd autoload, input group, and grant so
  existing installs pick them up on the next update without a reinstall.
- steamos-host.md: document the first-install reboot and the native controller
  passthrough requirements (input group + vhci-hcd + client-side Steam Input Off).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-21 19:21:38 +02:00
parent 081ff64087
commit aacd610b68
3 changed files with 91 additions and 7 deletions
+18 -2
View File
@@ -58,8 +58,10 @@ It is idempotent — safe to re-run. In one pass it:
1. creates the `pf2` Debian-trixie distrobox and installs the build toolchain,
2. builds `punktfunk-host` (and the web console),
3. writes config to `~/.config/punktfunk/` (a generated web-console login password),
4. raises the UDP socket buffers to 32 MB and adds you to the `input` group (needs `sudo`; skipped
with a warning if unavailable),
4. raises the UDP socket buffers to 32 MB, installs the gamepad udev rule + the `vhci-hcd` autoload
and adds you to the `input` group (virtual gamepads / **native Steam Deck controller passthrough**),
and seeds the KDE RemoteDesktop grant for Desktop-mode input (needs `sudo`; skipped with a warning
if unavailable),
5. installs + starts the `punktfunk-host` and `punktfunk-web` **systemd user services** (with linger,
so they run without a login session).
@@ -82,6 +84,14 @@ When it finishes it prints the web-console URL and how to pair.
> If you only ever use native clients, install with `--no-gamestream` for a host with no GameStream
> surface at all.
> **First install — reboot once before streaming.** KWin only authorizes Desktop-mode screen capture
> on a fresh session, and the new `input` group (native Steam Deck controller passthrough) only takes
> effect on a new login — so after the **first** install, **reboot the Deck** (a re-run that changes
> nothing doesn't need it). Streaming **Game Mode** with a generic Xbox pad works right away; **Desktop
> capture and the native Steam Deck controller need the reboot.** If a client connects and every
> session ends with `KWin does not expose zkde_screencast_unstable_v1` or the pad shows up as an Xbox
> 360 controller, you haven't rebooted yet.
## 3. Pair a device
By default the host **requires PIN pairing** (secure). Two ways to pair:
@@ -128,6 +138,12 @@ bash ~/punktfunk/scripts/steamdeck/update.sh
thrash the managed session. Pick one mode per session.
- **Keep the device awake.** On handhelds, Game Mode auto-suspends on idle, which drops the host off
the network mid stream — disable auto-suspend (Settings → Power) for a headless host.
- **Native Steam Deck controller passthrough** presents the client's pad as a real Steam Deck
controller (paddles, trackpads, gyro) via a virtual USB device — that needs the `input` group and the
`vhci-hcd` module live, so it only works **after the first-install reboot** above; until then the pad
degrades to a generic Xbox 360 controller (still fully playable). If you're streaming *to* another
Steam Deck, also set Steam Input to **Off** for Punktfunk on that Deck — see
[Stream to a Steam Deck](/docs/steam-deck).
- **It survives OS updates**, but a major SteamOS bump can move library versions; if the host fails to
start after an update, just re-run `update.sh` to rebuild against the new base.
- Deeper reference (services, container, manual steps): [`scripts/steamdeck/README.md`](https://git.unom.io/unom/punktfunk/src/branch/main/scripts/steamdeck/README.md).
+47 -5
View File
@@ -48,6 +48,9 @@ BIN="$TARGET_DIR/release/punktfunk-host"
CONFIG="$HOME/.config/punktfunk"
UNITS="$HOME/.config/systemd/user"
XRD="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
# Set when this run does something that only a fresh login picks up (input-group add, first-time
# KWin .desktop grant). Drives the loud "reboot before streaming" note in the summary.
NEED_RELOGIN=0
# --- 0. preflight ----------------------------------------------------------
log "Preflight"
@@ -148,11 +151,29 @@ fi
# the restricted Wayland globals it lists (see packaging/linux/io.unom.Punktfunk.Host.desktop).
# Exec must therefore be THIS install's binary path, not the packaged /usr/bin one. KWin reads
# grants at session start — after first install, restart the Desktop session (Game Mode and back).
DESKTOP_DST="$HOME/.local/share/applications/io.unom.Punktfunk.Host.desktop"
# First-time install of the grant: KWin only reads it at session start, so a fresh login is required
# before Desktop-mode capture works. A re-run that just rewrites it needs no relogin.
[ -f "$DESKTOP_DST" ] || NEED_RELOGIN=1
mkdir -p "$HOME/.local/share/applications"
sed "s|^Exec=.*|Exec=$BIN|" "$SRC/packaging/linux/io.unom.Punktfunk.Host.desktop" \
> "$HOME/.local/share/applications/io.unom.Punktfunk.Host.desktop"
sed "s|^Exec=.*|Exec=$BIN|" "$SRC/packaging/linux/io.unom.Punktfunk.Host.desktop" > "$DESKTOP_DST"
ok "KWin desktop-capture authorization (io.unom.Punktfunk.Host.desktop → $BIN)"
# KDE Desktop-mode INPUT: a normal Plasma login lacks the RemoteDesktop portal grant the host's libei
# input path needs, so it would pop an "Allow remote control?" dialog a headless host can't answer.
# Seed it once (per-user, no root) — mirrors packaging/bazzite/kde-desktop-setup.sh. Game Mode
# (gamescope) needs none of this; the .desktop above already grants org_kde_kwin_fake_input.
GRANT_SRC="$SRC/scripts/headless/kde-authorized"
GRANT_DST="$HOME/.local/share/flatpak/db/kde-authorized"
if [ -s "$GRANT_DST" ]; then
ok "KDE RemoteDesktop grant already present"
elif [ -s "$GRANT_SRC" ]; then
mkdir -p "$(dirname "$GRANT_DST")"
install -m644 "$GRANT_SRC" "$GRANT_DST"
systemctl --user restart xdg-permission-store 2>/dev/null || true
ok "seeded KDE RemoteDesktop grant (Desktop-mode input)"
fi
if [ "$WITH_WEB" = 1 ] && [ ! -f "$CONFIG/web.env" ]; then
# Random login password + session secret for the web console, generated once.
# `|| true` swallows the SIGPIPE `tr` takes when `head` closes the pipe (pipefail would abort).
@@ -169,7 +190,7 @@ else
fi
# --- 4. system tuning (needs sudo; skipped gracefully if unavailable) ------
log "System tuning (UDP buffers + input group) — needs sudo"
log "System tuning (UDP buffers + gamepad rules + vhci-hcd + input group) — needs sudo"
if sudo -n true 2>/dev/null; then
printf 'net.core.wmem_max=33554432\nnet.core.rmem_max=33554432\n' \
| sudo tee /etc/sysctl.d/99-punktfunk-net.conf >/dev/null
@@ -178,9 +199,23 @@ if sudo -n true 2>/dev/null; then
if [ -f "$SRC/scripts/60-punktfunk.rules" ]; then
sudo install -m644 "$SRC/scripts/60-punktfunk.rules" /etc/udev/rules.d/60-punktfunk.rules
sudo udevadm control --reload-rules && sudo udevadm trigger || true
ok "installed udev rule (virtual gamepads)"
ok "installed udev rule (virtual gamepads + native Steam Deck controller)"
fi
# vhci-hcd: the usbip transport that makes the virtual Steam Deck pad a *real* USB device so Steam
# Input adopts it (else it degrades to plain UHID, which Steam ignores — "no controller appears").
# Persist the autoload AND load it now so passthrough works without waiting for a reboot.
if [ -f "$SRC/scripts/punktfunk-modules.conf" ]; then
sudo install -m644 "$SRC/scripts/punktfunk-modules.conf" /etc/modules-load.d/punktfunk.conf
sudo modprobe vhci-hcd 2>/dev/null || warn "could not load vhci-hcd now (loads on next boot) — needed for the native Steam Deck pad"
ok "vhci-hcd autoload installed (native Steam Deck controller transport)"
fi
if id -nG "$USER" | grep -qw input; then
ok "already in the 'input' group"
else
sudo usermod -aG input "$USER"
NEED_RELOGIN=1
warn "added $USER to the 'input' group (applies on next login)"
fi
id -nG "$USER" | grep -qw input || { sudo usermod -aG input "$USER"; warn "added $USER to 'input' group — log out/in (or reboot) for gamepad support"; }
else
warn "passwordless sudo unavailable — skipping UDP-buffer + udev tuning."
warn "Without it, high-bitrate streaming drops packets. Apply manually later:"
@@ -265,3 +300,10 @@ else
echo " • Pairing required (secure default). From a client, pick this host and enter the PIN the host shows."
fi
echo " • Update later: bash $SRC/scripts/steamdeck/update.sh"
if [ "$NEED_RELOGIN" = 1 ]; then
echo
warn "ONE MORE STEP before streaming — reboot the Deck (or fully log out and back in)."
echo " KWin only authorizes Desktop-mode screen capture on a fresh session, and the new 'input'"
echo " group (native Steam Deck controller passthrough) only applies to a new login. Streaming"
echo " Game Mode with a generic Xbox pad works now; Desktop capture + the native Deck pad need the reboot."
fi
+26
View File
@@ -44,6 +44,32 @@ sed "s|^Exec=.*|Exec=$TARGET_DIR/release/punktfunk-host|" "$SRC/packaging/linux/
> "$HOME/.local/share/applications/io.unom.Punktfunk.Host.desktop"
ok "KWin desktop-capture authorization refreshed"
# Retrofit the system bits install.sh now sets up but older installs predate (idempotent; the sudo
# parts are skipped if passwordless sudo isn't available). vhci-hcd = usbip transport for the native
# Steam Deck pad; 60-punktfunk.rules = /dev/uhid + vhci access; input group = uhid write; the
# kde-authorized grant (per-user, no root) = Desktop-mode input. A newly-added input group still needs
# a re-login to apply.
if sudo -n true 2>/dev/null; then
if [ -f "$SRC/scripts/60-punktfunk.rules" ]; then
sudo install -m644 "$SRC/scripts/60-punktfunk.rules" /etc/udev/rules.d/60-punktfunk.rules
sudo udevadm control --reload-rules >/dev/null 2>&1 || true
sudo udevadm trigger >/dev/null 2>&1 || true
fi
if [ -f "$SRC/scripts/punktfunk-modules.conf" ]; then
sudo install -m644 "$SRC/scripts/punktfunk-modules.conf" /etc/modules-load.d/punktfunk.conf
sudo modprobe vhci-hcd 2>/dev/null || true
ok "vhci-hcd autoload ensured (native Steam Deck controller)"
fi
id -nG "$USER" | grep -qw input || { sudo usermod -aG input "$USER"; ok "added $USER to 'input' group — log out/in for it to apply"; }
fi
GRANT_SRC="$SRC/scripts/headless/kde-authorized"
GRANT_DST="$HOME/.local/share/flatpak/db/kde-authorized"
if [ ! -s "$GRANT_DST" ] && [ -s "$GRANT_SRC" ]; then
mkdir -p "$(dirname "$GRANT_DST")"
install -m644 "$GRANT_SRC" "$GRANT_DST"
ok "seeded KDE RemoteDesktop grant (Desktop-mode input)"
fi
log "Restarting services"
systemctl --user restart punktfunk-host.service
ok "punktfunk-host restarted"