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
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:
+3
-1
@@ -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.
|
||||||
|
|||||||
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -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
|
`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
|
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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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`).
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|
||||||
|
|||||||
@@ -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 |
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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 |
|
||||||
|
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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`.
|
||||||
|
|||||||
@@ -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)`;
|
- 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.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,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.
|
||||||
|
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
Reference in New Issue
Block a user