From e18d06ae55614c1f1b8396277abe7bdd41381bfa Mon Sep 17 00:00:00 2001 From: Mariano Abad Date: Mon, 10 Aug 2026 09:51:45 -0300 Subject: [PATCH] fix: settle the DRM probe before routing login to X11, and read seat0 fresh Two findings from the #15792 review, both verified against the code: - drm_login_screen_seat0_username asked the cached probe, so a client arriving before warm_availability publishes its verdict read "no DRM" and, with allow-linux-headless=Y, try_start_x_session could start Xorg over a live Wayland greeter. Ask the probing form instead, and only after the cheap seat0 read says a Wayland greeter is actually there: a bounded definitive verdict is affordable on a login-time path. - get_supported_display_seat0_username trusted the seat0 values cached in DesktopManager::new(), which go stale across a logout or a fast user switch: a stale non-greeter name skipped the greeter probe and was returned as the supported display owner. Read seat0 fresh on every query; every call site is connection-time, so the extra loginctl read is cheap. Regression-tested on a real sddm Wayland greeter: capture streams the greeter, the RustDesk password dialog is the only prompt, and five typed characters appeared in the greeter password field over uinput with zero "Rejected unauthorized connection" lines in the service log. --- src/platform/linux_desktop_manager.rs | 31 +++++++++++++++------------ src/server/drm_capturer.rs | 5 +++-- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/platform/linux_desktop_manager.rs b/src/platform/linux_desktop_manager.rs index ba3da2d1a..6f7cee68e 100644 --- a/src/platform/linux_desktop_manager.rs +++ b/src/platform/linux_desktop_manager.rs @@ -230,20 +230,20 @@ pub fn is_headless() -> bool { } /// The Wayland greeter on seat0, if the DRM backend can capture and inject into it. -/// -/// The cached probe, to match the routing gate in `Connection`: not yet settled answers false, -/// which is what upstream does today. #[cfg(feature = "drm")] fn drm_login_screen_seat0_username() -> Option { - if !crate::server::drm_capturer::is_available_cached() { - return None; - } let values = get_values_of_seat0_with_gdm_wayland(&[0, 2]); if !is_gdm_user(&values[1]) || get_display_server_of_session(&values[0]) != DISPLAY_SERVER_WAYLAND { return None; } + // The probing form, asked only once a Wayland greeter is on seat0: the cached probe answers + // false while the warm-up is unsettled, and that would let try_start_x_session put Xorg over + // a live greeter. This is a login-time path, so a bounded definitive verdict is affordable. + if !crate::server::drm_capturer::is_available() { + return None; + } Some(values[1].clone()) } @@ -293,22 +293,25 @@ impl DesktopManager { } fn get_supported_display_seat0_username(&self) -> Option { - // A Wayland greeter the DRM backend can serve is a supported display. Asked here and not in - // `new()` because the seat0 read there hides greeters, and because the DRM probe has not - // settled at startup. + // Read seat0 fresh on every query: the values cached in `new()` go stale across a logout + // or fast-user-switch, which would skip the greeter probe below and hand back the previous + // session owner. Queried here and not in `new()` also because the read there hides greeters. + let seat0_values = get_values_of_seat0(&[0, 2]); + let seat0_username = seat0_values[1].clone(); #[cfg(feature = "drm")] - if self.seat0_username.is_empty() || is_gdm_user(&self.seat0_username) { + if seat0_username.is_empty() || is_gdm_user(&seat0_username) { if let Some(username) = drm_login_screen_seat0_username() { return Some(username); } } - if is_gdm_user(&self.seat0_username) && self.seat0_display_server == DISPLAY_SERVER_WAYLAND + if seat0_username.is_empty() { + None + } else if is_gdm_user(&seat0_username) + && get_display_server_of_session(&seat0_values[0]) == DISPLAY_SERVER_WAYLAND { None - } else if self.seat0_username.is_empty() { - None } else { - Some(self.seat0_username.clone()) + Some(seat0_username) } } diff --git a/src/server/drm_capturer.rs b/src/server/drm_capturer.rs index 5001a763f..0de88ee24 100644 --- a/src/server/drm_capturer.rs +++ b/src/server/drm_capturer.rs @@ -827,8 +827,9 @@ pub(crate) fn is_available_cached() -> bool { matches!(&*DRM_STATE.lock().unwrap(), ProbeState::Available(..)) } -/// MAY BLOCK for seconds: never a routing gate. -pub(super) fn is_available() -> bool { +/// MAY BLOCK for seconds: never a routing gate. The one cross-module caller is the login-screen +/// headless decision, where an unsettled "no" would start Xorg over a live Wayland greeter. +pub(crate) fn is_available() -> bool { let verdict = { let mut st = DRM_STATE.lock().unwrap(); if let ProbeState::Unavailable(since) = &*st {