From 9171f0771a5a0031fc47f8f9a675816fa383205b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20H=C3=B6rmann?= Date: Thu, 26 Mar 2026 08:33:51 +0100 Subject: [PATCH] fix: properly unlock on niri by flushing Wayland connection before exit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ext-session-lock-v1 protocol does not guarantee a `finished` event after the client calls `unlock_and_destroy` — that event is only sent when the compositor independently terminates the lock. Waiting for it caused rustlock to hang forever on niri (and any spec-compliant compositor). The previous attempt to fix this by setting exit=true immediately broke unlocking because the while loop stopped calling event_loop.dispatch, leaving the unlock_and_destroy bytes unflushed in the client-side Wayland send buffer and never reaching the compositor. Store the Connection in WaylandLock and explicitly call conn.flush() after session_lock.unlock(), ensuring the unlock request is sent before we exit. Co-Authored-By: Claude Sonnet 4.6 --- src/main.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index d40daef..ebfbdbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -104,6 +104,7 @@ impl log::Log for DualLogger { } struct WaylandLock { + conn: Connection, loop_handle: LoopHandle<'static, Self>, lock_manager: Arc>, config: Config, @@ -123,7 +124,6 @@ struct WaylandLock { captured_backgrounds: Vec>, pending_screenshots: usize, exit: bool, - unlocking: bool, screenshot_manager: Option, grace_until: Option, system_manager: Arc, @@ -140,8 +140,9 @@ impl WaylandLock { log::info!("✅ Authentication successful - unlocking session"); if let Some(session_lock) = &self.session_lock { session_lock.unlock(); - self.unlocking = true; - log::debug!("Unlock requested - waiting for compositor finished event"); + let _ = self.conn.flush(); + self.exit = true; + log::debug!("Unlock requested - exiting"); } else { log::error!("No session_lock available to unlock!"); self.exit = true; @@ -617,6 +618,7 @@ fn main() -> Result<(), Box> { let pool = SlotPool::new(1, &shm_state)?; let mut state = WaylandLock { + conn: conn.clone(), loop_handle: event_loop.handle(), lock_manager: lock_manager.clone(), config: config.clone(), @@ -636,7 +638,6 @@ fn main() -> Result<(), Box> { captured_backgrounds: Vec::new(), pending_screenshots: 0, exit: false, - unlocking: false, screenshot_manager: ScreenshotManager::new(&globals, &qh).ok(), grace_until: None, system_manager: system_manager.clone(), @@ -727,9 +728,6 @@ fn main() -> Result<(), Box> { } } - if state.unlocking { - return calloop::timer::TimeoutAction::ToDuration(Duration::from_millis(100)); - } let mut status = state.system_manager.get_status(); status.keyboard_layout = Some(state.current_layout.to_string());