Files
punktfunk/clients/windows
enricobuehler f373dffb5e chore: migrate the main workspace and pf-vkhdr-layer to edition 2024 (WP20)
The safety half of the rust-safety programme's §8.4: `std::env::set_var`/`remove_var` are
`unsafe fn` in edition 2024, converting the class of bug the programme found the hard way
(the 972af299 environ data race lived in a file with ZERO occurrences of the word
`unsafe`) from invisible to counted and compiler-enforced.

Manifests: [workspace.package] edition 2021→2024, rust-version 1.82→1.85 (the pinned
toolchain is 1.96.0, so no toolchain bump — only the declared floor rises); the 13 crates
pinning `edition = "2021"` literally now inherit it (Trap 1: the root bump alone reaches
only `edition.workspace = true` crates and would have left pf-encode/pf-capture/pf-inject
et al. on 2021 while reading as complete); pf-driver-proto's stale rust-version 1.82 pin
now inherits; pf-vkhdr-layer (a separate workspace, inherits nothing) bumped to 2024. The
four vendored crates (fec-rs, cros-codecs, usbip-sim, the patched ndk) stay on 2021
deliberately — upstream code stays pristine. The excluded usbip-poc standalone PoC is
untouched.

Mechanical, done textually across ALL cfg branches so no platform's half is left behind
(Trap 3 — 44% of the host's unsafe is Windows-only and a one-platform `cargo fix` misses
it): 148 `#[no_mangle]` → `#[unsafe(no_mangle)]` (83 in abi.rs); 12 bare extern blocks →
`unsafe extern`; `gen` is a reserved keyword, so pf-vdisplay's generation stamps
(registry.rs, windows/manager.rs) and the WinUI shell's animation counters rename
gen → generation (internal identifiers only, no serde/wire surface); two
match-ergonomics patterns take the compiler's suggested reference form.

env mutation: every `set_var`/`remove_var` site (20 files) now sits in an `unsafe` block
whose SAFETY comment states the real serialization argument (pf-vdisplay's ENV_LOCK,
CONFIG_DIR_TEST_LOCK, ART_ROOTS_LOCK, vkdecode's gpu_lock, the `--test-threads=1`
contracts of the hardware spikes, or single-threaded startup). Two genuine hazards
surfaced en route — exactly the WP3b-class finds this migration exists to make visible —
and are fixed here:
- windows/service.rs spawned the network-profile warner thread BEFORE `load_host_env()`,
  so a child-spawning thread (child spawn snapshots the env block) was live while
  `set_var` ran in a loop; the load now precedes the spawn.
- pf-console-ui's `fake_home()` re-set HOME outside its OnceLock on EVERY call, so two
  parallel tests could race the write; the set now happens exactly once inside
  `get_or_init`.

cbindgen (Trap 2): 0.29.4 parses `#[unsafe(no_mangle)]` — verified empirically; the
header regenerates byte-identical. The ci.yml drift check could never catch "failed to
regenerate" (build.rs demotes a cbindgen failure to a warning and writes nothing, leaving
the checked-in header untouched and the diff clean), so the step now first asserts the
"punktfunk-core: wrote" line and the absence of "cbindgen failed" (sh -e safe: no `!`
pipeline, no tee-masked exit).

rustfmt: style_edition pinned to 2021 at the root — edition 2024 would otherwise flip the
style edition and reformat ~370 untouched files inside this same commit, burying the
migration diff. The drivers workspace pins its already-current 2024 style. Adopting the
2024 style tree-wide is its own future one-line-plus-reformat commit.

Census: the primary metric moves UP BY DESIGN — 2435 → 2453 operations, unsafe blocks
1534 → 1577, and env_set_var is now a counted category (45 ops). The newly counted env
sites are a truer number, not a regression; baseline snapshot saved as punktfunk-planning
design/rust-safety-census-baseline-2026-08-12-edition-2024.txt. Gate C's env ratchet is
now compiler-enforced (the hygiene-script header says so); the two shrunk file counts
(nvenc_cuda 49→2 via the test helpers, shell/tests 2→1) are lowered in the same commit
per the gate's own rule.

Drop order (the semantic change most likely to bite this codebase): the migration lint
`-W tail-expr-drop-order` reports zero findings on the macOS-visible halves of
pf-encode / pf-zerocopy / pf-capture / pf-frame; the Linux and Windows halves run the
same lint on the gate boxes. The four #[ignore]d alloc/drop-cycle tests on the hardware
boxes remain owed, as before this change.
2026-08-12 16:12:35 +02:00
..

punktfunk — Windows client

The native Windows app for streaming a punktfunk host to your PC. A modern WinUI 3 app that discovers hosts on your network, pairs with a PIN, and streams at your display's own resolution and refresh rate — with hardware-accelerated video decode and HDR.

It's pure Rust: the UI is WinUI 3 driven through windows-reactor (a declarative, React-like framework), and it links the shared punktfunk-core directly to speak the fast punktfunk/1 protocol.

