From ef39050dbc39799effdacda09e69694611fce5c6 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Fri, 10 Jul 2026 09:13:42 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20repo-wide=20housekeeping=20=E2=80=94=20?= =?UTF-8?q?sync=20README=20&=20docs=20with=20the=20code=20as=20shipped?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 ( 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 --- CONTRIBUTING.md | 4 ++- README.md | 26 +++++++++++---- SECURITY.md | 2 +- clients/android/README.md | 2 +- .../native/vendor/ndk/README.punktfunk.md | 2 +- clients/apple/README.md | 3 +- clients/decky/README.md | 2 ++ clients/linux/README.md | 23 +++++++------ clients/session/README.md | 11 ++++--- clients/windows/README.md | 3 +- crates/pf-driver-proto/README.md | 2 +- crates/punktfunk-core/README.md | 2 +- crates/punktfunk-host/README.md | 4 +-- docs-site/README.md | 3 +- docs-site/content/docs/clients.md | 22 +++++++------ docs-site/content/docs/configuration.md | 13 ++++++++ docs-site/content/docs/gnome.md | 4 +++ docs-site/content/docs/host-cli.md | 16 ++++++---- docs-site/content/docs/install-client.md | 6 ++-- docs-site/content/docs/pairing.md | 5 +-- docs-site/content/docs/roadmap.md | 13 ++++---- docs-site/content/docs/stats.md | 18 ++++++++++- docs-site/content/docs/status.md | 32 ++++++++++++------- docs-site/content/docs/virtual-displays.md | 8 ++--- packaging/bazzite/README.md | 2 +- packaging/debian/README.md | 3 +- packaging/flatpak/README.md | 17 +++++++--- packaging/linux/steam-deck-gadget/README.md | 2 +- .../steam-deck-gadget/usbip-poc/README.md | 2 +- packaging/rpm/README.md | 3 +- packaging/windows/README.md | 7 ++-- .../windows/drivers/pf-dualsense/README.md | 6 ++-- packaging/windows/drivers/pf-xusb/README.md | 2 +- scripts/steamdeck/README.md | 2 +- scripts/windows/README.md | 2 +- tools/latency-probe/README.md | 2 +- tools/loss-harness/README.md | 2 +- 37 files changed, 182 insertions(+), 96 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e4fac5f0..fdb123a4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 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. diff --git a/README.md b/README.md index e09f56d5..e6c5c205 100644 --- a/README.md +++ b/README.md @@ -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 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 -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 @@ -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 | | **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 | -| **macOS / iOS / tvOS client** (`clients/apple`) | ✅ Streaming live: VideoToolbox decode, 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 | +| **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` + `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 | -| **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 | +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 (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, @@ -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: ```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 clippy --workspace --all-targets -- -D warnings cargo fmt --all --check @@ -137,18 +142,27 @@ and the [docs site](https://docs.punktfunk.unom.io). crates/ 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 + 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/ 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) android/ Android phone + TV app (Kotlin · Rust JNI core · AMediaCodec · AAudio) probe/ headless reference / measurement client for punktfunk/1 decky/ Steam Deck Decky plugin 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 docs-site/ public documentation site (Fumadocs) — https://docs.punktfunk.unom.io include/punktfunk_core.h cbindgen-generated C header (checked in) tools/ latency-probe · loss-harness (measurement) +ci/ CI container images (rust-ci · fedora-rpm) ``` ## Design invariants diff --git a/SECURITY.md b/SECURITY.md index cf328f23..ba4ed0ec 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -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 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, admin, a paired client, …). - Steps to reproduce, a proof-of-concept, or a crash/log if you have one. diff --git a/clients/android/README.md b/clients/android/README.md index 8ba01ce3..1d06356e 100644 --- a/clients/android/README.md +++ b/clients/android/README.md @@ -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 17–21, not 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 -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 builds the `.so` as part of the normal build. diff --git a/clients/android/native/vendor/ndk/README.punktfunk.md b/clients/android/native/vendor/ndk/README.punktfunk.md index f08d710b..1627c115 100644 --- a/clients/android/native/vendor/ndk/README.punktfunk.md +++ b/clients/android/native/vendor/ndk/README.punktfunk.md @@ -7,7 +7,7 @@ in the workspace root. **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 `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. Drop this vendor copy when upstream exposes the raw pointer or a frame-rendered diff --git a/clients/apple/README.md b/clients/apple/README.md index 76193017..7dabf655 100644 --- a/clients/apple/README.md +++ b/clients/apple/README.md @@ -122,7 +122,8 @@ PUNKTFUNK_AUTOCONNECT= PUNKTFUNK_MODE=1280x720x60 swift run PunktfunkCli - **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 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 diff --git a/clients/decky/README.md b/clients/decky/README.md index e07775e0..be63aa4b 100644 --- a/clients/decky/README.md +++ b/clients/decky/README.md @@ -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/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/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/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=` (pinned game) / `PF_BROWSE=1` + `PF_MGMT=` (on-screen library); ids are validated space/quote-free at pin AND launch time. | | `src/backend.ts` | Typed `callable` bridges to `main.py`. | diff --git a/clients/linux/README.md b/clients/linux/README.md index 5e06107e..a73c26eb 100644 --- a/clients/linux/README.md +++ b/clients/linux/README.md @@ -1,17 +1,20 @@ # punktfunk — Linux client 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 -low-latency stream on glass at your display's own resolution and refresh rate. +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`](../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. ## Features -- **Zero-copy hardware decode** — FFmpeg VAAPI decode → DRM-PRIME dmabuf → `GdkDmabufTexture` - (Tier-1 zero-copy on Intel and AMD), with an automatic software-HEVC fallback on NVIDIA or when - VAAPI is unavailable. +- **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. @@ -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. 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 coverflow of - a paired host's library (drifting aurora backdrop, center-focus posters, button hints): 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). +- **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 diff --git a/clients/session/README.md b/clients/session/README.md index 06d0c10f..e937722b 100644 --- a/clients/session/README.md +++ b/clients/session/README.md @@ -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 only, no Skia anywhere in the dependency tree. -Decode follows the Settings preference (auto: Vulkan Video → VAAPI → software): -FFmpeg's Vulkan Video decoder runs on the presenter's own device where the stack -supports it (every vendor, zero copy); VAAPI dmabufs import per-plane elsewhere; -software is the universal fallback. 10-bit Main10 and HDR10 are advertised +Decode follows the Settings preference (auto: Vulkan Video → VAAPI → software on Linux, +Vulkan Video → D3D11VA → software on Windows): FFmpeg's Vulkan Video decoder runs on the +presenter's own device where the stack supports it (every vendor, zero copy); VAAPI +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 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, default ≈1000 nits). The host still gates the upgrade behind its `PUNKTFUNK_10BIT` 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=` (multi-GPU), and `PUNKTFUNK_HW_FAULT=import` (fault every VAAPI dmabuf import — proves the three-strike demotion to software on healthy hardware). diff --git a/clients/windows/README.md b/clients/windows/README.md index 88f49133..32c49568 100644 --- a/clients/windows/README.md +++ b/clients/windows/README.md @@ -26,7 +26,8 @@ the fast **`punktfunk/1`** protocol. (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 / 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 re-captures it, and system shortcuts (Alt+Tab, Win, …) can act locally or forward to the host. diff --git a/crates/pf-driver-proto/README.md b/crates/pf-driver-proto/README.md index 3f60bc0e..bb50a488 100644 --- a/crates/pf-driver-proto/README.md +++ b/crates/pf-driver-proto/README.md @@ -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. 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`). diff --git a/crates/punktfunk-core/README.md b/crates/punktfunk-core/README.md index 59a57e33..ce55a72f 100644 --- a/crates/punktfunk-core/README.md +++ b/crates/punktfunk-core/README.md @@ -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 - **[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 diff --git a/crates/punktfunk-host/README.md b/crates/punktfunk-host/README.md index 3ad47e2f..441a0116 100644 --- a/crates/punktfunk-host/README.md +++ b/crates/punktfunk-host/README.md @@ -73,7 +73,7 @@ src/ vdisplay/ per-compositor virtual outputs (kwin · gamescope · mutter · wlroots) capture/ · capture.rs screen/dmabuf capture (+ Windows IDD-push) 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) audio/ · audio.rs Opus out + virtual mic (PipeWire / WASAPI) 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 - **[Clients](../../clients/)** — the apps that connect (Apple · Linux · Windows · Android · probe) - **[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 diff --git a/docs-site/README.md b/docs-site/README.md index 08a2a324..07b99147 100644 --- a/docs-site/README.md +++ b/docs-site/README.md @@ -4,8 +4,7 @@ The punktfunk documentation site: [Fumadocs](https://fumadocs.dev) on [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 -for the **user-facing** guides; repo-internal design rationale lives in -[`../design/`](../design/README.md). +for the **user-facing** guides; design rationale lives in the internal punktfunk-planning repo. ## API reference diff --git a/docs-site/content/docs/clients.md b/docs-site/content/docs/clients.md index ecea1270..ed4d381a 100644 --- a/docs-site/content/docs/clients.md +++ b/docs-site/content/docs/clients.md @@ -35,9 +35,10 @@ protocol's FEC/encryption extensions, but for a healthy LAN that rarely matters. ## Linux desktop client (GTK4) `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), -PipeWire audio, and SDL3 controllers (rumble, lightbar, DualSense touchpad/motion). Like the Apple -app it discovers hosts on your network automatically, does PIN pairing, and pins reconnects. +`punktfunk/1` directly, with hardware decode via Vulkan Video on every GPU vendor (including +NVIDIA), falling back to VAAPI dmabuf and then software, PipeWire audio, and SDL3 controllers +(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 [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 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 -(software fallback), 10-bit/HDR present, WASAPI audio + mic, SDL3 controllers (rumble, lightbar, -DualSense), network discovery, and the full PIN-pairing trust surface. It builds for both `x86_64` -and `aarch64` and ships as a **signed MSIX**. Launch it and pick a host from the list, just like the -other native apps. +list, settings, PIN pairing); the stream itself runs in punktfunk's Vulkan presenter. Its decoder +tries **Vulkan Video, then D3D11VA, then software**, with 10-bit/HDR present, WASAPI audio + mic, +SDL3 controllers (rumble, lightbar, DualSense), network discovery, and the full PIN-pairing trust +surface. It builds for both `x86_64` and `aarch64` and ships as a **signed MSIX**. Launch it and +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. -> If anything misbehaves, **[Moonlight](/docs/moonlight)** is a proven alternative for Windows. +> Hardware decode is validated on NVIDIA and Intel GPUs; HDR10 is implemented with on-glass +> validation still pending. If anything misbehaves, **[Moonlight](/docs/moonlight)** is a proven +> alternative for Windows. A headless CLI path exists for scripting/measurement: diff --git a/docs-site/content/docs/configuration.md b/docs-site/content/docs/configuration.md index a36e9152..38718e06 100644 --- a/docs-site/content/docs/configuration.md +++ b/docs-site/content/docs/configuration.md @@ -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_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 | 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_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 | Setting | Values | Meaning | diff --git a/docs-site/content/docs/gnome.md b/docs-site/content/docs/gnome.md index 04341685..1f6a69cd 100644 --- a/docs-site/content/docs/gnome.md +++ b/docs-site/content/docs/gnome.md @@ -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 [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 To run with no monitor and no login, keep a GNOME Wayland session up at all times and start the host diff --git a/docs-site/content/docs/host-cli.md b/docs-site/content/docs/host-cli.md index 26bea0d8..762bbf9c 100644 --- a/docs-site/content/docs/host-cli.md +++ b/docs-site/content/docs/host-cli.md @@ -35,6 +35,7 @@ punktfunk-host serve --gamestream | `--mgmt-bind ` | 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 ` | 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`. | +| `--data-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. @@ -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 **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 -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` @@ -68,14 +70,16 @@ punktfunk-host punktfunk1-host --source virtual | `--seconds ` / `--frames ` | Bound each session by wall-clock seconds or frame count. | | `--max-concurrent ` | Stream at most N sessions at once (default 4); overflow waits in the queue. | | `--max-sessions ` | Exit after N sessions (0 = serve forever). | -| `--allow-pairing` | Accept PIN pairing; the host prints a PIN when a client pairs. | -| `--require-pairing` | Only serve paired devices (implies `--allow-pairing`). | +| `--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.) | | `--pairing-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 ` | Pin the video data plane to this fixed UDP port and stream direct (no hole-punch). Same as `PUNKTFUNK_DATA_PORT`. | +| `--idle-timeout-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`. | -`--max-concurrent`, `--allow-pairing`, and `--require-pairing` are **`punktfunk1-host`-only** — `serve` does not -accept them. On `serve` you arm pairing from the web console instead, and concurrency is fixed at -the built-in default (4 sessions) rather than settable from the command line. +`--max-concurrent` and `--allow-tofu` are **`punktfunk1-host`-only** — `serve` does not accept them. +On `serve` you arm pairing from the web console instead (`--open` is its serve-any-device switch), +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 hosts from another machine with `punktfunk-probe --discover`. Where multicast doesn't work (some diff --git a/docs-site/content/docs/install-client.md b/docs-site/content/docs/install-client.md index eb167d33..b957d779 100644 --- a/docs-site/content/docs/install-client.md +++ b/docs-site/content/docs/install-client.md @@ -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. -> The Windows client's hardware-decode (D3D11VA) and HDR paths are complete but still pending -> validation on real GPU hardware. If anything misbehaves, **[Moonlight](/docs/moonlight)** is a -> solid alternative for Windows. +> The Windows client's hardware decode is validated on NVIDIA and Intel GPUs; its HDR path is +> complete but still pending on-glass validation. If anything misbehaves, +> **[Moonlight](/docs/moonlight)** is a solid alternative for Windows. ## macOS diff --git a/docs-site/content/docs/pairing.md b/docs-site/content/docs/pairing.md index 1eb826bf..19d73543 100644 --- a/docs-site/content/docs/pairing.md +++ b/docs-site/content/docs/pairing.md @@ -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 to arm pairing on `serve`. -(The standalone headless test host, `punktfunk1-host`, takes `--allow-pairing`/`--require-pairing` on its -command line instead; the production `serve` host arms pairing from the console.) +(The standalone headless test host, `punktfunk1-host`, requires pairing by default too and takes +`--allow-tofu` on its command line to accept unpaired clients; the production `serve` host arms +pairing from the console.) Then, on the client: diff --git a/docs-site/content/docs/roadmap.md b/docs-site/content/docs/roadmap.md index 79dcdde6..015cef9c 100644 --- a/docs-site/content/docs/roadmap.md +++ b/docs-site/content/docs/roadmap.md @@ -16,9 +16,9 @@ see [Status & Progress](/docs/status). | GameStream host (works with Moonlight) | ✅ | | Native `punktfunk/1` protocol | ✅ | | Linux host (KWin · GNOME · gamescope · Sway) | ✅ | -| Windows host (NVIDIA) | ✅ beta | +| Windows host (NVIDIA · AMD · Intel) | ✅ beta | | Apple client (macOS · iOS · iPadOS · tvOS) | ✅ | -| Linux client (GTK4) | ✅ | +| Linux client (GTK4 shell + Vulkan session) | ✅ | | Android client (phone · TV) | ✅ | | Windows client | 🟡 | | 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 the web console, and mDNS LAN auto-discovery. - **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 - an in-app network speed test that informs the bitrate picker. + 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, and **automatic adaptive bitrate** + (the encoder re-targets mid-stream when bitrate is set to Automatic). ## 🟡 In progress -- **Windows client on-glass validation.** The hardware (D3D11VA) decode, HDR present, and GUI are - built and ship as a signed MSIX — they just need verification on real GPU hardware. +- **Windows client on-glass validation.** Hardware decode and the GUI are validated on NVIDIA and + Intel; the HDR present path still needs verification on real HDR hardware. - **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. - **Web console parity.** Surfacing the speed test and bitrate picker the apps already have. diff --git a/docs-site/content/docs/stats.md b/docs-site/content/docs/stats.md index 410eeb53..de0b0e64 100644 --- a/docs-site/content/docs/stats.md +++ b/docs-site/content/docs/stats.md @@ -20,6 +20,22 @@ life: 4. **displayed** — the picture is handed to the screen (as close to "photons" as the 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 ``` @@ -73,7 +89,7 @@ always spelled out rather than pretending: | 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` | | macOS/iOS fallback presenter | `capture→received` | the system video layer hides decode and present timing entirely | diff --git a/docs-site/content/docs/status.md b/docs-site/content/docs/status.md index d166098d..17eb0d4f 100644 --- a/docs-site/content/docs/status.md +++ b/docs-site/content/docs/status.md @@ -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 | | **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 | -| **Linux client** (`punktfunk-client`, GTK4/libadwaita) | ✅ full client; VAAPI zero-copy decode + software fallback | -| **Windows client** (`punktfunk-client`, WinUI 3) | ✅ stage 1 complete; ships as signed MSIX; on-glass hardware validation pending | +| **Linux client** (`punktfunk-client`, relm4/GTK4 shell + Vulkan session) | ✅ full client; Vulkan Video → VAAPI → software decode | +| **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 | | **Web console** (over the management API) | ✅ status · devices · pairing | @@ -54,10 +54,10 @@ host is newer than the Linux host.) | 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. | -| **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. | -| **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.** | -| **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. | +| **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`) | 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 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), 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 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 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. - **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. @@ -77,11 +77,21 @@ The stack has been validated live on a range of hosts and clients: 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` 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 - keyboard, absolute mouse, fullscreen, and a stats overlay. VAAPI → `GdkDmabufTexture` - zero-copy decode on Intel/AMD with a proven software fallback. + keyboard, absolute mouse, fullscreen, and a stats overlay. (Since re-architected: streams + 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 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. @@ -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) 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 - GPU hardware. +- **On-glass validation of the Windows client's HDR present path** (hardware decode and the + GUI are already validated on NVIDIA and Intel). - **gamescope multi-user isolation** — per-session input/audio so concurrent sessions can be fully independent desktops (the shared-desktop multi-view case already works). diff --git a/docs-site/content/docs/virtual-displays.md b/docs-site/content/docs/virtual-displays.md index ee7a2a7b..26fe8058 100644 --- a/docs-site/content/docs/virtual-displays.md +++ b/docs-site/content/docs/virtual-displays.md @@ -19,9 +19,9 @@ opened on. > you also use in person, or a multi-monitor workstation. > **What's live today:** **keep-alive** (linger, or **forever**), **topology** (extend / primary / -> exclusive), **conflict handling**, **per-client identity + persistent scaling** (Windows *and* -> KDE/KWin), and **multi-monitor layout** (several clients as monitors of one desktop) are all -> enforced. A reconnect always resumes the kept display — even a fast one — instead of spawning a +> exclusive), **conflict handling**, **per-client identity + persistent scaling** (Windows, KDE/KWin +> *and* GNOME/Mutter), and **multi-monitor layout** (several clients as monitors of one desktop) are +> 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 > `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. | | **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. | ## Legacy environment knobs diff --git a/packaging/bazzite/README.md b/packaging/bazzite/README.md index 3a6c9550..622b979f 100644 --- a/packaging/bazzite/README.md +++ b/packaging/bazzite/README.md @@ -339,7 +339,7 @@ control port (UDP 9777) + mDNS + the mgmt/library API (TCP 47990, HTTPS + mTLS). 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 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 when the host runs `serve --gamestream` (the bundled unit's default); on a bare-`serve` native-only diff --git a/packaging/debian/README.md b/packaging/debian/README.md index a7f2ae89..a66c4a7a 100644 --- a/packaging/debian/README.md +++ b/packaging/debian/README.md @@ -2,7 +2,8 @@ `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 -and publishes on every push to `main` (a rolling `0.5.0~ciN.g` build to the **`canary`** apt +and publishes on every push to `main` (a rolling `~ciN.g` 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 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 diff --git a/packaging/flatpak/README.md b/packaging/flatpak/README.md index 13f7799f..95c9957b 100644 --- a/packaging/flatpak/README.md +++ b/packaging/flatpak/README.md @@ -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-session`, binary `punktfunk-session`) — is published two ways by CI (`.gitea/workflows/flatpak.yml`), on every push to `main` (a rolling -`0.0.1-ciN.` build) and on `v*` tags (a clean `X.Y.Z`): +`-ciN.g` 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 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: ```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 -ciN.gSHA. VER=1.2.3 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 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) + `--socket=pulseaudio` (PipeWire-pulse: playback + mic) + `--share=network`. Alongside it: `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 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. +**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 The OSTree repo flatpak-builder produces is GPG-signed in CI and rsynced to unom-1, where a tiny diff --git a/packaging/linux/steam-deck-gadget/README.md b/packaging/linux/steam-deck-gadget/README.md index 28b819a1..6c42e3cf 100644 --- a/packaging/linux/steam-deck-gadget/README.md +++ b/packaging/linux/steam-deck-gadget/README.md @@ -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 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 -`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**. SteamOS ships every piece (`CONFIG_USB_DUMMY_HCD=m`, `CONFIG_USB_RAW_GADGET=m`, diff --git a/packaging/linux/steam-deck-gadget/usbip-poc/README.md b/packaging/linux/steam-deck-gadget/usbip-poc/README.md index b86fdc5b..3e3a99ad 100644 --- a/packaging/linux/steam-deck-gadget/usbip-poc/README.md +++ b/packaging/linux/steam-deck-gadget/usbip-poc/README.md @@ -17,7 +17,7 @@ sudo modprobe vhci_hcd 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 `raw_gadget`→`usbip`→UHID). `usbip` crate API: a custom `UsbInterfaceHandler` — `get_class_specific_descriptor()` = the 9-byte HID descriptor; `handle_urb()` dispatches EP0 diff --git a/packaging/rpm/README.md b/packaging/rpm/README.md index b0406e2f..6b70713a 100644 --- a/packaging/rpm/README.md +++ b/packaging/rpm/README.md @@ -3,7 +3,8 @@ `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 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` build to the `*-canary` +builds and publishes on every push to `main` (a rolling `-0.ciN.g` 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 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 diff --git a/packaging/windows/README.md b/packaging/windows/README.md index 8f5a6b77..c059e2de 100644 --- a/packaging/windows/README.md +++ b/packaging/windows/README.md @@ -3,7 +3,7 @@ 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`. -> 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) @@ -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 - > 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 -> [`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. ## 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 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 -`0.3.` **canary** builds to the `canary/` alias. +`.` **canary** builds (base derived from the latest stable tag by +`scripts/ci/pf-version.ps1`) to the `canary/` alias. diff --git a/packaging/windows/drivers/pf-dualsense/README.md b/packaging/windows/drivers/pf-dualsense/README.md index cc3595ee..bf374511 100644 --- a/packaging/windows/drivers/pf-dualsense/README.md +++ b/packaging/windows/drivers/pf-dualsense/README.md @@ -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 device Location (`WdfDeviceAllocAndQueryProperty`) to poll its own `*-boot-` bootstrap 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 - attach). + punktfunk-planning: `gamepad-channel-sealing.md` — and its `pad_index` is validated against this + index on attach). - 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`. diff --git a/packaging/windows/drivers/pf-xusb/README.md b/packaging/windows/drivers/pf-xusb/README.md index ac88593f..a67fbe42 100644 --- a/packaging/windows/drivers/pf-xusb/README.md +++ b/packaging/windows/drivers/pf-xusb/README.md @@ -15,7 +15,7 @@ instance (= player slot 0–3) with `CreateFile`, and polls it with buffered IOC - 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 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-` mailbox (`pf_driver_proto::gamepad::PadBootstrap`); a game's rumble (`SET_STATE`) is published back for the host to forward to the client. diff --git a/scripts/steamdeck/README.md b/scripts/steamdeck/README.md index 2dbcb87c..72cdd208 100644 --- a/scripts/steamdeck/README.md +++ b/scripts/steamdeck/README.md @@ -3,7 +3,7 @@ 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.) -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. ## Why build on-device (not a package or prebuilt binary) diff --git a/scripts/windows/README.md b/scripts/windows/README.md index 7cb2a6e1..c76b54a6 100644 --- a/scripts/windows/README.md +++ b/scripts/windows/README.md @@ -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 -`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 fresh install) is what creates the task in the first place. diff --git a/tools/latency-probe/README.md b/tools/latency-probe/README.md index 599f037e..89a116f8 100644 --- a/tools/latency-probe/README.md +++ b/tools/latency-probe/README.md @@ -1,6 +1,6 @@ # 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), and tracks p50/p99 — so latency regressions are quantifiable rather than felt. diff --git a/tools/loss-harness/README.md b/tools/loss-harness/README.md index b06297e8..6bf9973d 100644 --- a/tools/loss-harness/README.md +++ b/tools/loss-harness/README.md @@ -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 ``` -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).