fix: properly unlock on niri by flushing Wayland connection before exit

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 <noreply@anthropic.com>
This commit is contained in:
Matthias Hörmann
2026-03-26 08:33:51 +01:00
parent 601c9677b7
commit 9171f0771a
+5 -7
View File
@@ -104,6 +104,7 @@ impl log::Log for DualLogger {
} }
struct WaylandLock { struct WaylandLock {
conn: Connection,
loop_handle: LoopHandle<'static, Self>, loop_handle: LoopHandle<'static, Self>,
lock_manager: Arc<Mutex<LockManager>>, lock_manager: Arc<Mutex<LockManager>>,
config: Config, config: Config,
@@ -123,7 +124,6 @@ struct WaylandLock {
captured_backgrounds: Vec<Option<cairo::ImageSurface>>, captured_backgrounds: Vec<Option<cairo::ImageSurface>>,
pending_screenshots: usize, pending_screenshots: usize,
exit: bool, exit: bool,
unlocking: bool,
screenshot_manager: Option<ScreenshotManager>, screenshot_manager: Option<ScreenshotManager>,
grace_until: Option<Instant>, grace_until: Option<Instant>,
system_manager: Arc<SystemManager>, system_manager: Arc<SystemManager>,
@@ -140,8 +140,9 @@ impl WaylandLock {
log::info!("✅ Authentication successful - unlocking session"); log::info!("✅ Authentication successful - unlocking session");
if let Some(session_lock) = &self.session_lock { if let Some(session_lock) = &self.session_lock {
session_lock.unlock(); session_lock.unlock();
self.unlocking = true; let _ = self.conn.flush();
log::debug!("Unlock requested - waiting for compositor finished event"); self.exit = true;
log::debug!("Unlock requested - exiting");
} else { } else {
log::error!("No session_lock available to unlock!"); log::error!("No session_lock available to unlock!");
self.exit = true; self.exit = true;
@@ -617,6 +618,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let pool = SlotPool::new(1, &shm_state)?; let pool = SlotPool::new(1, &shm_state)?;
let mut state = WaylandLock { let mut state = WaylandLock {
conn: conn.clone(),
loop_handle: event_loop.handle(), loop_handle: event_loop.handle(),
lock_manager: lock_manager.clone(), lock_manager: lock_manager.clone(),
config: config.clone(), config: config.clone(),
@@ -636,7 +638,6 @@ fn main() -> Result<(), Box<dyn Error>> {
captured_backgrounds: Vec::new(), captured_backgrounds: Vec::new(),
pending_screenshots: 0, pending_screenshots: 0,
exit: false, exit: false,
unlocking: false,
screenshot_manager: ScreenshotManager::new(&globals, &qh).ok(), screenshot_manager: ScreenshotManager::new(&globals, &qh).ok(),
grace_until: None, grace_until: None,
system_manager: system_manager.clone(), system_manager: system_manager.clone(),
@@ -727,9 +728,6 @@ fn main() -> Result<(), Box<dyn Error>> {
} }
} }
if state.unlocking {
return calloop::timer::TimeoutAction::ToDuration(Duration::from_millis(100));
}
let mut status = state.system_manager.get_status(); let mut status = state.system_manager.get_status();
status.keyboard_layout = Some(state.current_layout.to_string()); status.keyboard_layout = Some(state.current_layout.to_string());