Features

  • Hardware decode, GPU present — Punktfunk's own decoders, no FFmpeg anywhere in the client: Vulkan Video (pf-vkdecode) leads on NVIDIA and AMD, D3D11VA (pf-dxvadec driving ID3D11VideoDecoder) leads on Intel, whichever isn't first is the fallback, and an OpenH264/rav1d CPU rung is last. Either hardware rung hands its surface to the Vulkan presenter without a CPU copy.
  • HDR10 — advertise 10-bit/HDR, detect PQ in-band, and flip the swapchain to R10G10B10A2 + ST.2084 with HDR10 metadata.
  • Your display's native mode — the host builds a virtual display at exactly your WxH@Hz.
  • Audio both ways — WASAPI render + mic capture.
  • Full controller support — SDL3 gamepads with rumble, lightbar, and DualSense feedback.
  • Your display's native mode, really — "Native display" resolves the actual size + refresh of the monitor the window is on at connect time.
  • Find hosts automatically — mDNS discovery lists hosts on your LAN, alongside saved and manual entries. First connect does a one-time SPAKE2 PIN pairing (or TOFU on trusted LANs), then reconnects on a pinned identity. Saved hosts carry per-host actions: a network speed test (probe burst over the real data plane → recommended bitrate, applied in one tap) and forget.
  • Polished shell — host cards, settings (resolution / refresh / host compositor / decoder / codec / bitrate / HDR / forwarded controller / gamepad type / system shortcuts / audio channels / mic / stats-overlay level), the tiered stats overlay (Off / Compact / Normal / Detailed — Ctrl+Alt+Shift+S cycles it live in the session window), and the full trust surface. Stream input uses Win32 low-level hooks with Moonlight-style capture: Ctrl+Alt+Shift+Q releases the pointer, a click on the stream re-captures it, and system shortcuts (Alt+Tab, Win, …) can act locally or forward to the host.

Builds and ships for both x64 and ARM64 as a signed MSIX.

Get it

Install the signed MSIX from the package registry — see docs.punktfunk.unom.io/docs/install-client. A stock Moonlight client also works over GameStream if you prefer.

Build from source

Windows-only (the crate builds as a stub on other platforms so the workspace stays green). You need the MSVC toolchain and CMake (SDL3 builds from source) — nothing else: decode is native since M10, so there is no FFMPEG_DIR to point anywhere, and the Windows App SDK runtime bootstrap is staged next to the exe by windows-reactor-setup from this crate's own build.rs.

cargo build -p punktfunk-client-windows --target x86_64-pc-windows-msvc

# CLI paths for testing (no window):
punktfunk-client --discover                                   # list hosts on the LAN
punktfunk-client --headless --connect host[:port] [--pin HEX] # connect, count frames, print stats
punktfunk-client --headless --speed-test --connect host[:port]  # probe burst → recommended bitrate

CARGO_HOME must be an ASCII path — non-ASCII characters break SDL3's MSVC precompiled-header build. Packaging (MSIX manifest, signing) lives in packaging/.

Layout

Decode/present/input live in the spawned punktfunk-session binary (clients/session), not here — this crate is the shell that discovers, pairs, and launches it.

src/
  main.rs                 entry point + CLI paths (--discover · --headless · --speed-test)
  bin/punktfunk-console.rs  the couch/HTPC Start-menu entry (re-execs with --console)
  app/                    WinUI 3 shell (windows-reactor), one module per screen:
                          mod (root/router) · hosts · connect · pair · speed · settings ·
                          library · help · licenses · stream · style (shared cards/pills)
  deeplink.rs             punktfunk:// activation, single-instance hand-off, shortcut writer
  spawn.rs                punktfunk-session child process + its stdout event contract
  shell_window.rs         hide/restore the shell HWND around a session
  gpu.rs                  DXGI adapter enumeration for the GPU picker
  trust.rs · discovery.rs persistent identity, TOFU/PIN pairing, mDNS browse
  probe.rs · wol.rs       speed probe · Wake-on-LAN
  logfile.rs              log tee to %LOCALAPPDATA%
packaging/                MSIX manifest, signing, pack script

Manual smoke checklist

The windows-reactor pin is a moving target and WinUI regressions rarely show up in cargo check — walk this after a reactor bump or a change to the render/state architecture:

  • Hosts — discovery populates tiles; tile hover fill; "…" menu → Forget and Rename; add-host modal connects; WOL wait screen cancels.
  • Settings — every section renders; combos still show their selection after a section switch AND a scope switch (the historic blank-combo reconciler bug); profile create / rename / delete (with confirm); colour swatches repaint; the Overridden marker appears on edit and clears on Reset; GPU combo lists adapters.
  • Pair — PIN entry pairs (the typed PIN must reach the Connect click — use_ref mirror path).
  • Session — connect → session spawns → HUD stats tick; Ctrl+Alt+Shift+Q releases the pointer; shell window restores on exit.
  • Shell — speed test completes; library grid loads; punktfunk:// deep link routes (second instance hands off and exits); window icon appears; screen-entrance animations play.