fix(host/linux): park the seat pointer on the streamed surface — a capture-mode client cannot
ROOT CAUSE of the GNOME capture-mode cursorless stream (and the same latent hole on KWin, whose screencast also gates cursor data on includesCursor): a virtual output is created fresh per session while the seat pointer stays wherever it was — usually a physical monitor. A capture-model (pointer-lock) client sends only RELATIVE deltas, so nothing ever moves the pointer INTO the streamed output: input lands on the wrong monitor, Mutter suppresses SPA_META_Cursor entirely (should_cursor_metadata_be_set: visible AND in-stream), and both the embedded and the encoder-blend composite models stream cursorless. The stream loop now parks the pointer at the streamed surface's centre through the session's own input pipeline (capability routing, region ladder, anchor — the path client events take): armed per (re)built display and by the capture-model flip, retried on a schedule because the first park of a session can land on a still-cold EIS connection (devices not resumed — the injector drops it; observed on-glass), and kept trying while a capture-model session still has no live overlay. A desktop-model client overrides the park with its first absolute move. Verified end-to-end on GNOME 50.3/Mutter (RTX 5070 Ti): the reference client in capture mode now shows the host-composited cursor moving in the decoded H.265 dump, and the PyroWave session hands the overlay to the wavelet CSC blend. One-shot per-session/per-stream breadcrumbs (first meta region, first bitmap, first overlay handed to the blend / still-cursorless) make the next field triage a grep instead of a rebuild. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,10 @@ pub(super) struct CursorState {
|
||||
/// channel (the blend path uses the pre-adjusted `x`/`y` and never reads it).
|
||||
hot_x: i32,
|
||||
hot_y: i32,
|
||||
/// One-shot breadcrumb latch: this stream saw a `SPA_META_Cursor` region (the Meta param
|
||||
/// negotiated). Per-stream deliberately — a host serves many sessions per process, and a
|
||||
/// process-wide latch made the second session's triage read as "no meta".
|
||||
seen_meta: bool,
|
||||
}
|
||||
|
||||
impl CursorState {
|
||||
@@ -93,6 +97,13 @@ pub(super) fn update_cursor_meta(cursor: &mut CursorState, spa_buf: *mut spa::sy
|
||||
if meta.is_null() {
|
||||
return;
|
||||
}
|
||||
// One-shot breadcrumb (per stream): the producer DID attach a cursor-meta region (the Meta
|
||||
// param negotiated). Field triage for a cursorless stream starts by grepping for this line
|
||||
// — its absence means the negotiation dropped the meta, not that the pointer never moved.
|
||||
if !cursor.seen_meta {
|
||||
cursor.seen_meta = true;
|
||||
tracing::info!("cursor meta: first SPA_META_Cursor region observed on this stream");
|
||||
}
|
||||
// SAFETY: `meta` is non-null and points into the held buffer's metadata array.
|
||||
let (region_size, data) = unsafe { ((*meta).size as usize, (*meta).data as *const u8) };
|
||||
if data.is_null() || region_size < std::mem::size_of::<spa::sys::spa_meta_cursor>() {
|
||||
@@ -180,6 +191,12 @@ pub(super) fn update_cursor_meta(cursor: &mut CursorState, spa_buf: *mut spa::sy
|
||||
cursor.bw = bw;
|
||||
cursor.bh = bh;
|
||||
cursor.serial = cursor.serial.wrapping_add(1);
|
||||
// One-shot sibling of the region breadcrumb above (per stream, via the serial's 0→1 edge):
|
||||
// the first BITMAP — before this line has fired, `overlay()` is `None` and every
|
||||
// blend/forward path is cursorless by construction.
|
||||
if cursor.serial == 1 {
|
||||
tracing::info!(w = bw, h = bh, "cursor meta: first cursor bitmap received");
|
||||
}
|
||||
}
|
||||
|
||||
/// The byte range inside the producer's cursor-meta region that a `bh`-row, `row`-wide,
|
||||
@@ -349,6 +366,7 @@ mod tests {
|
||||
serial: 1,
|
||||
hot_x: 0,
|
||||
hot_y: 0,
|
||||
seen_meta: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user