9a7b3a46d02badb0a0ec095abe9c7e3738241532
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9a7b3a46d0 |
fix(pf-capture): gamescope cursor — auth without setenv, frame-space coords, live targets (2.5)
**2.5a (L4) — stop `set_var`ing `XAUTHORITY` from a live multithreaded host.** The old connect swapped the process-global var around each `RustConnection::connect` under a mutex. That lock serialises this source against itself and nothing else: `getenv` takes no lock, so every OTHER thread's read raced it — and the concurrency is by construction, since `attach_gamescope_cursor` runs while the PipeWire thread is starting up (libspa plugin load, EGL/CUDA init). The primary path now parses the MIT-MAGIC-COOKIE-1 entry out of the given file itself and hands it to `DefaultStream::connect` + `RustConnection::connect_to_stream_with_auth_info` — the same two steps `RustConnection::connect` performs internally, minus its env-derived auth lookup — so nothing in this process touches the environment. The env swap survives only as a fallback for a file we cannot parse, and a wrong cookie pick cannot do damage: the server rejects it and the fallback (which does libxcb's full family/address match) takes over. Sharing `pf_vdisplay`'s process-wide env lock was not the fix — wrong layer, and it would still not fix `getenv`. **2.5b (L6) — publish the pointer in FRAME coordinates.** `QueryPointer` answers in the nested root's space, but `CursorOverlay::x/y`'s contract is frame pixels, and gamescope's `-w/-h` (nested root) and `-W/-H` (output + PipeWire node) are independent knobs — at `-W 1280 -H 720 -w 640 -h 360` they differ by 2×, so the pointer drew at a fraction of its real position. The root size comes free off the setup reply we already parse; the negotiated frame size arrives from the PipeWire thread's `param_changed` through a new `Arc<AtomicU64>` (`0` = not negotiated ⇒ pass through, as before). Scaling is computed in `i64` — a 5K coordinate times a 5K width overflows `i32`. Position only: the bitmap stays at root scale, warned once so a mismatched session is visible. **2.5c (L7) — the targets are a PROVIDER, not a snapshot.** The list was discovered once, before the game launched. gamescope creates the game's Xwayland at launch and advertises only the FIRST in any child's environ (verified on this box: `--xwayland-count 2` makes `:2` and `:3`, only `:2` is advertised), so the game's display was invisible — and when the connected Big Picture display then reported "gamescope is not drawing the pointer here", the source blanked the cursor for the whole game session, which is the exact regression the module doc says it fixed. The worker now re-runs the provider every 2 s: it adopts new Xwaylands, reconnects dead ones, and `spawn` no longer returns `None` on an empty list — a stream that starts before the game converges instead of staying cursorless. The provider is a host-facade closure, same one-way-edge shape as `FrameChannelSender`. **2.5d (L8) — bound the teardown join.** `Drop` joined the worker unbounded while the worker blocks in `RustConnection` replies with NO read timeout, so a peer that stops answering but keeps its socket open hung capturer teardown — on the session path. Now: a completion channel, `recv_timeout(250 ms)`, then detach with a warning (the thread only touches its own X connections and an `Arc`'d slot). 7 new tests, all host-runnable: the cookie reader against a hostile `.Xauthority` (wrong protocol, wrong display, wildcard entry, truncation mid-length-prefix, an over-long length prefix, a missing file), display-string parsing, and the root→frame mapping incl. the 5K overflow. pf-capture 20/20; workspace clippy --all-targets clean on Linux and windows-msvc. Phase 7.2 owes the on-glass nested-gamescope validation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4b2d2d1e14 |
fix(gamescope/cursor): follow gamescope's own cursor verdict — the corner-parked pointer
ci / web (push) Successful in 55s
ci / docs-site (push) Successful in 58s
apple / swift (push) Successful in 1m23s
decky / build-publish (push) Successful in 23s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 16s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 11s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
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 8s
ci / bench (push) Successful in 6m36s
apple / screenshots (push) Successful in 6m34s
deb / build-publish (push) Successful in 9m41s
deb / build-publish-host (push) Successful in 10m13s
arch / build-publish (push) Successful in 12m43s
android / android (push) Successful in 16m41s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 23m41s
docker / deploy-docs (push) Successful in 25s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 22m4s
ci / rust (push) Successful in 29m44s
gamescope hides its pointer by WARPING the X pointer to the root's bottom-right corner pixel; it does not swap in a transparent X cursor, so XFixesGetCursorImage keeps handing back the last opaque arrow. The XFixes source picked its display by "whichever pointer moved", and a parked pointer never moves again — so it froze on the parked Xwayland and composited that arrow at (w-1, h-1): a sliver of cursor welded to the corner of the stream for the rest of the session while the real pointer went undrawn. Reported on-glass in every game (a game grabs the pointer, so the hide is permanent): "part of cursor shows up on bottom right ... isn't where it really is". Follow GAMESCOPE_CURSOR_VISIBLE_FEEDBACK instead — gamescope publishes it on every nested Xwayland root: 1 on the server whose pointer it is drawing, 0 on the others and 0 on all of them once the pointer is hidden. It answers both questions correctly for a STATIC pointer, which motion cannot: which Xwayland owns it, and whether to draw it at all. Read at connect, re-read on its root PropertyNotify (event drain now discriminates CursorNotify vs PropertyNotify) with a 250 ms resync as the self-heal. A pointer gamescope draws nowhere is published visible:false, not dropped — the encode loop overwrites the frame's overlay from this slot and strips invisible ones, so a None would leave the last visible overlay standing on repeat frames. Honouring the atom also gives the stream gamescope's own idle auto-hide, which this source never had. The pointer-motion heuristic stays as the fallback for a gamescope that publishes no verdict (logged at session start via cursor_feedback=false), so nothing regresses to a cursorless stream. Measured on a live 1920x1080 Steam Gaming Mode session (RTX 5070 Ti, gamescope c31743d+, --xwayland-count 2 --hide-cursor-delay 3000): idle 3 s => pointer (1919,1079) + feedback 0; real evdev motion => pointer live + feedback 1. End-to-end against that session the source now publishes visible=false while parked and tracks the live pointer within one 250 ms sample of motion. cargo clippy -p pf-capture --lib -D warnings and the 5 new pick_active tests are green at cargo 1.96.1 (CI parity); cargo fmt --all --check clean. |
||
|
|
7058647264 |
fix(vdisplay/capture): channel-less sessions composite the pointer on a sticky-declared target
A declared IddCx hardware cursor is IRREVOCABLE for its OS target's life (§8.6), and the sticky exclusion survives monitor REMOVE→ADD because each client gets a STABLE target id — so once any desktop-mode session declared, every later pure-capture session on that target streamed a cursor-less desktop: DWM excluded the pointer, no channel forwarded it, no blend drew it (the exact no-regression gap §8.6's per-session cap gate cannot see). Driver: track every successful SetupHardwareCursor per target (DECLARED_TARGETS — scoped to the WUDFHost's life, exactly the sticky state's scope) and report it in a new AddReply::cursor_excluded tail field (dual-size discipline, both skews degrade cleanly; no proto bump). Host: the flag rides AddedMonitor → Monitor → WinCaptureTarget; a session WITHOUT the cursor channel on a flagged target forces composite mode in the IDD-push capturer — GDI poller + blend for the session's life, pinned on (set_cursor_forward cannot clear it: with no client drawing, un-compositing would erase the pointer entirely). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d5ae8dcc3e |
feat(capture): gamescope cursor via XFixes shape + QueryPointer position (Phase C)
gamescope excludes its pointer from the PipeWire node it feeds us and can't embed one either (`set_hw_cursor` is inert), so every gamescope stream was cursorless. Read the pointer from gamescope's nested Xwayland instead — XFixesGetCursorImage for shape/hotspot/visibility, core QueryPointer for position — and publish a CursorOverlay into the capturer's existing `cursor_live` slot, so the encoder blend composites it into the video exactly like the SPA_META_Cursor path. - pf-capture/src/linux/xfixes_cursor.rs (new): the XFixes reader. Connects to EVERY nested Xwayland (Gaming Mode runs one per --xwayland-count) and follows the focused one each tick (the display whose pointer moves), reading that display's own cursor shape. Un-premultiplies ARGB -> straight RGBA. Drop stops the thread. - Capturer::attach_gamescope_cursor + the PortalCapturer override spawn it into the same `cursor_live` slot; pf_vdisplay::gamescope_xwayland_cursor_targets discovers the (DISPLAY, XAUTHORITY) pairs via the GAMESCOPE_WAYLAND_DISPLAY scan. - host: SessionPlan.gamescope_cursor (set from the compositor at both resolve sites AND on a mid-stream Desktop->Gaming switch); the blend gate now builds the encoder blend for gamescope; a sibling composite arm attaches capturer.cursor() per tick for capture-mode clients (no channel needed). native NV12 is disabled for these sessions — that encode path can't blend the cursor (it assumes gamescope embeds the pointer), so we capture RGB and route to the proven CUDA VkSlotBlend / compute-CSC blend. - the pipewire thread no longer clobbers `cursor_live` with None on a buffer that carries no SPA_META_Cursor (gamescope) — that raced the XFixes writer and strobed the composited pointer on/off. On-glass (home-bazzite-2, RTX 5070 Ti, Gaming Mode): cursor visible + steady in Big Picture and CS2 menus, follows focus between the two Xwaylands, hidden in-game. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |