Files
punktfunk/docs/windows-client-bootstrap.md
T
enricobuehler 296b976b8f
apple / swift (push) Successful in 54s
audit / cargo-audit (push) Failing after 1m19s
android / android (push) Failing after 2m22s
ci / web (push) Successful in 41s
ci / docs-site (push) Successful in 33s
ci / bench (push) Successful in 1m56s
deb / build-publish (push) Successful in 3m28s
ci / rust (push) Successful in 7m23s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
decky / build-publish (push) Successful in 12s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 18s
flatpak / build-publish (push) Successful in 3m59s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 5m21s
docker / deploy-docs (push) Successful in 7s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 4m43s
feat(windows-client): SDL3 gamepads + docs — full stage-1 parity, MSVC-green
Adds the SDL3 gamepad service (near-verbatim port of the GTK client's — SDL3 is
cross-platform) and wires it into the winit app: per-session capture (buttons/axes,
DualSense touchpad + motion 0xCC), feedback (rumble, lightbar, raw DualSense effects),
single-pad-forwarded model with auto pad-type from the physical controller. Built from
source on Windows (no system SDL3).

- gamepad.rs: GamepadService (app-lifetime SDL thread) attach/detach on session
  connect/end; auto_pref resolves "Automatic" to the attached pad's type.
- app.rs: hold the service, attach on Connected, detach on Ended/Failed/close. Also
  simplify the keydown path (drop the identical if/else arms).
- main.rs: start the service for the windowed path, resolve GamepadPref from settings +
  the physical pad.

Build gotcha documented + fixed in the dev loop: SDL3's build-from-source MSVC
precompiled-header chokes on the `ü` in the dev box's username embedded in the cargo
registry path (MSB8084/C4828) — CARGO_HOME must be an ASCII path
(C:\Users\Public\.cargo). Unrelated to our code.

Docs: CLAUDE.md M4 + docs/windows-client-bootstrap.md status banner (winit-not-Reactor
rationale, CARGO_HOME gotcha, what's pending) + docs-site clients.md "Windows desktop
client (in development)". Crate is build + clippy + fmt + test green on
x86_64-pc-windows-msvc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 22:11:35 +00:00

11 KiB
Raw Blame History

Windows native client — bootstrap handoff

A handoff for an agent picking up the native Windows punktfunk/1 client. The host side is done and live-validated on a real RTX 4090; the client is the remaining piece. This doc is the concrete starting point: the locked decisions, the reference code to port, the stack swaps, the dev loop, and the gotchas. Read it top to bottom, then start at Phase 1 (de-risk Reactor first).

Status — stage 1 landed (2026-06-15)

The client is implemented in crates/punktfunk-client-windows (binary punktfunk-client) and is build + clippy + fmt + test green on x86_64-pc-windows-msvc (built on the dev VM). Done: winit window + Direct3D11 flip-model swapchain present (WARP on the GPU-less box; runtime-compiled fullscreen-triangle shaders, Contain-fit letterbox), FFmpeg software HEVC decode, WASAPI render

  • mic capture, keyboard/mouse/wheel capture (physical-KeyCode→VK, click-to-capture), SDL3 gamepads, mdns-sd discovery, and the full trust surface (identity + TOFU + SPAKE2 PIN over --connect/--discover/--pair/--headless).
  • Reactor was evaluated and rejected (a research pass + the points below): windows-rs Reactor ships no SwapChainPanel and no ISwapChainPanelNative::SetSwapChain escape hatch, so it cannot host a DXGI presenter. The client uses the doc's sanctioned fallback — winit + a raw D3D11 swapchain on the HWND — which builds and runs against WARP on the GPU-less VM. A native WinUI look would need the SwapChainPanel hatch to land upstream first.
  • Build gotcha (in addition to the ASCII output path below): CARGO_HOME itself must be on an ASCII path (e.g. C:\Users\Public\.cargo). SDL3's build-from-source compiles a precompiled header whose #include embeds the registry source path; the ü in the dev box's username makes MSVC's PCH/structured-output fail (MSB8084 / C4828). Set CARGO_HOME=C:\Users\Public\.cargo.
  • Still pending: live host validation (the dev box has no GPU → glass-to-glass numbers defer to the RTX box), D3D11VA hardware decode + 10-bit/HDR present, a native host-list/settings GUI (CLI flags for now), and RAWINPUT relative-mouse pointer-lock. Phases 47 below are the map.

What we're building

A native Windows client that connects to a punktfunk/1 host (serve --native / m3-host), decodes HEVC, presents it low-latency, plays Opus audio, and captures local mouse/keyboard/gamepad to send back — i.e. the Windows analogue of the GTK4 Linux client (crates/punktfunk-client-linux), which is the architectural template. The Windows client is close to a 1:1 port of the Linux client with the platform layers swapped.

Locked decisions (from the Windows-host/client plan, docs/windows-host.md + project memory)

  • Pure Rust. windows-rs + Windows App SDK "Reactor" (WinUI 3 from Rust, merged windows-rs PR #4479). No C++/C#. De-risk Reactor + SwapChainPanel FIRST — it's the only novel/uncertain piece; everything else is a known-good port.
  • Links punktfunk-core directly (Cargo path dep, features = ["quic"]) — no C ABI, exactly like the GTK client. NativeClient is already Sync (mutexed plane receivers), so it drops into a UI app cleanly. The C ABI (punktfunk_connect + next_au/next_audio/next_rumble/next_hidout/ send_input/send_rich_input) is the Apple path; the native Rust clients call crates/punktfunk-core/src/client.rs (NativeClient) methods directly.
  • Video widget = WinUI 3 SwapChainPanel (built-in), fed a D3D11 swapchain via ISwapChainPanelNative::SetSwapChain.
  • Decode = FFmpeg-next + D3D11VA (HEVC; Main10 for 10-bit/HDR — see below).
  • Audio playback = WASAPI render + Opus decode (opus crate, vendors libopus via cmake; set CMAKE_POLICY_VERSION_MINIMUM=3.5).
  • Input capture→send: the client captures LOCAL input and sends it. Mouse (abs + relative) + keyboard via the inverse VK table (port keymap.rs); gamepad via SDL3 (already a workspace dep, cross-platform) → NativeClient::send_input/send_rich_input. (SendInput/ViGEm are HOST-side injection — not used by the client.)
  • Discovery = mdns-sd (cross-platform, browses _punktfunk._udp).
  • Trust = shared client identity + SPAKE2 PIN pairing + TOFU (port trust.rs; same identity files/logic as the other native clients).

The reference: crates/punktfunk-client-linux/src/

Port these files (near 1:1; only the platform layers change):

Linux file Role Windows swap
main.rs / app.rs app shell, lifecycle WinUI 3 App/Window via Reactor
ui_hosts.rs host list / connect screen WinUI 3 page
ui_settings.rs settings WinUI 3 page
ui_stream.rs the streaming view WinUI 3 page hosting SwapChainPanel
video.rs FFmpeg decode + present FFmpeg D3D11VA → D3D11 swapchain in SwapChainPanel
audio.rs Opus decode + playback WASAPI render (was PipeWire)
session.rs NativeClient connect + plane pumps reuse almost verbatim (core is cross-platform)
trust.rs identity, PIN, TOFU reuse almost verbatim
discovery.rs mDNS browse reuse verbatim (mdns-sd)
keymap.rs inverse VK table reuse; Windows VK is the native source so this is simpler
gamepad.rs SDL3 pad capture + rumble/feedback reuse almost verbatim (SDL3 is cross-platform)

session.rs, trust.rs, discovery.rs, keymap.rs, gamepad.rs are mostly platform-neutral (they touch punktfunk-core + SDL3 + mdns, all cross-platform) — expect to reuse them with minimal changes. The real work is video.rs (D3D11VA + swapchain), audio.rs (WASAPI), and the WinUI shell.

10-bit + HDR (NEW — landed this session, the client MUST handle it)

The host now negotiates and emits HEVC Main10 + BT.2020 PQ HDR10 when the captured desktop is HDR (and 10-bit SDR Main10 when negotiated). The Apple client already does the matching present; the Windows client should mirror it:

  • Advertise caps in the Hello: video_caps = VIDEO_CAP_10BIT | VIDEO_CAP_HDR (crates/punktfunk-core/src/quic.rs). The host enables 10-bit only if the client advertised it. (The native-client connector in client.rs currently hardcodes video_caps: 0 with a TODO — thread the real caps through when you wire decode; or detect HDR purely in-band, see next.)
  • Detect HDR in-band from the HEVC VUI (transfer characteristics = SMPTE ST 2084 / PQ), exactly like the Apple client's VideoDecoder.isHDRFormat (clients/apple/Sources/PunktfunkKit/). This handles a mid-session HDR toggle without renegotiation. Welcome.bit_depth (8/10) is also available.
  • Decode Main10 → P010 (10-bit) via D3D11VA.
  • Present HDR: swapchain in DXGI_FORMAT_R10G10B10A2_UNORM (or R16G16B16A16_FLOAT), IDXGISwapChain3::SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) + SetHDRMetaData for HDR10; the host's stream is BT.2020 PQ, so present PQ. For SDR, the existing DXGI_FORMAT_B8G8R8A8_UNORM + BT.709 path. (The host-side HDR conversion math is in crates/punktfunk-host/src/capture/dxgi.rs HDR_PS/HdrConverter if you need the inverse.)

Dev boxes

  • No-GPU dev box (UI + connect + software decode): ssh "Enrico Bühler"@192.168.1.57 — Win11 Pro 25H2 (build 26200), QEMU Q35, 8 vCPU/12 GB, no working GPU (so no NVENC, no D3D11VA hardware decode — use FFmpeg software decode here; this box is for UI/connect/protocol work). Has Rust 1.96 MSVC, VS 2026 + VC tools + Win SDK, Win App Runtime 2.2, SudoVDA + Parsec VDD.
  • Real-GPU box (HDR / hardware decode / end-to-end): ssh "Enrico Bühler"@192.168.1.174 — Win11, RTX 4090, runs the host. Use it to test the client against a live HDR host.

Dev-loop gotchas (both boxes)

  • Build under an ASCII path (C:\Users\Public\…). The username "Enrico Bühler" has a ü → MSVC LNK1201 PDB-write failure under ~/Developer.
  • Toolchain gaps: winget install NASM.NASM Kitware.CMake LLVM.LLVM (aws-lc-rs on the quic path, ffmpeg-sys needs libclang).
  • CMAKE_POLICY_VERSION_MINIMUM=3.5 in the build env (CMake 4 rejects libopus's old minimum).
  • File transfer = sftp (scp is broken under the PowerShell DefaultShell): printf 'put %s /C:/Users/Public/REL/PATH\n' LOCAL | sftp -b - "Enrico Bühler@192.168.1.57" — note the leading slash /C:/…. Let the VM regenerate its own Cargo.lock (don't transfer it).
  • Windows clippy is stricter than Linux CI and cfg(windows) code is excluded from Linux CI → run cargo clippy -p punktfunk-client-windows -- -D warnings ON THE VM before committing.
  • Work on main; fetch+merge origin/main before pushing.

Suggested phased plan

  1. De-risk Reactor (do this first). A windows-rs Reactor (WinUI 3) hello-world that hosts a SwapChainPanel and presents a cleared D3D11 swapchain into it. Confirm the windows-rs Reactor version/API (PR #4479) and ISwapChainPanelNative::SetSwapChain interop. If Reactor proves too raw, the fallback is winit + a child HWND swapchain, but try Reactor first per the decision.
  2. Crate scaffold. crates/punktfunk-client-windows, [target.'cfg(windows)'.dependencies]: punktfunk-core { path, features=["quic"] }, windows, the Reactor crate, ffmpeg-next, opus, sdl3, mdns-sd, anyhow, tracing. Mirror crates/punktfunk-client-linux/Cargo.toml.
  3. Connect + control plane. Port session.rs + trust.rs; validate headless against the 4090 box (m3-host/serve --native) — handshake, PIN/TOFU, plane counters — before any UI/decode.
  4. Decode + present. FFmpeg D3D11VA → SwapChainPanel. SDR (8-bit BGRA) first, then P010 + HDR colorspace (see the HDR section).
  5. Audio. WASAPI render + Opus decode (port audio.rs).
  6. Input. Mouse + keyboard capture→send (port keymap.rs), gamepad via SDL3 (port gamepad.rs), feedback from next_rumble/next_hidout.
  7. Discovery + UI. Port discovery.rs + ui_hosts.rs + ui_settings.rs to WinUI pages.

Key references

  • Template: crates/punktfunk-client-linux/src/* (the client to port).
  • Apple HDR present (the pattern to mirror): clients/apple/Sources/PunktfunkKit/{VideoDecoder, MetalVideoPresenter,Stage2Pipeline}.swift — in-band PQ detection, P010 decode, EDR present.
  • Core client API: crates/punktfunk-core/src/client.rs (NativeClient).
  • Protocol: crates/punktfunk-core/src/quic.rs (Hello.video_caps, Welcome.bit_depth, VIDEO_CAP_10BIT/VIDEO_CAP_HDR).
  • Full Windows plan + SudoVDA/host details: docs/windows-host.md.
  • Host HDR conversion (for the inverse math): crates/punktfunk-host/src/capture/dxgi.rs (HDR_PS, HdrConverter) + crates/punktfunk-host/src/encode/nvenc.rs (BT.2020/PQ VUI).