Files
punktfunk/packaging/arch/PKGBUILD
T
enricobuehler 61aa1053e7
apple / swift (push) Successful in 1m2s
android / android (push) Successful in 4m43s
ci / rust (push) Successful in 4m53s
ci / web (push) Successful in 54s
ci / docs-site (push) Successful in 57s
apple / screenshots (push) Successful in 5m6s
deb / build-publish (push) Successful in 2m31s
decky / build-publish (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
windows-host / package (push) Successful in 9m2s
ci / bench (push) Successful in 4m41s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m6s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m43s
feat(host/gamescope): headless game mode that follows the box + matches the client
Make Steam game mode work on a display-less streaming host and stream it at the
client's resolution:

* Ship /etc/gamescope-session-plus/sessions.d/steam (packaging/bazzite/
  gamescope-headless-session, installed by the RPM + Arch PKGBUILD): fall back to
  gamescope's headless backend when no display is connected, so "Switch to Game
  Mode" boots offscreen instead of crashing on the missing panel (and 5-striking
  back to desktop). No-op on display-attached boxes; only sets unset values so
  the host's per-client mode still wins.

* Default Bazzite/SteamOS to ATTACH (PUNKTFUNK_GAMESCOPE_ATTACH=1 in host.env):
  the box owns its session (Desktop<->Game, persistent), the host follows +
  captures it and never tears it down — so switching is rock-solid and a
  disconnect leaves the box in its mode (reconnect returns there).

* Resize-on-attach (gamescope.rs): on connect, ensure the box's own game-mode
  session runs at the CLIENT's resolution — reuse it when already matching (fast
  path, no restart), else reconfigure + restart the box's own autologin
  gamescope-session-plus@<client> at the client mode (cooperative: no competing
  unit, so no autologin-respawn fight). Detect the live gamescope's -W/-H via
  argv[0] in /proc (its /proc/<pid>/exe is unreadable for that process).

Validated live on a headless bazzite-deck-nvidia box: game mode boots headless +
stable (0 strikes); the host attaches + streams video/audio/EIS input; a
5120x1440 client reuses the matching session and streams at 5120x1440.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 11:09:45 +00:00

164 lines
11 KiB
Bash

