3eab1e41dfd26d78c73256407d09ffe254149da6
76
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6b3c582eb1 |
feat(client/present): use the driver's queue-free vblank mode where it exists
apple / swift (pull_request) Successful in 1m14s
apple / screenshots (pull_request) Skipped
ci / web (pull_request) Successful in 1m9s
ci / docs-site (pull_request) Successful in 1m30s
ci / rust-arm64 (pull_request) Successful in 2m51s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m3s
android / android (pull_request) Successful in 5m32s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 1m53s
ci / rust (pull_request) Successful in 7m13s
`VK_PRESENT_MODE_FIFO_LATEST_READY_EXT` is FIFO's tear-free vblank pacing that
presents the LATEST READY image at each refresh and retires the older ones,
instead of draining a queue. That is precisely what the software glass gate
emulates — so where the driver offers it, the driver does the job, and it does
it exactly where the gate matters most: a surface with no MAILBOX gets
newest-wins behaviour back without the app holding frames.
Found by asking the surface what it actually offers rather than trusting a
comment: the previous commit's `surface present modes` line read back
`[MAILBOX, 1000361000, FIFO]` on NVIDIA/Wayland, and 1000361000 is this mode.
The extension postdates the Vulkan headers ash 0.38 is generated from (1.3.281),
so there is no binding — hence the bare number in the log. It is hand-declared
here: mode value, extension name, and
`VkPhysicalDevicePresentModeFifoLatestReadyFeaturesEXT` spliced into the device
pNext chain. One trap worth naming: the SURFACE advertises the mode even with
the extension disabled, and using it on that basis is undefined — so the ladder
only offers it when the device feature actually came back true and we enabled it.
The gate/probe predicate had to split in two, and the distinction is the point:
* `needs_glass_gate()` — FIFO and FIFO_RELAXED only. NOT this mode: gating on
top of a driver that already retires stale images would hold frames back to
emulate something the presentation engine is doing, paying the serialisation
twice, which is the ~27 ms the last commit measured.
* `vblank_locked()` — the whole FIFO family INCLUDING this mode, because it
still presents on the refresh boundary, so the VRR cadence probe's premise
("with VRR off, a present waits for vblank") still holds.
Ranking: MAILBOX first (measured good at 1.4 ms), then LATEST_READY, then plain
FIFO — so a MAILBOX-less surface reaches newest-wins in the driver rather than
in our gate.
MEASURED ON GLASS (.21, NVIDIA 610.43.03, GNOME/Wayland): the extension probe,
feature enable and swapchain creation all succeed with a mode ash has no binding
for. Default ladder selects MAILBOX with `fifo_latest_ready=true`; the VRR ladder
selects `present_mode=1000361000` and measures `display 2.6 ms (pace 0.6 + latch
2.0)` — against 13-28 ms for plain FIFO + gate on the same box. The vblank-locked
path is now MAILBOX-class.
That changes the previous commit's reversal. The VRR ladder was reverted to
opt-in because it led with plain FIFO and cost ~27 ms; led with LATEST_READY it
costs 0.6 ms over MAILBOX. So `allow_vrr` is automatic again WHERE THE DEVICE
OFFERS THE MODE, and stays behind `PUNKTFUNK_VRR_FIFO=1` where it does not — on
those drivers the ladder would fall back to plain FIFO and the regression
returns. Both branches are pinned by tests. This also retires a dead switch: the
"Follow variable refresh rate" row did nothing at all after the reversal, and now
does something real on any driver with the extension.
⚠ Still unverified off this box: whether Windows and Intel drivers expose the
mode at all. Nothing measured here carries over — Windows Vulkan WSI goes through
DXGI, so exposing the enum and mapping it usefully onto flip-model semantics are
separate questions, and Intel is a different vendor stack again. Both facts are
logged unconditionally now (`surface present modes` + `fifo_latest_ready=`), so
one run on any box settles it. The code is safe either way: the mode is only
requested where the device feature enabled, and `allow_vrr` only goes automatic
there — everywhere else the shipped MAILBOX-first behaviour is unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
e08474d96d |
fix(client/present): log the surface's actual present modes, and document the VRR opt-in
"AMD's Windows driver offers no MAILBOX" is the premise the FIFO glass gate is built on, and it has been carried in a code comment rather than measured. Present modes are a property of the (surface, device) pair — they vary by platform surface, driver version and fullscreen state — so the only way to settle it is to read them back from real machines. One unconditional log line makes every field log answer the question. First reading, .21 (NVIDIA 610.43.03, GNOME/Wayland): surface present modes available=[MAILBOX, 1000361000, FIFO] Two things fall out. No IMMEDIATE and no FIFO_RELAXED on this surface, which is why a PUNKTFUNK_PRESENT_MODE=immediate run reported mode=fifo — the pin was not offered and the ladder fell through; previously that looked like a puzzling result and is now evidence. And 1000361000 is VK_PRESENT_MODE_FIFO_LATEST_READY_EXT: FIFO's tear-free vblank pacing that presents the LATEST READY image instead of draining a queue — the driver-native version of what the glass gate emulates in software, and a candidate to replace it wherever the driver exposes it (needs VK_EXT_present_mode_fifo_latest_ready enabled at device creation, so a work package rather than a tweak). Also documents PUNKTFUNK_VRR_FIFO, which the previous commit introduced without a docs entry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f422ae3e38 |
fix(client/present): what the first on-glass session found, including a reversed default
WP6 ran against .21 (CachyOS, RTX 5070 Ti, NVIDIA 610.43.03, GNOME/Wayland, 1080p60 HDMI, VRR provably disabled — `org.gnome.mutter experimental-features` is empty), host and client on the same box, `VK_KHR_present_wait` available. Five defects that unit tests and both CI gates had passed over: 1. The latch learner and the VRR probe observed NOTHING. Both derived spacings with `windows(2)` inside a single batch, but the run loop drains present-wait samples every pass, so a batch is normally ONE stamp. `period_us` read back exactly the mode fallback — correct by luck on a 60 Hz panel, wrong the moment a mode lies, which is the entire reason PanelGrid exists. The tests fed 40-stamp batches, a shape the live loop never produces. Spacings are now measured against the previous stamp across calls. 2. The VRR reference was circular. It compared spacings against the LEARNED period, but the grid cannot be learned from our own presents when the stream runs below panel rate — we only ever observe multiples ≥ our frame interval, so the learner adopts our own cadence and every delta is on-grid by construction. It learned 18-22 ms from a 40-50 fps stream and reported VRR on a display with VRR off. The reference is now the DISPLAY MODE's period, which is the vblank grid presents actually quantize to. 3. The probe is meaningless outside FIFO. MAILBOX deliberately decouples presents from scanout, so its stamps are never grid-quantized: same panel, same minute, FIFO read `no` (correct, period 16.4 ms) and MAILBOX read `yes` (wrong). Outside a FIFO-family mode the honest answer is Unknown, and that is now what it reports. 4. Round evaluation was per-CALL rather than per-sample, so the verdict depended on how the caller batched its stamps. Closed inside the sample loop now, with a test pinning bulk-vs-one-at-a-time equivalence — the same invariant (1) violated, in a second place. 5. `force_latency` was dead code without the `pyrowave` feature: a warning in the `--no-default-features` build CI actually ships (the Windows ARM64 leg). The gate only ever tested default features; it now tests both. DESIGN REVERSAL — the VRR FIFO-first ladder is opt-in (`PUNKTFUNK_VRR_FIFO=1`), no longer default. It shipped default-on for `allow_vrr` + fullscreen, which is the default configuration. Measured A/B, same box, back to back, reproduced across three runs: FIFO+engine `display 28.4 ms (pace 11.8 + latch 16.6)` versus MAILBOX `1.4 ms (0.2 + 1.2)`. Under a compositor the FIFO present's on-glass confirmation arrives a whole refresh later and the presenter serialises behind it. The VRR upside is real in principle but UNMEASURED — no VRR panel was available — and a default that is measurably ~27 ms worse on the hardware we could test, bought against an unproven win on hardware we could not, is the wrong way round. A test pins the default to MAILBOX; flip it back when a VRR panel confirms the win. NOT measured, and not claimed: the FIFO glass gate's own headline. The standing queue only forms when the stream rate approaches the panel rate, and an idle GNOME desktop is damage-driven at 40-50 fps on a 60 Hz panel, so `gated`/`forced` read 0 in every mode and the mechanism never engaged. The 11-13 ms figure is still the code's inherited documentation, not a fresh measurement. It needs its actual target: AMD-on-Windows (no MAILBOX, direct scanout) under load. Rig caveats recorded rather than smoothed over: host and client shared one GPU, so absolute latencies are contended and run-to-run variance was large, and it could not be visually confirmed what the physical screen showed. Mode selection, the fallback ladder, the VRR verdict and the counter plumbing are robust to that; absolute numbers are not. Gates: fmt, clippy -D warnings over the five client crates AND the `--no-default-features` build (added because defect 5 hid there), 160 tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e38e3c44c9 |
feat(client/present): V-Sync and VRR become real settings, and VRR is measured
WP3 of design/desktop-presentation-rebuild.md. The `vsync` and `allow_vrr` settings have existed since WP1 but nothing consumed them — the swapchain picked MAILBOX-or-FIFO once, from an env var, and froze. This makes them mean something, which is also what unblocks their settings rows (deliberately withheld from WP5 rather than shipped as dead switches). Present-mode selection is now a preference ladder, not a constant: * V-Sync off — IMMEDIATE, then FIFO_RELAXED, then the tear-free modes. Asking to tear and silently getting vsync is a lie, so the mode that actually took is named in the stats line and a refused preference is logged requested-vs-active. * V-Sync on + VRR allowed + fullscreen — FIFO first. On a variable-refresh panel with direct scanout the FIFO present IS the flip, so the panel follows the stream's cadence instead of a fixed grid; MAILBOX would decouple presents from scanout and re-quantize to the compositor's clock. This is only safe because WP2's glass gate bounds the standing queue that historically made FIFO costly. * Otherwise — MAILBOX then FIFO, the shipped default, unchanged. `PUNKTFUNK_PRESENT_MODE` still pins a mode outright and now falls back to the settings (rather than to mailbox) when the name is unknown. VRR detection is MEASURED, never queried. No portable query exists — SDL exposes none, Wayland does not report adaptive-sync state, Windows surfaces nothing through Vulkan — and the platforms that do answer have been caught lying (see the Android per-uid refresh-rate finding). The discriminator is quantization: on a fixed-refresh panel every on-glass instant lands on the vblank grid, so the spacing between presents is ~k×period for whole k even when the stream runs slower than the panel (it just picks a larger k); under real VRR the panel refreshes when we present, so the spacing follows our own cadence and sits off the grid. `CadenceProbe` folds each delta to its distance from the nearest multiple of the learned period and takes the median. Tri-state: it stays Unknown below 24 deltas and after a display change, so `vrr` is reported only when it has been measured — never inferred from what the display claims. Also fixes the read-once refresh rate: `native.refresh_hz` was sampled at startup and never revisited, so dragging the window to another monitor left a 60 Hz-seeded clock pacing a 144 Hz panel. `WindowEvent::DisplayChanged` now relearns the latch grid, resets the cadence verdict, and clears the served-slot latch. Settings rows for both, on all three surfaces (GTK, WinUI, console). The console's V-Sync row is reachable in Gaming Mode, which is the only editor a Deck user has. Gates: punktfunk-rust-ci linux/amd64 — fmt, clippy -D warnings over pf-client-core, pf-presenter, pf-console-ui, the session binary and the GTK client, 160 tests (the two new ones cover every ladder and both cadence regimes, including the case that matters most: a stream slower than a FIXED panel must still read as fixed). WinUI leg on the Windows runner .133: clippy=0 tests=0, against a tree proven by content to contain the edit. ⚠ On-glass validation is still owed and is NOT claimed here: every box with a real display was powered off when this landed, so the VRR ladder and the detector have been exercised only against synthetic stamps in unit tests. Rebase follow-up: `20de58a7` landed the same "panel grid can be wrong in both directions" defect fix on Android and extracted the corrected learner into `punktfunk_core::phase::PanelGrid` for the iOS and desktop presenters to share. This clock had the identical bug — it capped the learned period at the display mode's refresh, and the mode is only a CLAIM, so a display really running slower than it advertises pinned a grid whose instants never arrive, for the session, with no way back. Adopted the shared learner rather than carrying a second, buggier copy; still fed the window's MIN spacing, which preserves the k×period resistance the cap was actually aimed at while the streak requirement lets a genuinely slower panel be discovered. New test: seed 120 Hz, real panel 60 Hz, the clock must climb back out. Took the same commit's third lesson too: the adaptive margin widened on a latch over 1.5×period (a number picked here), and now widens on the latch exceeding one period plus the lead already applied — the slot actually aimed at. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b1ac4d02de |
feat(client/present): the display stat splits, and the intent reaches the settings UI
WP4 + WP5 of design/desktop-presentation-rebuild.md, on top of the WP1/WP2 engine. The engine shipped with no way to choose it and no way to see what it cost; this closes both. WP4 — the display stage splits into `pace` (decoded → present-submit, our own pipeline) + `latch` (submit → on-glass, the presentation queue and the vblank wait), off the `submitted_ns` stamp WP2 already carried. That split is what makes a high `display` self-diagnosing: latch dominating is the vsync floor or a standing queue, pace dominating is us. A `present:` line joins the Detailed tier naming the live swapchain mode — the answer to most "why is my latch a whole refresh" questions, since a MAILBOX request silently lands on FIFO wherever the driver has no mailbox — plus the engine's counters, rendered only when they are non-zero so a healthy latency session shows just the mode. Deviation from the plan: the planned `display_adj` twin is NOT here. It was specified as `display − latch_p50` for parity with the Apple HUD's shaved figure, but with a real per-sample `pace` percentile that twin is the same quantity derived worse (subtracting percentiles). `pace` IS the Apple-comparable number — Apple subtracts its OS present floor, the latch is ours — and the user docs now say exactly that. WP5 — Prioritize + Smoothness buffer on all three surfaces: the GTK dialog (a new Presentation group on the Display page), the WinUI settings page, and the console settings screen, which is the ONLY editor reachable in Gaming Mode and so the one that decides whether Deck users can reach this at all. The buffer control follows the intent the way echo cancellation follows the mic: hidden on the desktop shells, dimmed and inert on the console, where a row that vanished mid-list would shift everything under the cursor. The V-Sync and VRR rows are deliberately NOT here. Their settings exist and are profile-routed, but the swapchain does not honour them until WP3, and a toggle that does nothing is exactly how "Full chroma (4:4:4)" shipped inert on desktop for three releases after being announced. Buffer labels carry no millisecond hints (Apple/Android derive them from the session refresh): under a Native mode the shells do not know the refresh at settings time, so the captions state the cost as one refresh per frame rather than a confident wrong number. Docs: the stats page documents the split and the `present:` line, and stops claiming Linux/Windows measure to the present instant (untrue since present_wait); client-settings documents both new rows and drops the stale claim that the desktop 4:4:4 toggle has no effect (it was wired to VIDEO_CAP_444); configuration documents PUNKTFUNK_PRESENTER and PUNKTFUNK_PRESENT_DEBUG. Gates: punktfunk-rust-ci linux/amd64 — fmt, clippy -D warnings over pf-client-core, pf-presenter, pf-console-ui, the session binary and the GTK client, 158 tests. The WinUI leg cannot be reached by any Linux or macOS check, so it was compiled on the Windows runner .133: clippy -D warnings and tests both exit 0, against a tree proven by content to contain the edit. ⚠ The first run there reported a false pass — the script printed its done-marker while the log carried a test failure (a STATUS_DLL_NOT_FOUND launch failure, ffmpeg's DLLs missing from PATH); the harness now echoes each phase's exit code so the verdict is a fact in the log rather than an inference from a marker. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5f55fa874a |
feat(client/present): the desktop presenter gains the Apple/Android intent model
WP1+WP2 of design/desktop-presentation-rebuild.md. The shared Linux/Windows session client presented arrival-paced with no pacing layer at all: two depth-2 newest-wins hops into a drain-to-newest and an immediate present. That IS the lowest-latency intent, but it was unnamed, unselectable, and had no alternative — and on a surface without MAILBOX (AMD's Windows driver offers none, and any compositor holding images does the same) the swapchain's own FIFO becomes a standing queue worth a measured 11-13 ms at 60 Hz. WP1 — the settings cluster, under the keys the Apple client already writes into the shared profile catalog (present_priority / smooth_buffer / vsync / allow_vrr): mismatched names would ride SettingsOverlay::extra, carried but never applied. PresentPriority::resolve mirrors the Android reference exactly (anything but an explicit "smooth" is latency; a buffer outside 1..=3 becomes 2), so a profile authored on any client means the same thing on all of them. Only the first two are consumed here; vsync/allow_vrr land in WP3. WP2 — the engine (present_pace.rs, pure state + arithmetic, 6 tests): - FrameStore: newest-wins slot, or the smoothing FIFO with preroll-to-capacity, drop-oldest overflow, and an underflow that re-arms the preroll (repeat by omission) — the Apple/Android semantics, with qDrop/qDry counters. - LatchClock: the panel grid learned from VK_KHR_present_wait glass stamps, min positive spacing capped by the mode refresh (measured, never queried — VRR and Android's per-uid refresh lie both punish trusting a reported rate). It now also publishes the host-facing LatchGrid, so the phase-lock report and the local scheduler cannot disagree about the grid. - PresentGate: one undisplayed present in flight on FIFO surfaces, with the 100 ms stale force-open. This is the standing-queue killer, and it is inert on MAILBOX/IMMEDIATE and without present timing — where behaviour stays byte-for-byte the shipped arrival pacing. Wiring: glass samples drain every pass (a 1 Hz batch would starve clock and gate) and the waiter pushes an SDL wake, so a gate reopen never waits out the event timeout; smoothness serves one frame per latch slot and tightens the loop's wait to that deadline; the adaptive slot margin starts at 0 and widens +500 us per missed window toward 2.5 ms (a fixed lead was measured to be pure display tax). PUNKTFUNK_PRESENTER=arrival disables the whole engine for field A/B without a rebuild. PyroWave collapses smoothness to latency for the stream: its plane-ring retirement accounting assumes the depth-2 newest-wins hand-off, and all-intra frames make buffering moot anyway. Gates (punktfunk-rust-ci, linux/amd64, sources touched first so a warm target cannot print a vacuous Finished): clippy -D warnings across pf-client-core, pf-presenter and punktfunk-client-session; 80 + 32 tests pass; rustfmt clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
caa47e28e6 |
fix(client/decode): AV1 hardware decode stops silently opening libdav1d
apple / swift (push) Successful in 1m27s
ci / rust (push) Successful in 5m28s
android / android (push) Successful in 5m59s
ci / rust-arm64 (push) Successful in 2m24s
ci / web (push) Successful in 1m55s
arch / build-publish (push) Successful in 9m23s
ci / docs-site (push) Successful in 1m45s
deb / build-publish-client-arm64 (push) Successful in 2m23s
windows-host / package (push) Successful in 12m12s
windows-host / winget-source (push) Skipped
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 37s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 9s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
release / apple (push) Successful in 12m6s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 7s
deb / build-publish (push) Successful in 5m54s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
deb / build-publish-host (push) Successful in 5m19s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 1m0s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m43s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 27s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m44s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 7s
windows-host / canary-manifest (push) Successful in 24s
docker / deploy-docs (push) Successful in 45s
docker / builders-arm64cross (push) Successful in 8s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m7s
apple / screenshots (push) Successful in 5m51s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m17s
flatpak / build-publish (push) Successful in 5m38s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 2m14s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m7s
avcodec_find_decoder(id) returns the registry's FIRST decoder for the id, and
upstream orders the native av1 decoder LAST on purpose ("hwaccel hooks only,
so prefer external decoders" — allcodecs.c). All three hardware backends
selected by id, so every AV1 session opened libdav1d: a software decoder that
silently ignores hw_device_ctx and never calls get_format. Each frame then
failed the backend's hw-format guard and the session burned the demotion
ladder MID-STREAM — field-logged as 68 Vulkan fails → D3D11VA → 102 fails →
software, ~3 s of black — with "hardware decode active" already printed and
the D3D11 profile/pool probes all green. H.264/HEVC never hit this only
because their native decoders happen to be registered first.
Selection is now by capability: find_hw_decoder walks av_codec_iterate and
takes the first decoder whose avcodec_get_hw_config advertises the backend's
surface via HW_DEVICE_CTX, so a build without a usable hw decoder fails at
OPEN in milliseconds and the ladder runs there — the idiom the D3D11 probes
already follow. Registry order still wins among capable decoders, so
H.264/HEVC select exactly what they always did. The software path keeps the
id lookup on purpose: libdav1d is the fastest CPU AV1, and the native av1
decoder has no software path at all.
Every decode log now carries the selected decoder's name — decoder="av1" vs
decoder="libdav1d" is the whole diagnosis, and no log line said it. The
session log names the WIRE codec and drops the FFmpeg id for PyroWave
(ffmpeg_codec_id's fallthrough claimed codec_id=HEVC for wavelet sessions
that never touch FFmpeg).
The CPU lane also stops passing raw PQ off as a tone-map: software-decoded
frames deliberately never take the HDR10 swapchain, but a PQ stream there was
then shown UNtonemapped (washed out) with no warning — the pq-downgrade warn
keys off the swapchain answer — while the Detailed OSD badge claimed the
"HDR→SDR" tone-map that only the hardware lane's CSC runs. The presenter now
warns once when a PQ CpuFrame arrives, and the badge distinguishes
"HDR→SDR (raw)" (no tone-map pass) from the hardware lane's real "HDR→SDR".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
8d7e273a96 |
feat(client/present): PUNKTFUNK_PRESENT_MODE gains explicit mailbox and fifo_relaxed arms, and the docs stop guessing
The env knob silently folded 'mailbox' and every typo into the default arm, FIFO_RELAXED was not reachable at all, and clients/session/README.md claimed the default is FIFO (it is MAILBOX with a FIFO fallback). An AMD-on-Windows client always lands on FIFO because that driver offers no MAILBOX — now documented at the picker and in the docs-site client table, next to the ABR probe/ceiling knobs a field report went looking for and couldn't find. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
23f1debe69 | Merge remote-tracking branch 'origin/main' into audio/mic-latency-echo | ||
|
|
6a4ffcb15c |
fix(client): HDR stops leaking out of the stream and blowing out the console UI
ci / web (push) Successful in 1m3s
ci / rust-arm64 (push) Successful in 1m23s
ci / docs-site (push) Successful in 1m12s
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 7s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 6s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m29s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 49s
deb / build-publish-client-arm64 (push) Successful in 1m57s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
deb / build-publish-host (push) Successful in 3m40s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m55s
deb / build-publish (push) Successful in 5m59s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m7s
docker / builders-arm64cross (push) Successful in 4s
docker / deploy-docs (push) Successful in 27s
android / android (push) Successful in 8m10s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 2m12s
arch / build-publish (push) Successful in 9m35s
flatpak / build-publish (push) Successful in 6m57s
ci / rust (push) Successful in 10m44s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 7m54s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 6m17s
apple / swift (push) Successful in 5m10s
apple / screenshots (push) Canceled after 19m1s
The gamepad/console UI looked right on launch, and wrong forever after the first HDR session: connect to an HDR host, disconnect, and the UI came back overblown with wrong colours. The UI is not its own renderer. It is a `pf_presenter::overlay::Overlay` composited into the SAME swapchain the stream used, and it draws plain sRGB with no HDR awareness at all — so the swapchain's colorspace decides how its pixels are read. `present` switches SDR↔HDR10 from the FRAME's colour signalling, and a UI-only present is `FrameInput::Redraw`, which carries none: the mode block is skipped entirely and nothing ever hands HDR10 back. The UI's sRGB mid-tones were then emitted as PQ code points, i.e. near-peak nits. `leave_hdr` drops back to SDR, called where the UI-only present already happens and gated on the existing `browse_idle` — Browse mode with no live connector, i.e. the UI owns the screen. That covers every route back to the UI rather than just the Ended/Failed arms, and it is guarded internally so idle iterations stay free. It also bails when minimized, which is load-bearing rather than an optimization: `recreate_swapchain` keeps the old swapchain at a zero extent, but `set_hdr_mode` would by then have rebuilt the CSC and overlay pipes against the SDR format — mismatched against live HDR10 images. `present` early-returns on a zero extent above its HDR block, so this was unreachable until a caller outside `present` existed. Deliberately not applied to the `resize_scrim` arm of the same present: that scrim is a mid-stream gap in a session that is still HDR, and flipping there would rebuild the swapchain twice per resize. Does not address the adjacent case: an overlay drawn DURING a live HDR session (the stats HUD, the resize scrim) is blown out the same way. That needs the overlay to PQ-encode when `hdr_active`; dropping to SDR is only correct here because the console UI shows exactly when no stream is live. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a7143a6510 |
Merge origin/main into audio/mic-latency-echo
Main moved through the same surfaces while the audio work was in flight, so three files needed hand-resolution: - clients/windows/src/app/settings.rs — main gave Windows its speaker and microphone endpoint pickers, the gap this branch could only report. Both keep their rows: the pickers, then Echo cancellation, and the microphone description keeps the sentence naming the mute chord. - crates/pf-console-ui/src/screens/settings.rs — both sides grew the couch row list. Main's seven new rows and Echo cancellation are all reachable in Gaming Mode; the count is 22 and the rationale comment names echo cancellation among the fields that would otherwise be unreachable there. - crates/pf-presenter/src/run.rs — both sides added a stats test at the same line. Both are kept. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6af067da2d |
fix(client): the bottom rows stop smearing — the decode pool is taller than the picture
ci / web (push) Successful in 1m49s
ci / rust-arm64 (push) Successful in 2m19s
ci / docs-site (push) Successful in 2m29s
ci / rust (push) Successful in 7m3s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 19s
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 6s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 8s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 16s
deb / build-publish-client-arm64 (push) Successful in 2m28s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 21s
deb / build-publish (push) Successful in 4m17s
deb / build-publish-host (push) Successful in 4m51s
docker / builders-arm64cross (push) Successful in 6s
docker / deploy-docs (push) Successful in 36s
android / android (push) Successful in 8m24s
arch / build-publish (push) Successful in 8m28s
flatpak / build-publish (push) Successful in 6m8s
apple / swift (push) Successful in 5m54s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 13m5s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 3m10s
apple / screenshots (push) Canceled after 7m53s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 16m25s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 15m57s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Canceled after 0s
windows / build (x86_64-pc-windows-msvc) (push) Canceled after 5m3s
A user's 1080p stream repeated its last row of pixels over the final few rows, so the image looked stretched at the bottom. The Vulkan-Video CSC pass sampled the decoded planes with the fullscreen triangle's normalized 0..1 UVs, but its render target is built at the CROPPED frame size. Those are not the same rectangle: FFmpeg sizes the decode pool from `avctx->coded_*`, and H.264 codes `16 * mb_height` — so a 1080-row picture decodes into a 1088-row pool. Destination row 1079 sampled source row ~1087.5, dragging the 8 alignment rows into view and squashing the picture 0.7%. Encoders fill that padding by replicating the last picture line, which is why it reads as a smeared bottom row rather than garbage. Confirmed on glass (.173, RTX, H.264 1080p, vulkan-video): Vulkan Video first frame width=1920 height=1080 pool_w=1920 pool_h=1088 `VkVideoFrame` now carries the pool extent and `record_csc` takes a `uv_scale`, written to the shader's `params.zw` — which the CSC shader already reserved for a use like this. The chroma cositing offset is unchanged and stays correct: `textureSize` reports the pool width, which is the space the scaled UV is already in. Only the Vulkan-Video path passes a scale below 1.0. D3D11VA already clamps this in its VideoProcessor blit (the same bug, seen as a green bar there because DXVA padding is uninitialized rather than replicated); dmabuf imports its planes at the crop over the real stride; PyroWave allocates its ring at exact stream dims. Apple and Android crop at the OS layer. Every other call site passes [1.0, 1.0], so the change is inert there. Also adds a one-time first-frame layout log mirroring the D3D11VA one, so the frame-vs-pool gap is visible in the field instead of having to be re-derived from FFmpeg internals. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
7ec3107b4a |
feat(client): the mic has an off switch, and echo cancellation has a switch too
Ctrl+Alt+Shift+V mutes and unmutes the microphone mid-stream — V for
voice, since M and S were taken. The uplink keeps running while muted:
`MicStreamer::spawn` takes a shared AtomicBool the capture callback reads
every quantum, and a muted callback drains whole frames and sends
nothing. Stopping the stream instead would have re-primed the device
buffers and, on Linux, re-run source selection on every unmute — a
second of glitch for a key people press mid-sentence. The sequence
counter deliberately does NOT advance while muted, so the host sees one
continuous sequence with a pause rather than a gap the size of the mute,
which its de-jitter would try to conceal frame by frame (its 600 ms
stale-flush covers the rest).
The mute lives on SessionHandle as a MicControl with two flags, not one:
`live` is raised by the pump only once the uplink is actually running, so
a session with the mic off in Settings — or whose capture device wouldn't
open — reports "nothing to mute", the chord says so in the log, and no
indicator appears. Per session, never persisted.
Muted state draws as a persistent "Microphone muted" badge in the stream's
top-right corner, off `FrameCtx::mic_muted` rather than the stats text: it
has to be there with the stats overlay Off, which is where most people
leave it. The Detailed mic line still reads throughput, so it simply falls
to zero — the badge is what answers "am I muted".
Echo cancellation stops being an env-only lever. `Settings::echo_cancel`
(default on, `#[serde(default)]` so every stored file loads with it on)
now gates the same hooks PUNKTFUNK_NO_AEC gated: the echo-cancelled
PipeWire source preference and WASAPI's Communications stream category.
The env var still wins, one-way — it can only turn AEC off, never back on
— and both `aec_enabled` helpers say so. The row ships in the GTK, WinUI
and console settings, under the microphone toggle and greyed out while it
is off, matching what Apple and Android shipped in wave 1.
SettingsOverlay grows `echo_cancel` as a first-class field — apply,
absorb, clear, is_empty — instead of riding the `extra` passthrough, where
`clear_override("echo_cancel")` answered false. The JSON key is the one
Apple and Android already write, so one catalog round-trips through all
three.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
2ce0bea830 |
feat(client): desktop phase-locked capture, a real display volume on Windows, and the console's missing rows
The cross-platform half of the gap sweep: * Phase-locked capture reaches the DESKTOP clients (it shipped on Apple/Android only, though the desktop presenter has the best latch signal of all — true on-glass stamps via VK_KHR_present_wait). The presenter's 1 Hz fold publishes a latch grid (anchor = last on-glass instant; period = min positive present spacing, capped by the display mode's refresh so an arrival-paced sub-panel-rate stream can't claim a slower grid); the session pump folds every AU's arrival stamp against it with the SHARED `phase::circular_latch` statistic and sends the ~1 Hz PhaseReport (1 ms uncertainty — reference-client parity). The cap is advertised only when present timing is real (`VulkanDecodeDevice::present_timing` gates `SessionParams::phase_lock`), and the host's applied grid offset from the 0xCF tail is logged so an on-glass run can watch the controller engage. * `Hello::display_hdr` stops being hardcoded `None`: Windows reads the panel's colour volume from DXGI (`IDXGIOutput6::GetDesc1`, the `--window-pos` output else the primary, advanced-color outputs only, gated on the HDR setting) so the host's virtual-display EDID matches the real glass. Linux keeps the EDID defaults — no portable Wayland/X11 query exists — and the comment now says exactly that. * The console settings screen (the ONLY editor in Gaming Mode) learns the rows it was missing: render scale, full chroma 4:4:4, invert scroll, capture system shortcuts, fullscreen-on-stream, auto-wake and the game-library toggle. * A spec-run session's device picks (GPU adapter, speaker, microphone) now come from the `--resolved-spec` instead of a raw Settings load — the last store read the spec path still owed (§5), and what would make those fields profileable. * `session_args()` documents why the GTK/CLI spawners pass no `--window-pos` (Wayland exposes no global coordinates to read and SDL can't apply them). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
55e01b4dcb |
feat(client): the stats overlay learns whether your mic frames arrive
NativeClient::mic_stats reaches the per-second stream window: sent and dropped (queue-full + stale-shed) frame deltas join the tracing line and the Stats event, and the Detailed OSD tier renders a mic line while the uplink is live — a healthy 10 ms-frame mic reads ~100 f/s, and a drop term means the client is shedding backlog, not the network eating audio. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
74863c96b3 |
feat(client): 4:4:4 can stay on hardware, and the overlay says what you actually got
Two halves of the same honesty problem (client-gaps handoff items 1+2,
done together as the handoff asked).
The presenter learns full chroma: `vkframe_plane_views` accepts the
2-plane 4:4:4 pool formats (8-bit, and the 10-bit 3PACK16 sibling) —
what NVIDIA's Vulkan Video reports for HEVC RExt decode — with the
accepted set extracted into `vkframe_plane_formats` and pinned by a
decision-table test. The CSC shader needed nothing: its 4:2:0 siting
correction already self-disables when the plane widths match. The VAAPI
leg gets the same treatment (NV24 in `drm_fourcc_for` and the dmabuf
import, full-size chroma plane), and the Vulkan decoder's sw-format
gate admits NV24/P410. 3-plane 4:4:4 stays rejected — it needs a third
CSC binding — and demotes cleanly like every other unsupported format.
Design call (the handoff's fork, argued here as requested): (B)+(C),
not (A). No capability probe gates VIDEO_CAP_444 — software decode is
the guaranteed display floor on both OSes (swscale → RGBA), the decoder
ladder demotes on its own, and a probe-gated bit would turn the switch
inert on exactly the boxes that rely on the fallback. What (A) wanted
from a prediction, the overlay now delivers as ground truth:
The Detailed tier prints the encoder's target next to the measured
rate — `19.4 Mb/s · target 20 Mb/s (auto)` — and the resolved chroma,
`4:4:4→4:2:0` when the host declined the ask (mirroring `HDR→SDR`).
The target is live: `NativeClient::current_bitrate_kbps()` mirrors
every BitrateChanged ack, so an Automatic session's ABR re-targets are
visible as they move. This is the figure whose absence let the
settings-drop bug (
|
||
|
|
fd3c54bd43 |
feat(client): "Capture system shortcuts" finally decides where Alt+Tab goes
ci / web (push) Successful in 1m7s
ci / docs-site (push) Successful in 2m22s
ci / rust-arm64 (push) Successful in 3m34s
android / android (push) Canceled after 4m8s
apple / swift (push) Canceled after 4m13s
apple / screenshots (push) Canceled after 0s
decky / build-publish (push) Successful in 42s
arch / build-publish (push) Canceled after 4m25s
ci / rust (push) Canceled after 4m30s
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 10s
deb / build-publish (push) Canceled after 4m35s
deb / build-publish-host (push) Canceled after 3m29s
deb / build-publish-client-arm64 (push) Canceled after 2m15s
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 10s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 13s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 6s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 4s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 10s
docker / builders-arm64cross (push) Canceled after 0s
docker / deploy-docs (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 30s
flatpak / build-publish (push) Canceled after 0s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Canceled after 6m14s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , 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
The toggle has been stored, profileable and rendered in two settings UIs since profiles landed, and nothing read it. Windows grabbed the keyboard whenever input was captured, setting or no setting; Linux never grabbed at all, because the grab sat behind `#[cfg(windows)]` with a comment deferring the compositor story to "the shells" — which never picked it up. `Settings.inhibit_shortcuts` now reaches the presenter and gates the grab, on both platforms. SDL3 already maps `SDL_SetWindowKeyboardGrab` onto `zwp_keyboard_shortcuts_inhibit_manager_v1` on Wayland and `XGrabKeyboard` (plus `_XWAYLAND_MAY_GRAB_KEYBOARD`) on X11, so dropping the cfg is the Linux fix. Capture state still gates it, so releasing input hands the chords straight back, and the desktop mouse model never grabs. A compositor with no shortcuts-inhibit global says so once instead of failing silently — at debug under gamescope, which has no shortcuts to inhibit in the first place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
65cd388a52 |
fix(presenter,core): close the last of the proof-lint hole — the Vulkan contract, stated once
pf-presenter's 120 sites are `ash` calls almost without exception, so 120 independent arguments
would have been 120 restatements of the signature — the exact noise this program exists to remove.
They get the `abi.rs` treatment instead: the Vulkan contract stated once in `lib.rs`, each site
naming which of three shapes it is.
The three are not equal, and separating them is the point. CREATE and RECORD carry no real
precondition — the device is owned, the builders are locals, nothing executes until submit. DESTROY
does: the GPU must not still be using the object, and that is established by the path (a fence wait,
a `queue_wait_idle`, a retired swapchain), not by the call. Those sites say so, because getting it
wrong is a use-after-free no type catches. The contract also tells the next person that a block
outside the three shapes needs a real proof, and that writing "as above" is the signal it doesn't
belong in them.
punktfunk-core's Windows half is finished here too: `qos_windows.rs`'s `GetLastError` reads (called
before anything can reset the thread's error slot) and `udp/windows.rs`'s control-message write,
whose argument is that `ctrl` is sized by `WSA_CMSG_SPACE(4)` — computed two lines up — so header
plus payload cannot run past it, and `write_unaligned` is used because `WSA_CMSG_DATA` offers no
alignment guarantee.
⚠️ THE WINDOWS BLIND SPOT BIT A THIRD TIME. A Linux measurement put this crate pair at 113; the
real number was 129 — `d3d11.rs`, `win32.rs`, `qos_windows.rs`, `udp/windows.rs` are all
`cfg`-hidden. Every crate in this sweep had to be finished on .47 after being "done" on .21. For a
cross-platform crate the Linux number is a lower bound, never the answer.
All three crates now deny `undocumented_unsafe_blocks`, which was the goal: it applied to 8 of 11
crates carrying unsafe, and the three exempt ones held 381 items between them — including the C ABI
surface and the presenter. Verified: Linux .21 fmt + both CI clippy steps rc=0; Windows .47 all four
closed crates clippy `-D warnings` rc=0 plus the full Windows CI clippy set and pf-capture's tests.
Only small crates remain unguarded (75 items total, largest 26).
|
||
|
|
a2be3f4de9 |
feat(client): settings profiles — the override catalog, the one resolver, and the session flag
A client setting is global today: pick 4K@120 HDR and the retro box gets it too.
The one escape hatch (per-host `clipboard_sync`) proves the shape works but covers a
single field. This is P0 of design/client-settings-profiles.md — the headless core the
shells, Apple, Android and the deep-link grammar all build against. No UI yet.
A profile is a NAMED BUNDLE OF OVERRIDES, not a snapshot: `SettingsOverlay` is sparse
`Option`s, so a field the user never touched keeps following the global value live, and
fixing a global once fixes it everywhere. `Some(x)` equal to today's global is still
meaningful — it pins x against a later global change — which is why the UI will write
`Some` on touch and `None` on an explicit reset, never by diffing.
The catalog is its own `client-profiles.json`, deliberately not part of the settings
file: that file has five whole-file load-modify-save writers with no merge, so a profile
written by one would be dropped by the next. Which host uses which profile is a field on
the host record instead of a map keyed by host — that is what dissolves the client's
long-standing "what identifies a host record" question for this feature.
Resolution has exactly one implementation, `trust::effective_settings()`:
effective = overlay(profile).apply(global)
profile = one-off pick ?? host binding ?? none
Both session construction sites go through it (`main.rs` AND `console.rs` — touching one
and not the other is a Windows-only build break), so the console, and therefore Decky,
honor host bindings with no work of their own. Nothing here can fail a connect: a
deleted binding resolves as "no profile", and a one-off that can't be honored falls back
to the DEFAULTS rather than to the host's own profile — "connect with Work" must never
quietly stream "Game". `--profile <id|name>` is the one-off door for the shells' coming
"Connect with ▸" and for `punktfunk://…&profile=`; `--profile ""` forces the defaults on
a bound host. The active profile's name closes the stats overlay's first line, so
"which profile am I on?" is answerable without leaving the stream.
Also here, because this effort touches every one of these anyway:
- `KnownHost` gains `profile_id`, `pinned_profiles` and a lazily minted stable `id`. The
id is groundwork (design §4.5) — no lookup is re-keyed, `fp_hex`/`addr:port` stay.
- `upsert` now preserves the state the USER set on a record — the profile binding, its
pins, the clipboard decision, the id — against refreshes that carry none of it.
`clipboard_sync` survived that only because the function happened not to mention it.
- `KnownHost::default()` exists so construction sites read `..Default::default()`; five
hand-written literals were exactly how a new field gets silently dropped on re-pair.
- All three client stores now write temp+rename. A torn settings file loads as `Default`
— i.e. silently resets every setting the user has.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
5ea087ca47 |
feat(session): the stats overlay scales with the display's DPI
The stream chrome — stats OSD, capture hint, start banner, resize label — was hardcoded at 14 px with 12/10/8 px insets. The overlay composites into the swapchain 1:1 in PHYSICAL pixels, so on a 4K panel at 200 % all of it rendered at half its intended physical size: fine on a 1080p monitor, a squint on a HiDPI laptop. FrameCtx now carries a scale — SDL's window display scale (DPI × the display's content scale) times a PUNKTFUNK_OSD_SCALE preference — and every metric moved into a `base` module that is multiplied by it. Re-read per frame and quantized into the damage key, so dragging the window to a differently-scaled monitor re-renders at the new size rather than keeping the stale one. Sanitized because SDL returns 0.0 when it cannot resolve the window's display, which would collapse the panel to nothing. Two details worth keeping: the face is re-derived at the scaled size rather than the canvas transformed, because Skia rasterizes glyphs at the requested size where a magnified 14 px bitmap would be mush; and the long capture hint is fit-clamped to the window — at 2× it is wider than a 1080p screen, so scaling it naively would have traded a small OSD for a truncated one. Linux and Windows share this path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 744467d13a28b91ba88a23d6038da70263e9b502) |
||
|
|
e5e68f1d24 |
feat(presenter): DRM card selection + a usable kmsdrm swapchain error
ci / web (push) Successful in 1m2s
ci / docs-site (push) Successful in 1m15s
apple / swift (push) Successful in 5m25s
ci / bench (push) Successful in 7m44s
deb / build-publish-host (push) Successful in 10m30s
decky / build-publish (push) Successful in 30s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
android / android (push) Successful in 12m52s
deb / build-publish (push) Successful in 11m46s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 11s
arch / build-publish (push) Successful in 13m6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 45s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 12s
ci / rust-arm64 (push) Successful in 13m32s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m1s
deb / build-publish-client-arm64 (push) Successful in 9m12s
windows-host / package (push) Successful in 18m4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7m1s
flatpak / build-publish (push) Successful in 7m25s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m46s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 9m7s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 9m24s
ci / rust (push) Successful in 23m48s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m28s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m46s
docker / deploy-docs (push) Successful in 22s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m42s
release / apple (push) Successful in 31m30s
docker / build-push-arm64cross (push) Successful in 7m6s
apple / screenshots (push) Successful in 23m4s
Both come straight out of the first compositor-less bring-up (P3), on a two-GPU box: PUNKTFUNK_DRM_CARD=<n> pins SDL's KMSDRM device index. SDL enumerates /dev/dri/card* and takes the first it can open, which is regularly the wrong one: it chose the card a live compositor already held DRM master on and died at swapchain creation, while the idle card with the connected display sat unused. Kept an explicit operator choice rather than auto-detection — deciding "is this card already mastered" needs the very ioctl that taking master IS, so any in-process guess would be fragile. The swapchain error now carries what to actually check. "vkCreateSwapchainKHR: Initialization of an object has failed" is useless to someone bringing up a kiosk; on the kmsdrm backend it now names the pinned card and lists the three real causes in order (no connected connector / another DRM master / NVIDIA). Empty on every other backend, where it would be noise. The NVIDIA note is measured, not guessed: on NVIDIA proprietary + kmsdrm, Vulkan enumerates the GPU AND the display (VK_KHR_display reports the connected HDMI connector) and still fails — as root, with nvidia_drm.modeset=Y, on a card no compositor was using. Not permissions, not DRM master; their direct-display path wants the display leased via vkAcquireDrmDisplayEXT and SDL's kmsdrm surface path does not do that. Plan: punktfunk-planning design/embedded-arm64-client.md §P3 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b7cea71bbd |
fix(presenter): type the Vulkan extension pointers as c_char, not i8
`char` is signed on x86_64 but UNSIGNED on aarch64, so the hardcoded `Vec<*const i8>` compiles on every desktop target and then fails to match ash's `&[*const c_char]` when cross-compiling the client for ARM. Found by the aarch64 cross-build spike (punktfunk-planning: embedded-arm64-client.md) — the only portability defect in the client stack; nothing else in the client crates assumes an architecture. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6cbc3eec8b |
fix(sdl/cursor): scale the forwarded pointer to the video fit
apple / swift (push) Successful in 1m30s
ci / web (push) Successful in 54s
ci / docs-site (push) Successful in 1m3s
android / android (push) Canceled after 1m27s
apple / screenshots (push) Canceled after 1m39s
arch / build-publish (push) Canceled after 1m29s
ci / rust (push) Canceled after 1m29s
ci / bench (push) Canceled after 1m17s
deb / build-publish (push) Canceled after 1m15s
deb / build-publish-host (push) Canceled after 27s
decky / build-publish (push) Canceled after 21s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , 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
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 15s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Canceled after 1m18s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 15s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 8s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 18s
flatpak / build-publish (push) Canceled after 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 16s
docker / deploy-docs (push) Canceled after 0s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 17s
Parity with the Apple client (
|
||
|
|
92761813a9 |
feat(core+host+driver+client): mid-stream cursor-render flip — host composites in the capture model
The cursor channel excluded the host pointer from the video for the whole session, but the client only draws it under the DESKTOP mouse model — flipping ⌃⌥⇧M to capture mid-stream left the session pointer-blind (user-found). The fix makes render ownership LIVE state: - wire: CursorRenderMode 0x51 (client→host, control stream) — client_draws: true = desktop model (host excludes + forwards), false = capture model / released (host composites, full fidelity — DWM on Windows incl. real XOR inversion, encoder blend on Linux). Sessions start client_draws=true (the pre-message behavior). - host: control task stores the flag; the encode loop edge-detects it — forwarding + frame.cursor strip while the client draws, quiet forwarder + overlay-into-blend while composited. SessionPlan now grants blend CAPABILITY wherever the capture has a pointer (dropping the !cursor_forward gate) so the composite side needs no encoder rebuild; per-tick frame.cursor decides what is drawn. - Windows: Capturer::set_cursor_forward → retained IOCTL_SET_CURSOR_FORWARD sender (proto v6) → driver (un)declares the IddCx hardware cursor on the LIVE monitor. Un-declare re-issues the setup with EMPTY caps (candidate mechanism — the DDI has no documented un-setup); resetup-on-commit is skipped while off, so a mode commit's software-cursor default is the fallback path. Failure against a pre-v6 driver logs and keeps declared-at-ADD behavior. - SDL client: one edge-detected reconciler at the cursor pump site — desktop-active = client draws; capture model or released = host composites. Covers the chord, the M3 auto-flip, and engage/release. - C ABI v12: punktfunk_connection_set_cursor_render (additive; wire version unchanged — pre-§8 hosts ignore the unknown message type). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c3cbffe662 |
feat(host+client): host-driven mouse-model flip — remote-desktop sweep M3
The full Parsec model: launching a game from a desktop session flips the client into captured relative automatically, and back — no chords. - Capture overlay now distinguishes 'hidden' from 'no cursor yet': CursorOverlay grows a visible flag, overlay() returns Some whenever a bitmap is known (the encode loop strips invisible overlays after forwarding so no blend path ever draws one; the CPU composite guarded on visibility already). - Host forwarder maps it onto the reserved wire bit: visible ⇒ VISIBLE, hidden-but-known ⇒ RELATIVE_HINT (an app grabbed/hid the pointer), never any hint before the first bitmap — a cold start can't flip a desktop session into capture. - Presenter: edge-triggered auto-flip on hint changes via the new Capture::set_desktop (chord semantics untouched); a manual ⌃⌥⇧M sets an override latch that holds until the HOST's intent next changes, so the hint never fights the user. Leaving relative warps the local cursor to the host's last pointer position through the new content_to_window inverse-letterbox mapping (unit-tested against finger_to_content) — the hand-back is seamless. Verified on .21: fmt + clippy -D warnings + tests (presenter 20 incl. the inverse-mapping roundtrip, host 245). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4a01bc4463 |
feat(core+host+client): cursor channel — remote-desktop sweep M2a+M2b
The host cursor stops riding the video and becomes a real OS cursor on the client (the Parsec/RDP model): pointer feel no longer pays the capture→encode→network→decode→present round trip. Wire (M2a): - Hello grows a client_caps trailing byte (CLIENT_CAP_CURSOR) after the fixed display_hdr block — presence disambiguated by remaining length, which caps the post-HDR tail at 27 bytes (documented); Welcome answers HOST_CAP_CURSOR (capable-and-asked, the 444/clipboard precedent). - CursorShape (0x50, control stream): serial + dims + hotspot + straight RGBA, ≤120px/side so the u16 frame always fits (128² would overshoot); client caches by serial — re-showing a known shape costs 14 bytes, not a bitmap (RDP pointer-cache for free). - CursorState (0xD0 datagram): serial + visible/relative_hint flags + position, sent once per encode-loop tick — latest-wins, self-healing under loss, no refresh timer. relative_hint is reserved for M3. - Client core: two new planes (control-task + datagram-task arms) → next_cursor_shape/next_cursor_state; connect() grows client_caps (C ABI passes 0 until the v11 cursor poll fns exist). Host (M2b, Linux portal only): - handshake::cursor_forward is THE predicate (client asked ∧ Linux ∧ compositor ≠ gamescope) — Welcome bit and session wiring both read it. - SessionPlan.cursor_blend goes false for a forwarding session; the encode loop ticks a CursorForwarder every iteration: shape-serial diff → control-task bridge (mirrors probe_result), state datagram → conn. - CursorOverlay/capture CursorState carry the hotspot through (nearest-neighbor downscale backstop for XL cursors, unit-tested). Presenter: - CursorChannel drains both planes per loop iteration; shapes become SDL color cursors (from_surface + hotspot), applied while the desktop mouse model is engaged; visibility follows the host; capture/released hands back the system cursor. Sessions advertise the cap when they START in desktop mode. Verified on .21: fmt + clippy -D warnings (7 crates) + tests green (core 218 incl. new wire roundtrips, host 245 incl. e2e + forwarder downscale tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
df04880273 |
feat(client/input): desktop (absolute) mouse mode — remote-desktop sweep M1
New physical-mouse model beside capture: Desktop leaves the pointer uncaptured and sends absolute positions through the letterbox (MouseMoveAbs — every host injector already consumes it). Capture stays the default and the game model. - pf-client-core: MouseMode (capture|desktop) persisted in Settings, default capture so existing stores are unchanged. - pf-presenter: Capture grows a desktop model — latest-wins pending abs position coalesced per loop iteration (same 1000 Hz discipline as relative), flushed before clicks/keys/wheel so they land where the cursor is; Ctrl+Alt+Shift+M flips the model live; local cursor stays hidden over the window (the host's composited cursor is the one you see until the M2 cursor channel); Windows keyboard grab only engages for capture (a desktop stream is something you Alt-Tab away from). - gamescope gating: its EIS is relative-only, so desktop mode is pinned off there (resolved_compositor), with a log note. - Settings surfaces: GTK row (dynamic caption), WinUI combo, console-UI row + step test, capture-hint line. Verified: fmt + clippy -D warnings + tests (33/40/19) on Linux .21; clippy -D warnings for all five crates incl. punktfunk-client-windows native on .173. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
49c6dd00c9 |
fix(presenter): CPU frames never take the HDR10 passthrough surface
audit / bun-audit (push) Failing after 15s
ci / docs-site (push) Successful in 56s
ci / web (push) Successful in 58s
apple / swift (push) Successful in 1m22s
audit / cargo-audit (push) Successful in 2m20s
decky / build-publish (push) Successful in 32s
ci / bench (push) Successful in 7m0s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 13s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 12s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 15s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 14s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7m18s
release / apple (push) Successful in 9m28s
deb / build-publish-host (push) Successful in 10m32s
deb / build-publish (push) Successful in 11m48s
docker / deploy-docs (push) Successful in 23s
android / android (push) Successful in 14m36s
arch / build-publish (push) Successful in 15m4s
windows-host / package (push) Successful in 16m11s
flatpak / build-publish (push) Failing after 8m19s
apple / screenshots (push) Successful in 6m40s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m11s
ci / rust (push) Successful in 19m38s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m22s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m29s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m39s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m39s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m36s
Software decode uploads swscale RGBA with no CSC/tonemap pass. On the new desktop-compositor HDR10 surfaces (GNOME 48 / Plasma 6 + Mesa >= 25.1 offer ST2084 even on SDR desktops) that sRGB-encoded content was composed as PQ — the field-reported psychedelic picture (Fedora clients decode HEVC in software because stock Mesa strips it; reproduced + fix live-validated on GNOME Wayland: mode-0 soup -> HDR→SDR washed-out-but-correct). The CPU lane keeps the SDR swapchain and its known untonemapped-PQ gap; a real PQ→sRGB pass for CPU frames is the follow-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e7c6c96e5f |
fix(presenter): set the Wayland app_id so the session window gets its icon
SDL_APP_ID = io.unom.Punktfunk — compositors match it against the shipped .desktop for the window icon; without it KWin/mutter show the generic Wayland icon (field-noticed on the Deck). Linux analog of win32 AppUserModelID. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
da134ad045 |
feat(presenter): PUNKTFUNK_HDR10=0 refuses the HDR10 swapchain (SDR tonemap pin)
GNOME 48 / Plasma 6 with Mesa >= 25.1 now offer HDR10_ST2084 surfaces even on SDR desktops, silently flipping PQ streams into the mode-0 passthrough lane — previously unreachable on desktop compositors and never validated there. The hatch pins the shader tonemap for diagnosis and as the field escape. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
40c2f68231 |
feat(client): device-pick plumbing — GPU enumeration + audio endpoint targeting
The Settings GPU pick existed (adapter → PUNKTFUNK_VK_ADAPTER) but no Linux shell could enumerate anything to pick: the GTK shell deliberately links no Vulkan. pf_presenter::vk::list_adapters() reads the physical devices' marketing names (no surface, discrete first, deduped — the name is the whole match key in pick_device), surfaced as `punktfunk-session --list-adapters`. Audio gets the same treatment for the new speaker_device/mic_device settings (PipeWire node.name; empty = default): session main maps them onto PUNKTFUNK_AUDIO_SINK/SOURCE — a hand-set env still wins, like the adapter — and the playback/mic streams pass them as `target.object` (raw key: the keys::TARGET_OBJECT constant is feature-gated on a newer libpipewire than we require). pf_client_core::audio::devices() is the registry roundtrip the pickers read, exposed for debugging as `punktfunk-session --list-audio`. The WASAPI leg (Windows endpoint IDs) is still to come; the fields are ignored there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
51f7ff1b3f |
feat(client/windows): invert scroll direction
The Apple client's toggle, wired through the shared session presenter: Settings::invert_scroll -> SessionOpts -> Capture, applied at the single seam where wheel deltas enter (before accumulation, so the fractional remainders stay consistent with what was actually sent). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1ef0229bd9 |
feat(client/windows): PyroWave decode + surface it in the GUI codec picker
The decoder was gated to Linux because, when it landed, the Windows client still had its own in-process WinUI/D3D11 presenter and the PyroWave present path there was an open question. That client has since been retired: Windows now spawns the SAME Vulkan session presenter as Linux, and the decoder is plain Vulkan compute on the presenter's device (no fds, no dmabuf, no D3D11 interop), so the question that gated it answered itself. pyrowave-sys already builds on Windows too -- the Windows HOST encoder ships on it. So this is a port by un-gating: every cfg(all(target_os = "linux", feature = "pyrowave")) becomes any(linux, windows) -- decoder module, backend variant, Decoder::new_pyrowave, the CODEC_PYROWAVE advertisement, the session pump's opt-in/build/label arms, and the presenter's planar CSC pass. No new code. Then offer it in the Windows GUI, which is what prompted this. It stays preference-only (resolve_codec never auto-picks it) and a host or device that can't do PyroWave just falls back down the ladder to HEVC. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fb8deb31a5 |
feat(client): HDR pass-through on the D3D11VA path
A PQ stream on the D3D11VA backend was always tone-mapped to sRGB by the
VideoProcessor — with D3D11VA now auto's first choice on Intel (
|
||
|
|
188edde2b3 |
feat(pyrowave): Windows host HDR + 4:4:4, Rust client HDR present
Phase 3 of design/pyrowave-444-hdr.md. A PyroWave session now negotiates HDR
(10-bit) and 4:4:4 on a Windows host exactly like HEVC/AV1, and the Linux
client presents it through the real HDR10 path.
Host (Windows): BgraToYuvPlanes becomes mode-aware — SDR/BGRA and HDR/scRGB
variants at half- or full-res chroma. The HDR passes reuse HdrP010Converter's
exact colour math (scRGB -> PQ BT.2020 limited studio codes, verified by
hdr_p010_selftest) but write P010-style MSB-packed codes into two separate
shareable R16_UNORM/R16G16_UNORM textures; chroma keeps the pyrowave family's
centre-sited 2x2 box. idd_push pins the composition to the NEGOTIATED depth
(SDR sessions force advanced color off as before; 10-bit sessions enable it
and ride the FP16 ring), and the descriptor poller re-asserts that state
instead of following display flips the fixed-format encoder can't. The
encoder imports 8/16-bit planes per session and stamps the sequence header's
BT.2020/PQ/matrix bits on HDR (stamp_color_bits, extending 574e3e4e's range
stamp); supports_10bit/can_encode_10bit/can_encode_444 gates open (HDR
Windows-only — Linux capture has no HDR source).
Client: the plane ring becomes R16_UNORM for 10-bit sessions (with a
STORAGE_IMAGE format probe), the planar CSC pass joins the HDR10 swapchain
rebuild (set_hdr_mode previously destroyed it without rebuilding — latent),
st.hdr follows frame.color.is_pq(), and the planar push constants carry
depth-10 MSB-packed rows + the PQ tonemap mode, identical to the NV12 arm.
Verified: .173 (RTX 4090) deploy-config clippy + fmt + wire tests + the
extended pyrowave_win_smoke (10-case {SDR,HDR}x{420,444} matrix incl. R16
imports and header stamps); .21 (RTX 5070 Ti) clippy across 4 crates, host
186 tests, client/presenter/encode tests, both Linux GPU smokes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
aedee2a4e3 |
perf(latency): tier-0 attribution + tier-1 send-path levers from the latency plan
ci / web (push) Successful in 49s
ci / docs-site (push) Successful in 54s
decky / build-publish (push) Successful in 18s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
apple / swift (push) Successful in 1m23s
ci / rust (push) Failing after 4m15s
arch / build-publish (push) Failing after 4m35s
deb / build-publish (push) Failing after 4m4s
docker / deploy-docs (push) Successful in 24s
ci / bench (push) Successful in 5m35s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 3m45s
windows-host / package (push) Failing after 9m8s
release / apple (push) Successful in 5m49s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 5m25s
flatpak / build-publish (push) Failing after 8m3s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m58s
android / android (push) Successful in 14m9s
apple / screenshots (push) Successful in 6m28s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 7m0s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 9m28s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 12m26s
design/latency-reduction-2026-07.md T0.1/T0.2/T1.2/T1.3:
- T1.2 rate-capped front-loaded pacing: the paced overflow's budget is now
min(0.9x slack, overflow wire time at ~3x the live encoder bitrate)
(PUNKTFUNK_PACE_FACTOR, 0 = legacy deadline-only spread). A 300 KB-1 MB
frame's tail leaves in ~2-5 ms instead of smearing across ~15 ms at 60 fps;
GameStream schedule byte-identical (pins unchanged).
- T1.3 data-first wire order: packetize emits every block's data shards before
any parity (per-block parity pools keep all blocks' parity alive for the
second pass), so lossless completion stops waiting behind the parity tail.
EOF = last emitted packet; receiver already order-agnostic.
- T0.1 staged 0xCF: HostTiming gains an append-extensible per-stage tail
(queue/encode/pace us; seal+channel-wait derived as residual) - no cap bit
needed, old peers read the 13-byte prefix. Joined client-side into
Stats::host_{queue,encode,xfer,pace}_ms, the OSD detailed tier, and the
probe's report.
- T0.2 true on-glass present timing: VK_KHR_present_id/present_wait enabled
when supported; a PresentTimer waiter thread resolves each present id to
real visibility, replacing the submit-time display stamp (which undercounts
by up to a refresh and hides a silent-FIFO standing queue).
Validated on .21: core 185 + host 185 tests, pf-presenter 19, clippy
-D warnings across all five touched crates; loss-harness recovery curve
unchanged; C ABI harness round-trips.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
871ebb31ce |
feat(clients): render-scale setting on every client — shared punktfunk_core::render_scale
Client-side supersampling/downscaling: the client asks the host to render and encode at chosen-resolution × scale (the host does no scaling) and the presenter rescales the decoded frame to the display. >1 supersamples for sharpness; <1 lightens the host GPU and the link. Default 1.0 = Native, the prior behavior. The geometry lives once in punktfunk_core::render_scale (multiply, preserve aspect ratio, floor to even, clamp to the codec's per-axis ceiling — 4096 for H.264, 8192 otherwise), the Rust twin of the Apple client's RenderScale.swift, consumed by the native session client, the presenter's match-window path, the Windows/Linux settings UIs, Decky, and Android (settings + host connect + unit test). Implemented and platform-verified by the Apple-client-features session (Linux+Android+Apple green there); the punktfunk-core wiring (pub mod render_scale) is restored here after being lost in a working-tree reconciliation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
61118cbdd4 |
refactor(presenter/W8): split vk.rs into vk/ directory module
Break the 2513-line pf-presenter/src/vk.rs into a vk/ directory module (mod.rs +
6 concern submodules), keeping ALL type definitions in vk/mod.rs so every submodule
(a descendant of vk) sees the Presenter/OverlayPipe/etc. private fields with no
field bumps:
- vk/setup.rs : Presenter::new + device/format/present-mode selection
- vk/present.rs : the per-frame present path (present + CSC record + AVVkFrame
sync) — HOT PATH, moved whole
- vk/reconfig.rs : swapchain recreate/resize + HDR reconfiguration
- vk/resources.rs : video-image/staging (re)build + Retired-frame destruction
- vk/overlay_pipe.rs: the presenter-side overlay composite pipeline
- vk/gpu.rs : memory allocation, image barriers, geometry helpers (+ tests)
vk/mod.rs keeps FrameInput/Presenter/OverlayPipe/VideoImage/Staging/Retired/HwCtx*
+ the public accessors + Drop. Methods/free-fns a sibling submodule calls became
pub(super) (~18); zero field bumps, zero re-exports (Presenter/FrameInput never
leave mod.rs). lib.rs unchanged (`pub mod vk;` resolves to vk/mod.rs). The moved
overlay shader include_bytes! gained one `../` for the deeper dir. Pure move; no
behavior change; the hot present path keeps only static pub(super) calls (inlinable).
Verified both platforms: Linux (home-worker-5) clippy -p pf-presenter
(--all-targets -D warnings) + test; Windows (winbox, ASCII CARGO_HOME) clippy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
11045a0f70 |
chore: consolidate parallel-session WIP (HOLD — do not push)
Local snapshot of intermingled in-flight work, committed to unblock the encode
refactor (a clean ffmpeg_win.rs for the vbv-dedup follow-on). These hunks span
the same files and can't be cleanly split here; the commit bundles three
distinct workstreams that each belong in their own PR:
- logging rework (~43 files: level re-tiering, structured fields, `?e`,
hot-path flood latches)
- conflicting-host detection (detect.rs + detect/{linux,windows}.rs + wiring
in main.rs/mgmt.rs/Cargo.toml/docs/packaging)
- standby-sink DWM-stall attribution (windows/display_events.rs + capture/
vdisplay wiring)
NOT verified as a combination. NOT to be pushed until the refactor is done and
these are re-verified and reorganized into their proper per-workstream PRs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
a70811043e |
feat(presenter,docs): PyroWave mid-stream resize — HUD follows any mode switch; docs
ci / web (push) Successful in 52s
ci / docs-site (push) Successful in 54s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
decky / build-publish (push) Successful in 17s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 44s
ci / bench (push) Successful in 5m47s
flatpak / build-publish (push) Failing after 8m11s
docker / deploy-docs (push) Successful in 27s
arch / build-publish (push) Successful in 10m58s
deb / build-publish (push) Successful in 12m26s
android / android (push) Successful in 16m59s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m16s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 17m59s
ci / rust (push) Successful in 24m26s
windows-host / package (push) Failing after 12m3s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m50s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m16s
apple / swift (push) Successful in 4m57s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m14s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 6m3s
release / apple (push) Successful in 25m6s
apple / screenshots (push) Has been cancelled
- pf-presenter: the HUD/title mode line lived inside the match-window (D2)
gate, so an accepted switch from any OTHER trigger (the
PUNKTFUNK_DEBUG_RECONFIGURE lever, a host-side corrective rollback) left the
label stale. Hoisted into its own per-iteration tick that runs whenever a
stream is up.
- docs: pyrowave.md — the Automatic bitrate pin now follows a mid-stream
resize; drop the "resolution changes rebuild the stream" limitation.
Completes the resize-rebuild work whose core landed in
|
||
|
|
705a8baddf |
feat(core,host,client): PyroWave datagram-aligned packets + partial-frame delivery (Phase 4, §4.4)
ci / web (push) Successful in 1m10s
ci / docs-site (push) Successful in 1m16s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 12s
decky / build-publish (push) Successful in 27s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
apple / swift (push) Successful in 4m59s
ci / rust (push) Failing after 6m17s
ci / bench (push) Successful in 6m36s
docker / deploy-docs (push) Successful in 23s
flatpak / build-publish (push) Failing after 8m29s
arch / build-publish (push) Successful in 11m11s
android / android (push) Successful in 12m33s
deb / build-publish (push) Successful in 14m8s
windows-host / package (push) Successful in 14m56s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 15m2s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m4s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 4m29s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m6s
windows / build (aarch64-pc-windows-msvc) (push) Failing after 5m15s
release / apple (push) Successful in 26m8s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 5m48s
apple / screenshots (push) Successful in 20m29s
PyroWave AUs now packetize on the negotiated shard payload, so a lost datagram
costs a few wavelet blocks of localized blur rather than a whole frame — and the
client can render an aged-out lossy frame instead of freezing until the next one.
Host (opt-in, PyroWave only):
- The encoder packetizes at the shard payload behind a 4-byte window prefix
(used-len u16 + kind u16). Whole packets pack into WIN_PACKED windows; a packet
too large for one shard (PyroWave 32x32 blocks are atomic and can exceed a
shard) rides a WIN_FRAG_FIRST/CONT/LAST chain. `set_wire_chunking()` joins the
Encoder trait (forwarded through TrackedEncoder — the silent-no-op trap);
EncodedFrame.chunk_aligned marks the AU.
- virtual_stream tags the AU with USER_FLAG_CHUNK_ALIGNED and re-applies chunking
after every encoder (re)build, the adaptive-bitrate rebuild included.
Core:
- USER_FLAG_CHUNK_ALIGNED (0x40) wire bit. Reassembler opt-in
(set_deliver_partial): a chunk-aligned frame that ages out with holes is handed
over as Frame{complete:false} — received shards at their exact offsets, missing
ranges zero-filled — instead of being dropped. Partials age out on a tight 30ms
fuse (PARTIAL_WINDOW_NS) instead of the 120ms loss window: each frame is
independently decodable, so an ancient partial has no value in a live stream.
Newest-wins. A partial still counts as dropped for loss reporting.
Client (PyroWave decode):
- The session opts in when codec == PyroWave. The decoder walks the AU
window-by-window, skipping zero (missing) windows and reassembling FRAG chains,
then decodes whatever survived. A newest-decoded-index guard drops partials the
pump has already moved past (no time-travel present).
Also fixes a redundant-closure clippy nit in the PyroWave planar-present path.
Validated on an RTX 5070 Ti under 2% netem loss with FEC pinned off: 60fps
sustained entirely via partials, e2e 43ms p50 (146ms before the fuse) vs 23ms
lossless, no keyframe-recovery chatter. Tests green: core 149, host 310 + the
GPU-gated encoder smoke (framed-window walk + FRAG reassembly + upstream
round-trip), client 26; clippy clean on the pyrowave feature combos.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
f77eec1299 |
feat(client): PyroWave planar present path + Linux NVENC match-arm fix (Phase 2b, part 2)
android / android (push) Failing after 45s
ci / web (push) Successful in 51s
ci / docs-site (push) Successful in 56s
decky / build-publish (push) Successful in 17s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7s
arch / build-publish (push) Failing after 4m15s
ci / rust (push) Failing after 3m59s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 59s
deb / build-publish (push) Failing after 4m7s
ci / bench (push) Successful in 5m28s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 3m46s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 3m58s
flatpak / build-publish (push) Failing after 8m3s
docker / deploy-docs (push) Successful in 22s
windows-host / package (push) Successful in 14m21s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m31s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m35s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m57s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m46s
apple / swift (push) Successful in 4m59s
apple / screenshots (push) Successful in 21m40s
The arch package job (--features nvenc) tripped the same class of Codec::PyroWave non-exhaustive matches as windows-host had, in nvenc_cuda.rs (6 sites) — dispatch-guarded unreachable!() arms, plus the vk_util-extraction leftover unused imports in vulkan_video.rs. All Linux host feature combos (none / pyrowave / nvenc,vulkan-encode / all three) now compile clean on .21. Presenter: planar_csc.frag (+ committed .spv) — the 3-plane variant of nv12_csc.frag (separate Cb/Cr R8 planes, same push-constant CSC-row contract, siting correction self-disables at full-res chroma). CscPass grows a shared builder + new_planar()/bind_planes_planar() (GENERAL-layout descriptors — pyrowave planes stay GENERAL); the Vk presenter builds the planar pass when the device passed the pyrowave probe, FrameInput::PyroWave rides present_frame (no acquire barrier needed: the decoder fence-completed and barriered the planes on the same queue), and run.rs presents it with no demote rung (only device loss ends the session). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
575975687c |
feat(client): PyroWave decode backend on the presenter's device (Phase 2b, part 1)
ci / web (push) Successful in 55s
ci / docs-site (push) Successful in 1m2s
ci / rust (push) Failing after 4m1s
decky / build-publish (push) Successful in 18s
arch / build-publish (push) Failing after 4m45s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 7s
deb / build-publish (push) Failing after 4m46s
ci / bench (push) Successful in 5m12s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 3m47s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 4m39s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m41s
flatpak / build-publish (push) Successful in 6m20s
android / android (push) Successful in 13m47s
docker / deploy-docs (push) Successful in 28s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m59s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m55s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 6m50s
apple / swift (push) Successful in 4m45s
apple / screenshots (push) Successful in 20m33s
The presenter's device creation now probes + enables the PyroWave compute feature set alongside the Vulkan Video probe (shaderInt16, storageBuffer8BitAccess, subgroup size control — gated on support, harmless when unused) and exports the facts through VulkanDecodeDevice (pyrowave_decode capability + feature bools + apiVersion + the queue- family shape). pf-client-core (feature `pyrowave`, Linux): video_pyrowave.rs — the decoder runs pyrowave compute on the PRESENTER's own VkDevice, zero interop (plan §4.5): pinned content-equivalent create-info reconstruction satisfies pyrowave 0.4.0's lifetime rule without refactoring the presenter's creation; queue access rides the existing device-wide QueueLock (the FFmpeg/Skia contract); decode records into our command buffer, fence-synchronous (sub-ms), into a 4-deep ring of 3xR8 plane sets (decode REQUIRES storage usage + identity swizzles, so the encoder's RG8 trick doesn't apply). Backend::PyroWave + DecodedImage::PyroWave + Decoder::new_pyrowave + decodable_codecs_for (advertisement gated on the device probe) wired through the decode dispatch; no demote ladder (nothing else decodes it — fallback is session renegotiation, plan §4.6). Still to come for a live session: the presenter's planar-CSC render path for the new variant, pump/shell opt-in (preferred_codec) wiring, and the on-glass .21 run. Validated on .21: pf-client-core + pf-presenter compile with and without the feature, clippy clean, 26 client-core tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
46b7ffc001 |
fix(client): Linux auto decoder tries VAAPI before FFmpeg-Vulkan on desktop Mesa
ci / web (push) Successful in 48s
ci / docs-site (push) Successful in 56s
decky / build-publish (push) Successful in 16s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m1s
arch / build-publish (push) Successful in 11m4s
ci / bench (push) Successful in 6m7s
android / android (push) Successful in 15m32s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7m29s
flatpak / build-publish (push) Failing after 8m12s
deb / build-publish (push) Successful in 11m17s
ci / rust (push) Successful in 22m25s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m11s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m41s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m36s
docker / deploy-docs (push) Successful in 26s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m30s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m8s
apple / swift (push) Successful in 4m47s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m56s
apple / screenshots (push) Successful in 19m45s
Mesa now exposes Vulkan Video decode queues by default (and the session binary opts RADV in for the Deck's sake), which silently moved every desktop AMD/Intel box onto FFmpeg-Vulkan-on-Mesa under `auto` — user-reported (CachyOS/KDE) to judder or error-streak into the software demotion while an explicit VAAPI pick streams perfectly. Auto's hardware order is now device-aware (`VulkanDecodeDevice::prefer_vulkan_over_vaapi`, fed vendor id + device name by the presenter): Vulkan-first stays only where it is the established right answer — NVIDIA (no usable VAAPI) and the Deck's VanGogh (VAAPI dmabuf import chroma-fringes) — and everything else gets the battle-tested zero-copy VAAPI first, with Vulkan as its fallback. A mid-session Vulkan failure streak now also demotes to VAAPI before software, so a broken Mesa Vulkan path can never strand a box with a perfectly good VAAPI driver on CPU decode. The GTK shell's decoder setting gains the missing "Vulkan Video" option (values now mirror the console UI's auto/vulkan/vaapi/software) and drops its pre-Vulkan "Automatic (VAAPI → software)" label. Verified on the RTX 5070 Ti box (loopback session, auto → "Vulkan Video hardware decode active", 60 fps); policy locked by unit test; clippy -D warnings + pf-client-core/pf-presenter tests green on Linux. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6db91cbf40 |
feat(client): 3-finger swipe toggles the on-screen keyboard mid-stream
iOS + Android: a three-finger vertical swipe up/down summons/dismisses the device soft keyboard while streaming (trackpad + pointer modes). Mobile scroll is now exactly two fingers so it never collides with the 3+-finger gesture (3+ only fell into the old `>= 2` scroll path by accident). Android: a TYPE_NULL KeyCaptureView plus IME meta-shift wrapping feeds key events through. iOS: UIKeyInput plus a SoftKeyMap char->VK table with a GCKeyboard dup gate so a hardware keyboard and the soft keyboard don't double-emit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
927a571414 |
feat(console): touch-mode setting + request-access pairing + polish
Extend the gamepad/console shell (pf-console-ui) to parity with the other clients: - Settings gain a Touchscreen → Touch mode row (Trackpad / Direct pointer / Touch passthrough), the one couch-relevant Settings field the screen lacked. - The pair screen adds the no-PIN delegated-approval path: a "Request access" action (only when the host advertises a fingerprint to pin) opens a connect the host PARKS until the operator approves this device, then persists it as paired. A role-based row model keeps the cursor off stale indices; manual hosts stay PIN-only, matching the desktop shells. - Threads request_access through OverlayAction::Launch and ConnectIntent; the shell shows a "Waiting for approval…" takeover, and the session binary parks on a 185 s budget (PendingApproval → persist-as-paired via on_connected). Auto-wake (WoL) was already implemented end-to-end and is left as-is. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
49533ff90a |
style(touch): rustfmt the presenter finger dispatch + gesture engine
ci / web (push) Successful in 52s
ci / docs-site (push) Successful in 1m5s
apple / swift (push) Successful in 4m33s
ci / bench (push) Successful in 5m22s
android-screenshots / screenshots (push) Successful in 3m30s
ci / rust (push) Successful in 23m9s
decky / build-publish (push) Successful in 1m23s
android / android (push) Successful in 12m54s
arch / build-publish (push) Successful in 13m16s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 55s
web-screenshots / screenshots (push) Successful in 2m51s
docker / deploy-docs (push) Successful in 9s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 28s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 23s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 24s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 30s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 30s
flatpak / build-publish (push) Successful in 7m4s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m19s
linux-client-screenshots / screenshots (push) Successful in 7m45s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m47s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m47s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m51s
deb / build-publish (push) Successful in 12m5s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m2s
apple / screenshots (push) Successful in 19m24s
windows-host / package (push) Successful in 9m19s
release / apple (push) Successful in 27m34s
Wrap the long dispatch_finger call args, Abs struct literals, and Act::Button/ Scroll/MoveRel pushes per rustfmt (the CI fmt check on pf-presenter). No behavior change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
f88d0ae4dc |
feat(touch): cross-client touch-input modes on Linux + Windows
ci / web (push) Successful in 43s
ci / rust (push) Failing after 53s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
decky / build-publish (push) Successful in 16s
ci / docs-site (push) Successful in 1m5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 7s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m41s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m53s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 52s
apple / swift (push) Successful in 4m41s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 54s
ci / bench (push) Successful in 6m30s
docker / deploy-docs (push) Successful in 23s
flatpak / build-publish (push) Failing after 8m9s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
android / android (push) Has been cancelled
apple / screenshots (push) Has been cancelled
arch / build-publish (push) Has been cancelled
deb / build-publish (push) Has been cancelled
Bring the SDL presenter (Linux/Deck + Windows) to parity with the Android and
Apple clients: a persisted TouchMode selects how a touchscreen drives the host —
* Trackpad (default): relative cursor with pointer ballistics + the shared
gesture vocabulary (tap = left click, two-finger tap = right click,
two-finger drag = scroll, tap-then-drag = held left drag, three-finger tap =
cycle the stats overlay).
* Direct pointer: the cursor jumps to and follows the finger (absolute).
* Touch passthrough: every finger is a real host touchscreen contact.
Previously the presenter had no finger handling, so SDL synthesized mouse events
from touch and — under the stream's relative-mouse lock — walked the host cursor
into the corner (the reported Deck bug). SDL touch->mouse synthesis is now off;
DIRECT touchscreens route through a new incremental gesture engine (a port of
Android TouchInput.kt / Apple TouchMouse.swift), while INDIRECT trackpads keep
driving the mouse. Fingers map through the aspect-fit letterbox onto the content
rect.
TouchMode lives in the shared trust::Settings (default trackpad, so passthrough
is opt-in like the other clients); the GTK and WinUI settings screens both gained
a "Touch input" picker. Gesture engine, letterbox mapping, and settings
back-compat are unit-tested (28 tests green); clippy -D warnings clean; full
Linux client + session build verified on-host.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
2271f67202 |
style: rustfmt the recovery + resize changes (Windows CI fmt check)
ci / web (push) Successful in 59s
ci / docs-site (push) Successful in 1m1s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7s
decky / build-publish (push) Successful in 18s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 59s
apple / swift (push) Successful in 4m26s
ci / bench (push) Successful in 5m53s
ci / rust (push) Failing after 7m35s
windows-host / package (push) Failing after 8m56s
flatpak / build-publish (push) Failing after 8m12s
arch / build-publish (push) Successful in 11m41s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m54s
android / android (push) Successful in 13m28s
deb / build-publish (push) Successful in 13m42s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m16s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m42s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m33s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m0s
docker / deploy-docs (push) Successful in 20s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m48s
release / apple (push) Successful in 25m4s
apple / screenshots (push) Successful in 19m32s
The `cargo fmt --check` step on the x86_64-pc-windows-msvc job was failing: the mid-stream loss-recovery and resize-overlay commits landed with unformatted wraps across pf-presenter, pf-client-core, punktfunk-core, pf-console-ui, and a few host files. Applied `cargo fmt`, and hand-relocated two trailing comments in session.rs (a decoded-frame note and the wrap-counter note) to their own lines so rustfmt no longer column-aligns the following comment block to a deep indent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
89aa6767f9 |
feat(resize): scrim+spinner resize overlay in the shared presenter (Windows + GTK4)
ci / docs-site (push) Failing after 29s
ci / web (push) Successful in 43s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
decky / build-publish (push) Successful in 33s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 7s
docker / deploy-docs (push) Successful in 20s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m52s
apple / swift (push) Successful in 4m55s
ci / bench (push) Successful in 6m37s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m2s
arch / build-publish (push) Successful in 12m42s
android / android (push) Successful in 12m51s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m7s
deb / build-publish (push) Successful in 13m4s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m24s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m56s
apple / screenshots (push) Successful in 19m31s
ci / rust (push) Failing after 39s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 1m4s
flatpak / build-publish (push) Failing after 8m4s
The mid-stream Match-window trigger + Resolution tri-state already shipped on BOTH desktop clients via the session-always punktfunk-session binary (pf-presenter D1/D2, C1). This ports the last Apple-parity piece — the resize-in-progress indicator (clients/apple ResizeIndicator/ResizeIndicatorView) — into the SHARED pf-presenter overlay, so one implementation covers both the Windows and GTK4 session windows. - ResizeIndicator (run.rs): the Apple state machine in Rust — `steering` (a switch was requested) shows it, `decoded` (a frame reached the target size) clears it, `tick` times it out after 2.5 s for a switch the host rejected/capped. The live drag stays sharp; only the host's 0.3-2 s virtual-display + encoder rebuild gap is covered. A present-while-resizing path keeps the spinner animating through that frame-less gap. - DecodedImage::dimensions() (pf-client-core): the END signal — a decoded frame at the target size means the sharp new-mode picture is on glass (the accept ack alone lands ahead of the host's rebuild). Mirrors is_keyframe()'s cfg arms. - FrameCtx.resizing (pf-presenter/overlay.rs) + Skia draw (pf-console-ui): a full-screen 55% scrim + the shared rotating theme::spinner + "Resizing…" label. The overlay composites its own RGBA quad and can't sample the video to blur it as SwiftUI does, so a scrim stands in for the blur — same intent, one draw. resizing_since clocks the spinner; Drawn.resize_step defeats the damage gate so it redraws each frame. Verified on Linux: pf-presenter/pf-console-ui/session/linux-client build + clippy -D warnings clean; 8 pf-presenter tests green incl. 2 new ResizeIndicator tests. Windows session-binary compile (cfg-symmetric) + live on-glass both pending. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |