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.
This commit is contained in:
Mariano Abad
2026-07-28 08:45:46 -03:00
parent 3ea325b3b1
commit b47c9b60f1
2 changed files with 60 additions and 10 deletions

View File

@@ -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<scrap::wayland::display::DisplayRect>) {
WAYLAND_LAYOUT_DRIFTED.store(false, Ordering::Relaxed);

View File

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