Files
punktfunk/clients/linux
enricobuehlerandClaude Opus 5 0985726415
android / android (push) Canceled after 36s
apple / swift (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
arch / build-publish (push) Canceled after 37s
ci / rust (push) Canceled after 37s
ci / rust-arm64 (push) Canceled after 36s
ci / web (push) Canceled after 34s
ci / docs-site (push) Canceled after 34s
deb / build-publish (push) Canceled after 19s
deb / build-publish-host (push) Canceled after 0s
deb / build-publish-client-arm64 (push) Canceled after 0s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 0s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 0s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Canceled after 0s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
windows-host / canary-manifest (push) Canceled after 0s
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
windows-host / package (push) Canceled after 33s
windows-host / winget-source (push) Canceled after 0s
flatpak / build-publish (push) Successful in 5m43s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 6m59s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m9s
windows / build (aarch64-pc-windows-msvc) (push) Canceled after 2m19s
windows / build (x86_64-pc-windows-msvc) (push) Canceled after 0s
fix(host,web): an empty update channel stops looking like a broken host
A channel nobody has published to answers `manifest.json` with a 404, and the
check reported that the same way it reports a dead registry or a bad signature:
"Last check failed: feed returned HTTP 404". Every host on the stable channel
shows it today, because the stable manifest only publishes when someone
dispatches `announce` for a release tag — so the first thing an operator sees
from the new Updates card is a red failure caused by nothing being wrong.

The shared checker now distinguishes the two. `feed::fetch_manifest_blocking`
returns a typed `FeedError` instead of a string, and only a 404 on the manifest
ITSELF becomes `NotPublished` — a 404 on the detached signature still fails
loudly, because that is the half-published pair the manifest-then-signature
upload order can produce, and it must stay fail-closed.

The host carries that through as `UpdateStatus.not_published`, mutually
exclusive with `last_error`. It is benign only while no manifest has ever been
seen for the channel: once a check has succeeded, the same 404 means the feed
LOST a document it used to serve, which stays an error. The console then shows a
plain sentence naming the channel instead of the failure banner, and "None
published yet" rather than "Not checked yet".

The Linux client makes the same distinction but deliberately NOT the same
choice: `--check-update` keeps exiting 1 and keeps `error` set, because its
consumer is a shell script and an empty channel is the absence of evidence that
this build is current — not a confirmation that it is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 00:37:56 +02:00
..

punktfunk — Linux client

The native Linux app for streaming a punktfunk host to your desktop, laptop, or Steam Deck. It's a clean relm4/GTK4/libadwaita shell that finds hosts on your network, pairs with a PIN, and manages your settings and library — the stream itself runs in the sibling punktfunk-session Vulkan binary (clients/session), which the shell spawns, putting the picture on glass at your display's own resolution and refresh rate.

Built in Rust end to end (no C ABI): the shell shares its plumbing with the session binary through crates/pf-client-core, which links the punktfunk-core protocol crate and speaks the fast punktfunk/1 protocol — QUIC control plane, GF(2¹⁶) FEC + AES-GCM data plane.

Features

  • Zero-copy hardware decode — the session presenter decodes via Vulkan Video on every GPU vendor (including NVIDIA), falling back to FFmpeg VAAPI → DRM-PRIME dmabuf and then software when Vulkan Video is unavailable.
  • Your display's native mode — the host builds a virtual output at exactly your WxH@Hz; no scaling, no letterboxing. Steady 60 fps at 1080p60, ~6 ms capture→decoded on the LAN.
  • Audio both ways — PipeWire playback with a jitter ring, plus mic uplink to the host.
  • Full controller support — SDL3 gamepads with rumble and DualSense fidelity (lightbar, player LEDs, touchpad, motion, adaptive-trigger replay). Click-to-capture keyboard and mouse, with a release chord (Ctrl+Alt+Shift+Q) and focus-loss release.
  • Find hosts automatically — mDNS discovery lists hosts on your LAN; saved hosts persist. First connect does a one-time SPAKE2 PIN pairing (or TOFU on trusted LANs), then reconnects on a pinned identity.
  • Per-host speed test to pick a bitrate, plus compositor and mode preferences in Settings.
  • Game library browser (experimental, off by default) — "Browse library…" on a saved host shows its games (Steam + custom) as a poster grid; click one to launch it in the session. Fetched from the host's management API over mTLS — paired devices are authorized by their certificate, no extra host setup.
  • Gamepad library launcher (--browse host) — a console-style, controller-driven library view of a paired host's games, rendered by the session binary's Skia console UI: A plays the focused title, B quits, L1/R1 jump. Built for the Steam Deck plugin's "Open library" launch; session end returns to the launcher. Arrow keys/Enter/Esc drive it too (no pad needed).

Get it

Most people should install a package rather than build from source:

Distro Install
Flatpak (any distro, Steam Deck) io.unom.Punktfunk — see packaging/flatpak
Ubuntu / Debian (apt) sudo apt install punktfunk-client (after adding the repo)
Fedora / Bazzite (rpm) rpm-ostree install punktfunk-client
Arch (PKGBUILD) see packaging/arch

Per-device install steps and pairing walkthrough: docs.punktfunk.unom.io/docs/install-client.

Build & run from source

Requires GTK ≥ 4.16, libadwaita ≥ 1.5, FFmpeg 7 or 8 (with VAAPI for hardware decode), PipeWire, and SDL3 (with hidapi) development packages.

# from the repo root
cargo run -p punktfunk-client-linux                 # launch the app
cargo run -p punktfunk-client-linux -- --connect HOST[:PORT]   # skip the host list and connect
cargo run -p punktfunk-client-linux -- --browse HOST           # the gamepad library launcher

The binary is named punktfunk-client — the relm4/libadwaita desktop shell (hosts, pairing/trust, settings, the desktop library page). Every stream and the console game library run in the sibling punktfunk-session Vulkan binary; the shell spawns it for connects, and --connect/--browse on the shell exec it directly (so the Decky wrapper keeps working unchanged). Headless flags stay in the shell: --pair <PIN> --connect host[:port] (pairing ceremony), --wake host[:port], and --library host[:mgmt_port] (print a host's game library).

Layout

src/
  main.rs · app.rs        entry point, relm4 AppModel (window, trust gate, session child
                          lifecycle, typed messages), primary menu, CSS
  cli.rs                  headless paths (--pair/--wake/--library), the --connect/--browse
                          exec handoff to punktfunk-session, screenshot scenes
  ui_hosts.rs             hosts page component (FactoryVecDeque cards, saved + discovered
                          grids, add-host dialog, banner)
  ui_library.rs           game-library poster grid (per-host, launches titles)
  ui_trust.rs             TOFU / PIN-pairing / request-access dialogs
  ui_settings.rs          resolution · refresh · decoder · bitrate · compositor · mic
  spawn.rs                the session-child plumbing (stdout contract → AppMsg)
tools/screenshots.sh      store screenshot capture (app self-capture; Xvfb fallback)

The UI-agnostic plumbing — session pump, FFmpeg decode, PipeWire audio, SDL3 gamepads + keymap, trust store, mDNS discovery, library client, Wake-on-LAN — lives in crates/pf-client-core, shared with the Vulkan session binary.