srdusr
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--crates/wayland/src/udev/platform.rs4
-rw-r--r--crates/wayland/src/udev/session.rs55
2 files changed, 54 insertions, 5 deletions
diff --git a/crates/wayland/src/udev/platform.rs b/crates/wayland/src/udev/platform.rs
index cbcee64..1d2ef0e 100644
--- a/crates/wayland/src/udev/platform.rs
+++ b/crates/wayland/src/udev/platform.rs
@@ -269,8 +269,8 @@ impl UdevPlatform {
let handle = event_loop.handle();
register_drm_fd(&handle, &card)?;
- register_libinput(&handle, &session, &seat_name)?;
- register_session_notifier(&handle, notifier)?;
+ let libinput_handle = register_libinput(&handle, &session, &seat_name)?;
+ register_session_notifier(&handle, notifier, libinput_handle)?;
if let Err(e) = register_udev_monitor(&handle, &seat_name) {
log::warn!("udev: connector hotplug unavailable ({e}); monitors are fixed at startup");
}
diff --git a/crates/wayland/src/udev/session.rs b/crates/wayland/src/udev/session.rs
index a6b7349..196740b 100644
--- a/crates/wayland/src/udev/session.rs
+++ b/crates/wayland/src/udev/session.rs
@@ -38,9 +38,20 @@ pub(crate) fn register_drm_fd(handle: &LoopHandle<'static, CompState>, card: &Rc
Ok(())
}
-pub(crate) fn register_libinput(handle: &LoopHandle<'static, CompState>, session: &LibSeatSession, seat_name: &str) -> PlatformResult<()> {
+/// Registers the real libinput event source and hands back a second,
+/// reference-counted handle onto the exact same underlying context (`Libinput`
+/// wraps a `libinput_ref`/`libinput_unref`-counted C pointer - see its own
+/// `Clone` impl - so this is the same live context `LibinputInputBackend`
+/// dispatches events from, not a separate one) for `register_session_notifier`
+/// to call `suspend()`/`resume()` on across a VT switch. `LibinputInputBackend`
+/// itself only ever exposes an immutable `&Libinput` (`context()`), and is
+/// moved into calloop's event source registration below with no way to get
+/// a `&mut` back out afterward - cloning before that move is the only way
+/// to keep a callable handle at all.
+pub(crate) fn register_libinput(handle: &LoopHandle<'static, CompState>, session: &LibSeatSession, seat_name: &str) -> PlatformResult<Libinput> {
let mut libinput_context = Libinput::new_with_udev::<LibinputSessionInterface<LibSeatSession>>(session.clone().into());
libinput_context.udev_assign_seat(seat_name).map_err(|_| PlatformError::Other("udev: libinput udev_assign_seat failed".into()))?;
+ let resume_handle = libinput_context.clone();
let libinput_backend = LibinputInputBackend::new(libinput_context);
handle
@@ -48,10 +59,16 @@ pub(crate) fn register_libinput(handle: &LoopHandle<'static, CompState>, session
handle_libinput_event(data, event);
})
.map_err(|e| PlatformError::Other(format!("failed to register libinput backend: {e}")))?;
- Ok(())
+ Ok(resume_handle)
}
-pub(crate) fn register_session_notifier(handle: &LoopHandle<'static, CompState>, notifier: LibSeatSessionNotifier) -> PlatformResult<()> {
+/// `libinput` is a second handle onto the exact same context
+/// `register_libinput` gave its own event source - see that function's own
+/// doc comment for why a clone is the only way to get one at all. Kept
+/// alive by this closure for as long as this event source is registered
+/// (the entire life of the process), purely so `suspend()`/`resume()` can be
+/// called on it here.
+pub(crate) fn register_session_notifier(handle: &LoopHandle<'static, CompState>, notifier: LibSeatSessionNotifier, mut libinput: Libinput) -> PlatformResult<()> {
handle
.insert_source(notifier, move |event, &mut (), data: &mut CompState| {
let Some(udev) = data.udev.as_mut() else { return };
@@ -59,10 +76,42 @@ pub(crate) fn register_session_notifier(handle: &LoopHandle<'static, CompState>,
SessionEvent::PauseSession => {
log::info!("udev: session paused (VT switch away)");
udev.active = false;
+ // Not strictly required (the kernel revokes every input
+ // device's fd across a VT switch regardless), but this
+ // is the documented, correct way to tell libinput that's
+ // about to happen rather than let it discover revoked
+ // fds as surprise read errors - see `resume()`'s own
+ // doc comment below for why the other half of this pair
+ // is not optional at all.
+ libinput.suspend();
}
SessionEvent::ActivateSession => {
log::info!("udev: session resumed (VT switch back)");
udev.active = true;
+ // Without this, libinput's own internal device list
+ // stays exactly as it was before the switch away, still
+ // holding the same file descriptors the kernel already
+ // revoked the moment this session lost the VT --
+ // `receive_events`/`dispatch` on a revoked fd doesn't
+ // error, it just silently never produces another event,
+ // forever. Confirmed live: after one real VT switch, no
+ // further keyboard or mouse input reached this
+ // compositor for the rest of the session (30+ minutes,
+ // multiple confirmed attempts) with literally nothing
+ // logged anywhere to explain it - rendering, the DRM/
+ // DPMS state above, and even libseat's own session
+ // activation all recovered correctly on their own, which
+ // is what made this the one piece actually missing
+ // rather than a repeat of the DPMS bug. `resume()`
+ // (`libinput_resume`) is libinput's own documented API
+ // for exactly this: it re-opens every device through
+ // the (now reactivated) session and resumes producing
+ // real events, the same call every other libinput-based
+ // compositor's session-resume path makes and this one
+ // never did.
+ if libinput.resume().is_err() {
+ log::warn!("udev: libinput resume failed after VT switch back - input devices may not recover; a full restart will be needed if so");
+ }
let card = udev.card.clone();
// A flip issued right before the VT switch away may