# Maintainer: unom <noreply@anthropic.com>
#
# Arch Linux / SteamOS split package: punktfunk-host (the gaming-rig HOST, NVENC) and
# punktfunk-client (the GTK4 couch/Deck CLIENT). Mirrors the rpm subpackages
# (packaging/rpm/punktfunk.spec) and the two deb build scripts. On a Steam Deck you want
# `punktfunk-client` (it's what the Decky plugin launches); on a gaming rig, `punktfunk-host`.
#
# Two build modes:
# - AUR / standalone: makepkg in this dir (fetches the git tag below).
# - In-tree / CI: PF_SRCDIR=$(git rev-parse --show-toplevel) makepkg --holdver
# (builds the working tree instead of the tagged source — see build()).
#
# IMPORTANT: host encode is NVENC-only (crates/punktfunk-host/src/encode/linux.rs) — functional on
# NVIDIA hosts; an AMD Deck-as-HOST needs a VAAPI backend first. The CLIENT decodes via VAAPI
# (AMD/Intel, incl. the Deck) with a software fallback, so it works everywhere. See README.md.
pkgbase=punktfunk
# punktfunk-web (the browser console) is OPT-IN: building it needs `bun` (AUR-only as bun-bin on
# stock Arch/SteamOS), so a default makepkg builds only host+client with no JS tooling — mirroring
# the RPM spec's `%bcond_with web` (off by default). Set PF_WITH_WEB=1 to also build punktfunk-web
# (appended to pkgname + bun to makedepends below).
pkgname=('punktfunk-host' 'punktfunk-client')
pkgver=0.2.0
pkgrel=1
arch=('x86_64')
url="https://git.unom.io/unom/punktfunk"
license=('MIT OR Apache-2.0')
# All build deps for both crates (Arch runtime packages ship their own headers, so these cover
# build + link). aws-lc/ring need clang+cmake; nasm is for asm.
makedepends=('rust' 'cargo' 'clang' 'cmake' 'nasm' 'pkgconf' 'git'
'gtk4' 'libadwaita' 'sdl3' 'ffmpeg' 'pipewire' 'wayland' 'libxkbcommon' 'opus' 'libei')
# Opt-in punktfunk-web: only then is bun (build tool; the console runs on plain nodejs) required.
if [ "${PF_WITH_WEB:-0}" = 1 ]; then
pkgname+=('punktfunk-web')
makedepends+=('bun') # `bun-bin` from the AUR if bun isn't in your configured repos
fi
# AUR source (a tagged release). For an in-tree CI build, set PF_SRCDIR to the repo root and
# build() uses it instead; see the README.
source=("git+https://git.unom.io/unom/punktfunk.git#tag=v${pkgver}")
sha256sums=('SKIP')
_repo() { printf '%s' "${PF_SRCDIR:-$srcdir/punktfunk}"; }
build() {
cd "$(_repo)"
export RUSTUP_TOOLCHAIN=stable CARGO_TARGET_DIR="$srcdir/target"
export PUNKTFUNK_BUILD_VERSION="${pkgver}-${pkgrel}" # stamp --version / mgmt /health (build.rs)
# The host's zero-copy FFI link-needs libcuda at build time; nvidia-utils provides it on an
# NVIDIA builder. On a GPU-less builder symlink the CUDA stub into the link path first (same
# caveat the RPM documents): ln -s "$(find / -name libcuda.so -path '*stubs*'|head -1)" /usr/lib/
cargo build --release --locked -p punktfunk-host -p punktfunk-client-linux
# Management web console (opt-in): the node-server .output bundle (built with bun, run with node).
if [ "${PF_WITH_WEB:-0}" = 1 ]; then
( cd web && bun install --frozen-lockfile && bun run build )
fi
}
package_punktfunk-host() {
pkgdesc="Low-latency desktop/game streaming HOST (Moonlight-compatible + punktfunk/1)"
# NVENC + GPU EGL/CUDA come from the NVIDIA driver (nvidia-utils) — kept an optdepend, never a
# hard dep, exactly as the RPM (__requires_exclude libcuda) and deb (shlibdeps filter) do.
depends=('ffmpeg' 'pipewire' 'pipewire-pulse' 'wireplumber' 'opus' 'libei'
'mesa' 'libglvnd' 'libxkbcommon' 'wayland')
optdepends=('nvidia-utils: NVENC hardware encode + GPU EGL/CUDA zero-copy (REQUIRED to encode on NVIDIA)'
'gamescope: per-session nested compositor backend (no desktop login needed) — needs >=3.16.22'
'kwin: stream a KDE Plasma desktop (kwin VirtualDisplay backend)'
'mutter: stream a GNOME desktop (Mutter RecordVirtual backend)'
'sway: stream a wlroots desktop (Sway VirtualDisplay backend)'
'xdg-desktop-portal-kde: portal for the headless KDE session helper'
'xdg-desktop-portal-wlr: portal for the headless Sway session helper'
'punktfunk-web: browser management console (device pairing + status)')
install=punktfunk-host.install
# User-editable config: the headless game-mode drop-in (see below) — don't clobber local edits.
backup=('etc/gamescope-session-plus/sessions.d/steam')
local R; R="$(_repo)"; local T="$srcdir/target/release"
install -Dm0755 "$T/punktfunk-host" "$pkgdir/usr/bin/punktfunk-host"
# /dev/uinput + /dev/uhid -> input group (virtual gamepads + DualSense UHID)
install -Dm0644 "$R/scripts/60-punktfunk.rules" "$pkgdir/usr/lib/udev/rules.d/60-punktfunk.rules"
# 32 MB UDP socket buffers (send-side headroom at high bitrate)
install -Dm0644 "$R/scripts/99-punktfunk-net.conf" "$pkgdir/usr/lib/sysctl.d/99-punktfunk-net.conf"
# systemd USER units (the host runs in the graphical session, not as root); repoint ExecStart.
install -Dm0644 "$R/scripts/punktfunk-host.service" "$pkgdir/usr/lib/systemd/user/punktfunk-host.service"
sed -i 's#%h/punktfunk/target/release/punktfunk-host#/usr/bin/punktfunk-host#' \
"$pkgdir/usr/lib/systemd/user/punktfunk-host.service"
install -Dm0644 "$R/scripts/punktfunk-kde-session.service" "$pkgdir/usr/lib/systemd/user/punktfunk-kde-session.service"
sed -i 's#%h/punktfunk/scripts/headless/run-headless-kde.sh#/usr/share/punktfunk/headless/run-headless-kde.sh#' \
"$pkgdir/usr/lib/systemd/user/punktfunk-kde-session.service"
# KWin Desktop-mode authorization: non-launcher .desktop whose X-KDE-Wayland-Interfaces lets the
# host bind KWin's restricted zkde_screencast (virtual output) + fake_input globals on an
# interactive Plasma session. Must ship with the host (KWin caches the per-exe grant on first
# connect). See the file's header comment.
install -Dm0644 "$R/packaging/linux/io.unom.Punktfunk.Host.desktop" \
"$pkgdir/usr/share/applications/io.unom.Punktfunk.Host.desktop"
# headless session helpers + env templates + OpenAPI doc
install -Dm0755 "$R/scripts/headless/run-headless-kde.sh" "$pkgdir/usr/share/punktfunk/headless/run-headless-kde.sh"
install -Dm0755 "$R/scripts/headless/run-headless-sway.sh" "$pkgdir/usr/share/punktfunk/headless/run-headless-sway.sh"
install -Dm0644 "$R/scripts/headless/kde-authorized" "$pkgdir/usr/share/punktfunk/headless/kde-authorized"
install -Dm0644 "$R/scripts/headless/punktfunk-sink.conf" "$pkgdir/usr/share/punktfunk/headless/punktfunk-sink.conf"
install -Dm0644 "$R/scripts/host.env.example" "$pkgdir/usr/share/punktfunk/host.env.example"
install -Dm0644 "$R/packaging/bazzite/host.env" "$pkgdir/usr/share/punktfunk/host.env.bazzite"
install -Dm0644 "$R/packaging/kde/host.env" "$pkgdir/usr/share/punktfunk/host.env.kde"
# Headless GAME-mode fix: gamescope-session-plus drop-in that uses the headless backend when no
# display is connected (so SteamOS/Bazzite "Switch to Game Mode" works on a display-less streaming
# host). No-op on display-attached boxes; sourced as /etc/gamescope-session-plus/sessions.d/steam.
install -Dm0644 "$R/packaging/bazzite/gamescope-headless-session" \
"$pkgdir/etc/gamescope-session-plus/sessions.d/steam"
install -Dm0644 "$R/api/openapi.json" "$pkgdir/usr/share/punktfunk/openapi.json"
install -Dm0644 "$R/LICENSE-MIT" "$pkgdir/usr/share/licenses/punktfunk-host/LICENSE-MIT"
install -Dm0644 "$R/LICENSE-APACHE" "$pkgdir/usr/share/licenses/punktfunk-host/LICENSE-APACHE"
install -Dm0644 "$R/README.md" "$pkgdir/usr/share/doc/punktfunk-host/README.md"
}
package_punktfunk-client() {
pkgdesc="Low-latency desktop/game streaming CLIENT (GTK4) — the couch/Deck side"
# The GTK4/libadwaita client: SDL3 gamepads, FFmpeg (VAAPI) decode, PipeWire audio/mic.
depends=('gtk4' 'libadwaita' 'sdl3' 'ffmpeg' 'pipewire' 'wireplumber' 'pipewire-pulse'
'opus' 'libglvnd')
optdepends=('libva-mesa-driver: VAAPI hardware decode on AMD (incl. Steam Deck); software fallback otherwise'
'intel-media-driver: VAAPI hardware decode on Intel'
'gamescope: run the client fullscreen in a Gaming-Mode session')
install=punktfunk-client.install
local R; R="$(_repo)"; local T="$srcdir/target/release"
install -Dm0755 "$T/punktfunk-client" "$pkgdir/usr/bin/punktfunk-client"
install -Dm0644 "$R/packaging/linux/io.unom.Punktfunk.desktop" \
"$pkgdir/usr/share/applications/io.unom.Punktfunk.desktop"
# DualSense hidraw access (full pad fidelity through SDL's HIDAPI driver).
install -Dm0644 "$R/scripts/70-punktfunk-client.rules" \
"$pkgdir/usr/lib/udev/rules.d/70-punktfunk-client.rules"
# 32 MB UDP recv buffer (so high-bitrate streams don't overflow the kernel socket buffer).
install -Dm0644 "$R/scripts/99-punktfunk-client-net.conf" \
"$pkgdir/usr/lib/sysctl.d/99-punktfunk-client-net.conf"
install -Dm0644 "$R/LICENSE-MIT" "$pkgdir/usr/share/licenses/punktfunk-client/LICENSE-MIT"
install -Dm0644 "$R/LICENSE-APACHE" "$pkgdir/usr/share/licenses/punktfunk-client/LICENSE-APACHE"
}
package_punktfunk-web() {
pkgdesc="punktfunk management web console (Nitro/Node SSR) — pairing + status in the browser"
arch=('any')
# Runtime is plain node (the .output is portable JS — bun was only the build tool). Auto-wired to
# the host's mgmt token via the systemd --user units; enable with `systemctl --user enable --now punktfunk-web`.
depends=('nodejs')
local R; R="$(_repo)"
# Pre-built node-server bundle (from build()) + a PATH-stable launcher (matches the .deb/.rpm).
install -d "$pkgdir/usr/share/punktfunk-web/.output"
cp -r "$R/web/.output/server" "$pkgdir/usr/share/punktfunk-web/.output/server"
cp -r "$R/web/.output/public" "$pkgdir/usr/share/punktfunk-web/.output/public"
install -d "$pkgdir/usr/bin"
printf '%s\n' '#!/bin/sh' 'exec /usr/bin/node /usr/share/punktfunk-web/.output/server/index.mjs "$@"' \
> "$pkgdir/usr/bin/punktfunk-web-server"
chmod 0755 "$pkgdir/usr/bin/punktfunk-web-server"
# systemd USER units: the console runs per-user; web-init generates the login password on first start.
install -Dm0644 "$R/scripts/punktfunk-web.service" "$pkgdir/usr/lib/systemd/user/punktfunk-web.service"
install -Dm0644 "$R/scripts/punktfunk-web-init.service" "$pkgdir/usr/lib/systemd/user/punktfunk-web-init.service"
install -Dm0755 "$R/scripts/web-init.sh" "$pkgdir/usr/share/punktfunk-web/web-init.sh"
install -Dm0644 "$R/web/web.env.example" "$pkgdir/usr/share/punktfunk-web/web.env.example"
install -Dm0644 "$R/LICENSE-MIT" "$pkgdir/usr/share/licenses/punktfunk-web/LICENSE-MIT"
install -Dm0644 "$R/LICENSE-APACHE" "$pkgdir/usr/share/licenses/punktfunk-web/LICENSE-APACHE"
}