b0d28380b50a802affaa7dcabd202571b39bbd40
59 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9c2499fd45 |
feat(host/windows): DXGI Desktop Duplication capture backend
apple / swift (push) Successful in 53s
android / android (push) Failing after 2m25s
ci / web (push) Successful in 28s
ci / docs-site (push) Failing after 19s
ci / rust (push) Failing after 52s
decky / build-publish (push) Successful in 11s
ci / bench (push) Successful in 1m36s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
flatpak / build-publish (push) Failing after 2s
deb / build-publish (push) Successful in 3m22s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Failing after 1m18s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Failing after 1m42s
docker / deploy-docs (push) Successful in 21s
Windows Capturer via DXGI Desktop Duplication: create a D3D11 device on the SudoVDA adapter (by LUID), find the matching output (by GDI name), DuplicateOutput, and per AcquireNextFrame copy the desktop into a CPU-readable staging texture -> tightly-packed BGRA (FramePayload::Cpu, feeds the openh264 software encoder GPU-lessly). Handles WAIT_TIMEOUT (reuse last frame) and ACCESS_LOST (re-duplicate). Adds FramePayload::D3d11(D3d11Frame) for the future NVENC zero-copy path, and a VirtualOutput.win_capture identity (adapter LUID + GDI name) carried out of the SudoVDA backend. Pure helpers (pack_luid/gdi_name_matches/depad_bgra) unit-tested on the VM; the live duplication path needs a real GPU + an activated SudoVDA monitor. Compiles clean on Windows + Linux. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
dad5a08c1f |
chore(capture): tidy the GNOME flash diagnostic — it's the CORRUPTED skip
ci / docs-site (push) Failing after 40s
apple / swift (push) Successful in 1m17s
ci / rust (push) Successful in 1m20s
ci / web (push) Failing after 34s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 5s
deb / build-publish (push) Successful in 2m52s
docker / deploy-docs (push) Successful in 17s
rpm / build-publish (push) Successful in 5m12s
Live confirmation on worker-3: the flash was Mutter's CORRUPTED, size-0 cursor-update buffers (chunk_flags=CORRUPTED) carrying recycled old frames — drained=1 always, so latest-frame-only draining wasn't the lever, the CORRUPTED skip was (OBS issue 8630). Demote the verbose drain diagnostic to a rate-limited debug line and document the root cause inline. Validated: zero-copy back on GNOME (dmabuf->CUDA, 5120x1440) AND flash-free with FORCE_SHM off. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d8da12bbbd |
fix(capture/mutter): latest-frame-only dequeue (the real GNOME flash fix)
ci / web (push) Failing after 39s
apple / swift (push) Successful in 1m18s
ci / rust (push) Successful in 1m22s
ci / docs-site (push) Failing after 44s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
deb / build-publish (push) Successful in 3m17s
docker / deploy-docs (push) Successful in 19s
rpm / build-publish (push) Successful in 4m42s
Deep research (OBS Studio's linux-pipewire, Mutter bug tracker) found the GNOME stale-frame flash is a buffer-RECYCLING race, not damage (Mutter sends whole frames, no SPA_META_VideoDamage) and not buffer count. OBS's proven fix is latest-frame-only dequeue: each process callback, drain ALL queued PipeWire buffers, requeue the older ones, and consume only the NEWEST — plus skip CORRUPTED buffers. Our code dequeued one buffer per callback (oldest-first) and the bounded channel dropped the NEWEST when full, so during Mutter's bursty delivery the encoder got stale frames → the flash. Switch the process callback to raw dequeue_raw_buffer + drain-to-newest (requeue older), extract the consume logic into consume_frame(spa_buf) sourcing datas via the transparent Data cast, skip SPA_META_HEADER_FLAG_CORRUPTED / CORRUPTED-chunk buffers (size-0 skip kept SHM-only so dmabuf isn't regressed), and remove the earlier content-hash drop heuristic (it couldn't tell stale re-deliveries from legit repeating content). Diagnostic logs drain depth + chunk/header flags. Reverts none of the FORCE_SHM / dmabuf_fence work. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
79508b2666 |
fix(capture/mutter): drop stale re-delivered frames (the GNOME flash)
ci / web (push) Failing after 40s
apple / swift (push) Successful in 1m17s
ci / docs-site (push) Failing after 37s
ci / rust (push) Successful in 1m20s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
deb / build-publish (push) Successful in 2m53s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (push) Successful in 5m11s
Instrumented worker-3: even on the ordered FORCE_SHM download path, Mutter re-delivers COMPLETE OLD pool buffers — 655 frames in a 15 s session whose content exactly matched an earlier frame (not damage-incremental; full old frames, in runs, ~45% during motion). NVIDIA gives no fence to prevent it, so the producer delivery can't be made clean from our side. Detect it and drop it: hash a spatial sample of each captured frame; a frame whose content equals an EARLIER distinct frame (vs the current one, whose duplicates pass through) is a stale re-delivery — skip it so the encoder never emits the flash (try_latest re-sends the last good frame; brief hold instead of a backward jump). Runs on the CPU/SHM path (where Mutter+NVIDIA capture lives); never triggers on static content or non-Mutter compositors (no reverts). PUNKTFUNK_KEEP_STALE=1 disables it for A/B. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f9b857aac2 |
feat(capture): true SHM path (PUNKTFUNK_FORCE_SHM) for race-free Mutter+NVIDIA
ci / web (push) Failing after 37s
apple / swift (push) Failing after 1m3s
ci / rust (push) Failing after 1m11s
ci / docs-site (push) Failing after 43s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 5s
deb / build-publish (push) Successful in 2m55s
docker / deploy-docs (push) Successful in 17s
rpm / build-publish (push) Successful in 5m17s
Empirically, Mutter+NVIDIA dmabuf capture has NO working GPU sync — confirmed on worker-3: explicit sync fails buffer alloc (EINVAL, no cogl sync_fd), and the dmabuf carries no implicit fence (EXPORT_SYNC_FILE waited=false). So any dmabuf read — zero-copy import OR mmap — races Mutter's render and flashes the buffer's previous frame. The prior "CPU fallback" still listed DmaBuf in its buffer types, so Mutter kept handing dmabufs and it never fixed anything (got worse). PUNKTFUNK_FORCE_SHM=1 offers MemPtr+MemFd ONLY (no DmaBuf), forcing Mutter to glReadPixels-download into mappable memory — which orders against its render, so the frame is complete + current by construction (race-free). Costs the download (~3 ms) + zero-copy; correct at 1080p/4K60. KWin/gamescope are unaffected (they blit into the buffer, no read-before-render race) and keep zero-copy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
92c6da9546 |
fix(capture/mutter): restore zero-copy + sync via dmabuf implicit fence
ci / web (push) Failing after 42s
apple / swift (push) Failing after 1m5s
ci / rust (push) Failing after 1m10s
ci / docs-site (push) Failing after 44s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 5s
deb / build-publish (push) Successful in 2m54s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (push) Successful in 5m13s
The previous attempt ( |
||
|
|
8531135bb7 |
fix(capture/mutter): stale-frame flashes + stuck input after disconnect on GNOME
ci / web (push) Failing after 49s
apple / swift (push) Failing after 1m4s
ci / rust (push) Failing after 1m9s
ci / docs-site (push) Failing after 42s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 5s
deb / build-publish (push) Successful in 2m58s
docker / deploy-docs (push) Successful in 17s
rpm / build-publish (push) Successful in 4m17s
Deep dive into the two GNOME-only host bugs (KWin/gamescope clean):
1. Stale-frame flashes (windows at old positions, typed text reverting):
Mutter renders its virtual monitors DIRECTLY into the PipeWire buffer
pool, and NVIDIA has no implicit dmabuf fencing — our zero-copy
import raced the render and encoded each pool buffer's PREVIOUS
contents. Fix, in order of preference:
- Consumer-side PipeWire explicit sync (SPA_META_SyncTimeline): new
drm_sync module (DRM timeline-syncobj wait/signal via raw ioctls,
unit-tested incl. a live signal->wait round trip); announced
post-format via update_params (the OBS pattern — at connect time
the meta makes producers fail allocation, observed on KWin), with
a blocks=3 Buffers filter so the producer's sync pod wins; acquire
point awaited before any read (GPU import or CPU mmap), release
point signaled on every path.
- Where the producer can't do explicit sync (Mutter on NVIDIA today:
no cogl sync_fd, "error alloc buffers"), a sticky fallback flips
the capture to the synchronous CPU/shm path — Mutter's glReadPixels
download orders against its render, so frames are correct by
construction. First session pays one ~10 s probe+retry; later
sessions go straight there. Validated live on home-worker-3
(GNOME 50 + RTX 4090): clean fallback, 30 MB HEVC streamed.
- Sync is only announced on Mutter sessions (new VirtualOutput.mutter
tag): KWin+NVIDIA fails allocation when merely asked, and doesn't
need it (verified unchanged: zero-copy CUDA import + 1.1 MB/10 s).
PUNKTFUNK_EXPLICIT_SYNC=0 disables the probe outright.
2. Clicks wedged in the focused app after disconnect+reconnect: a client
vanishing mid-press left keys/buttons latched in the compositor —
Mutter keeps the destroyed EIS device's implicit grab and the focused
app stops taking clicks until restarted. EiState now tracks held
keys/buttons/touches (wire codes) and synthesizes releases through
the normal inject path before the EIS connection goes away.
GNOME hosts on NVIDIA temporarily lose zero-copy (correctness over
throughput); the moment Mutter+driver gain working explicit sync, the
sync path engages automatically and zero-copy returns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
75eb8fa0d6 |
feat(host): KDE-reliability phase 2 — pipeline retry, graceful capture teardown, refresh reconcile
Hardens the virtual-display → capture → encode bring-up against the transient failures that surfaced as black screens / wrong refresh on cold KDE sessions. - m3: build_pipeline_with_retry wraps the initial vd.create() + first-frame with bounded exponential backoff (4 attempts, 500ms→2s). is_permanent_build_error classifies config/version/missing-tool failures so they fail fast instead of burning the retry budget. Encoder + frame clock now pace to the *achieved* refresh reported in VirtualOutput::preferred_mode, not the requested rate. - capture/linux: PortalCapturer::Drop sends a pipewire channel quit and joins the thread, so a dropped/failed/retried capturer releases its PipeWire thread + EGL/ CUDA context promptly instead of leaking it to process exit. First-frame timeout now reports the node id and distinguishes "format never negotiated" from "negotiated but no buffers arrived" via a negotiated flag set in param_changed. - vdisplay/kwin: set_custom_refresh reads back the active mode from kscreen-doctor and returns the refresh KWin actually gave us (a rejected custom mode silently leaves the output at 60Hz); create() carries it into preferred_mode. - vdisplay/gamescope: find_gamescope_node requires the Video/Source object (the node.name=gamescope tag is on two objects; the other wedges the link); a version check warns on <3.16.22 (the PipeWire-1.6 capture-deadlock signature). Live-validated against headless KWin: 720p120 build with requested=120 achieved=120, zero-copy CUDA frames, and no per-session thread accumulation across back-to-back sessions. Tests: +3 unit (retry classifier, gamescope version parse); 49 host tests green, clippy/fmt clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
bfd64ce871 |
rename: lumen → punktfunk, everywhere
ci / rust (push) Has been cancelled
Full project rename, decided 2026-06-10: - Crates/binaries: punktfunk-core / punktfunk-host / punktfunk-client-rs. - C ABI: punktfunk_* symbols, Punktfunk* types, include/punktfunk_core.h, PUNKTFUNK_FEATURE_QUIC guard (header regenerated; cbindgen renames updated, incl. PUNKTFUNK_BTN_*/PUNKTFUNK_AXIS_* wire constants). - Protocol: punktfunk/1 — control-plane magic LMN1 → PKF1, nonce salt lmn1 → pkf1. WIRE BREAK: clients must be rebuilt from this revision. - Env knobs: PUNKTFUNK_VIDEO_SOURCE / PUNKTFUNK_COMPOSITOR / PUNKTFUNK_ZEROCOPY / …. - Host config dir: ~/.config/punktfunk (the box's dir was migrated in place — the persistent identity is unchanged, pinned fingerprints stay valid). - Swift package: PunktfunkKit + PunktfunkCore.xcframework + PunktfunkConnection (Sources/PunktfunkClient app + tests renamed with it); build-xcframework.sh updated. - scripts/: 60-punktfunk.rules, punktfunk-host.service; OpenAPI doc regenerated. Also: scripts/headless/run-headless-kde.sh — full headless Plasma bringup. Root cause of "desktop but no apps/settings" over the stream: plasmashell launched without XDG_MENU_PREFIX=plasma-, so the launcher resolved a nonexistent applications.menu and rendered an empty menu. The script sets the complete KDE session env (menu prefix, KDE_FULL_SESSION, session version) and rebuilds ksycoca before starting plasmashell. Gate: 97/97 tests, clippy -D warnings (both feature sets), fmt, C-ABI harness PASS, zero lumen references left outside .git. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |