docs: repo-wide housekeeping — sync README & docs with the code as shipped
windows-drivers / probe-and-proto (push) Successful in 47s
ci / web (push) Successful in 58s
apple / swift (push) Successful in 1m13s
decky / build-publish (push) Successful in 16s
ci / docs-site (push) Successful in 1m19s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 12s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 43s
windows-drivers / driver-build (push) Successful in 1m45s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 55s
ci / bench (push) Successful in 7m18s
flatpak / build-publish (push) Successful in 6m18s
docker / deploy-docs (push) Successful in 25s
windows-host / package (push) Successful in 8m31s
release / apple (push) Successful in 11m41s
android / android (push) Successful in 13m4s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m57s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m16s
arch / build-publish (push) Successful in 16m24s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m6s
deb / build-publish (push) Successful in 17m54s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m27s
ci / rust (push) Successful in 18m23s
apple / screenshots (push) Successful in 5m58s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 20m24s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 18m43s

Six parallel audits swept the root docs, docs-site, every per-directory
README, and the packaging docs; every claim below was verified against
the source before editing.

- README: Layout gains the six missing crates (pf-client-core,
  pf-presenter, pf-console-ui, pf-ffvk, pf-driver-proto, punktfunk-tray),
  clients/session, api/ and ci/; Linux/Windows client rows reflect the
  shell + Vulkan-session split and the Vulkan Video -> VAAPI/D3D11VA ->
  software decode chains; the "every client over a C ABI" claim is
  corrected (Rust clients link the core directly); tiered stats overlay
  + console shell noted; Apple row mentions AV1.
- CONTRIBUTING: drop the dead CLAUDE.md link (deliberately untracked);
  point at the README's build/invariants sections. SECURITY: 0.9.0.
- host-cli/pairing: --allow-pairing/--require-pairing are no-op legacy
  names — pairing is required by default, --allow-tofu is the real flag;
  document --data-port and --idle-timeout-ms.
- configuration: document PUNKTFUNK_RECOVER_SESSION_CMD (session-crash
  recovery hook), PUNKTFUNK_MDNS, PUNKTFUNK_DATA_PORT.
- virtual-displays/gnome: GNOME per-client scaling shipped (host-
  persisted) — flip the  to  and describe how it works.
- stats: new "Detail levels" section (Off/Compact/Normal/Detailed +
  per-platform cycle gestures); retire the GTK hand-off note.
- clients/install-client/status/roadmap: decode chains, Windows client
  validation narrowed to HDR-only pending, adaptive bitrate, console
  shell, Apple AV1, Windows host vendor list.
- Sub-READMEs: clients/linux rewritten for the re-architecture; session
  Windows decode rung + d3d11va knob; Windows tiered overlay; Android
  minSdk 28; decky file table; host zerocopy/ path; scripts port
  47992 and steamos-host.md; pf-dualsense source path.
- packaging: canary version bases are tag-derived (<next-minor> via
  pf-version.sh/.ps1), codecs-extra not ffmpeg-full, document the
  pinned offline-Skia tarball + SKIA_BINARIES_URL and vulkan-headers.
- Convert 15 dangling design/*.md links to the punktfunk-planning
  prose convention (those docs live in the private planning repo).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 09:13:42 +02:00
parent b7cb75a48a
commit ef39050dbc
37 changed files with 182 additions and 96 deletions
+3 -1
View File
@@ -40,4 +40,6 @@ Generated artifacts are checked in and CI fails on drift: `include/punktfunk_cor
`api/openapi.json` (`cargo run -p punktfunk-host -- openapi`). Match the surrounding code's comment `api/openapi.json` (`cargo run -p punktfunk-host -- openapi`). Match the surrounding code's comment
density and naming. Commit messages end with the `Co-Authored-By` trailer (see `git log`). density and naming. Commit messages end with the `Co-Authored-By` trailer (see `git log`).
See [`CLAUDE.md`](CLAUDE.md) for the full build/test/run guide and design invariants. See the [README's Build & test section](README.md#build--test-from-source) and
[Design invariants](README.md#design-invariants) for the full build/test/run guide, and the
[docs site](https://docs.punktfunk.unom.io) for architecture and per-platform guides.
+20 -6
View File
@@ -22,7 +22,8 @@ punktfunk pairs a **virtual-display streaming host** with native clients on ever
the existing **GameStream** protocol, so any [Moonlight](https://moonlight-stream.org/) client works the existing **GameStream** protocol, so any [Moonlight](https://moonlight-stream.org/) client works
day one — and adds its own faster **`punktfunk/1`** protocol that breaks the ~1 Gbps FEC wall with a day one — and adds its own faster **`punktfunk/1`** protocol that breaks the ~1 Gbps FEC wall with a
**GF(2¹⁶) Leopard-RS** transport. A single shared **Rust core** (`punktfunk-core`) holds the **GF(2¹⁶) Leopard-RS** transport. A single shared **Rust core** (`punktfunk-core`) holds the
protocol, FEC, and crypto, linked into the host and every client over a stable C ABI. protocol, FEC, and crypto, linked into the host and every native client — directly as a Rust crate
on Linux and Windows, and over a stable C ABI from the Apple and Android apps.
## What makes it different ## What makes it different
@@ -58,12 +59,16 @@ protocol, FEC, and crypto, linked into the host and every client over a stable C
| **GameStream host** → stock Moonlight | ✅ Live end-to-end: pairing, RTSP, audio, per-client virtual output at native resolution, GPU zero-copy NVENC, gamepads | | **GameStream host** → stock Moonlight | ✅ Live end-to-end: pairing, RTSP, audio, per-client virtual output at native resolution, GPU zero-copy NVENC, gamepads |
| **Native protocol**`punktfunk/1` | ✅ Validated live: QUIC control + GF(2¹⁶) FEC/AES-GCM data plane, PIN pairing, mDNS discovery, mid-stream mode renegotiation | | **Native protocol**`punktfunk/1` | ✅ Validated live: QUIC control + GF(2¹⁶) FEC/AES-GCM data plane, PIN pairing, mDNS discovery, mid-stream mode renegotiation |
| **Windows host** (Windows 11 22H2+, x64) | 🟡 Implemented & shipping as a signed installer: its own all-Rust IddCx **virtual display** (secure-desktop capable) with a **sealed IDD-push** capture path — finished frames pushed straight into its own driver, not screen-scraped (no DDA/WGC) · GPU encode (NVENC on NVIDIA, AMF/QSV on AMD/Intel, software H.264 without a GPU) · WASAPI audio · bundled virtual-gamepad drivers (no ViGEmBus) · HDR incl. Vulkan-game HDR. NVIDIA live-validated; AMD/Intel CI-green | | **Windows host** (Windows 11 22H2+, x64) | 🟡 Implemented & shipping as a signed installer: its own all-Rust IddCx **virtual display** (secure-desktop capable) with a **sealed IDD-push** capture path — finished frames pushed straight into its own driver, not screen-scraped (no DDA/WGC) · GPU encode (NVENC on NVIDIA, AMF/QSV on AMD/Intel, software H.264 without a GPU) · WASAPI audio · bundled virtual-gamepad drivers (no ViGEmBus) · HDR incl. Vulkan-game HDR. NVIDIA live-validated; AMD/Intel CI-green |
| **macOS / iOS / tvOS client** (`clients/apple`) | ✅ Streaming live: VideoToolbox decode, controllers incl. DualSense, discovery, pairing, speed test | | **macOS / iOS / tvOS client** (`clients/apple`) | ✅ Streaming live: VideoToolbox decode (HEVC, and AV1 on hardware that decodes it), controllers incl. DualSense, discovery, pairing, speed test |
| **Linux client** (`clients/linux`, GTK4) | ✅ Streaming live: FFmpeg + VAAPI zero-copy decode, PipeWire audio, SDL3 controllers; ships as Flatpak/apt/rpm/Arch | | **Linux client** (`clients/linux` + `clients/session`) | ✅ Streaming live: relm4/GTK4 launcher shell that spawns a Vulkan session binary — Vulkan Video / VAAPI / software decode, PipeWire audio, SDL3 controllers, Skia console UI; ships as Flatpak/apt/rpm/Arch |
| **Android client** (`clients/android`, phone + TV) | ✅ Streaming live: AMediaCodec decode + HDR10, AAudio audio, controllers, discovery, pairing | | **Android client** (`clients/android`, phone + TV) | ✅ Streaming live: AMediaCodec decode + HDR10, AAudio audio, controllers, discovery, pairing |
| **Windows client** (`clients/windows`, WinUI 3) | ✅ Streaming live: D3D11VA hardware decode on all GPU vendors (NVIDIA + Intel validated on glass) with software fallback, WASAPI audio, SDL3 controllers, discovery, pairing; ships as signed MSIX (x64 + ARM64). HDR10 implemented, on-glass validation pending | | **Windows client** (`clients/windows`, WinUI 3) | ✅ Streaming live: WinUI 3 shell + Vulkan session presenter, hardware decode on all GPU vendors via Vulkan Video → D3D11VA → software (NVIDIA + Intel validated on glass), WASAPI audio, SDL3 controllers, discovery, pairing; ships as signed MSIX (x64 + ARM64). HDR10 implemented, on-glass validation pending |
| **Web console + management API** (`web/`) | ✅ TanStack console over the OpenAPI mgmt API: host status, paired devices, on-demand PIN pairing, GPU selection, performance capture graphs, live host logs | | **Web console + management API** (`web/`) | ✅ TanStack console over the OpenAPI mgmt API: host status, paired devices, on-demand PIN pairing, GPU selection, performance capture graphs, live host logs |
Every native client also ships a tiered **stats overlay** (Compact / Normal / Detailed) with a
shared vocabulary across platforms, and the session client carries a full gamepad-driven **console
shell** (`pf-console-ui`): host list, PIN pairing, settings, and an on-screen keyboard.
The **GameStream host works with a stock Moonlight client** — validated live on NVIDIA hardware The **GameStream host works with a stock Moonlight client** — validated live on NVIDIA hardware
(RTX 5070 Ti, RTX 4090): PIN pairing that persists across restarts, an app catalog, RTSP/ENet/audio, (RTX 5070 Ti, RTX 4090): PIN pairing that persists across restarts, an app catalog, RTSP/ENet/audio,
and **video at the client's exact resolution and refresh** via a per-session virtual output (KWin, and **video at the client's exact resolution and refresh** via a per-session virtual output (KWin,
@@ -117,7 +122,7 @@ Each client discovers hosts on the network automatically and does a one-time
For development, or as an install fallback where no package is available: For development, or as an install fallback where no package is available:
```sh ```sh
cargo build --workspace # the Rust core, host, Linux client, and probe (Linux & macOS) cargo build --workspace # core, host, tray, shared client crates, Linux shell + session client, probe (Linux & macOS)
cargo test --workspace # unit + loopback + proptest + C ABI harness cargo test --workspace # unit + loopback + proptest + C ABI harness
cargo clippy --workspace --all-targets -- -D warnings cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all --check cargo fmt --all --check
@@ -137,18 +142,27 @@ and the [docs site](https://docs.punktfunk.unom.io).
crates/ crates/
punktfunk-core/ protocol · FEC · pacing · crypto · QUIC control plane — the C ABI (lib + cdylib + staticlib) punktfunk-core/ protocol · FEC · pacing · crypto · QUIC control plane — the C ABI (lib + cdylib + staticlib)
punktfunk-host/ the host (Linux + Windows): virtual displays · capture · encode · input · GameStream · punktfunk/1 · mgmt punktfunk-host/ the host (Linux + Windows): virtual displays · capture · encode · input · GameStream · punktfunk/1 · mgmt
pf-client-core/ shared client plumbing (Linux + Windows): session pump · FFmpeg decode · audio · SDL3 gamepads · trust · discovery
pf-presenter/ Vulkan session presenter: SDL3 window · ash swapchain · frame present · input capture
pf-console-ui/ Skia console UI for the session client: gamepad shell · stats OSD · pairing · on-screen keyboard
pf-ffvk/ FFmpeg Vulkan hwcontext bindings (AVVkFrame) for Vulkan Video decode on the presenter's device
pf-driver-proto/ host ↔ pf-vdisplay driver contract: control IOCTLs + IDD-push frame transport (no_std)
punktfunk-tray/ host tray icon (Windows notification area / Linux StatusNotifierItem)
clients/ clients/
apple/ macOS / iOS / tvOS app (Swift · VideoToolbox · Metal · GameController) apple/ macOS / iOS / tvOS app (Swift · VideoToolbox · Metal · GameController)
linux/ Linux desktop app (Rust · GTK4/libadwaita · FFmpeg/VAAPI · PipeWire · SDL3) linux/ Linux launcher shell (Rust · relm4 / GTK4 / libadwaita) — spawns the session client to stream
session/ punktfunk-session, the Vulkan streaming session (Rust · SDL3 · ash · Skia console UI) — also runs standalone (gamescope, Decky)
windows/ Windows desktop app (Rust · WinUI 3 · D3D11 · WASAPI · SDL3) windows/ Windows desktop app (Rust · WinUI 3 · D3D11 · WASAPI · SDL3)
android/ Android phone + TV app (Kotlin · Rust JNI core · AMediaCodec · AAudio) android/ Android phone + TV app (Kotlin · Rust JNI core · AMediaCodec · AAudio)
probe/ headless reference / measurement client for punktfunk/1 probe/ headless reference / measurement client for punktfunk/1
decky/ Steam Deck Decky plugin decky/ Steam Deck Decky plugin
web/ web console (TanStack) over the management API — status · devices · pairing · GPUs · performance · logs web/ web console (TanStack) over the management API — status · devices · pairing · GPUs · performance · logs
api/openapi.json management-API OpenAPI spec (regenerated via `punktfunk-host openapi`, checked in)
packaging/ apt · rpm / COPR · Arch · Flatpak · Bazzite bootc image packaging/ apt · rpm / COPR · Arch · Flatpak · Bazzite bootc image
docs-site/ public documentation site (Fumadocs) — https://docs.punktfunk.unom.io docs-site/ public documentation site (Fumadocs) — https://docs.punktfunk.unom.io
include/punktfunk_core.h cbindgen-generated C header (checked in) include/punktfunk_core.h cbindgen-generated C header (checked in)
tools/ latency-probe · loss-harness (measurement) tools/ latency-probe · loss-harness (measurement)
ci/ CI container images (rust-ci · fedora-rpm)
``` ```
## Design invariants ## Design invariants
+1 -1
View File
@@ -14,7 +14,7 @@ exposes other users before a fix exists.
The more of this you can give us, the faster we can act: The more of this you can give us, the faster we can act:
- The component and version (e.g. `punktfunk-host 0.6.0`, Windows or Linux, which client). - The component and version (e.g. `punktfunk-host 0.9.0`, Windows or Linux, which client).
- The impact — what an attacker can do, and from what position (same LAN, a local service account, - The impact — what an attacker can do, and from what position (same LAN, a local service account,
admin, a paired client, …). admin, a paired client, …).
- Steps to reproduce, a proof-of-concept, or a crash/log if you have one. - Steps to reproduce, a proof-of-concept, or a crash/log if you have one.
+1 -1
View File
@@ -58,7 +58,7 @@ kit/ :kit — NativeBridge · native mDNS discovery · Gamepad · K
`build-tools;37.0.0`, **`cmake;3.22.1`** (builds libopus); **JDK 21** (AGP 9.2 runs on JDK 1721, not `build-tools;37.0.0`, **`cmake;3.22.1`** (builds libopus); **JDK 21** (AGP 9.2 runs on JDK 1721, not
a newer default); Rust with `rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android` and a newer default); Rust with `rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android` and
`cargo install cargo-ndk`. Toolchain is pinned (AGP 9.2 · Gradle 9.4.1 · Kotlin 2.3.21 · Compose BOM `cargo install cargo-ndk`. Toolchain is pinned (AGP 9.2 · Gradle 9.4.1 · Kotlin 2.3.21 · Compose BOM
2026.05.01 · compileSdk 37 · minSdk 31). 2026.05.01 · compileSdk 37 · minSdk 28).
**Android Studio:** open `clients/android` — it uses its bundled JBR 21, and the `cargoNdk*` task **Android Studio:** open `clients/android` — it uses its bundled JBR 21, and the `cargoNdk*` task
builds the `.so` as part of the normal build. builds the `.so` as part of the normal build.
+1 -1
View File
@@ -7,7 +7,7 @@ in the workspace root.
**The only change** is in `src/media/media_codec.rs`: `MediaCodec::as_ptr` is made **The only change** is in `src/media/media_codec.rs`: `MediaCodec::as_ptr` is made
`pub` (upstream keeps it private) so the Android client can register `pub` (upstream keeps it private) so the Android client can register
`AMediaCodec_setOnFrameRenderedCallback` through `ndk-sys` — the render-timestamp `AMediaCodec_setOnFrameRenderedCallback` through `ndk-sys` — the render-timestamp
callback behind the HUD's `display` stage (`design/stats-unification.md`), which the callback behind the HUD's `display` stage (punktfunk-planning: `stats-unification.md`), which the
wrapper doesn't expose. Grep for `punktfunk vendored patch` to find it. wrapper doesn't expose. Grep for `punktfunk vendored patch` to find it.
Drop this vendor copy when upstream exposes the raw pointer or a frame-rendered Drop this vendor copy when upstream exposes the raw pointer or a frame-rendered
+2 -1
View File
@@ -122,7 +122,8 @@ PUNKTFUNK_AUTOCONNECT=<box-ip> PUNKTFUNK_MODE=1280x720x60 swift run PunktfunkCli
- **App Store screenshots** are automated — `tools/screenshots.sh all` renders the real UI at the - **App Store screenshots** are automated — `tools/screenshots.sh all` renders the real UI at the
required pixel sizes via a DEBUG-only shot mode; the `apple` CI workflow captures the iOS sizes on required pixel sizes via a DEBUG-only shot mode; the `apple` CI workflow captures the iOS sizes on
every main push. See the script header for details. every main push. See the script header for details.
- Deeper design notes live in [`design/apple-stage2-presenter.md`](../../design/apple-stage2-presenter.md). - Deeper design notes live in the internal planning repo (punktfunk-planning:
`apple-stage2-presenter.md`).
## Related ## Related
+2
View File
@@ -76,6 +76,8 @@ restart is required for an out-of-band install to appear.
| `src/page.tsx` | The `/punktfunk` fullscreen page — Hosts (with per-host details) / Settings / About tabs. | | `src/page.tsx` | The `/punktfunk` fullscreen page — Hosts (with per-host details) / Settings / About tabs. |
| `src/settings.tsx` · `src/pair.tsx` | Stream-settings section; the gamepad-navigable PIN-pairing modal. | | `src/settings.tsx` · `src/pair.tsx` | Stream-settings section; the gamepad-navigable PIN-pairing modal. |
| `src/library.tsx` | The per-host game picker (pin/unpin, "Open library on screen") + the pinned-game launch helper. | | `src/library.tsx` | The per-host game picker (pin/unpin, "Open library on screen") + the pinned-game launch helper. |
| `src/hostmgmt.tsx` | Add / edit host dialogs — mutate the shared known-hosts store (`client-known-hosts.json`) via the flatpak client's headless modes, so a host saved here shows up in the desktop client too. |
| `src/ui.tsx` | Shared UI primitives for the fullscreen page + modals (right-aligned row actions, consistent Field layout). |
| `src/hooks.ts` · `src/boundary.tsx` | Shared discovery/update/pins hooks + actions; the render error boundary. | | `src/hooks.ts` · `src/boundary.tsx` | Shared discovery/update/pins hooks + actions; the render error boundary. |
| `src/steam.ts` | Steam-shortcut launch (`AddShortcut` / `SetAppLaunchOptions` / `RunGame`) — the focus-correct stream start. The shortcut's exe is `/bin/sh` with the wrapper passed as an argument, so the script never needs an exec bit (Decky's zip extraction drops it and the root-owned plugins dir can't be chmodded by the unprivileged backend). Launch extras ride env-prefix tokens: `PF_LAUNCH=<id>` (pinned game) / `PF_BROWSE=1` + `PF_MGMT=<port>` (on-screen library); ids are validated space/quote-free at pin AND launch time. | | `src/steam.ts` | Steam-shortcut launch (`AddShortcut` / `SetAppLaunchOptions` / `RunGame`) — the focus-correct stream start. The shortcut's exe is `/bin/sh` with the wrapper passed as an argument, so the script never needs an exec bit (Decky's zip extraction drops it and the root-owned plugins dir can't be chmodded by the unprivileged backend). Launch extras ride env-prefix tokens: `PF_LAUNCH=<id>` (pinned game) / `PF_BROWSE=1` + `PF_MGMT=<port>` (on-screen library); ids are validated space/quote-free at pin AND launch time. |
| `src/backend.ts` | Typed `callable` bridges to `main.py`. | | `src/backend.ts` | Typed `callable` bridges to `main.py`. |
+13 -10
View File
@@ -1,17 +1,20 @@
# punktfunk — Linux client # punktfunk — Linux client
The native **Linux** app for streaming a punktfunk host to your desktop, laptop, or Steam Deck. The native **Linux** app for streaming a punktfunk host to your desktop, laptop, or Steam Deck.
It's a clean GTK4/libadwaita app that finds hosts on your network, pairs with a PIN, and puts a It's a clean relm4/GTK4/libadwaita **shell** that finds hosts on your network, pairs with a PIN,
low-latency stream on glass at your display's own resolution and refresh rate. and manages your settings and library — the stream itself runs in the sibling
**`punktfunk-session`** Vulkan binary ([`clients/session`](../session/README.md)), which the shell
spawns, putting the picture on glass at your display's own resolution and refresh rate.
Built in Rust, it links the shared **`punktfunk-core`** directly (no C ABI) and speaks the fast 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. **`punktfunk/1`** protocol — QUIC control plane, GF(2¹⁶) FEC + AES-GCM data plane.
## Features ## Features
- **Zero-copy hardware decode** — FFmpeg VAAPI decode → DRM-PRIME dmabuf → `GdkDmabufTexture` - **Zero-copy hardware decode** — the session presenter decodes via **Vulkan Video** on every GPU
(Tier-1 zero-copy on Intel and AMD), with an automatic software-HEVC fallback on NVIDIA or when vendor (including NVIDIA), falling back to FFmpeg VAAPI → DRM-PRIME dmabuf and then software when
VAAPI is unavailable. Vulkan Video is unavailable.
- **Your display's native mode** — the host builds a virtual output at exactly your WxH@Hz; no - **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. 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. - **Audio both ways** — PipeWire playback with a jitter ring, plus mic uplink to the host.
@@ -26,10 +29,10 @@ Built in Rust, it links the shared **`punktfunk-core`** directly (no C ABI) and
shows its games (Steam + custom) as a poster grid; click one to launch it in the session. 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 Fetched from the host's management API over mTLS — paired devices are authorized by their
certificate, no extra host setup. certificate, no extra host setup.
- **Gamepad library launcher** (`--browse host`) — a console-style, controller-driven coverflow of - **Gamepad library launcher** (`--browse host`) — a console-style, controller-driven library view
a paired host's library (drifting aurora backdrop, center-focus posters, button hints): A plays of a paired host's games, rendered by the session binary's Skia console UI: A plays the focused
the focused title, B quits, L1/R1 jump. Built for the Steam Deck plugin's "Open library" launch; title, B quits, L1/R1 jump. Built for the Steam Deck plugin's "Open library" launch; session end
session end returns to the launcher. Arrow keys/Enter/Esc drive it too (no pad needed). returns to the launcher. Arrow keys/Enter/Esc drive it too (no pad needed).
## Get it ## Get it
+6 -5
View File
@@ -38,17 +38,18 @@ path + per-stage latency equation); any tier but Off also emits the stdout mirro
`--no-default-features` is the ~5 MB power-user build — same streaming, stats on stdout `--no-default-features` is the ~5 MB power-user build — same streaming, stats on stdout
only, no Skia anywhere in the dependency tree. only, no Skia anywhere in the dependency tree.
Decode follows the Settings preference (auto: Vulkan Video → VAAPI → software): Decode follows the Settings preference (auto: Vulkan Video → VAAPI → software on Linux,
FFmpeg's Vulkan Video decoder runs on the presenter's own device where the stack Vulkan Video → D3D11VA → software on Windows): FFmpeg's Vulkan Video decoder runs on the
supports it (every vendor, zero copy); VAAPI dmabufs import per-plane elsewhere; presenter's own device where the stack supports it (every vendor, zero copy); VAAPI
software is the universal fallback. 10-bit Main10 and HDR10 are advertised dmabufs import per-plane elsewhere (D3D11VA textures on Windows); software is the
universal fallback. 10-bit Main10 and HDR10 are advertised
(`VIDEO_CAP_10BIT|HDR`): P010 decodes through all three paths, and PQ streams present (`VIDEO_CAP_10BIT|HDR`): P010 decodes through all three paths, and PQ streams present
on an HDR10/ST.2084 swapchain when the desktop offers one (KDE HDR, gamescope) or on an HDR10/ST.2084 swapchain when the desktop offers one (KDE HDR, gamescope) or
tone-map in-shader to SDR when it doesn't (`PUNKTFUNK_TONEMAP_PEAK` tunes the rolloff, tone-map in-shader to SDR when it doesn't (`PUNKTFUNK_TONEMAP_PEAK` tunes the rolloff,
default ≈1000 nits). The host still gates the upgrade behind its `PUNKTFUNK_10BIT` default ≈1000 nits). The host still gates the upgrade behind its `PUNKTFUNK_10BIT`
policy. policy.
Debug/bisect knobs: `PUNKTFUNK_DECODER=vulkan|vaapi|software`, `PUNKTFUNK_PRESENT_MODE= Debug/bisect knobs: `PUNKTFUNK_DECODER=vulkan|vaapi|d3d11va|software`, `PUNKTFUNK_PRESENT_MODE=
mailbox|immediate` (default FIFO), `PUNKTFUNK_VK_DEVICE=<index>` (multi-GPU), and mailbox|immediate` (default FIFO), `PUNKTFUNK_VK_DEVICE=<index>` (multi-GPU), and
`PUNKTFUNK_HW_FAULT=import` (fault every VAAPI dmabuf import — proves the three-strike `PUNKTFUNK_HW_FAULT=import` (fault every VAAPI dmabuf import — proves the three-strike
demotion to software on healthy hardware). demotion to software on healthy hardware).
+2 -1
View File
@@ -26,7 +26,8 @@ the fast **`punktfunk/1`** protocol.
(probe burst over the real data plane → recommended bitrate, applied in one tap) and **forget**. (probe burst over the real data plane → recommended bitrate, applied in one tap) and **forget**.
- **Polished shell** — host cards, settings (resolution / refresh / host compositor / decoder / - **Polished shell** — host cards, settings (resolution / refresh / host compositor / decoder /
codec / bitrate / HDR / forwarded controller / gamepad type / system shortcuts / audio channels / codec / bitrate / HDR / forwarded controller / gamepad type / system shortcuts / audio channels /
mic), a status-chip stream HUD, and the full trust surface. Stream input uses Win32 low-level mic / stats-overlay level), the tiered stats overlay (Off / Compact / Normal / Detailed —
Ctrl+Alt+Shift+S cycles it live in the session window), and the full trust surface. Stream input uses Win32 low-level
hooks with Moonlight-style capture: Ctrl+Alt+Shift+Q releases the pointer, a click on the stream hooks with Moonlight-style capture: Ctrl+Alt+Shift+Q releases the pointer, a click on the stream
re-captures it, and system shortcuts (Alt+Tab, Win, …) can act locally or forward to the host. re-captures it, and system shortcuts (Alt+Tab, Win, …) can act locally or forward to the host.
+1 -1
View File
@@ -13,4 +13,4 @@ Defining every wire struct here — with `const` size/offset asserts and `bytemu
host↔driver ABI drift into a **compile error** instead of a silent frame or IOCTL corruption. host↔driver ABI drift into a **compile error** instead of a silent frame or IOCTL corruption.
See the crate root ([`src/`](src/)) for the wire types; the Windows virtual-display design is in See the crate root ([`src/`](src/)) for the wire types; the Windows virtual-display design is in
[`design/windows-virtual-display-rust-port.md`](../../design/windows-virtual-display-rust-port.md). the internal planning repo (punktfunk-planning: `windows-virtual-display-rust-port.md`).
+1 -1
View File
@@ -57,5 +57,5 @@ bash crates/punktfunk-core/tests/c/run.sh # standalone C-ABI link + round-tri
- **[`punktfunk-host`](../punktfunk-host/README.md)** — the streaming host built on this core - **[`punktfunk-host`](../punktfunk-host/README.md)** — the streaming host built on this core
- **[Clients](../../clients/)** — the apps that link this core over the C ABI (or directly, in Rust) - **[Clients](../../clients/)** — the apps that link this core over the C ABI (or directly, in Rust)
- **[`design/implementation-plan.md`](../../design/implementation-plan.md)** — why GF(2¹⁶) FEC, the - **punktfunk-planning: `implementation-plan.md`** (internal planning repo) — why GF(2¹⁶) FEC, the
latency budget, and the architecture thesis latency budget, and the architecture thesis
+2 -2
View File
@@ -73,7 +73,7 @@ src/
vdisplay/ per-compositor virtual outputs (kwin · gamescope · mutter · wlroots) vdisplay/ per-compositor virtual outputs (kwin · gamescope · mutter · wlroots)
capture/ · capture.rs screen/dmabuf capture (+ Windows IDD-push) capture/ · capture.rs screen/dmabuf capture (+ Windows IDD-push)
encode/ · encode.rs per-GPU encoders (nvenc · vaapi · ffmpeg_win (AMF/QSV) · sw) encode/ · encode.rs per-GPU encoders (nvenc · vaapi · ffmpeg_win (AMF/QSV) · sw)
zerocopy/ dmabuf → CUDA → NVENC bridges (EGL/GL tiled, Vulkan LINEAR) linux/zerocopy/ dmabuf → CUDA → NVENC bridges (EGL/GL tiled, Vulkan LINEAR)
inject/ · inject.rs input backends (libei · wlr · uinput gamepads · UHID DualSense/DS4) inject/ · inject.rs input backends (libei · wlr · uinput gamepads · UHID DualSense/DS4)
audio/ · audio.rs Opus out + virtual mic (PipeWire / WASAPI) audio/ · audio.rs Opus out + virtual mic (PipeWire / WASAPI)
gamestream/ Moonlight compat: nvhttp · pairing · rtsp · control · stream · gamepad · apps gamestream/ Moonlight compat: nvhttp · pairing · rtsp · control · stream · gamepad · apps
@@ -88,4 +88,4 @@ src/
- **[`punktfunk-core`](../punktfunk-core/README.md)** — the shared protocol · FEC · crypto core - **[`punktfunk-core`](../punktfunk-core/README.md)** — the shared protocol · FEC · crypto core
- **[Clients](../../clients/)** — the apps that connect (Apple · Linux · Windows · Android · probe) - **[Clients](../../clients/)** — the apps that connect (Apple · Linux · Windows · Android · probe)
- **[Packaging](../../packaging/README.md)** & **[docs](https://docs.punktfunk.unom.io)** — install & operate - **[Packaging](../../packaging/README.md)** & **[docs](https://docs.punktfunk.unom.io)** — install & operate
- **[`design/`](../../design/README.md)** — architecture rationale and deep-dive plans - **punktfunk-planning** (internal planning repo) — architecture rationale and deep-dive plans
+1 -2
View File
@@ -4,8 +4,7 @@ The punktfunk documentation site: [Fumadocs](https://fumadocs.dev) on
[TanStack Start](https://tanstack.com/start) (Vite + Nitro/bun preset). [TanStack Start](https://tanstack.com/start) (Vite + Nitro/bun preset).
Content lives in [`content/docs/`](content/docs) as `.md`/`.mdx`. This site is the source of truth Content lives in [`content/docs/`](content/docs) as `.md`/`.mdx`. This site is the source of truth
for the **user-facing** guides; repo-internal design rationale lives in for the **user-facing** guides; design rationale lives in the internal punktfunk-planning repo.
[`../design/`](../design/README.md).
## API reference ## API reference
+12 -10
View File
@@ -35,9 +35,10 @@ protocol's FEC/encryption extensions, but for a healthy LAN that rarely matters.
## Linux desktop client (GTK4) ## Linux desktop client (GTK4)
`punktfunk-client` is the native graphical Linux client — a GTK4 / libadwaita app that speaks `punktfunk-client` is the native graphical Linux client — a GTK4 / libadwaita app that speaks
`punktfunk/1` directly, with hardware decode (VAAPI → dmabuf on Intel/AMD, software fallback), `punktfunk/1` directly, with hardware decode via Vulkan Video on every GPU vendor (including
PipeWire audio, and SDL3 controllers (rumble, lightbar, DualSense touchpad/motion). Like the Apple NVIDIA), falling back to VAAPI dmabuf and then software, PipeWire audio, and SDL3 controllers
app it discovers hosts on your network automatically, does PIN pairing, and pins reconnects. (rumble, lightbar, DualSense touchpad/motion). Like the Apple app it discovers hosts on your network
automatically, does PIN pairing, and pins reconnects.
It ships as a real package, not just a source build — full steps in It ships as a real package, not just a source build — full steps in
[Install a Client](/docs/install-client#linux-desktop-flatpak): [Install a Client](/docs/install-client#linux-desktop-flatpak):
@@ -73,14 +74,15 @@ The app is in **Google Play Internal Testing** — request a tester invite on ou
`punktfunk-client` for Windows (`clients/windows`) is the native graphical client for Windows — pure `punktfunk-client` for Windows (`clients/windows`) is the native graphical client for Windows — pure
Rust, the same `punktfunk/1` core as the Apple, Linux, and Android apps, with a **WinUI 3** UI (host Rust, the same `punktfunk/1` core as the Apple, Linux, and Android apps, with a **WinUI 3** UI (host
list, settings, PIN pairing) and the video on a `SwapChainPanel`. It does D3D11VA hardware decode list, settings, PIN pairing); the stream itself runs in punktfunk's Vulkan presenter. Its decoder
(software fallback), 10-bit/HDR present, WASAPI audio + mic, SDL3 controllers (rumble, lightbar, tries **Vulkan Video, then D3D11VA, then software**, with 10-bit/HDR present, WASAPI audio + mic,
DualSense), network discovery, and the full PIN-pairing trust surface. It builds for both `x86_64` SDL3 controllers (rumble, lightbar, DualSense), network discovery, and the full PIN-pairing trust
and `aarch64` and ships as a **signed MSIX**. Launch it and pick a host from the list, just like the surface. It builds for both `x86_64` and `aarch64` and ships as a **signed MSIX**. Launch it and
other native apps. pick a host from the list, just like the other native apps.
> The hardware-decode and HDR paths are complete but still pending validation on real GPU hardware. > Hardware decode is validated on NVIDIA and Intel GPUs; HDR10 is implemented with on-glass
> If anything misbehaves, **[Moonlight](/docs/moonlight)** is a proven alternative for Windows. > validation still pending. If anything misbehaves, **[Moonlight](/docs/moonlight)** is a proven
> alternative for Windows.
A headless CLI path exists for scripting/measurement: A headless CLI path exists for scripting/measurement:
+13
View File
@@ -75,6 +75,12 @@ See your desktop page ([KDE](/docs/kde), [GNOME](/docs/gnome)) for when to set t
| `PUNKTFUNK_KWIN_VIRTUAL_PRIMARY` | `1` | Make the streamed per-session output the sole desktop so plasmashell + windows render on it (not on the headless bootstrap output). Set by the KDE appliance `host.env`. Superseded by the console's **Topology** setting. | | `PUNKTFUNK_KWIN_VIRTUAL_PRIMARY` | `1` | Make the streamed per-session output the sole desktop so plasmashell + windows render on it (not on the headless bootstrap output). Set by the KDE appliance `host.env`. Superseded by the console's **Topology** setting. |
| `PUNKTFUNK_MUTTER_VIRTUAL_PRIMARY` | `1` | GNOME/Mutter equivalent of the above. | | `PUNKTFUNK_MUTTER_VIRTUAL_PRIMARY` | `1` | GNOME/Mutter equivalent of the above. |
## Session recovery (Linux)
| Setting | Values | Meaning |
|---|---|---|
| `PUNKTFUNK_RECOVER_SESSION_CMD` | command | Operator hook fired (debounced) when a client connects while **no graphical session is live** for the host's user — the state a compositor crash leaves behind (gnome-shell SIGSEGV → GDM greeter, whose auto-login is once-per-boot). Typically `sudo -n systemctl restart gdm` with a matching NOPASSWD sudoers rule, or `systemctl restart display-manager` under a polkit rule; with auto-login enabled the restart brings the desktop back and the client's automatic retry lands in it. Unset/empty = disabled (the default). |
## Video quality ## Video quality
| Setting | Values | Meaning | | Setting | Values | Meaning |
@@ -110,6 +116,13 @@ See your desktop page ([KDE](/docs/kde), [GNOME](/docs/gnome)) for when to set t
| `PUNKTFUNK_RENDER_ADAPTER` | description substring | Multi-GPU boxes only: force the NVENC/capture GPU by adapter Description substring (e.g. `4090`). Leave unset on single-GPU machines. | | `PUNKTFUNK_RENDER_ADAPTER` | description substring | Multi-GPU boxes only: force the NVENC/capture GPU by adapter Description substring (e.g. `4090`). Leave unset on single-GPU machines. |
| `PUNKTFUNK_HOST_CMD` | e.g. `serve --gamestream` | The host subcommand the service launches. Default `serve --gamestream`; use `serve` for a secure native-only host. | | `PUNKTFUNK_HOST_CMD` | e.g. `serve --gamestream` | The host subcommand the service launches. Default `serve --gamestream`; use `serve` for a secure native-only host. |
## Network & discovery
| Setting | Values | Meaning |
|---|---|---|
| `PUNKTFUNK_MDNS` | `1` · `0` *(default on)* | mDNS adverts (native + GameStream). `0` skips them (same as `--no-mdns`) — for networks/containers where multicast doesn't work; add the host by address in the client instead. |
| `PUNKTFUNK_DATA_PORT` | port | Pin the per-session video data plane to a fixed UDP port and stream direct (no hole-punch) — open exactly that port in the host firewall. Same as `serve --data-port`; see [Troubleshooting](/docs/troubleshooting). Default: random port + hole-punch. |
## Auth, API & paths ## Auth, API & paths
| Setting | Values | Meaning | | Setting | Values | Meaning |
+4
View File
@@ -66,6 +66,10 @@ journalctl --user -u punktfunk-host -f # watch it come up and print its identi
Then bring up [The Web Console](/docs/web-console) to arm pairing and connect a Then bring up [The Web Console](/docs/web-console) to arm pairing and connect a
[client](/docs/clients). For an always-on box, see the [headless session](#headless-session) below. [client](/docs/clients). For an always-on box, see the [headless session](#headless-session) below.
Display scaling you set while streaming **sticks per client**: the host remembers each device's
scale and reapplies it on reconnect — see
[Persistent scaling](/docs/virtual-displays#persistent-scaling).
## Headless session ## Headless session
To run with no monitor and no login, keep a GNOME Wayland session up at all times and start the host To run with no monitor and no login, keep a GNOME Wayland session up at all times and start the host
+10 -6
View File
@@ -35,6 +35,7 @@ punktfunk-host serve --gamestream
| `--mgmt-bind <IP:PORT>` | Management API address (default `0.0.0.0:47990` — all interfaces, so paired clients can browse the game library over mTLS; pass `127.0.0.1:47990` to keep it loopback-only). | | `--mgmt-bind <IP:PORT>` | Management API address (default `0.0.0.0:47990` — all interfaces, so paired clients can browse the game library over mTLS; pass `127.0.0.1:47990` to keep it loopback-only). |
| `--mgmt-token <TOKEN>` | Override the bearer token for the management API. | | `--mgmt-token <TOKEN>` | Override the bearer token for the management API. |
| `--no-mdns` | Skip the mDNS adverts (native + GameStream) — for networks/containers where multicast doesn't work. Clients connect via a manually added host instead. Same as `PUNKTFUNK_MDNS=0`. | | `--no-mdns` | Skip the mDNS adverts (native + GameStream) — for networks/containers where multicast doesn't work. Clients connect via a manually added host instead. Same as `PUNKTFUNK_MDNS=0`. |
| `--data-port <PORT>` | Pin the per-session video data plane to this fixed UDP port and stream direct (no hole-punch) — open exactly that port in the host firewall. Same as `PUNKTFUNK_DATA_PORT`; default is a random port + hole-punch. |
These are the only flags `serve` accepts. These are the only flags `serve` accepts.
@@ -50,7 +51,8 @@ Every endpoint is documented in the interactive [**API Reference**](/api).
By default the host **requires pairing** — see [Pairing & Trust](/docs/pairing). On `serve` you By default the host **requires pairing** — see [Pairing & Trust](/docs/pairing). On `serve` you
**arm pairing from the web console** (or mgmt API); the host then displays a 4-digit PIN. Pass `--open` to **arm pairing from the web console** (or mgmt API); the host then displays a 4-digit PIN. Pass `--open` to
turn off the mandatory-pairing default and serve any device on the network (trusted single-user setups turn off the mandatory-pairing default and serve any device on the network (trusted single-user setups
only). The pairing flags below are `punktfunk1-host`-only and do **not** apply to `serve`. only). `punktfunk1-host` (below) requires pairing by default too; its `--allow-tofu` flag is the
test-host equivalent of `--open`.
## `punktfunk1-host` ## `punktfunk1-host`
@@ -68,14 +70,16 @@ punktfunk-host punktfunk1-host --source virtual
| `--seconds <N>` / `--frames <N>` | Bound each session by wall-clock seconds or frame count. | | `--seconds <N>` / `--frames <N>` | Bound each session by wall-clock seconds or frame count. |
| `--max-concurrent <N>` | Stream at most N sessions at once (default 4); overflow waits in the queue. | | `--max-concurrent <N>` | Stream at most N sessions at once (default 4); overflow waits in the queue. |
| `--max-sessions <N>` | Exit after N sessions (0 = serve forever). | | `--max-sessions <N>` | Exit after N sessions (0 = serve forever). |
| `--allow-pairing` | Accept PIN pairing; the host prints a PIN when a client pairs. | | `--allow-tofu` | Also accept **unpaired** clients (trust-on-first-use) and advertise pairing as optional. Pairing is required by default; trusted LANs only. (`--allow-pairing`/`--require-pairing` are the old names for the default behaviour and are accepted as no-ops.) |
| `--require-pairing` | Only serve paired devices (implies `--allow-pairing`). |
| `--pairing-pin <PIN>` | Use a fixed pairing PIN instead of a fresh random one per ceremony. For test harnesses/CI only — a guessable PIN defeats the ceremony's rate limit. | | `--pairing-pin <PIN>` | Use a fixed pairing PIN instead of a fresh random one per ceremony. For test harnesses/CI only — a guessable PIN defeats the ceremony's rate limit. |
| `--data-port <PORT>` | Pin the video data plane to this fixed UDP port and stream direct (no hole-punch). Same as `PUNKTFUNK_DATA_PORT`. |
| `--idle-timeout-ms <MS>` | Disconnect-detection latency — the QUIC control-connection idle timeout (default 8000). |
| `--no-mdns` | Skip the `_punktfunk._udp` advert; clients use `--connect HOST:PORT`. Same as `PUNKTFUNK_MDNS=0`. | | `--no-mdns` | Skip the `_punktfunk._udp` advert; clients use `--connect HOST:PORT`. Same as `PUNKTFUNK_MDNS=0`. |
`--max-concurrent`, `--allow-pairing`, and `--require-pairing` are **`punktfunk1-host`-only** — `serve` does not `--max-concurrent` and `--allow-tofu` are **`punktfunk1-host`-only** — `serve` does not accept them.
accept them. On `serve` you arm pairing from the web console instead, and concurrency is fixed at On `serve` you arm pairing from the web console instead (`--open` is its serve-any-device switch),
the built-in default (4 sessions) rather than settable from the command line. and concurrency is fixed at the built-in default (4 sessions) rather than settable from the command
line.
Both `serve` and `punktfunk1-host` advertise the host on the network so clients can discover it. List Both `serve` and `punktfunk1-host` advertise the host on the network so clients can discover it. List
hosts from another machine with `punktfunk-probe --discover`. Where multicast doesn't work (some hosts from another machine with `punktfunk-probe --discover`. Where multicast doesn't work (some
+3 -3
View File
@@ -99,9 +99,9 @@ certificate, so you import that certificate once before Windows will install the
3. Launch **Punktfunk** from the Start menu and pick your host. 3. Launch **Punktfunk** from the Start menu and pick your host.
> The Windows client's hardware-decode (D3D11VA) and HDR paths are complete but still pending > The Windows client's hardware decode is validated on NVIDIA and Intel GPUs; its HDR path is
> validation on real GPU hardware. If anything misbehaves, **[Moonlight](/docs/moonlight)** is a > complete but still pending on-glass validation. If anything misbehaves,
> solid alternative for Windows. > **[Moonlight](/docs/moonlight)** is a solid alternative for Windows.
## macOS ## macOS
+3 -2
View File
@@ -45,8 +45,9 @@ host's management console, click to arm pairing, and the host displays a 4-digit
list of paired devices. This works on a headless host over the network — there is no command-line flag list of paired devices. This works on a headless host over the network — there is no command-line flag
to arm pairing on `serve`. to arm pairing on `serve`.
(The standalone headless test host, `punktfunk1-host`, takes `--allow-pairing`/`--require-pairing` on its (The standalone headless test host, `punktfunk1-host`, requires pairing by default too and takes
command line instead; the production `serve` host arms pairing from the console.) `--allow-tofu` on its command line to accept unpaired clients; the production `serve` host arms
pairing from the console.)
Then, on the client: Then, on the client:
+7 -6
View File
@@ -16,9 +16,9 @@ see [Status & Progress](/docs/status).
| GameStream host (works with Moonlight) | ✅ | | GameStream host (works with Moonlight) | ✅ |
| Native `punktfunk/1` protocol | ✅ | | Native `punktfunk/1` protocol | ✅ |
| Linux host (KWin · GNOME · gamescope · Sway) | ✅ | | Linux host (KWin · GNOME · gamescope · Sway) | ✅ |
| Windows host (NVIDIA) | ✅ beta | | Windows host (NVIDIA · AMD · Intel) | ✅ beta |
| Apple client (macOS · iOS · iPadOS · tvOS) | ✅ | | Apple client (macOS · iOS · iPadOS · tvOS) | ✅ |
| Linux client (GTK4) | ✅ | | Linux client (GTK4 shell + Vulkan session) | ✅ |
| Android client (phone · TV) | ✅ | | Android client (phone · TV) | ✅ |
| Windows client | 🟡 | | Windows client | 🟡 |
| Web console + pairing | ✅ | | Web console + pairing | ✅ |
@@ -45,13 +45,14 @@ see [Status & Progress](/docs/status).
- **Secure by default** — SPAKE2 PIN pairing with pinned reconnects, one-click delegated approval from - **Secure by default** — SPAKE2 PIN pairing with pinned reconnects, one-click delegated approval from
the web console, and mDNS LAN auto-discovery. the web console, and mDNS LAN auto-discovery.
- **Tuned for latency** — concurrent sessions (stream one desktop to several devices at once), - **Tuned for latency** — concurrent sessions (stream one desktop to several devices at once),
mid-stream resolution renegotiation, a cross-machine clock-skew handshake, a 1 Gbps+ data plane, and mid-stream resolution renegotiation, a cross-machine clock-skew handshake, a 1 Gbps+ data plane,
an in-app network speed test that informs the bitrate picker. an in-app network speed test that informs the bitrate picker, and **automatic adaptive bitrate**
(the encoder re-targets mid-stream when bitrate is set to Automatic).
## 🟡 In progress ## 🟡 In progress
- **Windows client on-glass validation.** The hardware (D3D11VA) decode, HDR present, and GUI are - **Windows client on-glass validation.** Hardware decode and the GUI are validated on NVIDIA and
built and ship as a signed MSIX — they just need verification on real GPU hardware. Intel; the HDR present path still needs verification on real HDR hardware.
- **Apple presenter polish.** The lower-latency `VTDecompressionSession``CAMetalLayer` stage-2 - **Apple presenter polish.** The lower-latency `VTDecompressionSession``CAMetalLayer` stage-2
path is now the default; HDR brightness and 4:4:4 still need on-glass validation. path is now the default; HDR brightness and 4:4:4 still need on-glass validation.
- **Web console parity.** Surfacing the speed test and bitrate picker the apps already have. - **Web console parity.** Surfacing the speed test and bitrate picker the apps already have.
+17 -1
View File
@@ -20,6 +20,22 @@ life:
4. **displayed** — the picture is handed to the screen (as close to "photons" as the 4. **displayed** — the picture is handed to the screen (as close to "photons" as the
platform lets us measure). platform lets us measure).
## Detail levels
The overlay has four levels — **Off → Compact → Normal → Detailed** — that you cycle live
in-stream:
| Platform | Cycle with |
|---|---|
| Linux · Windows · Steam Deck | **Ctrl+Alt+Shift+S** |
| macOS / iPad (pointer or trackpad) | **⌃⌥⇧S** or a **three-finger tap** |
| Android · iPhone | a **three-finger tap** |
**Compact** is a one-line pill (fps · end-to-end ms · Mb/s, plus a loss flag when frames are being
lost). **Normal** adds the stream line and the p50/p95 headline. **Detailed** adds the decoder path,
HDR tag, and the per-stage breakdown. You can also set the level a stream starts at in each client's
Settings. The example below is the **Detailed** view.
## Reading the overlay ## Reading the overlay
``` ```
@@ -73,7 +89,7 @@ always spelled out rather than pretending:
| client | headline | why | | client | headline | why |
|---|---|---| |---|---|---|
| Windows, macOS/iOS (Metal presenter), Linux | `capture→on-glass` / `capture→displayed` | present instant available (GTK measures at hand-off to the compositor, which adds about one compositor cycle after it) | | Windows, macOS/iOS (Metal presenter), Linux | `capture→on-glass` / `capture→displayed` | present instant available (on Linux/Windows, measured right after the Vulkan swapchain present) |
| Android | `capture→displayed` | MediaCodec's per-frame render callback reports SurfaceFlinger's render timestamp; on the rare window where no callback is delivered (the platform may drop them under load) the HUD falls back to `capture→decoded` | | Android | `capture→displayed` | MediaCodec's per-frame render callback reports SurfaceFlinger's render timestamp; on the rare window where no callback is delivered (the platform may drop them under load) the HUD falls back to `capture→decoded` |
| macOS/iOS fallback presenter | `capture→received` | the system video layer hides decode and present timing entirely | | macOS/iOS fallback presenter | `capture→received` | the system video layer hides decode and present timing entirely |
+21 -11
View File
@@ -15,8 +15,8 @@ A high-level view of where punktfunk stands. The ordered plan of work is on the
| **Native protocol**`punktfunk/1` (QUIC control + UDP data, GF(2¹⁶) Leopard FEC + AES-GCM) | ✅ full session planes, validated live | | **Native protocol**`punktfunk/1` (QUIC control + UDP data, GF(2¹⁶) Leopard FEC + AES-GCM) | ✅ full session planes, validated live |
| **Windows host** (x64) | 🟡 implemented & shipping as a signed installer; NVIDIA/AMD/Intel encode, newer than the Linux host | | **Windows host** (x64) | 🟡 implemented & shipping as a signed installer; NVIDIA/AMD/Intel encode, newer than the Linux host |
| **macOS / iOS / iPadOS / tvOS client** | ✅ full client; on-glass-validated stage-2 presenter is the default | | **macOS / iOS / iPadOS / tvOS client** | ✅ full client; on-glass-validated stage-2 presenter is the default |
| **Linux client** (`punktfunk-client`, GTK4/libadwaita) | ✅ full client; VAAPI zero-copy decode + software fallback | | **Linux client** (`punktfunk-client`, relm4/GTK4 shell + Vulkan session) | ✅ full client; Vulkan Video → VAAPI → software decode |
| **Windows client** (`punktfunk-client`, WinUI 3) | ✅ stage 1 complete; ships as signed MSIX; on-glass hardware validation pending | | **Windows client** (`punktfunk-client`, WinUI 3) | ✅ ships as signed MSIX; hardware decode validated on NVIDIA + Intel; HDR on-glass validation pending |
| **Android client** (phone + Android TV) | ✅ full client; hardware HEVC decode + HDR10 | | **Android client** (phone + Android TV) | ✅ full client; hardware HEVC decode + HDR10 |
| **Web console** (over the management API) | ✅ status · devices · pairing | | **Web console** (over the management API) | ✅ status · devices · pairing |
@@ -54,10 +54,10 @@ host is newer than the Linux host.)
| Client | Highlights | | Client | Highlights |
|---|---| |---|---|
| **macOS / iOS / iPadOS / tvOS** | VideoToolbox HEVC decode, GameController capture, full DualSense feedback, mDNS discovery, PIN pairing + TOFU, network speed test, latency HUD. Stage-2 presenter (`VTDecompressionSession``CAMetalLayer`, ~11 ms p50 capture→present) is validated on glass and is the default (stage 1 remains the fallback when Metal is unavailable). Ships as one universal TestFlight build / App Store listing. | | **macOS / iOS / iPadOS / tvOS** | VideoToolbox HEVC + AV1 decode (AV1 on hardware that decodes it — M3-class Macs, A17 Pro-class iPhones), GameController capture, full DualSense feedback, mDNS discovery, PIN pairing + TOFU, network speed test, latency HUD. Stage-2 presenter (`VTDecompressionSession``CAMetalLayer`, ~11 ms p50 capture→present) is validated on glass and is the default (stage 1 remains the fallback when Metal is unavailable). Ships as one universal TestFlight build / App Store listing. |
| **Linux** (`punktfunk-client`) | GTK4/libadwaita. FFmpeg decode with VAAPI → DRM-PRIME dmabuf zero-copy (Intel/AMD; software fallback on NVIDIA), PipeWire audio + mic, SDL3 gamepads incl. DualSense, mDNS discovery, PIN pairing + TOFU, speed test. Ships as Flatpak, apt, rpm, and Arch packages. | | **Linux** (`punktfunk-client`) | relm4/GTK4 shell; streams run in the spawned Vulkan session presenter. Hardware decode via Vulkan Video (all vendors, incl. NVIDIA) with VAAPI dmabuf and software fallbacks, PipeWire audio + mic, SDL3 gamepads incl. DualSense, mDNS discovery, PIN pairing + TOFU, speed test, Skia console UI + tiered stats overlay. Ships as Flatpak, apt, rpm, and Arch packages. |
| **Windows** (`punktfunk-client`) | WinUI 3. D3D11VA zero-copy decode, HDR10, WASAPI audio + mic, SDL3 gamepads incl. DualSense, mDNS discovery, and the full PIN/TOFU trust surface are all implemented. Ships as a signed MSIX (x86_64 + ARM64). **Stage 1 complete; D3D11VA decode, HDR present, and the GUI are pending on-glass validation on real GPU hardware.** | | **Windows** (`punktfunk-client`) | WinUI 3 shell; streams run in the Vulkan session presenter with a Vulkan Video → D3D11VA → software decode chain, HDR10, WASAPI audio + mic, SDL3 gamepads incl. DualSense, mDNS discovery, and the full PIN/TOFU trust surface. Ships as a signed MSIX (x86_64 + ARM64). **Hardware decode validated on NVIDIA and Intel; HDR present pending on-glass validation.** |
| **Android** (phone + Android TV) | Kotlin app with a Rust core over JNI. NDK `AMediaCodec` hardware HEVC decode + HDR10 (Main10/BT.2020 PQ), Opus/Oboe audio + mic, gamepad input with rumble/HID feedback, mDNS discovery, PIN pairing + TOFU (Keystore identity), live stats HUD, and D-pad/controller focus navigation for TV. Ships to the Google Play Internal Testing track. | | **Android** (phone + Android TV) | Kotlin app with a Rust core over JNI. NDK `AMediaCodec` hardware HEVC decode + HDR10 (Main10/BT.2020 PQ), Opus/Oboe audio + mic, gamepad input with rumble/HID feedback, mDNS discovery, PIN pairing + TOFU (Keystore identity), tiered stats overlay (three-finger tap), custom resolutions, and D-pad/controller focus navigation for TV. Ships to the Google Play Internal Testing track. |
`punktfunk-probe` is a headless reference and measurement client (for testing and `punktfunk-probe` is a headless reference and measurement client (for testing and
benchmarking, not everyday use). benchmarking, not everyday use).
@@ -68,7 +68,7 @@ The stack has been validated live on a range of hosts and clients:
- **Hosts:** Ubuntu (GNOME / KDE), Fedora KDE, and Bazzite (gamescope) on machines with - **Hosts:** Ubuntu (GNOME / KDE), Fedora KDE, and Bazzite (gamescope) on machines with
RTX-class NVIDIA GPUs, across the KWin, gamescope, Mutter, and Sway/wlroots backends. RTX-class NVIDIA GPUs, across the KWin, gamescope, Mutter, and Sway/wlroots backends.
- **Clients:** native macOS, Linux, and Android clients against live hosts, plus stock - **Clients:** native macOS, Linux, Windows, and Android clients against live hosts, plus stock
Moonlight clients over the GameStream path. Moonlight clients over the GameStream path.
- **Cross-machine latency** is measured and skew-corrected (a wall-clock handshake removes - **Cross-machine latency** is measured and skew-corrected (a wall-clock handshake removes
the inter-machine clock offset), so capture-to-present numbers are valid across the LAN. the inter-machine clock offset), so capture-to-present numbers are valid across the LAN.
@@ -77,11 +77,21 @@ The stack has been validated live on a range of hosts and clients:
Notable capabilities that have landed, newest first: Notable capabilities that have landed, newest first:
- **Tiered stats overlay everywhere.** One shared stats vocabulary with **Compact / Normal /
Detailed** levels on every client, cycled live in-stream — see
[Understanding the Stats Overlay](/docs/stats).
- **Per-client display scaling on GNOME.** The host persists each client's scale itself and
reapplies it on reconnect (GNOME's virtual-monitor API exposes no stable identity to key it on).
- **Adaptive bitrate.** With bitrate set to **Automatic**, the host re-targets the encoder
mid-stream as the link's measured capacity changes.
- **Gamepad console shell.** The session client carries a full controller-driven shell — host
list, PIN pairing, settings, screen transitions, and an on-screen keyboard — usable with no
desktop at all (e.g. gamescope on a Steam Deck).
- **Native Linux client (stage 1).** GTK4/libadwaita app that links `punktfunk-core` - **Native Linux client (stage 1).** GTK4/libadwaita app that links `punktfunk-core`
directly: mDNS host list, TOFU + SPAKE2 PIN pairing, FFmpeg HEVC decode, PipeWire audio directly: mDNS host list, TOFU + SPAKE2 PIN pairing, FFmpeg HEVC decode, PipeWire audio
with mic uplink, SDL3 gamepad capture with rumble/lightbar feedback, layout-independent with mic uplink, SDL3 gamepad capture with rumble/lightbar feedback, layout-independent
keyboard, absolute mouse, fullscreen, and a stats overlay. VAAPI → `GdkDmabufTexture` keyboard, absolute mouse, fullscreen, and a stats overlay. (Since re-architected: streams
zero-copy decode on Intel/AMD with a proven software fallback. now run in a spawned Vulkan session presenter with Vulkan Video decode on all vendors.)
- **Delegated pairing approval.** An unpaired device that knocks on a pairing-required host - **Delegated pairing approval.** An unpaired device that knocks on a pairing-required host
appears as a pending request in the web console's Pairing page; one click approves and appears as a pending request in the web console's Pairing page; one click approves and
pins its certificate — no PIN fetched out of band. pins its certificate — no PIN fetched out of band.
@@ -112,7 +122,7 @@ See the [Roadmap](/docs/roadmap) for the ordered list. Near-term:
- **True glass-to-glass latency** — combine the client present-stamp (decode → present) - **True glass-to-glass latency** — combine the client present-stamp (decode → present)
with the host render → capture term for a complete end-to-end number. with the host render → capture term for a complete end-to-end number.
- **On-glass validation of the Windows client** (D3D11VA decode, HDR present, GUI) on real - **On-glass validation of the Windows client's HDR present path** (hardware decode and the
GPU hardware. GUI are already validated on NVIDIA and Intel).
- **gamescope multi-user isolation** — per-session input/audio so concurrent sessions can - **gamescope multi-user isolation** — per-session input/audio so concurrent sessions can
be fully independent desktops (the shared-desktop multi-view case already works). be fully independent desktops (the shared-desktop multi-view case already works).
+4 -4
View File
@@ -19,9 +19,9 @@ opened on.
> you also use in person, or a multi-monitor workstation. > you also use in person, or a multi-monitor workstation.
> **What's live today:** **keep-alive** (linger, or **forever**), **topology** (extend / primary / > **What's live today:** **keep-alive** (linger, or **forever**), **topology** (extend / primary /
> exclusive), **conflict handling**, **per-client identity + persistent scaling** (Windows *and* > exclusive), **conflict handling**, **per-client identity + persistent scaling** (Windows, KDE/KWin
> KDE/KWin), and **multi-monitor layout** (several clients as monitors of one desktop) are all > *and* GNOME/Mutter), and **multi-monitor layout** (several clients as monitors of one desktop) are
> enforced. A reconnect always resumes the kept display — even a fast one — instead of spawning a > all enforced. A reconnect always resumes the kept display — even a fast one — instead of spawning a
> second. The remaining gaps are noted inline: the Linux `primary` physical-keep *effect*, Sway > second. The remaining gaps are noted inline: the Linux `primary` physical-keep *effect*, Sway
> `exclusive`, and multi-display for a *single* client (that last is the next stage). > `exclusive`, and multi-display for a *single* client (that last is the next stage).
@@ -153,7 +153,7 @@ client a *stable display identity*, so your desktop environment keys its per-mon
|---|---|---| |---|---|---|
| **Windows** | ✅ today | Connect, set scaling in Settings while streaming — Windows remembers it per client. | | **Windows** | ✅ today | Connect, set scaling in Settings while streaming — Windows remembers it per client. |
| **KDE / KWin** | ✅ today | Set scaling in System Settings while streaming; KWin keys it to a stable per-client output name and reapplies it on reconnect. Validated live (150 %/125 % survive a full disconnect + reconnect). | | **KDE / KWin** | ✅ today | Set scaling in System Settings while streaming; KWin keys it to a stable per-client output name and reapplies it on reconnect. Validated live (150 %/125 % survive a full disconnect + reconnect). |
| **GNOME / Mutter** | | GNOME's virtual-monitor API exposes no stable identity to key config on. | | **GNOME / Mutter** | ✅ today | GNOME's virtual-monitor API exposes no stable identity to key config on, so the **host persists the scale itself**: set scaling in Settings while streaming — the host captures the change, remembers it per client, and reapplies it on reconnect. |
| **Sway / wlroots** | ❌ | Headless outputs can't carry a stable identity; pin scale in your sway config instead. | | **Sway / wlroots** | ❌ | Headless outputs can't carry a stable identity; pin scale in your sway config instead. |
## Legacy environment knobs ## Legacy environment knobs
+1 -1
View File
@@ -339,7 +339,7 @@ control port (UDP 9777) + mDNS + the mgmt/library API (TCP 47990, HTTPS + mTLS).
host runs `serve --gamestream` (both planes). The host runs `serve --gamestream` (both planes). The
per-port breakdown below is for reference (or for opening ports by hand); the ports are the code per-port breakdown below is for reference (or for opening ports by hand); the ports are the code
constants (`crates/punktfunk-host/src/gamestream/mod.rs`, `mgmt.rs`) and the GameStream-host port-map constants (`crates/punktfunk-host/src/gamestream/mod.rs`, `mgmt.rs`) and the GameStream-host port-map
(`design/gamestream-host-plan.md`). (punktfunk-planning: `gamestream-host-plan.md`).
**GameStream / Moonlight ports** (fixed; Moonlight derives them from the HTTP base). These only apply **GameStream / Moonlight ports** (fixed; Moonlight derives them from the HTTP base). These only apply
when the host runs `serve --gamestream` (the bundled unit's default); on a bare-`serve` native-only when the host runs `serve --gamestream` (the bundled unit's default); on a bare-`serve` native-only
+2 -1
View File
@@ -2,7 +2,8 @@
`punktfunk-host` is published as a `.deb` to **Gitea's Debian package registry** in the public `punktfunk-host` is published as a `.deb` to **Gitea's Debian package registry** in the public
`unom` org, so the Ubuntu hosts update with plain `apt`. CI (`.gitea/workflows/deb.yml`) builds `unom` org, so the Ubuntu hosts update with plain `apt`. CI (`.gitea/workflows/deb.yml`) builds
and publishes on every push to `main` (a rolling `0.5.0~ciN.g<sha>` build to the **`canary`** apt and publishes on every push to `main` (a rolling `<next-minor>~ciN.g<sha>` build the base is
derived from the latest stable tag by `scripts/ci/pf-version.sh` — to the **`canary`** apt
distribution) and on `vX.Y.Z` tags (a clean `X.Y.Z` to the **`stable`** distribution, plus attached distribution) and on `vX.Y.Z` tags (a clean `X.Y.Z` to the **`stable`** distribution, plus attached
to the unified Gitea Release). The two are separate apt distributions, so a stable box never jumps to the unified Gitea Release). The two are separate apt distributions, so a stable box never jumps
to a canary build — see [Release Channels](https://punktfunk.unom.io/docs/channels). The repo line to a canary build — see [Release Channels](https://punktfunk.unom.io/docs/channels). The repo line
+13 -4
View File
@@ -4,7 +4,8 @@ The native Linux **client** — the shell (crate `punktfunk-client-linux`, binar
`punktfunk-client`) plus the Vulkan session binary it execs for streaming (crate `punktfunk-client`) plus the Vulkan session binary it execs for streaming (crate
`punktfunk-client-session`, binary `punktfunk-session`) — is `punktfunk-client-session`, binary `punktfunk-session`) — is
published two ways by CI (`.gitea/workflows/flatpak.yml`), on every push to `main` (a rolling published two ways by CI (`.gitea/workflows/flatpak.yml`), on every push to `main` (a rolling
`0.0.1-ciN.<sha>` build) and on `v*` tags (a clean `X.Y.Z`): `<next-minor>-ciN.g<sha>` build, base derived from the latest stable tag by
`scripts/ci/pf-version.sh`) and on `v*` tags (a clean `X.Y.Z`):
1. **Hosted OSTree repo at `https://flatpak.unom.io`** (recommended) — a GPG-signed Flatpak 1. **Hosted OSTree repo at `https://flatpak.unom.io`** (recommended) — a GPG-signed Flatpak
remote served by a static Caddy container on unom-1, so users **install once and then remote served by a static Caddy container on unom-1, so users **install once and then
@@ -58,11 +59,11 @@ per-user (no root, survives SteamOS updates). This is what the Decky plugin uses
repo above is the better path for a human on the Deck: repo above is the better path for a human on the Deck:
```sh ```sh
# Pick a version: a tag like 1.2.3, or the newest main build's 0.0.1-ciN.gSHA. # Pick a version: a tag like 1.2.3, or the newest main build's <next-minor>-ciN.gSHA.
VER=1.2.3 VER=1.2.3
URL="https://git.unom.io/api/packages/unom/generic/punktfunk-client-flatpak/$VER/punktfunk-client-$VER.flatpak" URL="https://git.unom.io/api/packages/unom/generic/punktfunk-client-flatpak/$VER/punktfunk-client-$VER.flatpak"
# Flathub must be enabled (it is on the Deck) so the GNOME runtime + ffmpeg-full pull in: # Flathub must be enabled (it is on the Deck) so the GNOME runtime + codecs-extra extension pull in:
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
curl -fL -o /tmp/punktfunk-client.flatpak "$URL" curl -fL -o /tmp/punktfunk-client.flatpak "$URL"
@@ -138,11 +139,19 @@ extension point (auto-downloaded with the runtime; no app-side codec declaration
`--device=all` (GPU/VAAPI render node + evdev + the hidraw char-devices SDL3 needs for DualSense) `--device=all` (GPU/VAAPI render node + evdev + the hidraw char-devices SDL3 needs for DualSense)
+ `--socket=pulseaudio` (PipeWire-pulse: playback + mic) + `--share=network`. Alongside it: + `--socket=pulseaudio` (PipeWire-pulse: playback + mic) + `--share=network`. Alongside it:
`io.unom.Punktfunk.desktop`, `io.unom.Punktfunk.metainfo.xml`, `io.unom.Punktfunk.svg` (all `io.unom.Punktfunk.desktop`, `io.unom.Punktfunk.metainfo.xml`, `io.unom.Punktfunk.svg` (all
installed by the manifest). `cargo-sources.json` (the offline crate cache) is a pure function of installed by the manifest). A `vulkan-headers` module supplies what the session binary's ash/Vulkan
build needs. `cargo-sources.json` (the offline crate cache) is a pure function of
`Cargo.lock`; CI regenerates it each build and it is **gitignored** — generate it on any box with `Cargo.lock`; CI regenerates it each build and it is **gitignored** — generate it on any box with
network + `python3`/`aiohttp`/`tomlkit` (`build-flatpak.sh` does this automatically) and, for a network + `python3`/`aiohttp`/`tomlkit` (`build-flatpak.sh` does this automatically) and, for a
build host that lacks those (the Deck), rsync the generated file in alongside the manifest. build host that lacks those (the Deck), rsync the generated file in alongside the manifest.
**Offline Skia:** the session binary's Skia console UI (`pf-console-ui``skia-safe`) normally
downloads prebuilt `libskia` binaries at build time, which is dead in the offline sandbox — so the
manifest pins a `skia-binaries-….tar.gz` source and points the build at it with
`SKIA_BINARIES_URL: file://…`. When bumping the `skia-safe`/`skia-bindings` crate version, update
that pinned tarball (URL + sha256) to the matching `skia-binaries` release or the build breaks
offline.
## Hosting the repo (unom-1) + one-time setup ## Hosting the repo (unom-1) + one-time setup
The OSTree repo flatpak-builder produces is GPG-signed in CI and rsynced to unom-1, where a tiny The OSTree repo flatpak-builder produces is GPG-signed in CI and rsynced to unom-1, where a tiny
+1 -1
View File
@@ -12,7 +12,7 @@ binds the kernel `hid-steam` driver, but **Steam Input will not manage it**: Ste
controller to USB **interface 2**, and a UHID device has no USB interface number (`Interface: -1` in controller to USB **interface 2**, and a UHID device has no USB interface number (`Interface: -1` in
Steam's `controller.txt`), so Steam enumerates it but never promotes it. A single-interface DualSense Steam's `controller.txt`), so Steam enumerates it but never promotes it. A single-interface DualSense
is accepted at `-1` (no ambiguity), but the multi-interface Deck specifically needs interface 2. See is accepted at `-1` (no ambiguity), but the multi-interface Deck specifically needs interface 2. See
`design/steam-controller-deck-support.md` §11. punktfunk-planning: `steam-controller-deck-support.md` §11.
A real multi-interface USB device with the controller on interface 2 requires a **USB gadget**. A real multi-interface USB device with the controller on interface 2 requires a **USB gadget**.
SteamOS ships every piece (`CONFIG_USB_DUMMY_HCD=m`, `CONFIG_USB_RAW_GADGET=m`, SteamOS ships every piece (`CONFIG_USB_DUMMY_HCD=m`, `CONFIG_USB_RAW_GADGET=m`,
@@ -17,7 +17,7 @@ sudo modprobe vhci_hcd
sudo usbip attach -r 127.0.0.1 -b 0-0-0 sudo usbip attach -r 127.0.0.1 -b 0-0-0
``` ```
See `design/steam-deck-passthrough-plan.md` for the production build plan (vendor-trim the crate to See punktfunk-planning: `steam-deck-passthrough-plan.md` for the production build plan (vendor-trim the crate to
drop the `rusb`/libusb dep; in-process `vhci_hcd` attach to avoid the `usbip` CLI; transport-select drop the `rusb`/libusb dep; in-process `vhci_hcd` attach to avoid the `usbip` CLI; transport-select
`raw_gadget``usbip`→UHID). `usbip` crate API: a custom `UsbInterfaceHandler` `raw_gadget``usbip`→UHID). `usbip` crate API: a custom `UsbInterfaceHandler`
`get_class_specific_descriptor()` = the 9-byte HID descriptor; `handle_urb()` dispatches EP0 `get_class_specific_descriptor()` = the 9-byte HID descriptor; `handle_urb()` dispatches EP0
+2 -1
View File
@@ -3,7 +3,8 @@
`punktfunk-host` is published as an RPM to **Gitea's RPM package registry** in the public `unom` `punktfunk-host` is published as an RPM to **Gitea's RPM package registry** in the public `unom`
org (stable groups `bazzite`/`fedora-44`, canary groups `bazzite-canary`/`fedora-44-canary`), so org (stable groups `bazzite`/`fedora-44`, canary groups `bazzite-canary`/`fedora-44-canary`), so
Bazzite / Fedora Atomic hosts layer and update it with `rpm-ostree`. CI (`.gitea/workflows/rpm.yml`) Bazzite / Fedora Atomic hosts layer and update it with `rpm-ostree`. CI (`.gitea/workflows/rpm.yml`)
builds and publishes on every push to `main` (a rolling `0.5.0-0.ciN.g<sha>` build to the `*-canary` builds and publishes on every push to `main` (a rolling `<next-minor>-0.ciN.g<sha>` build the base
is derived from the latest stable tag by `scripts/ci/pf-version.sh` — to the `*-canary`
groups) and on `vX.Y.Z` tags (a clean `X.Y.Z-1` to the base groups, plus attached to the unified groups) and on `vX.Y.Z` tags (a clean `X.Y.Z-1` to the base groups, plus attached to the unified
Gitea Release) — separate repos, so a stable box never jumps to a canary build (see Gitea Release) — separate repos, so a stable box never jumps to a canary build (see
[Release Channels](https://punktfunk.unom.io/docs/channels)). The `baseurl` below subscribes to the [Release Channels](https://punktfunk.unom.io/docs/channels)). The `baseurl` below subscribes to the
+4 -3
View File
@@ -3,7 +3,7 @@
A one-file, signed `setup.exe` for the punktfunk streaming **host** on Windows, published to Gitea's A one-file, signed `setup.exe` for the punktfunk streaming **host** on Windows, published to Gitea's
generic package registry (`punktfunk-host-windows`) by `.gitea/workflows/windows-host.yml`. generic package registry (`punktfunk-host-windows`) by `.gitea/workflows/windows-host.yml`.
> Full picture (drivers-from-source, toolchain, CI, dev loop): **[`design/windows-build-and-packaging.md`](../../design/windows-build-and-packaging.md)**. This README is the `packaging/windows/` file index. > Full picture (drivers-from-source, toolchain, CI, dev loop): **punktfunk-planning: `windows-build-and-packaging.md`** (internal planning repo). This README is the `packaging/windows/` file index.
## Windows 11 22H2+ only (no Windows 10) ## Windows 11 22H2+ only (no Windows 10)
@@ -135,7 +135,7 @@ fresh install uses the generated random console password — read it from
> needed). Building from source keeps `.dll`/`.inf`/`.cat` in lockstep. nefcon (the device-node tool - > needed). Building from source keeps `.dll`/`.inf`/`.cat` in lockstep. nefcon (the device-node tool -
> the install creates the `root\pf_vdisplay` node with it, **never** `devgen`, which leaves persistent > the install creates the `root\pf_vdisplay` node with it, **never** `devgen`, which leaves persistent
> phantom devices) is fetched + SHA-256-verified from its pinned release in `stage-pf-vdisplay.ps1`. See > phantom devices) is fetched + SHA-256-verified from its pinned release in `stage-pf-vdisplay.ps1`. See
> [`design/windows-build-and-packaging.md`](../../design/windows-build-and-packaging.md) for the toolchain > punktfunk-planning: `windows-build-and-packaging.md` (internal planning repo) for the toolchain
> + signing details. > + signing details.
## Dev iteration on the test box (driver) ## Dev iteration on the test box (driver)
@@ -179,4 +179,5 @@ Push a `vX.Y.Z` tag — one tag releases every platform (see
[Release Channels](https://punktfunk.unom.io/docs/channels)). The workflow builds, signs, and [Release Channels](https://punktfunk.unom.io/docs/channels)). The workflow builds, signs, and
publishes `punktfunk-host-setup-X.Y.Z.exe` + the public `.cer`, refreshes the stable `latest/` publishes `punktfunk-host-setup-X.Y.Z.exe` + the public `.cer`, refreshes the stable `latest/`
alias, and attaches the installer to the unified Gitea Release. Main pushes publish rolling alias, and attaches the installer to the unified Gitea Release. Main pushes publish rolling
`0.3.<run>` **canary** builds to the `canary/` alias. `<next-minor>.<run>` **canary** builds (base derived from the latest stable tag by
`scripts/ci/pf-version.ps1`) to the `canary/` alias.
@@ -87,7 +87,7 @@ silently breaks them:
WUDFHost (so the per-instance statics don't collide), and the driver reads its pad index from the WUDFHost (so the per-instance statics don't collide), and the driver reads its pad index from the
device Location (`WdfDeviceAllocAndQueryProperty`) to poll its own `*-boot-<index>` bootstrap device Location (`WdfDeviceAllocAndQueryProperty`) to poll its own `*-boot-<index>` bootstrap
mailbox (the DATA section itself is unnamed — the sealed pad channel, mailbox (the DATA section itself is unnamed — the sealed pad channel,
`design/gamepad-channel-sealing.md` — and its `pad_index` is validated against this index on punktfunk-planning: `gamepad-channel-sealing.md` — and its `pad_index` is validated against this
attach). index on attach).
- Port of the WDK `vhidmini2` UMDF2 sample; the DualSense identity + 273-byte descriptor + feature - Port of the WDK `vhidmini2` UMDF2 sample; the DualSense identity + 273-byte descriptor + feature
blobs `0x05`/`0x09`/`0x20` come from `crates/punktfunk-host/src/inject/dualsense.rs`. blobs `0x05`/`0x09`/`0x20` come from `crates/punktfunk-host/src/inject/proto/dualsense_proto.rs`.
+1 -1
View File
@@ -15,7 +15,7 @@ instance (= player slot 03) with `CreateFile`, and polls it with buffered IOC
- registers the XUSB interface with `WdfDeviceCreateDeviceInterface(device, &XUSB_GUID, NULL)`; - registers the XUSB interface with `WdfDeviceCreateDeviceInterface(device, &XUSB_GUID, NULL)`;
- answers the XUSB IOCTLs (all `METHOD_BUFFERED`, delivered to user mode by the reflector) from - answers the XUSB IOCTLs (all `METHOD_BUFFERED`, delivered to user mode by the reflector) from
controller state the host publishes into an **unnamed** shared DATA section reached over the controller state the host publishes into an **unnamed** shared DATA section reached over the
**sealed pad channel** (`design/gamepad-channel-sealing.md`): the host duplicates the section **sealed pad channel** (punktfunk-planning: `gamepad-channel-sealing.md`): the host duplicates the section
handle into this driver's WUDFHost, bootstrapped via the named `Global\pfxusb-boot-<index>` handle into this driver's WUDFHost, bootstrapped via the named `Global\pfxusb-boot-<index>`
mailbox (`pf_driver_proto::gamepad::PadBootstrap`); a game's rumble (`SET_STATE`) is published mailbox (`pf_driver_proto::gamepad::PadBootstrap`); a game's rumble (`SET_STATE`) is published
back for the host to forward to the client. back for the host to forward to the client.
+1 -1
View File
@@ -3,7 +3,7 @@
Run a punktfunk **host** on a Steam Deck — stream its Game Mode (or KDE desktop) *to* other devices. Run a punktfunk **host** on a Steam Deck — stream its Game Mode (or KDE desktop) *to* other devices.
(Streaming *to* a Deck is the client; use the Flatpak + [Decky plugin](../../clients/decky/) instead.) (Streaming *to* a Deck is the client; use the Flatpak + [Decky plugin](../../clients/decky/) instead.)
User-facing guide: **docs-site → "Steam Deck (Host)"** (`docs-site/content/docs/steam-deck-host.md`). User-facing guide: **docs-site → "SteamOS (Host)"** (`docs-site/content/docs/steamos-host.md`).
This README is the deep reference for what the scripts do and how to operate them by hand. This README is the deep reference for what the scripts do and how to operate them by hand.
## Why build on-device (not a package or prebuilt binary) ## Why build on-device (not a package or prebuilt binary)
+1 -1
View File
@@ -51,7 +51,7 @@ powershell -ExecutionPolicy Bypass -File scripts\windows\build-web.ps1
``` ```
`bun install && bun run build` (Nitro `noExternals` -> a self-contained `.output`, no `bun install && bun run build` (Nitro `noExternals` -> a self-contained `.output`, no
`node_modules`/`.npmrc`), then restarts the `PunktfunkWeb` task and checks `:3000/login`. Use `node_modules`/`.npmrc`), then restarts the `PunktfunkWeb` task and checks `:47992/login`. Use
this to iterate on the console against an installed host - `punktfunk-host.exe web setup` (or a this to iterate on the console against an installed host - `punktfunk-host.exe web setup` (or a
fresh install) is what creates the task in the first place. fresh install) is what creates the task in the first place.
+1 -1
View File
@@ -1,6 +1,6 @@
# latency-probe # latency-probe
A **glass-to-glass latency** measurement tool (design/implementation-plan §10): it renders a A **glass-to-glass latency** measurement tool (punktfunk-planning: `implementation-plan.md` §10): it renders a
timestamp/QR on the host, reads it back off the client's capture (or a photodiode, for true photons), timestamp/QR on the host, reads it back off the client's capture (or a photodiode, for true photons),
and tracks p50/p99 — so latency regressions are quantifiable rather than felt. and tracks p50/p99 — so latency regressions are quantifiable rather than felt.
+1 -1
View File
@@ -12,5 +12,5 @@ builds. Use it to sanity-check the FEC before reaching for the real `punktfunk/1
cargo run -p loss-harness # from the repo root cargo run -p loss-harness # from the repo root
``` ```
Part of the measurement tooling (design/implementation-plan §10), alongside Part of the measurement tooling (punktfunk-planning: `implementation-plan.md` §10), alongside
[`latency-probe`](../latency-probe/README.md). [`latency-probe`](../latency-probe/README.md).