64abce6daa65f53fb13b3ecdcc9b52824ca90dd6
697 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
64abce6daa |
fix(windows-installer): pf-vdisplay CI build - default target dir + non-fatal cat guard
apple / swift (push) Successful in 59s
android / android (push) Successful in 4m23s
ci / rust (push) Successful in 4m43s
ci / web (push) Successful in 50s
ci / docs-site (push) Successful in 54s
windows-host / package (push) Failing after 5m39s
apple / screenshots (push) Successful in 5m15s
deb / build-publish (push) Successful in 2m31s
decky / build-publish (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 6s
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 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
ci / bench (push) Successful in 4m39s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m6s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m52s
The CI driver build panicked in wdk-sys's build script - "a Cargo.lock file should exist in the same directory as the top-level Cargo.toml". wdk-build's find_top_level_cargo_manifest() walks UP from OUT_DIR for the first ancestor holding a Cargo.lock and explicitly does NOT support non-default target dirs - but build-pf-vdisplay.ps1 pointed CARGO_TARGET_DIR at an out-of-tree dir (to isolate from CI's shared C:\t), so no ancestor of OUT_DIR had a Cargo.lock. Build into the driver workspace's DEFAULT target dir instead (its ancestors include the driver Cargo.lock); the driver's own [workspace] already isolates it and it has no CMake deps needing C:\t. Also make the Test-FileCatalog coverage guard non-fatal (it can't open a catalog signed by a not-yet-trusted cert). Validated on the runner with CARGO_TARGET_DIR=C:\t. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
bdfab8e0d5 |
fix(windows-installer): build pf-vdisplay from source in CI; ASCII scripts; upgrade-safe web console
windows-drivers / probe-and-proto (push) Successful in 24s
apple / swift (push) Successful in 1m4s
windows-drivers / driver-build (push) Successful in 1m8s
android / android (push) Successful in 4m4s
ci / rust (push) Successful in 4m39s
ci / web (push) Successful in 50s
ci / docs-site (push) Successful in 53s
apple / screenshots (push) Successful in 5m10s
windows-host / package (push) Failing after 5m35s
deb / build-publish (push) Successful in 2m29s
decky / build-publish (push) Successful in 13s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
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 3s
ci / bench (push) Successful in 4m42s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m57s
docker / deploy-docs (push) Successful in 17s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m46s
The pf-vdisplay virtual-display driver shipped as a checked-in PREBUILT binary
that went stale - two field failures on a fresh install (live-repro'd on a
German-locale Dell laptop):
* Bug A (every box): a repo-wide rename edited the vendored pf_vdisplay.inf
but never re-signed pf_vdisplay.cat, so the catalog stopped covering the INF
-> `pnputil /add-driver` fails SPAPI_E_FILE_HASH_NOT_IN_CATALOG -> driver
never installs -> every session dies "pf-vdisplay driver interface not
found".
* the prebuilt binary also predated IOCTL_SET_RENDER_ADAPTER (added to the
driver source after the vendor freeze) that the host needs to pin the IDD
render GPU on hybrid/Optimus boxes.
Fix: build the driver FROM SOURCE every release (build-pf-vdisplay.ps1, wired
into pack-host-installer.ps1) so .dll/.inf/.cat are always in lockstep and
current driver features ship. The runner's clang 22 made the driver's pinned
bindgen 0.71 emit opaque structs (157 layout-assert errors), so bump the
vendored wdk-sys/wdk-build bindgen 0.71 -> 0.72 (+ lock). The build self-signs
the driver per build (installer trusts the bundled .cer); a stable
DRIVER_CERT_PFX_B64 secret can override.
* Bug B (non-English boxes): the installer runs install-pf-vdisplay.ps1 etc.
via powershell.exe (5.1), which reads a BOM-less script in the ANSI codepage
- an em-dash's trailing 0x94 byte becomes a curly quote on German
Windows-1252 and the script aborts "unterminated string", so the driver
never installed (the gamepad script survived only because it was already
ASCII). Scrub every installer-run .ps1/.cmd to ASCII + add a CI gate that
fails on any non-ASCII so it can't regress.
* Bug C (upgrades): nothing stopped the OLD web console before re-registering
its task, so a stale server kept :3000 (the new one restart-looped on
EADDRINUSE) and served a broken old bundle (500 on /login). Stop + reap it
(runtime-agnostic, by the :3000 listener owner) in web-setup.ps1 and in the
.iss before the file copy + on uninstall.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
8e87e617df |
fix(windows-host): force EXTEND topology so a new IddCx display isn't cloned
A freshly-added IddCx virtual display lands in CLONE/duplicate mode when a physical display is already active (a laptop panel, an attached monitor): the cloned output shares that display's source, so the OS never commits a distinct path for it, never calls ASSIGN_SWAPCHAIN, and capture sees no frames - the session fails "not an active display path / needs a WDDM GPU to activate" and tears down with 0 frames (seen live on an Intel-iGPU + NVIDIA-Optimus laptop). force_extend_topology() applies the EXTEND preset (the programmatic Win+P "Extend") right after ADD so the IDD comes up as its own active path; the existing resolve_gdi_name -> set_active_mode -> isolate_displays_ccd bring-up then proceeds. Idempotent / no-op on a sole-display (headless single-GPU) box, so it's safe on the path that already worked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5bf787eb2b |
feat(host): web-console performance capture — record stream stats, graph them
apple / swift (push) Successful in 1m1s
android / android (push) Successful in 4m13s
ci / rust (push) Successful in 4m42s
ci / web (push) Successful in 50s
ci / docs-site (push) Successful in 53s
windows-host / package (push) Successful in 5m51s
apple / screenshots (push) Successful in 5m1s
deb / build-publish (push) Successful in 2m29s
decky / build-publish (push) Successful in 12s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 33s
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 5s
ci / bench (push) Successful in 4m35s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m9s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m10s
Arm streaming-perf-stats capture from the web console, play, stop, and review the run as graphs; finished captures are saved to disk as browsable/exportable recordings. Covers both the native punktfunk/1 path and GameStream. - stats_recorder.rs: one shared Arc<StatsRecorder> ring (created in gamestream::serve, shared with the mgmt API + both streaming loops, mirroring NativePairing). The hot-path gate is a runtime AtomicBool that replaces the startup-only PUNKTFUNK_PERF for *recording* (PERF stdout logging unchanged); bounded ring (~3 h); atomic temp+rename writes to ~/.config/punktfunk/captures/*.json; path-traversal-safe ids; poison-resilient locks. - native (punktfunk1.rs) + GameStream (stream.rs) emit a StatsSample at their existing ~2 s / ~1 s aggregation boundary — per-stage latency p50/p99, fps new/repeat, goodput, loss/FEC deltas — with no new per-frame work beyond the cheap atomic check. FrameMsg.was_measured keeps pre-arm in-flight frames out of the first window's percentiles (without zeroing the Windows-relay path's fps/encode). - mgmt.rs: 7 bearer-only /api/v1/stats/* endpoints (capture start/stop/status/live; recordings list/get/delete); api/openapi.json regenerated, in sync. - web: new "Performance" page (recharts, rendered SSR-safe) — capture control, live graphs while armed, recordings table (view / download-JSON / delete), and a detail view with the latency stacked-area bottleneck breakdown (p50/p99 toggle) + throughput + health. Charts adapt to either path's stage set. Design: design/stats-capture-plan.md. Built and adversarially reviewed via a multi-agent workflow; workspace build/clippy(-D warnings)/fmt/tests green, OpenAPI no-drift. Not yet on-glass validated against a live session. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0a6c9d8852 |
docs: point Android install at Discord for beta access + add community links
apple / swift (push) Successful in 1m32s
apple / screenshots (push) Successful in 3m26s
android / android (push) Successful in 4m7s
ci / rust (push) Successful in 4m36s
ci / web (push) Successful in 44s
ci / docs-site (push) Successful in 53s
deb / build-publish (push) Successful in 2m18s
decky / build-publish (push) Successful in 13s
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
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 42s
ci / bench (push) Successful in 4m42s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m12s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m8s
docker / deploy-docs (push) Successful in 6s
The Android app is in Google Play Internal Testing, so the public Play Store URL doesn't resolve for non-testers. Lead the Android install instructions with a "request a tester invite on Discord" CTA (the Play listing unlocks once a Google account is added to the test track), and surface the Discord + r/Punktfunk community links in the README, the docs intro, and the docs-site nav. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0eedfb3c1f |
docs: first-class Linux + Windows positioning + IDD-push differentiator
apple / swift (push) Failing after 0s
apple / screenshots (push) Has been skipped
windows-drivers-provision / provision (push) Successful in 13s
windows-drivers / probe-and-proto (push) Successful in 17s
windows-drivers / driver-build (push) Successful in 1m10s
android / android (push) Successful in 3m19s
ci / web (push) Successful in 39s
ci / docs-site (push) Successful in 53s
windows-host / package (push) Successful in 6m6s
ci / rust (push) Successful in 11m12s
decky / build-publish (push) Successful in 11s
ci / bench (push) Successful in 5m9s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 21s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3s
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 43s
deb / build-publish (push) Successful in 7m31s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m14s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m12s
release / apple (push) Failing after 1s
docker / deploy-docs (push) Successful in 19s
flatpak / build-publish (push) Successful in 4m43s
Drop the "Linux-first" framing across the README and docs site in favor of first-class Linux AND Windows hosts, and surface the Windows IDD-push virtual-display path as a distinct differentiator (punktfunk's own indirect display driver the host pushes frames into — a real virtual display, no physical monitor or dummy plug, even on the secure desktop). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f6490f4c28 |
fix: complete the docs/→design/ and openapi→api/ rename references
The file moves (docs/ → design/, docs/api/openapi.json → api/openapi.json) landed
in
|
||
|
|
d01a8fd17a |
feat(host): HDR Vulkan layer so Vulkan games get HDR on the virtual display
ci / web (push) Failing after 22s
windows-host / package (push) Failing after 4m16s
ci / rust (push) Failing after 4m56s
ci / docs-site (push) Successful in 1m7s
android / android (push) Successful in 9m19s
ci / bench (push) Successful in 4m47s
decky / build-publish (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Failing after 3s
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 3s
docker / deploy-docs (push) Has been skipped
deb / build-publish (push) Failing after 6m29s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Failing after 7m4s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Failing after 7m17s
apple / swift (push) Successful in 1m13s
apple / screenshots (push) Successful in 5m27s
NVIDIA/AMD Vulkan ICDs refuse to *advertise* an HDR color space for a surface on an
IddCx indirect/virtual display, so Vulkan games (Doom: The Dark Ages, id Tech, Indiana
Jones, …) report "device does not support HDR" — even though Windows HDR, DWM compose,
and the client PQ stream all work, and the ICD happily *accepts + presents* a forced HDR
swapchain there. The whole gap is enumeration; the community (Apollo/Sunshine/VDD) wrote
this off as kernel-side / unfixable.
Add VK_LAYER_PUNKTFUNK_hdr_inject (packaging/windows/pf-vkhdr-layer/): a standalone
cdylib Vulkan implicit layer that appends {A2B10G10R10, HDR10_ST2084} + {RGBA16F, scRGB}
to vkGetPhysicalDeviceSurfaceFormats[2]KHR (no need to hook vkCreateSwapchainKHR — the
ICD doesn't validate the color space there). Self-gated on the surface monitor's actual
advanced-color state (DisplayConfig GET_ADVANCED_COLOR_INFO), so it is a complete no-op
on SDR sessions and real monitors (dedup). Always-on (registry-discovered) so it works
regardless of how a game is launched — env-scoping silently fails for already-running
Steam. Escape hatches: DISABLE_PF_VKHDR, PF_VKHDR_EXCLUDE, and a built-in kernel-anti-
cheat denylist.
The installer builds/signs/stages it and registers it under
HKLM64\SOFTWARE\Khronos\Vulkan\ImplicitLayers (opt-out "Install the HDR Vulkan layer"
task); windows-host CI fmt+clippy-gates it (msvc-only FFI).
Live-validated on the RTX box: Doom: The Dark Ages enables HDR over the pf-vdisplay
virtual display.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
3e7c9bd059 |
fix(host): remove unsound unsafe impl Sync for HelperRelay
apple / screenshots (push) Has been skipped
windows-drivers / probe-and-proto (push) Successful in 29s
audit / cargo-audit (push) Failing after 1m20s
windows-drivers / driver-build (push) Successful in 1m14s
ci / web (push) Successful in 46s
ci / docs-site (push) Successful in 1m3s
windows-host / package (push) Successful in 6m46s
apple / swift (push) Failing after 0s
release / apple (push) Failing after 0s
android / android (push) Failing after 2m5s
ci / bench (push) Successful in 4m34s
decky / build-publish (push) Successful in 22s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m25s
ci / rust (push) Successful in 8m36s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m11s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 59s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 2m37s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m3s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 29s
deb / build-publish (push) Successful in 7m50s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m52s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m33s
flatpak / build-publish (push) Successful in 3m56s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m46s
docker / deploy-docs (push) Successful in 22s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m26s
The one genuine soundness defect the unsafe-proof program surfaced (flagged SUSPECT in program 3/N). `HelperRelay` holds an `rx: Receiver<RelayAu>`, which is `!Sync` (std mpsc is single-consumer), so asserting `Sync` claimed more than the fields support — an `Arc<HelperRelay>` recv'd from two threads would compile and be UB. It was never live-exploited, and it turns out `Sync` is also unnecessary: the relay is a single-owner `mut relay` local in the punktfunk1 two-process mux loop (recv_timeout/try_recv/request_keyframe all called on the owning thread; no `Arc`, no `thread::spawn` capturing it). So the fix is simply to delete the impl — the struct keeps its sound `unsafe impl Send` (needed for the raw `HANDLE` fields), which is all the code uses. Box-verified: cargo clippy -p punktfunk-host --features nvenc --target x86_64-pc-windows-msvc -- -D warnings stays green without the Sync impl. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7aa787a789 |
docs(host): prove the last 3 files + crate-root deny (unsafe-proof program 4/N, final)
Completes the unsafe-proof program now that the parallel WIP has landed: - idd_push.rs (25 sites), nvenc.rs (7), punktfunk1.rs (21): a SAFETY proof on every unsafe block — D3D11/DXGI COM (same-device textures, immediate-context single-thread, keyed-mutex-held convert), the NVENC SDK table (versioned POD, register/map/lock-bitstream pairing), cross-process shm reads (atomic magic/generation handshake), and the C-ABI harness (each call cross-checked against its abi.rs `# Safety` doc). No SUSPECT (UB) blocks. - capture.rs / encode.rs: the parent-module deny is restored (their WIP children are now proven), and main.rs gains a crate-root #![deny(clippy::undocumented_unsafe_blocks)] — the permanent catch-all gate so no future unsafe block anywhere in the crate can land without a proof. - Fixed 4 blocks the agents missed: unsafe blocks nested inside `assert_eq!(...)` macro args (the comment-above-statement didn't associate) — hoisted to a `let`. - rustfmt-canonicalized the Windows files (the agents' SAFETY comments + some pre-existing 1.9.0 drift) so `cargo fmt --all --check` is clean. Verified: cargo clippy -p punktfunk-host --all-targets -- -D warnings AND cargo fmt -p punktfunk-host --check both green with the crate-root deny active. Windows cfg(windows) re-verified on the box next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3514702d8c |
feat(windows-host): IDD-push encodes native NV12/P010 (skip NVENC's SM-side CSC)
GPU-contention work (host-latency plan §5.A): the IDD-push output ring now hands NVENC native YUV instead of RGB, so NVENC skips its internal RGB→YUV colour conversion on the SM/3D engine the running game saturates. - idd_push.rs: out_ring is now NV12 (SDR, BT.709 limited) via a D3D11 VIDEO-engine BGRA→NV12 VideoConverter (keeps the CSC off the contended 3D/compute engine), or P010 (HDR, BT.2020 PQ limited) via the FP16→P010 shader (NVIDIA's VideoProcessor can't do RGB→P010). The ring drops its per-slot RTV (textures only), matching the WGC YUV ring; converters rebuild on a size/HDR flip. - nvenc.rs: NV12 input forces bit_depth=8 so an HDR→SDR toggle (or a 10-bit- negotiated client on an SDR display) re-inits the session at the matching depth — NV12 can't feed a 10-bit session (register_resource rejects it). - punktfunk1.rs: per-stage latency instrumentation under PUNKTFUNK_PERF (cap=try_latest, submit=encode_picture, wait=lock_bitstream µs p50/p99/max) to pinpoint where capture→encoded latency goes under GPU saturation. |
||
|
|
327a5fa828 |
docs(host): prove unsafe blocks in the Windows + cross-platform files + gate them (unsafe-proof program 3/N)
Continues the unsafe-proof program across the Windows/cross-platform host files
(~75 blocks, 21 files), each with a SAFETY proof of the real invariant and a
per-file #![deny(clippy::undocumented_unsafe_blocks)] gate:
capture/windows: dxgi.rs, wgc_relay.rs, wgc.rs, desktop_watch.rs, composed_flip.rs
(windows-rs COM: interface validity, same-D3D11-device textures,
immediate-context single-thread, borrowed args outlive the call)
windows: service.rs (SCM/token/CreateProcessAsUserW/event handles — OwnedHandle
liveness, no double-close/signal race), win_display, wgc_helper, interactive
vdisplay/windows: manager.rs, pf_vdisplay.rs (SwDeviceCreate/IddCx/ioctl handle
liveness via the OnceLock VDM singleton + OwnedHandle)
encode/windows: ffmpeg_win.rs (full AVBufferRef refcount audit — balanced, NO leaks,
unlike the vaapi sibling), sw.rs
cross-platform: gamestream/audio.rs (libopus), gamestream/stream.rs (sendmmsg),
inject/windows/sendinput.rs, audio/windows/wasapi_mic.rs,
session_tuning.rs, vdisplay.rs
Two findings (handled separately):
- wgc_relay.rs `unsafe impl Sync for HelperRelay` is UNSOUND (its mpsc Receiver is
!Sync) though not live-exploited — marked SUSPECT inline; fix pending box check
(it touches the in-flight punktfunk1.rs).
- capture.rs / encode.rs (PARENT modules of the WIP idd_push.rs / nvenc.rs) do NOT
get the file deny yet — it would propagate the lint into the undocumented WIP
children. The deny lands there once those are documented (after the WIP commits).
Linux-visible parts verified green (cargo clippy -p punktfunk-host --all-targets
-- -D warnings). The cfg(windows) deny gates are box-verified next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
9777ed7fb3 |
fix(host/vaapi): plug two AVBufferRef leaks in DmabufInner::open
Surfaced while writing the unsafe-soundness proofs (2/N): both are refcount leaks (sound — never dangling/double-free — so the SAFETY proofs held, but real bugs on the persistent punktfunk1-host listener that opens a fresh encoder per session). 1. Per-session leak: `par->hw_frames_ctx = av_buffer_ref(drm_frames)` created a second owned ref. `av_buffersrc_parameters_set` takes its OWN ref of `par->hw_frames_ctx`, and `av_free(par)` frees only the struct, not the ref — so the extra ref leaked every session, pinning the DRM frames ctx + device. Fix: assign `drm_frames` borrowed (the standard ffmpeg pattern); our single owned ref lives in DmabufInner and is unref'd in Drop. 2. Error-path leak: the final `open_vaapi_encoder(...)?` returned without the unref ladder every other error path runs, leaking graph/drm_frames/ vaapi_device/drm_device on encoder-open failure. Fix: match + clean up before returning (nv12_ctx is borrowed from the sink → freed by graph teardown). cargo clippy -p punktfunk-host --all-targets -- -D warnings clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ba68a98873 |
docs(host): prove every unsafe block in the Linux FFI files + gate them (unsafe-proof program 2/N)
Continues the structural unsafe-proof program (every unsafe carries a documented
proof of soundness; the file gains #![deny(clippy::undocumented_unsafe_blocks)]
so it stays proven). This batch covers all 10 remaining pure-Linux files
(104 blocks), each proof stating the REAL invariant — not boilerplate:
zerocopy/cuda.rs (26) leaked process-lifetime libcuda fn-ptr table; opaque
CUcontext never dereferenced; free-exactly-once via the
Arc<Mutex<PoolInner>> ownership graph; dmabuf fd take/close split
zerocopy/egl.rs (18) eglGetProcAddress'd procs with the GL context current;
EGLImage liveness; the two-call modifier-query bounds
zerocopy/vulkan.rs (4) copy-bounds arithmetic (src_size>=span); Send = thread
confinement to the punktfunk-pipewire thread
dmabuf_fence.rs (4) poll/ioctl/close fd liveness + ownership
capture/linux/mod.rs (16) spa_data repr(transparent) cast; null-checked spa
derefs; single-loop-thread buffer ownership until requeue
inject/linux/gamepad.rs (10) uinput ioctl request-number ↔ struct-size match
(static-asserted); InputEventRaw no-padding for the byte cast
encode/linux/vaapi.rs (15) + encode/linux/mod.rs (9) ffmpeg object ownership/
free ladders; VAAPI/DRM graph; Send = single-thread transfer
inject/linux/wlr.rs (2), vdisplay/linux/kwin.rs (1)
No memory-unsafety SUSPECT blocks were found — the unsafe is sound. The vaapi
agent did flag two real AVBufferRef *leaks* (not UB) in DmabufInner::open; marked
inline with NOTE(leak) and addressed in a follow-up.
Verified: cargo clippy -p punktfunk-host --all-targets -- -D warnings is clean
(each file's deny gate hard-errors on any undocumented block).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
22359f5dc8 |
docs(host): prove every unsafe block in drm_sync.rs + gate it (unsafe-proof program 1/N)
Start of the structural unsafe-proof program (per the "every unsafe needs a documented proof of soundness" goal): each `unsafe` block gets an accurate `// SAFETY:` proof of WHY it is sound, and the file gains `#![deny(clippy::undocumented_unsafe_blocks)]` so the proof requirement is permanently enforced (a future undocumented unsafe in this file fails CI). drm_sync.rs (10 blocks: libc open/ioctl/clock_gettime/close + 3 in tests): each proof states the real invariant — fd liveness/ownership, the ioctl request number encoding the matching struct size, the `&mut req` being a live correctly-sized `#[repr(C)]` struct, and (for the timeline ioctls) the `handles`/`points` arrays outliving the synchronous call with `count_handles` matching their length. The gate grows file-by-file (CI stays green; undone files don't carry the lint yet); it promotes to a crate-root deny once every file is done. ~122 Linux blocks + the Windows files remain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7e9023faad |
feat(gamestream): launch apps on Windows + Linux non-gamescope hosts
GameStream's apps.json `cmd` is delivered via set_launch_command, which ONLY the Linux gamescope backend nests. On Windows (no gamescope) and Linux kwin/mutter/wlroots (which stream the existing desktop) the command was silently dropped. Now, after capture is live, stream.rs spawns it via library::launch_gamestream_command for those backends — Windows: into the interactive USER session (spawn_in_active_session, since the host is SYSTEM); Linux: a plain `sh -c` spawn into the host's own graphical session so the app lands on the streamed (primary) output. Linux gamescope keeps nesting via set_launch_command and is skipped here to avoid a double launch. The command is operator-typed apps.json (trusted), never client-set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5acc12d9e9 |
feat(library): shared cover-art warmer + cache (GOG + Xbox art)
A disk-backed art cache (library-art-cache.json in the canonical host config dir) is the source of truth read by all_games(), so the library list + launch-resolve never block on the network. A host-lifetime background warmer (start_art_warmer, started in serve()) fetches uncached art OFF the hot path: GOG via the public no-auth api.gog.com product API, Xbox via the unofficial no-auth displaycatalog (keyed by StoreId). Both best-effort (protocol-relative URLs normalized to https; results cached even when empty so they aren't re-fetched). The GOG + Xbox providers now read cached_art() (title-only until warmed). Cross-platform (ureq blocking HTTP — no tokio on this path) so the fetch/parse code is compiled + checked everywhere; a host whose stores all self-provide art (Steam CDN / Heroic CDN / Lutris data: URLs) does no fetching. Dep: ureq (webpki roots, no system certs). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
aed0bf0c2a |
feat(library): Windows Xbox / Game Pass store provider
XboxProvider scans each fixed drive's <drive>:\XboxGames for GDK games (presence of Content\MicrosoftGame.config marks a game vs. an ordinary UWP app), parsing title / Identity name / Executable Id / StoreId via roxmltree. The PackageFamilyName is READ from the AppRepository\Packages\<PackageFullName> dir name (reduced to Name_Hash) — never computed from the publisher. Launch via the AUMID (shell:AppsFolder\<PFN>!<AppId>) through explorer in the interactive user session (UWP activation needs the user token, which spawn_in_active_session already provides). Cover art (displaycatalog) is deferred → title-only. Known v1 gaps: custom .GamingRoot install folders + non-GDK pure-UWP Store games (under the ACL-locked WindowsApps) aren't enumerated. New windows_launch_for `aumid` arm; XboxProvider wired into all_games() under cfg(windows). Dep: roxmltree (Windows). Windows unit tests cover MicrosoftGame.config parsing (incl. the ms-resource title fallback), the PackageFullName→PFN reduction, and the aumid launch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b65745284e |
feat(library): Windows Epic + GOG store providers
EpicProvider reads the launcher's local .item manifests under %ProgramData% (no auth, launcher need not run) with Playnite's exclusion filter (skip UE_* components + non-launchable addons + dead install dirs); cover art from the base64 catcache.bin (public Epic CDN, best-effort). Launch via the com.epicgames.launcher:// URI opened through explorer.exe — the namespace:catalogItemId:appName triple, with a bare-appName fallback so a launch is never dropped. GogProvider enumerates HKLM\SOFTWARE\WOW6432Node\GOG.com\Games (winreg) + each goggame-<id>.info primary FileTask into a direct-exe spawn (no Galaxy, dodges its cold-start/anti-cheat). GOG cover art (public api.gog.com) is deferred — it needs an HTTP fetch + cache off the hot all_games() path — so GOG is title-only for now. windows_launch_for gains epic/gog arms; both providers wired into all_games() under cfg(windows). Deps: base64 moved to the cross-platform table (Epic catcache decode + Lutris art encode both need it); winreg added on the Windows target. Windows unit tests cover the Epic exclusion filter + URI builder and the GOG spawn + play-task parsing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8ca695eb4c |
docs(windows-host): SCM event redesign done + runtime-validated (D2 complete)
The service.rs STOP/SESSION events are now OnceLock<OwnedHandle> (
|
||
|
|
61c02e695e |
refactor(windows-host): OwnedHandle for the SCM STOP/SESSION events (Goal-3, last unsafe reduction)
The service's STOP/SESSION manual-reset events were smuggled across the C SCM control-handler boundary as raw `isize` in `AtomicIsize` statics (the handler is a capture-free `'static` closure, so it can't hold a non-`Send` `HANDLE` — it has to reach the events through statics), reconstructed via `load_event`, and explicitly `CloseHandle`d at `run_service` end. Replace the raw-`isize` statics with `OnceLock<OwnedHandle>`: - `run_service` creates each event, wraps it in an `OwnedHandle`, derives a borrowed `HANDLE` for `supervise` (unchanged signature), and `set`s the OnceLock (once per process) — all BEFORE the handler is registered, so the handler always sees `Some`. - The handler reads `event_handle(&STOP_EVENT)` (a borrow) and `SetEvent`s it, with a defensive `None` guard (matches the old `SetEvent(HANDLE(0))` no-op if it ever fired pre-init). - The events are owned by the OnceLocks for the process lifetime (the service process exits right after `run_service` returns, so the OS reaps them at exit). Dropping the explicit `CloseHandle` also removes the latent close-then-signal window the old statics had (the raw isize lingered after the close). Deletes the `AtomicIsize`/`Ordering` import + `load_event` + the raw-isize smuggle — the last host-side raw-handle reduction. Behaviour-preserving (same events, same signal/wait/reset, same once-per-process init order). Linux check + fmt clean; the file is #[cfg(windows)] → to be box-validated (compile + a service stop/restart). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
203ad8069d |
fix(web): library badge shows the actual store, not always "Steam"
The GameCard badge hard-coded steam-vs-custom, so any non-Steam non-custom store rendered with the "Steam" label. Add storeLabel(store): steam/custom keep their localized strings, every other store is shown as a capitalized proper noun — so the new Lutris/Heroic providers (and future ones) surface correctly with no per-store translation. tsc --noEmit clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5f8c6b6147 |
feat(library): Lutris + Heroic store providers (Linux)
LutrisProvider reads the local pga.db (rusqlite, read-only/immutable so a running Lutris can't block us) → installed games, launch via `lutris lutris:rungameid/<id>`, cover art from Lutris's on-disk cache inlined as data: URLs (no public CDN keyed by a stable id, unlike Steam/Heroic). HeroicProvider parses Heroic's store_cache JSON — legendary/gog/nile = Epic+GOG+Amazon in one provider — installed-only with an install-dir existence cross-check (works around Heroic's gog is_installed bug #2691), free public CDN cover art, launch via `heroic --no-gui heroic://launch?...` (the single-instance-Electron gamescope-escape caveat is documented; needs live confirm). New command_for arms (lutris_id digits-guard, heroic runner+appName-guard) + both providers wired into all_games(); everything Linux-gated (the launchers are Linux-only), so the Windows/macOS host build is unaffected. Deps rusqlite (bundled SQLite, no system dep) + base64 added to the Linux target only. Unit tests with sqlite/json fixtures (installed-only filtering, CDN-art mapping, launch guards); live `library` enumeration returns [] gracefully on a box without the launchers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cd3368fc71 |
docs(windows-host): KeyedMutexGuard done + record the on-glass build validation
Goal 3: the IDD-push hot-loop KeyedMutexGuard ( |
||
|
|
bd05bc8c30 |
fix(windows): clippy/build cleanups the on-glass build surfaced (-D warnings)
Built the host crate (`cargo clippy --features nvenc -D warnings`) and the driver workspace (`cargo build`) on the RTX box — the project's intended Windows gate, which `cargo check` (what the goal1/§2.5 work used) never runs. It surfaced lint issues accumulated across the goal1 / §2.5 / this-session Windows work: - 9× redundant `as *mut c_void` after `.as_raw_handle()` (already `*mut c_void`): idd_push.rs (3, this session), service.rs (3, this session), manager.rs (3, pre-existing §2.5 — my OwnedHandle work copied the idiom). Removed the casts + the now-unused `use std::ffi::c_void` in idd_push.rs / manager.rs (service still uses it). - `if_same_then_else` in session_plan.rs::resolve_topology (pre-existing goal1 stage 3): collapsed the two `false` arms into one condition (behavior identical). - `unused_unsafe` in the driver `pod_init!` macro: it expands at call sites already inside an `unsafe` block, where its own `unsafe` is redundant — `#[allow( unused_unsafe)]` (needed at the non-unsafe sites, redundant at the nested ones). After these, BOTH builds are clean on the box — validating the whole session's blind Windows + driver work compiles + passes clippy on real hardware. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
658564353c |
refactor(windows-host): KeyedMutexGuard RAII for the IDD-push consume hot loop (Goal-3, hw-validated)
The IDD-push consume loop acquired the slot's keyed mutex by hand (`AcquireSync(0,8)` … work … `ReleaseSync(0)`), with a comment warning that a `?`-return between acquire and release would leak the lock and stall the driver on that slot — the reason the HDR converter is built *before* the acquire. Replace with a `KeyedMutexGuard` RAII (acquire → `ReleaseSync` on drop), scoped to JUST the convert/copy block so the lock releases at the EXACT same point as before (the driver gets the slot back immediately; not held across the rest of `try_consume`). Now the release can't be skipped on any early return/panic — the leak footgun is gone by construction, and the hot loop has no raw `ReleaseSync`. Behavior/latency-equivalent (same acquire params, same release point). Windows- only (CI + on-glass gated); to be validated on the RTX box (host clippy build + a PERF=1 latency A/B vs the shipping binary — the change should show no delta). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6b3cbce120 |
wip: host latency/GPU-contention notes + Windows packaging tweaks
Pre-existing working-tree changes committed to the branch on request: the gpu-contention investigation doc, host-latency-plan additions, and small pack-host-installer / stage-pf-vdisplay packaging-script edits. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
739fa74e68 |
docs(library): game-store provider design (Xbox/Epic/EA, Heroic/Lutris, …)
Web-researched + adversarially-verified design for extending library.rs with more store providers: the LibraryProvider extension point, the two cross-cutting pieces (Windows interactive-session launch wiring + a layered artwork strategy), new LaunchSpec kinds, per-store enumeration/launch/art recipes with priority/effort/ confidence, a phased plan, and the verification corrections. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c87ca577a3 |
feat(windows-host): launch the chosen library title into the interactive session
Make the no-op Windows `set_launch_command` real. New `windows/interactive.rs` `spawn_in_active_session` (WTSGetActiveConsoleSessionId → WTSQueryUserToken → CreateProcessAsUserW(winsta0\default) under the LOGGED-IN USER token, factored from the wgc_relay primitive) + `library::launch_title` resolving a store-qualified id to a concrete process via `windows_launch_for` (steam_appid → Steam.exe/explorer.exe steam:// URI; command → cmd.exe /c). Threaded as `SessionContext.launch` into both native data-plane paths (`virtual_stream`, `virtual_stream_relay`) and fired after capture is live so the title renders onto the captured desktop and grabs foreground. Security invariant intact: the client sends only the store-qualified id; the host resolves the recipe from its own library and the URI/flags are handed to a concrete EXE as plain args (never cmd /c of a client string). Linux unchanged (gamescope nesting via the handshake PUNKTFUNK_GAMESCOPE_APP path). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e68b7330ae |
docs(windows-host): record the shared gamepad RAII reduction (e5c2b4e)
Goal 3 scorecard + §4 P2: the OwnedHandle/RAII rollout now covers the three gamepad backends via the shared inject/windows/gamepad_raii.rs (Shm + SwDevice). Scratched the IOCTL-dispatcher item (control.rs's read_input/write_output_complete are already generic — would be churn, not reduction). The only remaining unsafe reductions are the deliberately-left service.rs SCM-handler event smuggling and the on-glass-gated KeyedMutexGuard hot-loop RAII. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e5c2b4e7f5 |
refactor(windows-host): shared Shm/SwDevice RAII for the 3 gamepad backends (Goal-3 unsafe reduction)
The DualSense, DualShock 4, and XUSB Windows pad backends each hand-rolled the SAME per-pad resource handling: a `CreateFileMappingW` + `MapViewOfFile` shared section (with the permissive D:(A;;GA;;;WD) SDDL the restricted-token driver needs) and an identical `Drop` doing `SwDeviceClose` + `UnmapViewOfFile` + `CloseHandle` — three copies, each a chance to drift or leak on an error path. New `inject/windows/gamepad_raii.rs` owns both resources with RAII: - `Shm` — the section handle (`OwnedHandle`) + its view; `Shm::create(name, size)` does the SDDL + map + zero-fill leak-safely, `base()` gives the mapped pointer, `Drop` unmaps then closes (in that order). - `SwDevice` — the `SwDeviceCreate`'d devnode; `Drop` calls `SwDeviceClose`. All three backends now hold `_sw: Option<SwDevice>` + `shm: Shm` instead of raw `hsw`/`map`/`view`, access the section via `self.shm.base()`, and have NO manual `Drop`. Deletes the duplicated `create_shm_section` (DualSense/DS4 now use `Shm::create`) and the three hand-written Drops; the DS4 device-type byte is still written before the magic, the SwDeviceCreate `None` fallback still works, and the field drop order (devnode removed, then section unmapped+closed) matches the old manual order. Net: 3 manual `Drop`s + a duplicated section-creation path → one shared RAII module; fewer unsafe ops, leak-on-error fixed by construction. Linux `cargo check` clean (the inject mod wiring); the backends are #[cfg(windows)] → CI-gated. Drafted + adversarially verified (no double-free, imports correct under -D warnings, behavior preserved); my own spot-checks confirm. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7ad3a57e68 | fix theme | ||
|
|
22bef1fd0a |
docs(windows-host): record the Goal-3 unsafe reductions (OwnedHandle rollout + pod_init!)
Scorecard Goal 3 + §4 P2: the OwnedHandle RAII rollout (idd_push |
||
|
|
bf577044f1 |
refactor(windows-drivers): pod_init! macro — 27 unsafe { mem::zeroed() } POD inits -> 1 (Goal-3 #3)
The driver zero-initialised C POD structs (IddCx/WDF descriptors) with 27
scattered `let mut x: T = unsafe { core::mem::zeroed() };`, each carrying its own
`// SAFETY` about the all-zero bit pattern being valid + the caller setting `.Size`
etc. right after.
Replace with one `pod_init!(T)` macro (in log.rs, reachable everywhere via the
existing `#[macro_use] mod log;` — same mechanism as `dbglog!`) that owns the
single `unsafe { zeroed::<T>() }` + the SAFETY rationale. All 27 sites
(adapter 6, callbacks 3, entry 4, monitor 10, swap_chain_processor 4) now read
`let mut x = pod_init!(T)`. Zero behavior change (mem::zeroed semantics identical);
the type is passed explicitly so no inference depends on the removed annotation.
27 `unsafe` blocks → 1. Driver still `deny(unsafe_op_in_unsafe_fn)`-clean (the
macro expands to an explicit `unsafe {}`; the one nested-in-user-unsafe site is
fine — no `unused_unsafe` for macro-generated blocks). Driver-only (CI-gated);
adversarially reviewed (macro scoping, all sites, no leftover raw zeroed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
4c95ba72a3 |
refactor(windows-host): OwnedHandle for the service child + job handles (Goal-3 unsafe reduction #2)
The SCM supervisor scattered manual `CloseHandle(pi.hProcess)`/`(pi.hThread)`
across ~5 supervise-loop match arms and hand-closed the job object — easy to miss
an arm (leak) or double-close.
- `spawn_host` returns an owned `Child { process: OwnedHandle, _thread: OwnedHandle,
pid }` instead of raw `PROCESS_INFORMATION`; the supervise loop borrows
`child.process` (`HANDLE(as_raw_handle() as *mut c_void)`) for wait/Terminate and
the `Child` auto-closes both handles when it drops / is replaced each iteration.
- The job object → `OwnedHandle` (borrowed for AssignProcessToJobObject), auto-closed.
- Deletes ~9 manual `CloseHandle` calls. The `_thread` handle is RAII-only (`_`-prefixed
so `dead_code`/`-D warnings` doesn't flag it).
Deliberately LEFT the `STOP_EVENT`/`SESSION_EVENT` `AtomicIsize` statics as-is — they
are smuggled into the C SCM control handler, so `OwnedHandle`-ifying them is a separate,
riskier supervisor redesign out of scope here (noted in a comment).
Behavior preserved (the supervise state machine / wait semantics / restart-on-
session-change / kill-on-close are unchanged). Windows-only (CI-gated); adversarially
reviewed (no double-close, handles outlive their borrows, idiom matches manager.rs).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
011607ec10 |
refactor(windows-host): RAII for IDD-push handles/views — fix a leak (Goal-3 unsafe reduction #1)
The IDD-push capturer held raw `HANDLE`s for the shared header mapping, the
frame-ready event, the debug section, and each ring slot's shared texture, with
manual `CloseHandle` scattered across two `Drop` impls — and the MapViewOfFile
VIEWS (header/dbg_block) were never UnmapViewOfFile'd (a real view leak).
- New `MappedSection { handle: OwnedHandle, view }` RAII: `Drop` UnmapViewOfFile's
the view THEN the `OwnedHandle` closes the mapping (unmap-before-close).
- `map`+`header` → `section: MappedSection` (+ a cached `header` ptr borrowing into
it, declared after `section` for drop order); same for `dbg_map`+`dbg_block`.
- `event: HANDLE` → `OwnedHandle` (borrowed as `HANDLE(as_raw_handle() as *mut
c_void)` for WaitForSingleObject); `HostSlot.shared` → `OwnedHandle` (its manual
`Drop` deleted). Removed the manual `CloseHandle`s + the `CloseHandle` import.
Net: deletes two `Drop` impls' worth of manual handle/view teardown and fixes the
view leak — fewer unsafe ops, RAII-correct. Behavior preserved (recreate_ring
writes the header in place; the keepalive still drops last so REMOVE is last).
Windows-only (CI-gated); adversarially reviewed (no double-free / UAF / dangling
header; handle interop matches manager.rs). Linux check unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
803573b4ec | improve web ui | ||
|
|
00cf51d610 |
refactor: rename pf-vdisplay-proto -> pf-driver-proto (it spans all drivers)
The shared host<->driver ABI crate already contains more than the virtual display: the IDD-push frame ring + control plane AND the gamepad shared-memory layouts (XusbShm / PadShm). "pf-vdisplay-proto" was a misnomer — the name now represents all the drivers it serves. Mechanical rename, no behavior change: - git mv crates/pf-vdisplay-proto -> crates/pf-driver-proto (package name + path-deps in the host crate and the driver workspace). - pf_vdisplay_proto -> pf_driver_proto across host + driver Rust, both Cargo.lock files, the workspace members, the CI path triggers (windows-drivers.yml), and the docs/INF comments. The runtime Global\pfvd-* shared-object names are a SEPARATE contract and are deliberately untouched (host<->driver name matching). - The pf-vdisplay DRIVER crate + its INF service name (Root\pf_vdisplay, UmdfService=pf_vdisplay, pf_vdisplay.dll) are unchanged — only the full `pf_vdisplay_proto` token was replaced, never the `pf_vdisplay` driver name. Linux-verified: cargo test -p pf-driver-proto (const size-asserts compile) + cargo clippy -p punktfunk-host -D warnings clean; Cargo.lock regenerated. The driver-workspace side (path-dep + imports + its Cargo.lock) is Windows-CI-gated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
84a3b95f17 |
refactor(windows-host): delete the SudoVDA backend — pf-vdisplay is the sole vdisplay (Goal 2)
Goal 2 ("drop every trace of SudoVDA") is done. The SudoVDA driver is no longer
shipped (only pf-vdisplay; the old vdisplay-driver tree was deleted in
|
||
|
|
8cde8621ce |
fix(windows-drivers): reclaim pf-vdisplay monitor ids on REMOVE (P1, slot-reclaim)
The driver assigned each virtual monitor a monotonically-increasing NEXT_ID used as the EDID serial / IddCx ConnectorIndex / container GUID, and never reclaimed it on REMOVE. Under sustained ADD/REMOVE churn the connector index kept climbing, so IddCx/PnP allocated a NEW OS target slot every cycle and orphaned the old one (ghost "Generic Monitor (punktfunk)" nodes) until the adapter's target capacity was exhausted and ADD failed 0x80070490 ERROR_NOT_FOUND. Fix: `create_monitor` now allocates the LOWEST free id (`alloc_monitor_id`, computed under the MONITOR_MODES lock with the push) instead of a counter, so a departed monitor's id is reclaimed and a fresh ADD reuses its target slot rather than orphaning it. With <= N live monitors the id stays bounded to 1..=N+1. Deleted the now-unused NEXT_ID + AtomicU32/Ordering import. CI-compile-gated only — the wedge reproduces solely under sustained churn on the RTX box, so this needs an on-glass reconnect-storm A/B to confirm (box is ephemeral/down). Marked on-glass-pending in windows-host-rewrite.md §4; keep reset-pf-vdisplay.ps1 as the recovery until validated. NOT to be relied on (or merged to main) until that A/B passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0bf3984614 |
feat(windows-host): IDD-push is the default capture path for fresh installs (P1)
Make the validated IDD-push zero-copy path the default for a fresh install, without penalising dev / non-pf-driver runs: - The shipped default config now enables it. Both seed sites set `PUNKTFUNK_VDISPLAY=pf` + `PUNKTFUNK_IDD_PUSH=1`: the hardcoded default the service writes on `service install` (`ensure_default_host_env`) AND the `host.env.example` template the installer bundles. A fresh install therefore runs the validated path (the installer also bundles the pf-vdisplay driver); it falls back to DDA if the driver can't attach. - `idd_push` is now **value-aware** instead of a bare presence flag, so an operator can turn it OFF with `PUNKTFUNK_IDD_PUSH=0` in host.env — a `var_os` presence check read `=0` as "on". Unset still ⇒ off (the code default is unchanged, so existing host.env files and dev/CI runs are unaffected; only the shipped default config opts in). Also scrubbed the stale "SudoVDA" wording in host.env.example. Linux cargo clippy -p punktfunk-host -D warnings clean; the service.rs default string is Windows-only (CI-gated). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
75ee53d1dd |
feat(web): Storybook for offline UI design + light theme + brand spinner
Stand up Storybook so the management console can be designed without a running host, plus the design-system work that surfaced along the way. Storybook (@storybook/react-vite): - Slim Start/Nitro-free vite config; the preview imports the app's real src/styles.css directly so the design tokens stay single-sourced (no mirror). - Stories for the @unom/ui primitives (Button/Card/Inputs/Badge), brand marks, the AppShell (throwaway in-memory TanStack router), and every data-driven page (Dashboard/Host/Clients/Library/Settings) rendered offline via a window.fetch stub + typed fixtures. The route page components are exported so stories can render them. Light theme: - styles.css now carries a light :root (lavender, from the docs palette) with the existing violet chrome moved to .dark; the live console still pins html.dark by default, so this only adds the option (Storybook's toolbar toggles it). - Fixes a stray `*/` inside a comment that prematurely closed it and silently broke Tailwind's @theme processing. Spinner: - The punktfunk lens recreated with motion/react: two circles surge through one another in depth (JS perspective scale + z-index — robust where mix-blend-mode flattens CSS preserve-3d) with a screen-blend lens highlight. Replaces the skeleton loading state in QueryState; removes ui/skeleton.tsx. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0255a8289c |
docs(windows-host): consolidate 5 scattered docs into one current source of truth
The Windows-host docs were scattered across a design plan, a staged-refactor plan, an audit, an audit-remediation tracker, and a game-capture-bug analysis — several badly stale (the audit/remediation predate the Goal-1 branch landing and call DONE items "not started"). Verified the true state of every audit finding / goal / milestone against current code+git (4-agent workflow), then rewrote windows-host-rewrite.md as ONE consolidated, accurate doc: - §1 Status scorecard (Goals 1-3, M0-M6, GB1, audit P0/P1/P2) with DONE/PARTIAL/ OPEN + commit evidence. - §2 Architecture as-built (layering, HostConfig→SessionPlan→SessionContext, the VirtualDisplayManager ownership model, IDD-push-primary capture incl. secure desktop + GB1 recovery, encode/EncoderCaps, pf-vdisplay-proto, the driver, service/packaging). - §3 Validated invariants (the jewels). - §4 Prioritized open tasks (the genuine remaining work). - §5 Operations (RTX-box recipe, CI, env, build). - §6 Deep reference (/INTEGRITYCHECK answer, the 6 iddcx bindgen knobs, the driver port checklist, resolved decisions). Deleted the four now-redundant docs (content folded in; history in git): windows-host-goal1-plan.md, windows-host-rewrite-audit.md, windows-host-rewrite-remediation.md, windows-host-rewrite-game-capture-bug.md. Repointed the 6 code/proto/driver doc-comment refs that targeted them at the consolidated windows-host-rewrite.md sections. Linux cargo check clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6bed5d9e8e |
docs(windows-rewrite): secure desktop validated on glass — mark M3 done, retire the biggest risk
Owner-confirmed on glass (2026-06-25, "works great"): the IDD-push primary path captures the lock/UAC secure desktop AND input reaches the streamed console session. This was the single biggest open risk — the whole capture strategy (Decision B: IDD-push primary for everything incl. secure desktop, WGC/DDA demoted) rested on it. Now proven, not asserted. - §15: M3 row → DONE (secure desktop); removed the secure-desktop gate from "What genuinely remains" (renumbered); added it to "Resolved since §11". - §11 "IDD-push input + secure desktop" open item → RESOLVED. - §14 critique "SINGLE BIGGEST RISK: the secure-desktop claim" → RESOLVED. The WGC-relay / secure-DDA path is no longer load-bearing — kept only as a non-IddCx-hardware fallback. Remaining rewrite work is migration/cleanup (M4 gamepad drivers, M5/M6, slot-reclaim), none blocking the validated path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
48202a0f89 |
docs(windows-rewrite): mark game-capture bug FIXED + bring rewrite status current (§15)
The fullscreen-game-breaks-IDD-push bug is FIXED by the resolution-listening recovery ( |
||
|
|
bf57aa4000 |
docs(windows-host-goal1): Stage 5 tightening 3 (EncoderCaps) DONE; refresh Remaining
The Goal-1 host refactor is now functionally complete — all 6 stages, §2.5, and
all three Stage-5 seam-trait tightenings have landed (EncoderCaps =
|
||
|
|
0ccd0fe676 |
feat(windows-host): EncoderCaps — query RFI/HDR-SEI caps (Goal-1 stage 5, tightening 3)
The last §2.3 seam-trait tightening: give `Encoder` a `caps() -> EncoderCaps`
so the session glue routes by *query* instead of relying on the no-op/`false`
defaults of `invalidate_ref_frames`/`set_hdr_meta`.
`EncoderCaps { supports_rfi, supports_hdr_metadata }` is a cheap `Copy` struct.
The trait gains a default `caps()` returning `EncoderCaps::default()` (all
false) — correct for every SDR/libavcodec backend (Linux NVENC, VAAPI, AMF/QSV,
software openh264), so they need no change. Only the Windows direct-NVENC path
(`NvencD3d11Encoder`) overrides it, reporting the real `rfi_supported` (probed
once at open via `nvEncGetEncodeCaps`) and `hdr` (HDR-SEI on keyframes).
Consumer: the GameStream encode loop (`gamestream/stream.rs`) hoists
`supports_rfi` once before the loop and gates the loss-recovery path on it —
`!(supports_rfi && enc.invalidate_ref_frames(..))` forces a keyframe directly
on non-RFI encoders instead of making an always-`false` call every loss event.
Behaviour-preserving (same keyframe/RFI outcome), one fewer no-op call, intent
explicit. The native host (punktfunk1) uses FEC+keyframes, no RFI consumer.
Linux `cargo clippy -p punktfunk-host --all-targets -D warnings` clean; the
three edited files are rustfmt-clean. The NVENC override is Windows-only
(1:1 with the existing impl style) → CI/on-glass gate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
e1ca2e4d3c |
docs(windows-host-goal1): record §2.5 done + on-glass results + Remaining list
The plan tracker referenced "§2.5 — see below" but had no §2.5 section and no "what's left". Add:
* a Status banner (all 6 stages + §2.5 done; branch not merged),
* the §2.5 section — the 3-step ownership-model rewrite (VirtualDisplayManager/MonitorLease,
the deleted globals), the CURRENT_MON_GEN-write-only finding, and the on-glass reconnect-leak
result (the vdm-init-order panic found+fixed, 0 leaks, IDD-push zero-copy verified),
* a "Remaining (next session)" list: EncoderCaps, optional namespace collapse, merge to main, and
the pf-vdisplay driver slot-reclaim fix (driver WIP, not the host refactor) with the dev scripts.
Mark §2.5 IMPLEMENTED in the design doc (windows-host-rewrite.md) with the write-only-gen deviation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
e119aa50e9 |
feat(windows-packaging): dev-iteration scripts — reset + redeploy pf-vdisplay driver
Today's manual driver recovery (wedged under ADD/REMOVE churn → ERROR_NOT_FOUND) and the manual
host-stop/install/host-start dance around drivers/deploy-dev.ps1 are now two scripts:
* reset-pf-vdisplay.ps1 — recover a wedged driver: stop host → pnputil /remove-device the ghost
"Generic Monitor (punktfunk)" nodes → Disable+Enable the adapter
(Restart-PnpDevice doesn't exist on the box PS) → start host. No reboot
(the box boots to Proxmox). -Verify probes to confirm ADD recovered.
* redeploy-pf-vdisplay.ps1 — one-shot dev redeploy wrapping deploy-dev.ps1 with the host stop/start
(the running host holds the driver DLL) + a post-install adapter reload
(pnputil updates the store but the live device keeps the old binary).
Both standalone (don't touch deploy-dev.ps1). README gains a "Dev iteration on the test box" section.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
683c81be03 |
fix(windows-host): §2.5 — open the backend before the IDD-push preempt (vdm() init order)
On-glass caught a runtime panic the box compile couldn't: `VirtualDisplayManager used before a backend initialised it`. Step 3 put the preempt (`vdm().begin_idd_setup`) BEFORE `vdisplay::open` in virtual_stream, but vdisplay::open is what constructs the backend that calls manager::init() — so vdm() was reached before init and panicked on the first IDD-push session. (The old IDD_SETUP_LOCK/IDD_SESSION_STOP globals needed no init, so the prior ordering was fine.) Fix: open the backend first (it does no monitor work — just constructs the marker + opens the control device, initialising the manager), THEN run the preempt, THEN build the pipeline (which creates the monitor). The preempt still precedes this session's monitor creation, so the semantics are unchanged. Validates why §2.5 needs the on-glass gate, not just the compile. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |