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.
This commit is contained in:
Mariano Abad
2026-07-21 15:29:09 -03:00
parent e00d7d7737
commit 31485f83de
2 changed files with 55 additions and 1 deletions

View File

@@ -635,7 +635,27 @@ pub(super) fn get_display_infos() -> Option<Vec<DisplayInfo>> {
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

View File

@@ -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,