faefbae830a8e8d1d1595dc317f2b49ffc19bb6f
1678
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
faefbae830 |
fix(pf-presenter): spell MAKEINTRESOURCE(1) as ptr::without_provenance — clippy 1.96's manual_dangling_ptr reads the integer-ordinal cast as a dangling pointer and fails the Windows -D warnings gate (masked on main by the client bins failing to build first)
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m16s
ci / bun-nix (pull_request) Successful in 26s
ci / web (pull_request) Failing after 1m35s
apple / swift (pull_request) Successful in 1m42s
apple / screenshots (pull_request) Skipped
ci / docs-site (pull_request) Successful in 1m18s
ci / rust (pull_request) Failing after 2m21s
ci / rust-arm64 (pull_request) Successful in 3m45s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m42s
android / android (pull_request) Successful in 4m29s
|
||
|
|
44fa12a298 | test(pf-vkdecode): bind the PTL level reads so the SAFETY comments precede their unsafe blocks (clippy::undocumented_unsafe_blocks counts nothing inside macro arguments) | ||
|
|
a02014ec19 |
fix(pf-vkdecode): treat an over-declared stream level as a clamp, not a refusal
A 2026-08-12 field report (RTX 5060 client): every HEVC session demoted to D3D11VA with 81 "outside device caps: stream level (Std code point 12) above the device's maxLevelIdc (H.265 Std level 11)" refusals — the host's AMF encoder stamps general_level_idc 6.2 (the codec maximum) on a 4K120 stream that needs 5.2, and NVIDIA's driver caps H.265 decode at 6.1. The hardware decodes the actual stream trivially; only the declaration was oversized. AV1 passed the same gate, which is why "native-vulkan runs only with AV1". The declared level is a claim, and the stream's real demands are enforced where they are physical facts — coded extent and DPB depth, both checked at session build. So the up-front level gate (H.264 + H.265) now warns once and proceeds, and every SPS/VPS handed to the Vulkan parameters object has its level clamped to the device ceiling (a set above maxLevelIdc is invalid usage). AV1's gate is untouched: its code space is the bitstream's own and no over-declaration has been seen in the field. Verified on .173 (RTX 4090, driver 610.88): HEVC and AV1 both decode on the native Vulkan rung at 60 fps against an NVENC host; unit tests pin the clamp (lowers, only lowers, mutates the driver-visible block in place). |
||
|
|
57703fe31c |
Merge remote-tracking branch 'origin/main' into worktree-edition-2024
ci / rust-arm64 (pull_request) Failing after 58s
ci / web (pull_request) Successful in 1m0s
ci / bun-nix (pull_request) Successful in 2m9s
apple / swift (pull_request) Successful in 1m38s
apple / screenshots (pull_request) Skipped
ci / docs-site (pull_request) Successful in 4m10s
ci / rust (pull_request) Failing after 5m14s
android / android (pull_request) Successful in 9m15s
|
||
|
|
90a3304c2b |
Merge pull request 'The Windows host no longer blocks system sleep while idle' (#179) from worktree-win-sleep-blockers into main
deb / build-publish (push) Failing after 1m16s
ci / bun-nix (push) Successful in 27s
ci / web (push) Successful in 1m5s
ci / docs-site (push) Successful in 1m11s
apple / swift (push) Successful in 1m37s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 12s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 9s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 8s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
deb / build-publish-client-arm64 (push) Failing after 1m24s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 7s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 16s
android / android (push) Canceled after 3m59s
apple / screenshots (push) Canceled after 2m19s
ci / rust-arm64 (push) Failing after 3m56s
ci / rust (push) Failing after 3m56s
arch / build-publish (push) Canceled after 4m1s
deb / build-publish-host (push) Canceled after 2m54s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 1m11s
docker / deploy-docs (push) Canceled after 0s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 1m7s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 17s
windows-host / package (push) Canceled after 4m4s
windows-host / canary-manifest (push) Canceled after 0s
windows-host / winget-source (push) Canceled after 0s
Reviewed-on: #179 |
||
|
|
017867f211 |
fix(ci): the gates the PR run reaches and the boxes could not
ci / rust-arm64 (pull_request) Failing after 1m43s
ci / web (pull_request) Successful in 1m52s
ci / bun-nix (pull_request) Successful in 28s
ci / docs-site (pull_request) Successful in 3m36s
apple / swift (pull_request) Successful in 1m44s
apple / screenshots (pull_request) Skipped
ci / rust (pull_request) Failing after 2m52s
android / android (pull_request) Successful in 6m23s
- adl_emul.rs adl_malloc: panic-free (a reachable expect in an extern fn is an abort — gate B; the Err arm is unreachable, ADL treats null as failure) - punktfunk-host main.rs: reword the carve-out comments so gate C's textual count stays at its baseline (comments count) - clients/linux: forbid(unsafe_code) -> deny with two named allows — the SDL device-filter clear and the spawn test's HOME scoping are unsafe calls in edition 2024 (caught by the aarch64 leg, the only one with glib) |
||
|
|
535e95c4c0 |
fix(punktfunk-host): idle-stop the virtual-mic stream so a Windows host can sleep
The mic pump's WASAPI backend rendered silence into the virtual mic's
render endpoint (typically the Steam Streaming Microphone) for the whole
host lifetime. A RUNNING stream makes the Windows audio stack hold a
kernel power request ("An audio stream is currently in use", visible in
powercfg /requests), so every idle Punktfunk host blocked system sleep
forever — field-reported 2026-08-12 ("doesn't go to sleep anymore since
installing punktfunk; powercfg shows the Steam Streaming Microphone").
Stop the render stream (IAudioClient::Stop — client stays initialized,
the mic endpoint keeps existing) after 10 s of silence-only output at an
unchanged queue length, and park the render thread on a condvar the
producer notifies on the empty->non-empty edge, so the next mic frame
resumes the stream within one device period — well under the jitter
buffer's prime depth, so nothing audible changes. Keying the idle window
on the queue LENGTH (not emptiness) also covers a sub-prime tail a
vanished client left behind, while any fresh burst moves the length and
resets the window instead of being dropped.
During a session the box stays awake through the session's own
DisplayWakeRequest (pf-frame), never through this silence.
PUNKTFUNK_MIC_ALWAYS_ON=1 restores the old always-running stream in case
a virtual audio driver misbehaves while its render side is paused.
|
||
|
|
b385f0a031 |
Merge pull request 'Rust edition 2024: the whole tree, with the env-mutation class made visible (WP20)' (#177) from worktree-edition-2024 into main
ci / rust (push) Failing after 56s
apple / swift (push) Successful in 1m36s
ci / web (push) Successful in 1m9s
ci / docs-site (push) Successful in 1m15s
deb / build-publish-client-arm64 (push) Failing after 2m11s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 15s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 18s
windows-msix / package (x64, , x86_64-pc-windows-msvc, C:\t) (push) Failing after 2m28s
apple / screenshots (push) Successful in 5m59s
ci / rust-arm64 (push) Failing after 8m18s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 1m5s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3m43s
windows-drivers / probe-and-proto (push) Successful in 23s
deb / build-publish-host (push) Successful in 7m58s
decky / build-publish (push) Successful in 36s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 11s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 7s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 8s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
windows-drivers / driver-build (push) Successful in 1m50s
arch / build-publish (push) Failing after 11m24s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 51s
ci / bun-nix (push) Failing after 10m37s
deb / build-publish (push) Failing after 3m38s
flatpak / build-publish (push) Failing after 8m49s
android / android (push) Successful in 16m29s
docker / builders-arm64cross (push) Successful in 9s
docker / deploy-docs (push) Successful in 35s
release / apple (push) Successful in 10m11s
nix / flake (push) Successful in 15m25s
windows-host / package (push) Successful in 13m20s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 34s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Failing after 2m16s
windows / build (aarch64-pc-windows-msvc) (push) Failing after 56s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 20m13s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m15s
Reviewed-on: #177 |
||
|
|
c68e0be688 |
Merge remote-tracking branch 'origin/main' into worktree-edition-2024
ci / bun-nix (pull_request) Successful in 24s
windows-drivers / probe-and-proto (pull_request) Successful in 34s
ci / docs-site (pull_request) Successful in 1m23s
apple / swift (pull_request) Successful in 1m45s
apple / screenshots (pull_request) Skipped
ci / web (pull_request) Successful in 3m4s
windows-drivers / driver-build (pull_request) Successful in 2m23s
ci / rust (pull_request) Failing after 4m6s
ci / rust-arm64 (pull_request) Failing after 5m5s
windows / build (x86_64-pc-windows-msvc) (pull_request) Failing after 1m54s
nix / flake (pull_request) Successful in 13m36s
windows / build (aarch64-pc-windows-msvc) (pull_request) Failing after 54s
android / android (pull_request) Successful in 15m6s
|
||
|
|
71c1970b93 |
fix(client/console): a stats tier picked between streams reached nothing until a restart
ci / bun-nix (pull_request) Successful in 31s
apple / swift (pull_request) Successful in 1m43s
apple / screenshots (pull_request) Skipped
ci / rust (pull_request) Failing after 59s
android / android (pull_request) Successful in 3m59s
ci / web (pull_request) Successful in 4m18s
ci / rust-arm64 (pull_request) Successful in 5m5s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 4m17s
ci / docs-site (pull_request) Successful in 4m32s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 3m15s
Field report: "no matter what I select the stats overlay is stuck showing as detailed" on the Deck, cured by restarting the client app. The console (Gaming Mode, and therefore Decky) builds its window and its run loop ONCE and streams every session through them, and the loop took its stats tier from the settings snapshot read at process start. Its own settings screen writes the chosen tier to the file and redraws its row, so the choice looked taken while every stream kept the tier the process happened to start on — Detailed for anyone who had been on Detailed. Only a restart re-read it. The desktop shells were never affected: they spawn a session process per stream, which resolves settings for itself. The tier now rides `SessionParams` per launch, so browse mode adopts what THIS launch resolved and the start-of-process value only seeds the loop until the first stream. Two things fall out of resolving per launch rather than per process: a profile bound to a host can finally move the tier in console mode (part of the documented P4 gap), and the adoption sits in the `Start` arm rather than `StreamState::new`, so the codec-fallback retry can't snap the overlay back and undo an in-stream cycle. The `--stats` rule (a floor that lifts Off to Normal and demotes nothing) was written out three times and is now one tested helper. The rest of the console's latched presentation tier — touch and mouse model, shortcut inhibit, match-window, render scale — is unchanged and still needs the models rebuilt per launch. Gate: clippy --all-targets -D warnings, plain build, and tests for pf-client-core, pf-presenter and punktfunk-client-session, all green in pf-lxcheck2 (linux/amd64); clippy proven non-vacuous by touching the four edited files. cargo fmt --all --check clean. |
||
|
|
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
|
||
|
|
28b6633058 |
Merge pull request 'feat(display): edid_lock policy axis — pin AMD connector EDID emulation while streaming' (#176) from worktree-edid-lock-toggle into main
audit / bun-audit (sdk) (push) Successful in 25s
audit / bun-audit (plugin-kit) (push) Successful in 26s
audit / bun-audit (web) (push) Successful in 23s
audit / docs-site-audit (push) Successful in 22s
audit / pnpm-audit (push) Successful in 13s
apple / swift (push) Successful in 1m36s
audit / license-gate (push) Successful in 4m52s
ci / bun-nix (push) Successful in 23s
audit / miri (push) Successful in 5m50s
apple / screenshots (push) Successful in 5m56s
ci / web (push) Successful in 3m49s
ci / rust-arm64 (push) Successful in 4m6s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 16s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 23s
ci / docs-site (push) Successful in 3m50s
audit / c-abi-asan (push) Successful in 5m55s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 16s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 15s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 15s
deb / build-publish-client-arm64 (push) Successful in 2m56s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 2m38s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 2m54s
audit / cargo-audit (push) Successful in 36s
deb / build-publish-host (push) Successful in 8m38s
release / apple (push) Successful in 9m55s
deb / build-publish (push) Successful in 6m27s
docker / deploy-docs (push) Successful in 37s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Failing after 14m53s
docker / builders-arm64cross (push) Skipped
android / android (push) Successful in 15m34s
ci / rust (push) Failing after 14m34s
flatpak / build-publish (push) Successful in 20m11s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 20m1s
nix / flake (push) Failing after 20m46s
arch / build-publish (push) Successful in 21m46s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m45s
windows-host / package (push) Successful in 38m25s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 29s
windows-msix / package (x64, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 5m20s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m17s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m45s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 4m20s
|
||
|
|
c58217e403 |
Merge pull request 'fix(client): stop the double-arm race re-freezing RFI-healed streams' (#174) from worktree-gate-double-arm-fix into main
android / android (push) Canceled after 0s
apple / swift (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
arch / build-publish (push) Canceled after 0s
audit / cargo-audit (push) Canceled after 0s
audit / bun-audit (plugin-kit) (push) Canceled after 0s
audit / bun-audit (sdk) (push) Canceled after 0s
audit / bun-audit (web) (push) Canceled after 0s
audit / docs-site-audit (push) Canceled after 0s
audit / pnpm-audit (push) Canceled after 0s
audit / license-gate (push) Canceled after 0s
audit / miri (push) Canceled after 0s
audit / c-abi-asan (push) Canceled after 0s
ci / rust (push) Canceled after 0s
ci / rust-arm64 (push) Canceled after 0s
ci / web (push) Canceled after 0s
ci / docs-site (push) Canceled after 0s
ci / bun-nix (push) Canceled after 0s
deb / build-publish (push) Canceled after 0s
deb / build-publish-host (push) Canceled after 0s
deb / build-publish-client-arm64 (push) Canceled after 0s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 0s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 0s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Canceled after 0s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
flatpak / build-publish (push) Canceled after 0s
nix / flake (push) Canceled after 0s
release / apple (push) Canceled after 0s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 0s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 0s
windows-host / package (push) Canceled after 0s
windows-host / canary-manifest (push) Canceled after 0s
windows-host / winget-source (push) Canceled after 0s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Canceled after 0s
windows-msix / package (x64, , x86_64-pc-windows-msvc, C:\t) (push) Canceled after 0s
windows / build (aarch64-pc-windows-msvc) (push) Canceled after 0s
windows / build (x86_64-pc-windows-msvc) (push) Canceled after 0s
|
||
|
|
23f9b1130e |
Merge pull request 'Black-stream fixes: CCD restore snapshot pruning, iOS silent-black recovery, devnode journal retention' (#175) from worktree-black-stream-fixes into main
android / android (push) Canceled after 0s
apple / swift (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
arch / build-publish (push) Canceled after 0s
ci / rust (push) Canceled after 0s
ci / rust-arm64 (push) Canceled after 0s
ci / web (push) Canceled after 0s
ci / docs-site (push) Canceled after 0s
ci / bun-nix (push) Canceled after 0s
deb / build-publish (push) Canceled after 0s
deb / build-publish-host (push) Canceled after 0s
deb / build-publish-client-arm64 (push) Canceled after 0s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 0s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 0s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Canceled after 0s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
release / apple (push) Canceled after 24s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 0s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 0s
windows-host / package (push) Canceled after 0s
windows-host / canary-manifest (push) Canceled after 0s
windows-host / winget-source (push) Canceled after 0s
|
||
|
|
6a506a8fa9 |
fix(vdisplay/driver,pf-frame): no punktfunk process holds REALTIME GPU priority by default
windows-drivers / probe-and-proto (pull_request) Successful in 30s
ci / bun-nix (pull_request) Successful in 56s
ci / docs-site (pull_request) Successful in 1m16s
ci / web (pull_request) Successful in 1m17s
ci / rust-arm64 (pull_request) Successful in 1m31s
windows-drivers / driver-build (pull_request) Successful in 1m47s
android / android (pull_request) Successful in 4m40s
ci / rust (pull_request) Successful in 9m54s
apple / swift (pull_request) Failing after 13m27s
apple / screenshots (pull_request) Skipped
The RX 9070 XT field A/B (2026-08-11/12 logs) convicted BOTH of our REALTIME GPU-scheduling levers of generating the metronomic capture-stall class the stall program has chased for weeks — compose-silence holes of 150-800 ms in which ETW shows NO process presenting while the GPU stays responsive: - the vdisplay driver's IddCxSetRealtimeGPUPriority raise beat at ~1.75-1.78 s (PFVD_NO_RT_GPU=1 alone removed that metronome: ~0.35 stalls/s metronomic -> 10 sparse aperiodic over 3.9 min); - the host auto-gate's HIGH->REALTIME upgrade (pf-frame dxgi.rs, T2.3) beat at ~3.58 s in the AV1 sessions where it promoted (vram_pct=1, 12:59:26); pinning PUNKTFUNK_GPU_PRIORITY_CLASS=high removed that residual too (13:45 session: zero metronomic, stall rate at the clean-run baseline). Neither period matches any punktfunk clock: the full periodic-actor census (driver: event-paced drain + 16 ms E_PENDING wait, 33 ms cursor poll, 3 s watchdog reap; host: 250 ms descriptor poll, 5/50/100 ms probes + ~2 s scanline retarget, 2 s VRAM gate, 2 s exclusive re-assert, 3.33 s pinger, 1 s stats, ~1 Hz phase-lock, fps/2 LTR marks) has nothing in the 1.69-2.29 s band, and every host-side actor ran unchanged in the A/B that killed the fast metronome. The periodicity is emergent from holding an unreachable-priority queue against the WDDM scheduler on this AMD family (the period even differs by which of our processes holds REALTIME); it is not a punktfunk cadence being amplified, so there is nothing punktfunk-periodic to fix - the fix is to stop holding REALTIME by default, which is also canonical parity (no shipping IDD raises it, and HIGH was the class that delivered the original Sunshine-parity encode win). - Driver: PFVD_NO_RT_GPU (default-ON, opt-OUT) becomes the PFVD_RT_GPU ladder, default OFF on every vendor: unset = no raise (canonical IDD behavior); =thread = SetGPUThreadPriority(+7), a graduated in-band middle rung for field A/B (not default: unmeasured here, and the host measured the same call as "no help" for its own starvation case); anything else = the old REALTIME DDI. PFVD_NO_RT_GPU stays recognized and WINS over the opt-in, so the field boxes that carry it through the default-ON era keep meaning OFF. Both directions remain A/B-able without a rebuild (machine env + device restart). The CPU half of the original branch-2 hardening (MMCSS / TIME_CRITICAL) is untouched - it addressed the delivery holes that were actually observed. - Host: PUNKTFUNK_GPU_PRIORITY_CLASS default auto -> high. `auto` (the gated REALTIME upgrade) stays available as an explicit opt-in, `realtime` still pins; unrecognized values now land on the HIGH default instead of silently opting into the gate - a typo must not buy the hazard. The VRAM/HAGS gate machinery is unchanged for `auto`; it guards the NVENC-hang hazard but cannot see this one. - stall.rs: the no-OS-event METRONOMIC warning now carries rt_gpu_driver / rt_gpu_host fields (the machine-env state of both levers) and names clearing them as the FIRST cure, ahead of the display-hardware suspects - a field log self-answers the triage question this program just spent a week on. No console policy axis for the driver knob: the lever is default-safe now, the driver reads config at WUDFHost scope where machine env already matches the device-restart lifecycle, and a policy axis would need pf-driver-proto churn (or a device-key registry write) for an experimental lever that only exists to be A/B-ed. If the `thread` rung ever proves out as a default-worthy raise, that is the moment to revisit. |
||
|
|
712ee935d6 |
fix(win-display): a failed devnode re-enable must keep its crash-journal entry
ci / rust-arm64 (pull_request) Successful in 1m36s
ci / docs-site (pull_request) Successful in 1m22s
apple / swift (pull_request) Successful in 1m43s
apple / screenshots (pull_request) Skipped
ci / bun-nix (pull_request) Successful in 3m34s
ci / web (pull_request) Successful in 4m2s
android / android (pull_request) Successful in 4m13s
ci / rust (pull_request) Successful in 5m56s
enable_instances cleared every requested id from the pnp-disabled-monitors journal unconditionally — a mid-life re-enable failure erased its own crash-recovery entry, so neither the running host nor the next start would ever retry, leaving the operator's monitor disabled (invisible to Windows and every display listing) until a manual Device Manager fix. Field 2026-08-12 (Android-client host running the pnp_disable_monitors axis): displays gone from the client after 2-3 sessions, only a full host restart bringing them back — the restart path only works while the journal still has the entry. Only successfully re-enabled ids clear now; failures stay journaled (and are logged with the consequence) so startup_recover retries them. |
||
|
|
d6b9862f1e |
fix(win-display): prune unplugged targets from the CCD restore snapshot
A monitor unplugged mid-session left the teardown replaying a snapshot whose paths reference an absent target — SetDisplayConfig rejects the WHOLE array with 0x57 ERROR_INVALID_PARAMETER, nothing restores, the desk stays dark, and the next session snapshots the wreckage (the poisoned-snapshot chain; field 2026-08-12: rc=0x57 across a mid-session unplug, then sessions flipping between black and working at random, incl. one that genuinely encoded black). The restore now prunes the snapshot against a live QDC_ALL_PATHS availability sweep before replaying: stale paths drop, the mode table is rebuilt with only the entries the survivors reference (an orphaned mode entry is itself an 0x57), and an empty survivor set skips the apply so the dark-desk backstop decides. Verbatim behavior is unchanged when everything is still attached or when the availability query itself fails. |
||
|
|
f4e39a442b |
feat(display): edid_lock policy axis — pin AMD connector EDID emulation while streaming
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m45s
apple / swift (pull_request) Successful in 1m45s
apple / screenshots (pull_request) Skipped
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m14s
ci / web (pull_request) Successful in 1m27s
ci / bun-nix (pull_request) Successful in 34s
android / android (pull_request) Successful in 5m52s
ci / rust (pull_request) Failing after 7m55s
ci / docs-site (pull_request) Successful in 7m54s
ci / rust-arm64 (pull_request) Successful in 9m13s
nix / flake (pull_request) Successful in 14m26s
Productizes the adl-emul probe (the prior commit) as the display-policy axis its PR promised: the ADL FFI moves to pf_win_display::adl_emul (one surface shared by the probe tool and the host, so a reporter's probe and the console's toggle exercise byte-identical driver calls), and an EXPERIMENTAL edid_lock axis joins ddc_power_off/pnp_disable_monitors — orthogonal to presets, off by default. At the first Exclusive isolate the host pins each occupied AMD connector's live EDID + ADL_EMUL_MODE_ALWAYS (the software HPD dummy) BEFORE the physicals deactivate; last-member teardown unlocks. Pinned emulation outlives the process, so a crash journal (edid-lock-active.json) unlocks on the next host start, mirroring the pnp_disable_monitors recovery. Inert without an AMD driver. The console shows the toggle ONLY when the GPU inventory lists an AMD adapter — the lever exists nowhere else, and a toggle that can never act is the 'saved and then did nothing' trap the enforced-axes list exists to prevent. |
||
|
|
2a62fe7857 |
fix(client): stop the double-arm race re-freezing RFI-healed streams
ci / bun-nix (pull_request) Successful in 30s
ci / docs-site (pull_request) Successful in 1m22s
ci / rust-arm64 (pull_request) Successful in 1m42s
ci / web (pull_request) Successful in 1m51s
apple / swift (pull_request) Successful in 1m50s
apple / screenshots (pull_request) Skipped
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m28s
android / android (pull_request) Successful in 6m39s
windows / build (x86_64-pc-windows-msvc) (pull_request) Failing after 13m7s
ci / rust (pull_request) Successful in 14m4s
Every unrecoverable loss armed the client's freeze gate twice: instantly at frame-index-gap detection (which fires the RFI), and ~120 ms later when the reassembler ages the lost frame into frames_dropped and poll() re-armed unconditionally. An LTR-RFI recovery anchor lands in ~60 ms — between the two signals — so the stale climb re-froze a bit-exact-healed stream, the host swallowed the re-ask as an RFI echo, and the picture stayed frozen until the overdue backstop extracted a full IDR: the field 'H265 freezes on every loss, AV1 fine' signature on AMD hosts (AMF is the only LTR-RFI backend; the slower IDR path usually lands after the climb and dodged the race). The gap-arm now pre-credits the expected climb (ReanchorGate::arm_expecting_drops; credit expires after DROP_CREDIT_WINDOW so a straggler-filled gap can't mask a later real loss), and poll() consumes credited climbs instead of re-arming. Plumbed through every embedder: pf-client-core's session pump, Android's sync/async loops (note_frame_index now returns the gap width), and the Swift client via new ABI exports punktfunk_connection_note_frame_index_ex + punktfunk_reanchor_gate_arm_expecting_drops (additive; the bool ABI stays). |
||
|
|
5002849737 |
feat(pf-encode): WP4 — AvFrame/AvSwsContext RAII across all three libav backends
ci / bun-nix (pull_request) Successful in 31s
ci / docs-site (pull_request) Successful in 1m17s
ci / rust-arm64 (pull_request) Successful in 1m45s
apple / swift (pull_request) Successful in 1m49s
apple / screenshots (pull_request) Skipped
ci / web (pull_request) Successful in 2m7s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m56s
android / android (pull_request) Successful in 6m21s
ci / rust (pull_request) Successful in 7m42s
windows / build (x86_64-pc-windows-msvc) (pull_request) Failing after 12m28s
Two newtypes beside AvBuffer/AvFilterGraph (same house shape: alloc/from_raw rejects the allocator's null once, as_ptr lends, Drop frees, no Clone; NonNull inside so the Options get a niche). 8 av_frame_alloc + 3 sws_getContext sites converted; all 22 hand-placed av_frame_free and 5 sws_freeContext calls are gone, and the three hand-written Drop impls (CpuInner, SystemInner, NvencEncoder) with them. The live defect this closes: ZeroCopyInner::submit (ffmpeg_win) leaked the frame AND one pooled hwframe surface on each of three ? exits between the pool pull and the send — under a SAFETY comment asserting no leak — and with POOL=8, eight such failures starved the pool and wedged the encoder with no error naming the cause. Every exit now returns the surface. Drop-order care (the hidden cost the survey flagged): NvencEncoder's sws_csc moved to field #1 (its hand-Drop freed it before all fields; this path runs on every stall-watchdog recovery via *self = fresh); CpuInner's nv12/sws declaration order flipped to match its hand-Drop; SystemInner's already agreed. Pinned by FIELD ORDER comments, not offset_of asserts — the survey's assert suggestion is the wrong tool: offset_of measures repr(Rust) memory layout, which the compiler may reorder independently of the declaration order that drop order actually follows. The dmabuf path keeps its early descriptor release via an explicit drop() at the exact point the hand-written free sat. Gates: .25 clippy -D warnings + tests green (nvenc,vulkan-encode,pyrowave); .133 check --all-targets + clippy --release -D warnings + 80 tests green (nvenc,amf-qsv,qsv; test step needs ffmpeg\bin on Path — 0xC0000135 otherwise). Owed on hardware: the #[ignore]d alloc/drop cycles on .136/.116/.173/.47 and the pool-exhaustion assertion (9th submit succeeds after 8 forced failures). |
||
|
|
9a59504ba4 |
fix(punktfunk-core): validate InputKind before forming &InputEvent in the C ABI
abi.rs's two send-input entry points built &InputEvent straight out of caller memory with ev.as_ref(); InputKind is repr(u8) with 16 valid discriminants, so a C embedder writing ev->kind = 42 was immediate UB the moment the reference formed — in a file whose stated principle is that failures become status codes. New read_input_event() checks null, reads the tag as a raw byte, validates through the same InputKind::from_u8 the wire path uses, and only then forms the reference; bad tags return InvalidArg. Every other field is a plain integer, valid for any pattern. Test stages the event in MaybeUninit storage so the test itself never holds a reference to the invalid value. 380 lib tests + the C harness round-trip + clippy -D warnings green on .25; header regenerated. |
||
|
|
e8c306b9c0 |
fix(punktfunk-host): WP3c/3d — align the TOKEN_USER buffer, make EqualSid fail closed
3c: forming &TOKEN_USER (align 8) out of a bare [u8; 256] (align 1) was UB by the validity rule whenever the stack slot landed misaligned — shipped codegen happened to 8-align it, which is luck, not a contract. Fixed with a repr(align(8)) wrapper that keeps the buffer at 256 BYTES; the comment records why [u64; 32] is the wrong shape (len() would silently become 32 and misclassify every hand-run host as SYSTEM via ERROR_INSUFFICIENT_BUFFER, invisibly to a SYSTEM-side test). Length arg now size_of_val. 3d: EqualSid().is_ok() read BOTH 'SIDs differ' and 'EqualSid failed' as Err, so a genuine failure yielded 'not SYSTEM' — the fail-OPEN direction, contradicting the documented fail-closed contract. Now split three ways on the last-error code, with SetLastError(0) cleared first so a stale value cannot misclassify. Gate: cargo check -p punktfunk-host + cargo clippy --release -D warnings both green on .133 (real MSVC, fresh extraction, sentinel-verified). |
||
|
|
c3b57438e1 |
chore(ci): c-abi-asan job in audit.yml — the harness under ASAN+LSAN, weekly + on demand
Same shape as the miri job (dated nightly, own san- cache prefixes, non-blocking day one via a step-level ::warning::, a proved-it-ran grep). run.sh gains PF_SAN_TOOLCHAIN so CI can pin its dated nightly — bare +nightly would ask for the rolling channel the job never installs. Both the pinned and vanilla paths re-verified green on .25. |
||
|
|
e20b614059 |
chore(safety): PF_SAN sanitizer gate for the C ABI harness
PF_SAN=address builds the punktfunk-core staticlib on nightly with -Zsanitizer/-Zbuild-std and the C harness with clang -fsanitize, so ASAN instruments both sides of the boundary at once and LSAN (detect_leaks=1) becomes the first automated check on abi.rs's Box::into_raw/from_raw leak contract. Verified on the .25 box: green run passes byte-exact; deleting one punktfunk_session_free() in the harness makes LSAN report the 308 Rust-side allocations behind the handle and the script exit 1. The harness binary moves from mktemp to target/ — a debug+ASAN static binary can exceed a tmpfs /tmp (it did, on .25's 3.6G tmpfs). |
||
|
|
d67ab9ede4 |
chore(safety): two .133 gate findings — cfg the abi lock helper, re-anchor a layer proof
ci / bun-nix (pull_request) Successful in 40s
ci / web (pull_request) Successful in 1m24s
ci / docs-site (pull_request) Successful in 1m30s
android / android (pull_request) Canceled after 1m45s
apple / swift (pull_request) Canceled after 1m41s
apple / screenshots (pull_request) Canceled after 0s
ci / rust (pull_request) Canceled after 1m43s
ci / rust-arm64 (pull_request) Canceled after 1m43s
nix / flake (pull_request) Canceled after 1m29s
windows-drivers / probe-and-proto (pull_request) Canceled after 0s
windows-drivers / driver-build (pull_request) Canceled after 1m25s
windows / build (aarch64-pc-windows-msvc) (pull_request) Canceled after 0s
windows / build (x86_64-pc-windows-msvc) (pull_request) Canceled after 0s
The tray leg builds punktfunk-core with default-features off, where lock_recover's only callers (the quic-gated punktfunk_connection_* entry points) do not exist — dead code under -D warnings. The helper takes the same feature gate. In pf-vkhdr-layer, rustfmt had reflowed destroy_surface's lookup into a multiline closure, leaving the SAFETY comment outside the closure that contains its unsafe block — the box's clippy rightly stopped accepting the adjacency. The comment moves inside, directly above the block. |
||
|
|
abec2a1457 |
chore(safety): nvenc_core — plain union-arm writes are safe by language rule
The .25 gate corrected the carve-out: rustc flags `unsafe { u.arm.field = x }`
as unused_unsafe — plain assignment through a union projection is safe
(writing an arm cannot itself be UB; the hazard is the mismatched READ).
The 11 plain writes go back to bare statements under their codec matches.
What stays in per-op unsafe blocks with arm-guard proofs is the real unsafe
surface: union reads, borrows, and the bindgen bitfield-setter calls — which
is exactly the surface the shipped 4:4:4 bug lived on (set_chromaFormatIDC
stamped under a wrong codec).
|
||
|
|
5f097d530d |
chore(safety): exempt the two bindings-only sys crates from the hoisted deny
Linux fallout from the hoist the mac could not see: bindgen emits unsafe blocks (layout tests/accessors) into OUT_DIR, where nobody hand-writes SAFETY proofs — pyrowave-sys failed clippy on .25 with 17 of them, and libvpl-sys would do the same on the Windows leg. Both crates are bindings-only by charter (the safe wrapper lives with the consumer), so the allow is crate-wide with the rationale at the crate root; the hand-written link-sanity tests keep their proofs by convention. |
||
|
|
2bfd1cd2d5 |
chore(safety): three unsafe-hygiene grep gates, blocking in ci.yml (WP2c gates)
scripts/ci/check-unsafe-hygiene.sh — textual gates for three classes no lint covers: A. unsafe fn markers carrying no contract. unsafe_op_in_unsafe_fn forces real ops into blocks, so an unsafe fn with no `unsafe` in its body is a marker with no contract ( |
||
|
|
dfebb9dfbb |
chore(safety): hoist the unsafe lints into the workspace tables (WP2c hoist)
undocumented_unsafe_blocks joins unsafe_op_in_unsafe_fn in
[workspace.lints], and the ~100 scattered per-file #![deny(...)] attributes
(85 files) are deleted — a new crate, or a new module in an old one, is now
covered on creation rather than on remembering. The per-file form is how
pf-vkhdr-layer, wdk-probe and half of pf-clipboard stayed uncovered.
There are THREE workspaces, so the claim is made three times: the main
Cargo.toml, packaging/windows/drivers (workspace table + [lints]
workspace = true in all seven members), and packaging/windows/pf-vkhdr-layer
(its [lints] table, previous commit). pf-update now opts into workspace
lints; the two vendored member snapshots (cros-codecs, usbip-sim) stay out
deliberately and now both say so.
Newly-covered fallout was two link-sanity tests (pyrowave-sys, libvpl-sys)
— proofs written. Stale prose that claimed the workspace held
unsafe_op_in_unsafe_fn at "warn" (it has been deny) or pointed at the
deleted attributes is corrected.
nvenc_core.rs is carved OUT of the unsafe_op_in_unsafe_fn fence: its
exemption rationale ("raw entry-table calls almost line for line") was
false — the file makes zero FFI calls. Its unsafe surface is C-union writes
whose soundness hangs on which codec arm is active, and its own 4:4:4 note
records the shipped bug (hevcConfig bytes stamped onto an AV1 config) that
per-operation blocks make visible. It now runs the strictest discipline in
the crate: clippy::multiple_unsafe_ops_per_block at deny, one union access
per block, each naming its codec guard.
Verified here: cargo fmt clean in all three workspaces; native clippy
-D warnings clean for everything that compiles on macOS (the three
pre-existing mac-native failures — pf-client-core wol.rs, pf-encode
dead-code/closure-call, probe mic_burst — reproduce on the clean tree).
Linux/Windows legs ride the .25/.133 gate.
|
||
|
|
23fa03b051 |
chore(safety): close the three crate-level lint gaps (WP2b)
pf-vkhdr-layer — the sharpest gap: an implicit layer injected into every Vulkan game process, 32 unsafe usages, zero SAFETY comments, own workspace so no lint table reached it, and an explicit missing_safety_doc allow. Now: a [lints] table (unsafe_op_in_unsafe_fn + clippy::undocumented_unsafe_blocks, both deny), the allow removed, every unsafe operation in an explicit block with a real proof (loader layer protocol / Vulkan valid-usage), # Safety docs on the contract-carrying fns, const layout asserts for the SurfaceFormat2Raw mirror, the five helpers with no caller-facing contract demoted to safe fns, and the two redundant `unsafe impl Send` deleted (fn pointers and vk handles are Send intrinsically — the type-check proves it). wdk-probe — 21 unsafe blocks, 12 proofs: the 9 missing SAFETY comments are written (the iddcx_rt.rs DDI slot-dispatch ones are about table population and PFN/index pairing, not pattern fill), the sibling denies added at the crate root, missing_safety_doc allow dropped, # Safety on DriverEntry, and the crate joins windows-drivers.yml's clippy list — it was the only driver crate not in it. pf-clipboard — the undocumented_unsafe_blocks deny moves from host/windows.rs to the crate root so host/wayland.rs (4 blocks), host/mutter.rs (2) and any future backend under host/ are covered on creation. All existing blocks already carry proofs; free today, structural tomorrow. Verified here: pf-vkhdr-layer cargo fmt --check + clippy --release -D warnings at x86_64-pc-windows-msvc. wdk-probe and pf-clipboard compile checks need the WDK/Linux boxes and ride the .133/.25 gate. |
||
|
|
bc70a58fb1 |
Merge main into chore/rust-safety-programme
windows-drivers / probe-and-proto (pull_request) Successful in 22s
ci / bun-nix (pull_request) Successful in 36s
ci / web (pull_request) Successful in 1m17s
apple / swift (pull_request) Successful in 1m39s
apple / screenshots (pull_request) Skipped
windows-drivers / driver-build (pull_request) Successful in 1m58s
ci / rust-arm64 (pull_request) Successful in 3m18s
ci / docs-site (pull_request) Successful in 3m55s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m29s
android / android (pull_request) Successful in 4m46s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m30s
ci / rust (pull_request) Failing after 10m11s
nix / flake (pull_request) Successful in 15m6s
Two conflicts: the test-module import list in gamescope.rs (union — the branch's takeover-state tests and main's WSI opt-out tests both stay), and next_frame_timed_out in pf-capture, where the branch still carried the pre-#168 else-if chain — resolved to main's match-based refactor, which already embeds the same arm semantics plus the provisional-budget latch gate. |
||
|
|
ce25aca7bd |
Merge pull request 'Two black screens from the .41 field session — a NO_FOCUS window stole the composite, and one truncated timeout downgraded the host forever' (#168) from worktree-blackscreen-fixes into main
apple / swift (push) Successful in 1m42s
ci / web (push) Successful in 1m14s
ci / docs-site (push) Successful in 1m20s
ci / bun-nix (push) Successful in 19s
deb / build-publish-client-arm64 (push) Successful in 1m46s
deb / build-publish (push) Successful in 5m9s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 6s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 8s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 10s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
apple / screenshots (push) Canceled after 1m18s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 42s
deb / build-publish-host (push) Successful in 7m18s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m28s
arch / build-publish (push) Successful in 11m28s
android / android (push) Canceled after 6m31s
ci / rust (push) Canceled after 2m21s
ci / rust-arm64 (push) Canceled after 1m13s
docker / builders-arm64cross (push) Successful in 11s
docker / deploy-docs (push) Canceled after 5s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 4m10s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 3m2s
windows-host / package (push) Canceled after 0s
windows-host / canary-manifest (push) Canceled after 0s
windows-host / winget-source (push) Canceled after 0s
|
||
|
|
fcf4c9fd63 |
fix(mgmt): unpair now revokes a LIVE session on both planes
ci / bun-nix (pull_request) Successful in 37s
ci / web (pull_request) Successful in 1m38s
apple / swift (pull_request) Successful in 1m50s
apple / screenshots (pull_request) Skipped
ci / rust-arm64 (pull_request) Successful in 2m38s
ci / docs-site (pull_request) Successful in 2m32s
windows-drivers / driver-build (pull_request) Successful in 1m50s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m29s
android / android (pull_request) Successful in 6m31s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m54s
ci / rust (pull_request) Failing after 10m42s
nix / flake (pull_request) Canceled after 8m26s
windows-drivers / probe-and-proto (pull_request) Canceled after 0s
An unpair removed the certificate but left the revoked client's running
session streaming until the client chose to leave. Now it is a complete
revocation:
- GameStream: when the removed certificate owns the active launch, the
session is quit_session'd — the ENet control thread's ended-session arm
gives the client the standard TERMINATION+disconnect. (An owner-less
launch cannot be attributed and is left to the WP0 port teardown when the
last pairing goes.) The endpoint docstring's long-standing caveat
('removes the client from the listing without severing its ability to
reconnect') is retired: TLS handshakes complete by design, authorization
is per-request, and a live session no longer survives its own revocation.
- Native: session_status::stop_by_fingerprint signals the unpaired
client's live session(s) to tear down deliberately (quit+stop), matched
by the registry's client label — the fingerprint's 12-hex-char prefix for
every pairable client; anonymous/TOFU sessions carry IP labels and are
never touched (they have no pairing to revoke).
(The unpair-didn't-PERSIST half of 'unpairing was broken' was already fixed
in
|
||
|
|
022ede651f |
fix(pf-capture): the truncated first attempt no longer latches the sticky downgrades
The pipeline retry loop deliberately shortens its first attempt's first-frame wait to 2.5s so a stream bound during a gamescope re-init fails over quickly. But the portal capturer's timeout diagnosis treated EVERY expiry as a verdict: it latched whichever offer it implicated — HDR capture off for the source, the raw-dmabuf offer off, the EGL→CUDA offer off — process-wide and permanently, when the attempt was truncated by design and a gamescope cold start routinely delivers nothing inside that window while accepting every offer a few seconds later (observed on .41: pid 1962 hit the expiry at connect and every later session in that process ran silently degraded). This is bug #6 from the pf-capture sweep, verified then and unfixed until now. The truncated attempt is now declared PROVISIONAL end to end: a new `Capturer::next_frame_within_provisional` (default: delegates) lets the retry loop say "this budget is the schedule, not a verdict", and the portal capturer's timeout classification — split out as the pure `classify_first_frame_timeout` + `timeout_convicts`, with tests — names the same suspect in the error text but latches nothing unless the expired budget was full-length. |
||
|
|
9c6e06d3b9 |
feat(host): GameStream is now a cargo feature — WP19, compile-time isolation
A new 'gamestream' feature (default ON — every stock package is behaviorally identical, and GameStream stays runtime-opt-in via --gamestream / PUNKTFUNK_GAMESTREAM) gates the whole Moonlight-protocol surface: control (the ENet plane), rtsp, nvhttp, pairing, serverinfo, the _nvstream mDNS advert, the compat media path (stream/video/audio), pen/gamepad/input decode, apps, crypto, cert (the RSA identity), and tls's Moonlight-client-cert leniency. AppState keeps the shared vocabulary unconditional and cfg-gates the Moonlight-only fields; the mgmt API's PIN endpoints (routes, handlers, OpenAPI entries, lane classifications, tests) exist only under the feature. Building --no-default-features --features pyrowave yields the hardened NATIVE-ONLY host: no rusty_enet (the c2rust-transpiled C ENet stack, 158 unsafe sites) and no rsa (the identity split's legacy fallback became a pem-only read — rustls/ring serves an existing RSA cert without the crate — so the accepted Marvin advisory no longer applies to native-only builds). Both claims are ASSERTED, not assumed: a new CI leg keeps the native-only flavor clippy-clean and fails if cargo tree finds either crate in its graph. serve --gamestream (or the env knob) against such a binary refuses to start with a clear error rather than serving less than the operator configured. En route: the logs-paging test assumed a quiet process-global log ring between its cursors and raced other tests' legitimate log lines (the identity tests added new emitters) — it now asserts on its own markers within the page. Gates: Linux amd64 — BOTH flavors clippy --all-targets -D warnings clean; default tests identity 3/3, mgmt 37/37, gamestream 59/59; native-only tests identity 3/3, mgmt 35/35, residue 4/4; rusty_enet+rsa absent native-only, present default. .133 Windows — both flavors clippy clean (clean-first, sentinel-checked), tree claims hold, and the WP0 port-lifecycle functional gate PASSES on the default build. |
||
|
|
f5fa9649b7 |
fix(console): a pinned card's library launches with that card's profile
Pinning a profile onto a host gives it its own card on the console home, and pressing A on that card has always connected with the pinned profile as the one-off the resolver prefers over the host's binding. Y on the same card opens a library — it is paired and saved, which is the only thing the hint bar asks — and every title launched off that shelf went out with no profile at all, so the host's default binding won. Connecting straight from the card honoured the profile; going through its library did not, which is the shape a user reads as "the pin works until I pick a game". The screen was the leak: LibraryScreen copied the row's address, port, fingerprint and mgmt port, and dropped `pin` — so its launch had nothing to send and hardcoded `profile: None`, under a comment asserting that game launches follow the binding. They should follow the card: a launch off a pinned card's shelf is that card's connect with a title attached. The screen now carries the row's pin and sends its id as the one-off. It also says so, in the card's own `host · profile` shape: the shelf's title names the profile, and so does the connecting card, so which of a host's cards you came in on is legible from the screen rather than inferred from the tile you pressed two screens ago. Off the host's primary tile there is no pin and nothing changes — `None`, and the binding decides. Console-only. The Apple and Android consoles keep Y off pinned cards outright (`hasLibrary: profile == nil`), the GTK client clones the card's whole ConnectRequest — profile included — into its library, and the Windows client offers "Browse library" from the primary tile only. The console's own copy-link and wake-then-connect paths already carried the pin. Both directions are tested, and the pinned one was checked against a reverted fix: it fails with exactly the reported symptom (left: None, right: "hdr"). |
||
|
|
e658ad726b |
feat(host): the identity split — the native planes get their own P-256 identity
One RSA-2048 identity served every plane, because Moonlight mandates RSA and the planes grew out of the GameStream host. The native punktfunk/1 QUIC plane and the management API now share a separate ECDSA P-256 identity (native-cert.pem/native-key.pem, src/identity.rs): ring-generated via rcgen (no rsa crate on the native path — the accepted Marvin advisory stops applying once WP19 gates the compat planes), real SANs (localhost, loopback, machine hostname — the legacy cert had none), and browser-compatible on purpose: Ed25519 was rejected because no mainstream browser accepts an Ed25519 server cert and /api/docs is opened in one. GameStream keeps the RSA identity untouched (Moonlight pins it; its pairing hashes bind its X.509 signature bytes). Migration is pin-preserving by construction. Clients TOFU-pin ONE leaf-DER SHA-256 for both QUIC and the mgmt/library API, so the identity is resolved ONCE in serve (the planes cannot race the first-run mint) under the rule: identity files exist → use them; else the native trust store is EMPTY → mint P-256 (fresh installs); else keep presenting the legacy RSA cert the paired clients pinned, and log the migration path (unpair all, restart, re-pair). Fingerprint pinning is algorithm-agnostic — existing shipped clients pair against P-256 hosts unchanged. Followers updated: the tray's loopback pin and the plugin SDK's mgmt CA prefer native-cert.pem → cert.pem; the Windows runner ACL grant lists both (the grant loop tolerates absent files). The in-process native tests now run on an EPHEMERAL identity — they previously read, and would newly have MINTED, identity files in the real config dir, which on a dev box that is also a live host would have switched its identity and stranded every pinned client. Gates: Linux amd64 clippy --all-targets -D warnings clean (host+tray); identity 2/2, mgmt 37/37, control 6/6, native 68/68 (C-ABI roundtrips over the ephemeral identity). .133 Windows clippy clean; the port-lifecycle gate re-run PASSES with the split live — the fresh host minted P-256 and served mgmt over it (curl 200/204), ports tracked the paired list as before. |
||
|
|
21f43d7f48 |
Merge pull request 'Games ran black in Nobara's Game Mode — the session script re-exports the WSI variable #144 cleared' (#165) from worktree-nobara-wsi-layer-clobber into main
apple / swift (push) Successful in 1m39s
ci / bun-nix (push) Successful in 55s
ci / web (push) Successful in 1m13s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 11s
ci / rust-arm64 (push) Successful in 1m43s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 12s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
ci / docs-site (push) Successful in 2m22s
deb / build-publish-client-arm64 (push) Successful in 2m8s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 1m1s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m22s
ci / rust (push) Successful in 4m57s
android / android (push) Successful in 8m21s
deb / build-publish-host (push) Successful in 10m37s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 16s
arch / build-publish (push) Successful in 9m36s
docker / builders-arm64cross (push) Successful in 6s
docker / deploy-docs (push) Failing after 2m39s
apple / screenshots (push) Successful in 6m30s
deb / build-publish (push) Successful in 7m34s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m28s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m35s
windows-host / package (push) Successful in 15m1s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 19s
Reviewed-on: #165 |
||
|
|
23d0452157 |
feat(host): GameStream opt-in on every route; the native plane is deny(unsafe_code)-enforced
The user direction after WP0: ENet exists only for Moonlight, so the native
plane must be provably safe and the compat planes a deliberate choice.
Opt-in, everywhere. Windows already was (unchecked installer task). The three
opt-out surfaces are flipped: the shipped systemd user unit (deb/RPM/Arch/
sysext) no longer bakes --gamestream into ExecStart — a new
PUNKTFUNK_GAMESTREAM=1 host.env knob (pf-host-config, OR-ed with the CLI
flag) is the packaged opt-in; the NixOS module default goes true→false, with
a module-check assertion that unset = native-only; the Deck installer takes
--gamestream to opt in (--no-gamestream kept as explicit-off). Docs
(quickstart, running-as-a-service, moonlight, ubuntu/fedora/arch firewall
sections, gnome/sway, how-it-works) rewritten to the opt-in shape; the
CHANGELOG carries the upgrade note.
Enforced-safe. punktfunk-core is #![deny(unsafe_code)] crate-wide — every
module that parses network bytes is safe Rust as a compile error, not a
census result. Carve-outs are exactly two documented classes, neither of
which interprets attacker bytes: the client surface (abi, client) and the
transport syscall-batching shims (udp/{apple,linux,windows}, qos_windows).
In punktfunk-host, the modules a secure-default host exposes — native
(cfg-not-test: its tests exercise the client C ABI on purpose),
native_pairing, mgmt, mgmt_token, discovery, wol — are #[forbid(unsafe_code)].
Gates: Linux amd64 container clippy --all-targets -D warnings clean over
core+host-config+host; core 204 tests green under the deny; mgmt 46/46,
control 6/6. .133 Windows clippy (shipped features, clean-first,
sentinel-checked) clean — covers the qos_windows/udp-windows carve-outs.
macOS + iOS cargo check green (the apple.rs carve-out compiles for real).
|
||
|
|
9e28cd101c |
fix(pf-vdisplay): the WSI opt-out never reached the games — the session script re-exports it
ci / docs-site (pull_request) Successful in 1m23s
ci / web (pull_request) Successful in 1m24s
apple / swift (pull_request) Successful in 1m46s
ci / bun-nix (pull_request) Successful in 1m25s
apple / screenshots (pull_request) Skipped
ci / rust-arm64 (pull_request) Successful in 2m27s
android / android (pull_request) Successful in 5m53s
ci / rust (pull_request) Successful in 11m10s
A Nobara 44 field box on 0.27.0 got a working Game Mode takeover — right mode, right refresh, perf overlay in the stream — and then every game it launched played sound and took input over a BLACK PICTURE, with no error anywhere. #144 disabled the distro's `VkLayer_FROG_gamescope_wsi` with `--setenv=ENABLE_GAMESCOPE_WSI=0` on the transient unit, because that layer speaks the distro gamescope's `gamescope_swapchain` protocol and ours rejects its `swapchain_feedback`, killing the client. `gamescope-session-plus` then runs an unconditional `export ENABLE_GAMESCOPE_WSI=1` near the top of the script, before it launches anything — so the opt-out lived exactly as long as it took the script to start, and gamescope, Steam and every game got the layer back. The host logged that it had disabled it, which is what made this cost a field round-trip: the claim was true of the unit and false of everything in it. Nothing else looked wrong because the casualty is Vulkan clients specifically. Steam's Big Picture UI is not one, so the session came up looking perfectly healthy right up until a game started. Send `DISABLE_GAMESCOPE_WSI=1` as well. The Vulkan loader resolves an implicit layer's two manifest knobs in a fixed order (`loader_implicit_layer_is_enabled`): `enable_environment` must equal `"1"` to switch the layer on, and `disable_environment` is consulted last — "has priority over everything else" — where the mere PRESENCE of the variable, at any value, forces it off. The session script never mentions that one, so it is the only one that survives it. Both spellings now go out through one place, so the transient unit (`launch_session`) and the box's own session drop-in (`write_session_plus_dropin`) cannot drift apart — the same shape `SessionBind` already uses for the bind. `ENABLE_GAMESCOPE_WSI=0` stays alongside for a layer built without a `disable_environment`, and because it is what an operator reads the unit for. |
||
|
|
13d5721049 |
feat(gamestream): the ENet control port exists only while a pairing does (WP0)
rusty_enet — a c2rust-style transpile of C ENet, 158 unsafe sites — parsed unauthenticated UDP on 47999 from GameStream startup, before any client had ever paired: the host's entire pre-auth-reachable unsafe surface. Pairing itself is HTTPS on nvhttp and never touches the port, so it now binds only while the paired-client list is non-empty: a Gate in control.rs reconciles the port to the list (armed only under --gamestream), pairing phase 4 brings it up before the new client can /launch, and removing the last pairing tears it down — a live client gets the same termination+disconnect farewell as a host-side session end. A never-paired host on a hostile LAN exposes no ENet. En route: the management API's unpair never called save_paired, so a restart resurrected the client — and would now have silently re-opened the port; it persists (the test now runs against a throwaway PUNKTFUNK_CONFIG_DIR so it can't clobber a real paired.json). rusty_enet is pinned =0.4.0 per the WP, left to the cargo-audit job to flag advisories against it. Gate (amd64 container): clippy --all-targets -D warnings clean; gamestream::control 6/6; mgmt::tests 37/37 incl. the regenerated api/openapi.json. On-box .133 verification (ports/pair/stream) still owed. |
||
|
|
d4366e7464 |
fix(pf-encode): the Vulkan extension probe walked a driver-filled array with no bound
ci / web (pull_request) Successful in 1m20s
apple / swift (pull_request) Successful in 1m38s
apple / screenshots (pull_request) Skipped
windows-drivers / driver-build (pull_request) Successful in 1m49s
windows-drivers / probe-and-proto (pull_request) Successful in 27s
ci / docs-site (pull_request) Successful in 1m22s
ci / rust-arm64 (pull_request) Successful in 2m50s
ci / bun-nix (pull_request) Successful in 24s
android / android (pull_request) Successful in 4m37s
ci / rust (pull_request) Successful in 10m25s
`ext_advertised` did `CStr::from_ptr(e.extension_name.as_ptr())` over a
driver-filled `[c_char; VK_MAX_EXTENSION_NAME_SIZE]`, and `vk_build.rs` open-coded
the identical call a second time. Neither had an in-Rust bound: a driver that
fills all 256 bytes without a NUL runs the walk into the NEXT
`ExtensionProperties`, and on the LAST element past the allocation.
The SAFETY comment asserted the spec guarantee ("a spec-guaranteed NUL-terminated
byte array") instead of enforcing it. That is the defect class this programme
keeps finding: a proof that restates what the other side promised rather than
checking it. Vulkan drivers are exactly the other side.
The bounded answer already shipped in the same crate — `pyrowave.rs:210` uses
ash's `extension_name_as_c_str()` for the identical job. It stops at
VK_MAX_EXTENSION_NAME_SIZE and returns Err when there is no terminator, so a
malformed entry is a non-match instead of an overrun. Both sites now route
through the one helper, which is no longer unsafe at all.
Deletes 2 unsafe operations and one duplicated walk.
⚠ The pre-existing test could not have caught this: it only ever built
well-formed, NUL-terminated entries. Added a case whose LAST element is 256
non-NUL bytes — the exact shape that used to leave the array — and a
prefix-match case, so the bound is now asserted rather than assumed.
Verified on 192.168.1.25 (Ubuntu, cargo 1.96.0 — the pinned toolchain):
cargo check -p pf-encode --features vulkan-encode,pyrowave --locked ok
cargo test -p pf-encode --features vulkan-encode,pyrowave ext_advertised
2 passed / 0 failed
cargo clippy -p pf-encode --all-targets --locked
--features vulkan-encode,pyrowave -- -D warnings clean
Linux-only code (`enc/linux/`), so the Windows leg is unaffected.
|
||
|
|
cd72f77a3c |
fix(pf-encode): the AMF layout guards broke Windows clippy — 0*SLOT and 1*SLOT
windows-drivers / probe-and-proto (pull_request) Successful in 29s
apple / swift (pull_request) Successful in 1m39s
apple / screenshots (pull_request) Skipped
windows-drivers / driver-build (pull_request) Successful in 2m10s
ci / rust-arm64 (pull_request) Successful in 2m53s
ci / web (pull_request) Successful in 1m15s
android / android (pull_request) Successful in 4m44s
ci / bun-nix (pull_request) Successful in 26s
ci / rust (pull_request) Canceled after 5m25s
ci / docs-site (pull_request) Canceled after 1m7s
`27f08340` wrote every vtable offset assertion as `offset_of!(T, f) == N * SLOT`
so the slot INDEX stays visible in the assertion. For N=0 and N=1 that is
`0 * SLOT` and `1 * SLOT`, which clippy rejects as `erasing_op` and
`identity_op` — six errors, and windows-host.yml runs clippy with `-D warnings`,
so the branch as pushed would have turned the Windows leg red.
This is the blind spot the programme document names in §1.5, demonstrated on the
programme's own first code commit: 44% of the host's unsafe is `#[cfg(windows)]`,
no Linux or macOS check compiles it, and `cargo fmt`/`cargo check` on a Mac are
all clean. Only the .133 gate sees it.
Fixed with a `const fn slot(i: usize) -> usize` rather than by writing the two
offending cases as bare `0` and `SLOT`: that would have made those two the only
assertions where the slot index is invisible, and the index is the entire point.
Also records the cheap local gate that would have caught this without a Windows
round-trip: `amf_sys.rs` depends on nothing but `c_void`, so copying it into a
throwaway one-file crate and running `cargo clippy -- -D warnings` reproduces the
exact error on any host. Verified by reintroducing `0 * SLOT` and watching the
harness fail with the same message the runner gave.
Verified on 192.168.1.133 (Windows CI runner, the box with the WDK), after a
`cargo clean -p pf-encode` that reported `Removed 47 files, 135.5MiB` so the
recompile is real and not a cached green:
cargo check -p pf-encode ok
cargo check -p pf-encode --all-targets --features nvenc,amf-qsv,qsv ok
cargo clippy -p pf-encode --all-targets --features nvenc,amf-qsv,qsv
-- -D warnings exit 0 (was 101)
cargo clippy -p punktfunk-host --features nvenc,amf-qsv,qsv -- -D warnings
exit 0 (was 101)
The gate also greps the extracted tree for the assertions before building, so a
stale upload cannot produce a passing run.
|
||
|
|
cd3f5474bf |
fix(pf-driver-proto): a layout test read an align-8 struct out of an align-1 buffer
ci / web (pull_request) Successful in 1m11s
ci / docs-site (pull_request) Successful in 1m18s
apple / swift (pull_request) Successful in 1m48s
apple / screenshots (pull_request) Skipped
ci / bun-nix (pull_request) Successful in 20s
windows-drivers / driver-build (pull_request) Successful in 2m14s
windows-drivers / probe-and-proto (pull_request) Successful in 40s
android / android (pull_request) Successful in 4m11s
ci / rust-arm64 (pull_request) Successful in 3m7s
ci / rust (pull_request) Successful in 7m4s
`control_structs_roundtrip_through_bytes` built the legacy-size wire form in a
stack `let mut legacy = [0u8; 40]` (align 1) and then called
`bytemuck::from_bytes::<control::AddRequest>`. `AddRequest` opens with
`session_id: u64`, so it is align 8, and `from_bytes` hands back a REFERENCE
into the buffer — it panics unless the buffer happens to be 8-aligned.
A stack `[u8; 40]` usually is, which is why this passed on every machine and
every CI leg since it was written. Under Miri it fails outright: Miri does not
let an accidentally-favourable stack slot stand in for a guarantee.
Switched to `pod_read_unaligned`, which reads by value and has no alignment
precondition. That is not a new idea here — `ChannelProof::parse` at lib.rs:1013
already carries a comment saying "`pod_read_unaligned`, NOT `from_bytes`" for
exactly this reason. This site is the only other one in the crate that reads a
POD out of a stack byte array; every other `from_bytes` call in the tests reads
from `bytes_of(&x)`, which is aligned by construction.
Test-only, so no shipped defect — but the crate is `#![forbid(unsafe_code)]` and
is path-dep'd by BOTH the main workspace and the driver workspace, so it is the
layout oracle for every frame and IOCTL that crosses that boundary. A test that
cannot be trusted to fail is worth fixing there more than anywhere else.
Found by the first Miri run ever performed against this repo.
Verified on 192.168.1.25 (Ubuntu, cargo 1.96.0):
cargo +nightly miri test -p pf-driver-proto 21/21
cargo +nightly miri test -p pf-driver-proto --target x86_64-pc-windows-msvc
21/21
cargo test -p pf-driver-proto --locked ok
cargo clippy -p pf-driver-proto --all-targets --locked -- -D warnings clean
The cross-target run is the interesting one: it interprets the crate at MSVC
layout on a Linux box with no Windows anywhere. Nothing else in CI does that.
|
||
|
|
972af2992f |
fix(pf-capture): the gamescope cursor fallback rewrote environ under a live multithreaded host
`connect_via_env_swap` did set_var("XAUTHORITY", …) / connect / restore, guarded
by a mutex that serialised this source against itself and against nothing else.
`getenv` takes no lock. setenv/unsetenv rewrite the process-global `environ`, and
glibc REALLOCATES that array when a variable is added — while, at that exact
moment, the PipeWire thread is inside pw_init()'s dlopen making bare getenv()
calls and EGL/CUDA init is running alongside. The file's own doc already called
the pattern "unsound from a live multithreaded host"; it stayed as a fallback.
Three things made it worse than the comment suggested:
- The damaging branch is the one where XAUTHORITY is ABSENT and therefore gets
ADDED (the realloc case). scripts/punktfunk-host.service deliberately does not
import the login shell's environment, so absent is the DOCUMENTED NORMAL
configuration for the shipped unit, not an edge case.
- `rediscover` re-runs this every 2 s for the whole session. A display whose
connect fails is never pushed into `displays`, so the dead-display skip never
covers it — the race is not once at startup, it repeats forever.
- It is unfixable in place. Sharing pf_vdisplay's ENV_LOCK is the wrong layer: it
cannot make C `getenv` take a lock.
The fix is to stop writing `environ` at all. Connecting with an explicitly empty
auth token is what the swap actually achieved: we only reach the fallback when
our own lookup found no usable MIT-MAGIC-COOKIE-1 entry, and x11rb's internal
lookup reads the same file with a STRICTER matcher (it matches family/address
too, which we deliberately do not), so where we find nothing it finds nothing
either and connects unauthenticated. That is exactly why the swap "worked"
against a nested Xwayland started without -auth.
Gives up one case: an .Xauthority using an auth family we decline to guess at but
x11rb would have handled. A gamescope Xwayland writes a single-entry
MIT-MAGIC-COOKIE-1 file, so it is not reachable here, and declining to attach a
cursor overlay beats tearing `environ` out from under a live session.
Also removes XAUTH_LOCK, whose only user this was.
Verified on 192.168.1.25 (Ubuntu, cargo 1.96.0 — the pinned toolchain, pipewire
dev headers present): `cargo check -p pf-capture --locked` and
`cargo clippy -p pf-capture --all-targets --locked -- -D warnings` both clean.
Not verified on glass: the fallback is only reached when the cookie parse fails,
so a normal gamescope session does not enter it. Forcing it needs a nested
Xwayland started without -auth, or a mangled cookie file, on .181/.136.
|
||
|
|
df6f270e7b |
chore(safety): forbid unsafe on the crates that are already at zero
Five permanent ratchets, all free today — the point is that they cannot regress
tomorrow. Each crate was re-measured at the commit, not taken from a survey.
`forbid(unsafe_code)`:
punktfunk-encode-worker the binary that carries cap_sys_nice. Its header
claims "no Wayland, no D-Bus, no network, no
plugins"; this makes the memory-safety half of that
claim mechanical. `forbid`, not `deny`, so it cannot
be re-opened by an #[allow] further down.
pf-update-check parses a signed, network-fetched manifest and its own
header says it "owns the part where being wrong is a
security bug". Signature checking is worthless if the
parser around it can be walked out of bounds.
pf-vaadec its header states the design constraint outright — it
links no libva and compiles on macOS, "which is the
point". The crate is full of hand-declared libva
repr(C) mirrors; one raw deref and it stops being the
CPU-testable half.
tools/cursor-probe free, and a probe is where "just deref it to see" is
most tempting.
`deny(unsafe_code)` + one localized allow:
pf-update root runs this. Its single unsafe operation, a bare
geteuid, moves into a named `effective_uid()` helper
carrying the crate's one #[allow(unsafe_code)].
Deliberately NOT rewritten to rustix, contrary to the programme document's first
draft: pf-update's Cargo.toml states that its zero-dependency posture IS a
security invariant of a root helper ("no HTTP client, no TLS, no argument
parsing"), and the extern block says the same. Pulling a general-purpose syscall
crate into a root helper to delete one `unsafe` would trade a real property for
a cosmetic one. The localized allow keeps the ratchet: any NEW unsafe anywhere
in the crate is a build error.
Verified: `cargo check -p pf-vaadec -p pf-update-check` and
`cargo check -p pf-update -p cursor-probe` clean on macOS, plus
`cargo check -p pf-update --target x86_64-unknown-linux-gnu` — pf-update's whole
body is behind `cfg(target_os = "linux")`, so the macOS check does not reach the
line that changed. punktfunk-encode-worker is not built here (pf-encode's C
dependencies do not cross-compile from macOS) and needs the Linux CI leg.
|
||
|
|
27f0834025 |
fix(pf-encode): const-assert the AMF vtable and POD layouts
amf_sys.rs mirrors five AMF COM vtables by hand and amf.rs dispatches through them BY SLOT POSITION — 18 distinct slots across the five tables. The mirrors carried 118 `Slot` placeholders whose only job is to hold the following slots at their C offsets, and not one layout assertion of any kind. A slot inserted, removed or reordered in an AMF header bump calls an arbitrary function pointer through a mismatched signature: no compile error, no runtime signal. `AMF_MIN_VERSION` does not defend against this. It checks a version NUMBER, not a layout, and it is a floor with no ceiling. The three POD checks that did exist (`AmfVariant`, `AmfGuid`, `AmfHdrMetadata`) lived in amf.rs's `#[cfg(test)]` module, so they were verified only when someone ran pf-encode's tests, on Windows, with AMF enabled — and NEVER in a release build, which is exactly where a mis-mirrored `AMFVariantStruct` does its damage: it crosses the FFI BY VALUE on every SetProperty. This is the same hole `a8dd348b` closed for the cuda.h mirrors and missed here. Adds ~40 `const _: () = assert!(...)` guards next to the mirrors: size of each of the five vtables, the byte offset of every slot amf.rs actually calls, the three POD layouts promoted out of the test module, and the AMFData/AMFBuffer shared-prefix agreement that `create_surface_from_dx11_native`'s AMFSurface-through-AMFData reinterpretation silently depends on. Verified by compiling amf_sys.rs standalone (it needs only `c_void`, and a repr(C) struct of code pointers has the same layout on any 64-bit target, so a macOS const-eval proves the Windows arithmetic), and by deliberately breaking one offset to confirm the guard actually fires rather than silently passing. That check earned its keep immediately: `alloc_buffer` sits at slot 43, not 42. Counting AMFInterface(3) + AMFPropertyStorage(10) + the AMFContext block by hand is exactly the error these assertions exist to catch. Zero runtime behaviour change. The `AMF_MIN_VERSION` ceiling is deliberately NOT part of this commit: a ceiling would make the next AMF driver release refuse encode on every AMD box, so it needs a warn-and-continue policy plus an env override and a real AMF session to gate it. |
||
|
|
4b686f026a |
Merge branch 'main' into worktree-vd-sweep-2
# Conflicts: # api/openapi.json |
||
|
|
dc4d8d6832 |
fix(pf-vdisplay): correct the regressions this sweep introduced
An adversarial review of the sweep's own diff raised 39 claims; 23 survived independent verification. This commit fixes them. Several are cases where the sweep traded one bug for another. **The display budget was enforced in the wrong place.** The new Linux `max_displays` ceiling sat in `registry::acquire` — which runs again on every mid-stream rebuild. All three create-before-drop paths hold the old lease while acquiring the new display, and only the mode-switch path passes `supersedes`, so a session at the ceiling counted itself against the budget and could never recover from capture loss or a Game↔Desktop switch. At `max_displays = 1` that is a single streaming client. Moved to `admission::admit`, which is where Windows has always applied it and which is reached once per connect — so a rebuild cannot hit it. **"Cannot tell" was collapsed into "wrong mode".** `unanimous_output_size` returning `None` for two disagreeing gamescopes was compared with `== Some(target)`, so ambiguity took the destructive branch: a nested per-title gamescope — the normal Game Mode shape — made every connect restart the box's session and kill the running game. Now a three-state `BoxOutputSize`, where `Ambiguous` mirrors the live node instead of re-moding, and the post-restart wait asks "did what we asked for come up" rather than demanding unanimity. **Decide-then-act lost its mutual exclusion.** Re-scoping the `MANAGED_SESSION` guard fixed the shutdown restore but let two concurrent creates at the same mode both relaunch, the second stopping the unit the first was polling. A separate `MANAGED_LAUNCH` mutex restores the exclusion without putting launch progress back into the lock the restore samples. **Per-axis policy salvage was applied to a selector.** `preset` chooses the other axes, so salvaging it to the default silently re-pointed the whole document; it now refuses the document instead. A file whose every axis is unreadable also reported `configured() == Some(default)` — flipping Linux identity from Shared to PerClient — and now correctly reports unconfigured. **The six `#[serde(default)]` on `EffectivePolicy` are reverted**: they loosened `POST/PUT /display/presets` (an omitted axis defaulted where it used to 400), which nobody asked for. The catalog salvage they were added for now lives in a private Deserialize-only mirror type, so the read path stays lenient and the wire contract stays strict. Also: the Windows create path stored the OS-committed refresh in the field `acquire` uses as its resize discriminator, so a same-mode re-acquire looked like a hotplug — the requested and committed modes are now separate fields; `output_within`'s timeout arm detached both reader threads (now bounded by a drain grace, capped at 16 MiB, and logged honestly — a `systemd-run --pipe` unit escapes the process group and cannot be reached); `reenable_outputs_kscreen` abandoned the mode restore whenever kscreen-doctor hit its budget even though the enable may have landed (now tri-state); `write_atomic` replaced a symlinked portal config with a regular file, severing dotfiles management; several new budgets were too short for the helper they bound (`steam -shutdown` was being killed before it could deliver the request; `linger_enabled` read a 300 ms timeout as "not lingering" and hard-failed a correctly configured box); and a restore logged an operator-facing error for a `systemctl` call that had merely outlived its budget while systemd still owned the queued job. Verified: 107 tests on macOS, 202 on Linux (executed in a container, not merely type-checked), Linux and Windows clippy clean at `-D warnings`, fmt clean. |
||
|
|
8b98d0b3ec |
fix(pf-capture): a sweep found nine real defects behind comments that asserted the opposite
Reviewed the whole crate (15.6 kloc) for bugs, safety, structure and comment truth. Both compile gates are green: `scripts/xcheck.sh windows clippy` and `cargo clippy -p pf-capture --all-targets --locked -- -D warnings` in the amd64 CI image (the Linux half needs libpipewire, so it cannot ride xcheck). Code defects, each one contradicted by a comment sitting next to it: * `pipeline_depth` clamped to `OUT_RING` (3) while both `repeat_last` and `OUT_RING` state the safe maximum is 2. `d` frames in flight need `d + 1` textures, so `PUNKTFUNK_IDD_DEPTH=3` rotated onto the slot NVENC was still reading and the convert overwrote it in place — torn frames, silently. Now `OUT_RING - 1`. * The GDI cursor poller published `visible: true` for a NULL `hCursor` carrying `CURSOR_SHOWING` — how an app hides the pointer for its own window. The last rasterised arrow was then blended into a game that had hidden its cursor. Every rasterise gate already tested `handle != 0`; the published verdict now agrees. * The ETW event callback did `RING.lock().unwrap()`. That is an `extern "system"` fn, so a poisoned lock panicked across an FFI boundary and ABORTED the host — a diagnostic taking down capture. Poison-tolerant now, which also makes the poison unreachable. * `ChannelBroker::send` bounded the ring with `debug_assert`, so a release build instead panicked mid-`duplicate_and_deliver`, unwinding past the reap and leaking every handle already planted in the driver's WUDFHost. Refuses before the first duplication. * `set_active(false)` did not clear `stall_since`, so a pooled capturer carried a stale stall clock into its next stream and reported capture loss microseconds in. * `attach_gamescope_cursor` evaluated `spawn` before dropping the old source: two readers published into one slot, and a failed spawn destroyed a working reader. Idempotent now. * `PUNKTFUNK_FORCE_SHM` used a bare `== "1"` compare, silently ignoring `=true`/`=on`. * `spa_meta_bitmap.offset == 0` is SPA's "no image data" signal, distinct from the `bitmap_offset == 0` position-only case. Unhandled, it decoded the header's own words as cursor pixels and cached them. * A `VideoInfoRaw::parse` failure was swallowed, so a malformed Format pod surfaced as the generic "no acceptable format" timeout. It is logged, and parsed once, not twice. Comment corrections, all verified against the code they describe: four claims that a failed open falls back to DDA (removed — the caller drops the keepalive under "no fallback"); three comparisons to the removed WGC path; "we do NOT gate HDR on the client's VIDEO_CAP_10BIT" (it does, in three places); the P010 sampler's "4 explicit taps / 2x2 box" (two taps, left-cosited — the box was the bug it replaced); the cursor meta cap quoted as 256x256 (1024, and 256 is the value that cost the whole Linux cursor channel on-glass); the poller's "~60 Hz" (4 ms, ~250 Hz); "several minutes of coverage" (~26 s); "8 frames in 400 ms >= 20 fps" (7 intervals, so 17.5); three "process-wide" HDR latch claims (per-source, which is why HdrSource exists); a SAFETY proof claiming a view is "unmapped never" (Drop unmaps it); the Linux module header describing a bounded channel and BGRx-only frames (one-deep overwriting slot, several formats); and a doc line stranded on `DisplayDescriptor` by an earlier split, restored to `IddPushCapturer`, which had none. |