From 31485f83ded020842bea90428f2e3de01ae778ed Mon Sep 17 00:00:00 2001 From: Mariano Abad Date: Tue, 21 Jul 2026 15:29:09 -0300 Subject: [PATCH] drm: keep the demoted-display list consistent instead of stretching PipeWire (review 4.5) A DRM display demoted to PipeWire has no geometry-consistent per-connector stream on a multi-monitor host -- the portal exposes a single whole-desktop stream. The fallthrough served that whole-desktop frame while the list still advertised the demoted connector geometry, so the client stretched the frame and offset all input by the connector origin (the primary-index-0 demotion reaches this even after the get_capturer_for_display exact-index fix). Dropping the display from the list is not an option: its position IS the capturer index, so a drop would shift every later display and desync get_capturer_info. So instead: get_display_infos advertises a multi-monitor demoted display OFFLINE at its stable index, and get_capturer_for_display serves the PipeWire fallback only when its rect matches the advertised geometry, else bails. A single-display host still falls through (whole-desktop == that display). All new logic is drm-gated. --- src/server/drm_capturer.rs | 22 +++++++++++++++++++++- src/server/wayland.rs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/server/drm_capturer.rs b/src/server/drm_capturer.rs index e72199de2..bf405a7f4 100644 --- a/src/server/drm_capturer.rs +++ b/src/server/drm_capturer.rs @@ -635,7 +635,27 @@ pub(super) fn get_display_infos() -> Option> { ProbeState::Available(list) => list.clone(), _ => return None, }; - Some(augment_with_wayland_geometry(&list)) + let multi = list.len() > 1; + let mut infos = augment_with_wayland_geometry(&list); + // review 4.5: on a multi-monitor host a display demoted to PipeWire has no geometry-consistent + // per-connector stream to fall through to -- the portal exposes a single whole-desktop stream, so + // serving it for one connector would stretch the frame and offset all input. Advertise such a + // display OFFLINE while keeping its list position, so the index space stays aligned with + // get_capturer_info() (dropping it would shift every later index) and the client re-enumerates + // against a consistent list instead of driving a display the server then refuses. A single-display + // host is left online: there the whole-desktop stream IS that display, so the PipeWire fallback is + // geometry-consistent and get_capturer_for_display serves it. + if multi { + let failures = DRM_DISPLAY_FAILURES.lock().unwrap(); + for (idx, info) in infos.iter_mut().enumerate() { + if let Some((count, since)) = failures.get(&(idx as i32)).copied() { + if count >= DRM_GRAB_MAX_FAILURES && since.elapsed() < DEMOTE_COOLDOWN { + info.online = false; + } + } + } + } + Some(infos) } /// Index (into the cached DRM display list) of the compositor's PRIMARY output. DRM connector order diff --git a/src/server/wayland.rs b/src/server/wayland.rs index ab1a54b49..9f7ac25f8 100644 --- a/src/server/wayland.rs +++ b/src/server/wayland.rs @@ -383,6 +383,40 @@ pub(super) fn get_capturer_for_display( unsafe { let cap_display_info = &*cap_display_info; let rect = cap_display_info.rects[cap_display_info.current]; + // review 4.5: reaching here with DRM active means get_capturer_info bailed (a demoted + // display) and we fell through to PipeWire. Serve this stream ONLY if its rect matches the + // geometry we advertised for this index. The portal typically exposes one whole-desktop + // stream, so on a multi-monitor host that rect is the FULL desktop while the advertised DRM + // geometry is a single connector -> serving it would stretch the frame and offset all + // input. Bail instead; get_display_infos advertised the display offline, so the client + // re-enumerates against a consistent list. A single-display host matches (whole-desktop == + // that display) and is served normally. On a pure-PipeWire host is_available() is false and + // this guard is skipped, preserving upstream behavior exactly. + #[cfg(feature = "drm")] + if super::drm_capturer::is_available() { + if let Some(advertised) = super::drm_capturer::get_display_infos() + .and_then(|l| l.get(display_idx).cloned()) + { + let consistent = advertised.x == rect.0 .0 + && advertised.y == rect.0 .1 + && advertised.width as usize == rect.1 + && advertised.height as usize == rect.2; + if !consistent { + bail!( + "drm display {} demoted with no geometry-consistent PipeWire stream (advertised {}x{}+{}+{} vs stream {}x{}+{}+{}); advertised offline", + display_idx, + advertised.width, + advertised.height, + advertised.x, + advertised.y, + rect.1, + rect.2, + rect.0 .0, + rect.0 .1 + ); + } + } + } Ok(super::video_service::CapturerInfo { origin: rect.0, width: rect.1,