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>