- HDR cursor: sRGB→linear decode + scale to HDR graphics white (PUNKTFUNK_HDR_CURSOR_NITS, default 203 per BT.2408) in the FP16 cursor composite, so it's no longer ~2.5x too dim. SDR path unchanged; the masked-color (I-beam) inversion blend left unscaled. Cursor cbuffer widened 16→32 + bound to PS. (Validated live: cursor now correct brightness in HDR.) - Secure-desktop recovery: recreate_dupl now PROBES the rebuilt duplication with a 50ms AcquireNextFrame and only adopts it when live (Ok/WAIT_TIMEOUT); a born-lost one (immediate ACCESS_LOST) is dropped so the caller repeats the last frame + retries. Plus reassert_isolation() re-detaches physical displays on every recovery (re-routing the secure/HDR desktop to the virtual output, the delta a fresh reconnect has). NOTE: the born-lost ACCESS_LOST storm in HDR is NOT yet resolved by these — still under investigation (animations/secure-UI/cursor-trail in HDR remain). - docs/windows-client-bootstrap.md: handoff for the native Windows Rust client (windows-rs Reactor + WinUI 3 SwapChainPanel, D3D11VA decode, WASAPI audio, SDL3 input; ports crates/punktfunk-client-linux; 10-bit/HDR present; dev boxes + gotchas). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8.9 KiB
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).
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 +SwapChainPanelFIRST — it's the only novel/uncertain piece; everything else is a known-good port. - Links
punktfunk-coredirectly (Cargo path dep,features = ["quic"]) — no C ABI, exactly like the GTK client.NativeClientis alreadySync(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 callcrates/punktfunk-core/src/client.rs(NativeClient) methods directly. - Video widget = WinUI 3
SwapChainPanel(built-in), fed a D3D11 swapchain viaISwapChainPanelNative::SetSwapChain. - Decode = FFmpeg-next + D3D11VA (HEVC; Main10 for 10-bit/HDR — see below).
- Audio playback = WASAPI render + Opus decode (
opuscrate, vendors libopus via cmake; setCMAKE_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/ViGEmare 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 inclient.rscurrently hardcodesvideo_caps: 0with 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(orR16G16B16A16_FLOAT),IDXGISwapChain3::SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020)+SetHDRMetaDatafor HDR10; the host's stream is BT.2020 PQ, so present PQ. For SDR, the existingDXGI_FORMAT_B8G8R8A8_UNORM+ BT.709 path. (The host-side HDR conversion math is incrates/punktfunk-host/src/capture/dxgi.rsHDR_PS/HdrConverterif 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ü→ MSVCLNK1201PDB-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.5in 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 ownCargo.lock(don't transfer it). - Windows clippy is stricter than Linux CI and
cfg(windows)code is excluded from Linux CI → runcargo clippy -p punktfunk-client-windows -- -D warningsON THE VM before committing. - Work on
main; fetch+mergeorigin/mainbefore pushing.
Suggested phased plan
- De-risk Reactor (do this first). A windows-rs Reactor (WinUI 3) hello-world that hosts a
SwapChainPaneland presents a cleared D3D11 swapchain into it. Confirm the windows-rs Reactor version/API (PR #4479) andISwapChainPanelNative::SetSwapChaininterop. If Reactor proves too raw, the fallback iswinit+ a child HWND swapchain, but try Reactor first per the decision. - 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. Mirrorcrates/punktfunk-client-linux/Cargo.toml. - 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. - Decode + present. FFmpeg D3D11VA →
SwapChainPanel. SDR (8-bit BGRA) first, then P010 + HDR colorspace (see the HDR section). - Audio. WASAPI render + Opus decode (port
audio.rs). - Input. Mouse + keyboard capture→send (port
keymap.rs), gamepad via SDL3 (portgamepad.rs), feedback fromnext_rumble/next_hidout. - Discovery + UI. Port
discovery.rs+ui_hosts.rs+ui_settings.rsto 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).