From b47c9b60f1ed9a4188f76275f8dee660990bf7a2 Mon Sep 17 00:00:00 2001 From: Mariano Abad Date: Tue, 28 Jul 2026 08:45:46 -0300 Subject: [PATCH] drm: give the DRM uinput update the timeout and the bookkeeping (M3, M6) The DRM path sets the uinput absolute range itself, because it bypasses check_init. That copy awaited update_mouse_resolution raw, and it was missing three things check_init has sixty lines above it. No timeout: uinput set_resolution reads its reply with no timeout of its own, so a hung uinput socket blocked every video-service start on this branch, and wedged the hotplug worker inside rt.block_on with UINPUT_REFRESH_BUSY latched true, after which every later hotplug refresh was silently skipped for the process lifetime. It is bounded at 3 s now, the same bound check_init uses. No bookkeeping: it never called set_wayland_uinput_rect or set_wayland_layout_baseline, which is why the #15601 layout-drift remap never activated on the DRM path. Both are recorded now, and only after a successful apply, so a transient failure is retried rather than remembered as applied. No cache invalidation: the cached Wayland layout can predate compositor changes made while no session was active, which is the case #15601 is about. Dropped first, as check_init does. It also stops reprogramming the device when the range has not changed (M6): a display in a rebuild loop called this about once a second, and reapplying an identical range is an IPC roundtrip plus a uinput reconfiguration under a user who may be at the console. The layout baseline is still re-snapshotted on every call, since it is what the client coordinates are measured against. Left as a separate copy rather than folded into check_init: check_init ships in every Linux build and the standing rule for this feature is that the drm-off build does not change by a line. Both configs build, 94 tests pass. --- src/server/display_service.rs | 7 ++++ src/server/wayland.rs | 63 +++++++++++++++++++++++++++++------ 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/server/display_service.rs b/src/server/display_service.rs index 18c1239d6..e8c08f3f7 100644 --- a/src/server/display_service.rs +++ b/src/server/display_service.rs @@ -65,6 +65,13 @@ pub(super) fn set_wayland_uinput_rect(rect: (i32, i32, i32, i32)) { WAYLAND_UINPUT_RECT.lock().unwrap().rect = Some(rect); } +// The uinput ABS range currently programmed into the device, for the DRM path's "reapply only when +// it changed" check. The PipeWire path compares it inline in refresh_wayland_uinput_rect_if_changed. +#[cfg(all(target_os = "linux", feature = "drm"))] +pub(super) fn wayland_uinput_rect() -> Option<(i32, i32, i32, i32)> { + WAYLAND_UINPUT_RECT.lock().unwrap().rect +} + #[cfg(target_os = "linux")] pub(super) fn set_wayland_layout_baseline(baseline: Vec) { WAYLAND_LAYOUT_DRIFTED.store(false, Ordering::Relaxed); diff --git a/src/server/wayland.rs b/src/server/wayland.rs index 8256b25d4..b363ad135 100644 --- a/src/server/wayland.rs +++ b/src/server/wayland.rs @@ -113,19 +113,62 @@ struct CapDisplayInfo { /// too, otherwise on a multi-monitor host the injected pointer lands on the wrong output — and the /// hardware cursor, which lives on whichever CRTC the pointer is over, never appears on the captured /// CRTC (the "cursor not visible" symptom). Reads the layout from the Wayland outputs, so it is -/// independent of the capture backend. DRM-only: check_init keeps its own inline copy so the -/// drm-off build stays byte-identical to upstream. +/// independent of the capture backend. +/// +/// This is the DRM path's single copy of what `check_init` does inline for PipeWire, and it does the +/// same three things, for the same reasons: +/// +/// - drops the cached Wayland layout first, because it can predate compositor changes made while no +/// session was active (rustdesk#15601), and on the hotplug path it is stale by definition; +/// - bounds the IPC wait, because `uinput::client::set_resolution` reads its reply with no timeout of +/// its own, so a hung uinput socket would otherwise block every video-service start on this branch +/// and wedge the hotplug worker inside `rt.block_on`, leaving `UINPUT_REFRESH_BUSY` latched true so +/// that every later hotplug refresh is silently skipped for the process lifetime; +/// - records the applied rect and snapshots the per-display layout baseline, which is what arms the +/// #15601 drift remap. Without it the remap never activates on the DRM path at all. +/// +/// It stays a separate copy rather than being folded into `check_init` because `check_init` ships in +/// every Linux build and this feature must not change the drm-off one by so much as a line. #[cfg(feature = "drm")] pub(super) async fn update_uinput_resolution() { - if crate::input_service::wayland_use_uinput() { - if let Some((minx, maxx, miny, maxy)) = - scrap::wayland::display::get_desktop_rect_for_uinput() - { - log::info!("update mouse resolution: ({minx}, {maxx}), ({miny}, {maxy})"); - allow_err!(input_service::update_mouse_resolution(minx, maxx, miny, maxy).await); - } else { - log::warn!("Failed to get desktop rect for uinput"); + if !crate::input_service::wayland_use_uinput() { + return; + } + scrap::wayland::display::clear_wayland_displays_cache(); + let Some(rect) = scrap::wayland::display::get_desktop_rect_for_uinput() else { + log::warn!("Failed to get desktop rect for uinput"); + return; + }; + // Re-snapshot the baseline on every call: this runs at session init and after every hotplug, and + // the baseline is what the client's coordinates are measured against. + let snapshot_layout = || { + super::display_service::set_wayland_layout_baseline( + scrap::wayland::display::get_display_rects_for_uinput(), + ); + }; + // Reprogram the device only when the range actually changes. A display stuck in a rebuild loop + // calls this about once a second, and reapplying an identical range is an IPC roundtrip plus a + // uinput device reconfiguration under a user who may be at the console. + if super::display_service::wayland_uinput_rect() == Some(rect) { + snapshot_layout(); + return; + } + let (minx, maxx, miny, maxy) = rect; + log::info!("update mouse resolution: ({minx}, {maxx}), ({miny}, {maxy})"); + match timeout( + 3_000, + input_service::update_mouse_resolution(minx, maxx, miny, maxy), + ) + .await + { + // Record the rect only after a successful apply, so a transient failure is retried on the + // next call instead of being remembered as applied. + Ok(Ok(())) => { + super::display_service::set_wayland_uinput_rect(rect); + snapshot_layout(); } + Ok(Err(err)) => log::error!("Failed to update mouse resolution: {}", err), + Err(err) => log::error!("Failed to update mouse resolution: {}", err), } }