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.
This commit is contained in:
Joseph Dunn
2026-05-22 16:36:21 -05:00
parent 6c2e3fca5a
commit ff20423a84
+11 -3
View File
@@ -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<dyn Error>> {
.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<dyn Error>> {
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(())
}