From ff20423a84327c7dbd50877c17b1c861af9b20a7 Mon Sep 17 00:00:00 2001 From: Joseph Dunn Date: Fri, 22 May 2026 16:36:21 -0500 Subject: [PATCH] fix: clean up lock surfaces and drain compositor events on unlock Hyprland was treating multi-output rustlock unlocks as client crashes and showing its "lockscreen died" failsafe instead of unlocking. Two issues: 1. Order. ext-session-lock-v1 recommends destroying every ext_session_lock_surface_v1 before issuing unlock_and_destroy on ext_session_lock_v1. rustlock did the opposite. Move lock_surfaces.clear() into handle_auth_result so it runs before session_lock.unlock(), and drop the now-redundant clear from the auth-feedback callback. 2. Drain. After unlock_and_destroy the compositor sends keyboard/pointer leave events and delete_id acks; if the client disconnects before processing them, Hyprland treats the disconnect as unclean. Add a conn.roundtrip() after the main loop to drain those events before exit. Single-output setups tolerate both of these (which is why the bug does not show up on the upstream author's laptop). The failsafe only reproduces with multiple outputs. --- src/main.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index fe376cb..d126809 100644 --- a/src/main.rs +++ b/src/main.rs @@ -139,6 +139,11 @@ impl WaylandLock { if success { log::info!("✅ Authentication successful - unlocking session"); + // ext-session-lock-v1 recommends destroying lock surfaces before + // issuing unlock_and_destroy. With multiple outputs Hyprland + // otherwise treats the unlock as a client crash and shows its + // failsafe screen. + self.lock_surfaces.clear(); if let Some(session_lock) = &self.session_lock { session_lock.unlock(); let _ = self.conn.flush(); @@ -808,9 +813,6 @@ fn main() -> Result<(), Box> { .insert_source(auth_feedback_rx_actual, |event, _, state| { if let calloop::channel::Event::Msg(success) = event { state.handle_auth_result(success); - if success { - state.lock_surfaces.clear(); - } } })?; @@ -906,6 +908,12 @@ fn main() -> Result<(), Box> { event_loop.dispatch(Duration::from_millis(16), &mut state)?; } + // After unlock_and_destroy the compositor sends keyboard/pointer leave and + // delete_id events that must be processed before we disconnect, otherwise + // Hyprland treats the disconnect as a client crash and shows its failsafe + // screen (only reproducible on multi-output setups). + let _ = state.conn.roundtrip(); + log::info!("Exiting rustlock"); Ok(()) }