git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

src/server/lock_manager.rs (27K)

  1 // SPDX-FileCopyrightText: © 2026 The River Developers
  2 // SPDX-License-Identifier: GPL-3.0-only
  3 
  4 use crate::ffi;
  5 use crate::server::{Server, WlListener, wl_signal_add, wl_listener_remove, WlList};
  6 use crate::scene_node_data::{SceneNodeData, SceneNodeDataVal};
  7 use crate::seat::Focus;
  8 
  9 /// Delay before each successive attempt to bring the locker back, in ms. The
 10 /// length of this is also the attempt limit.
 11 ///
 12 /// It backs off because the bad case is a locker that dies during startup:
 13 /// respawning that at full speed is a fork bomb against a user who cannot see
 14 /// the screen. It gives up rather than retrying forever for the same reason,
 15 /// and giving up is safe — the session simply stays locked, which is where it
 16 /// already was.
 17 const RESPAWN_BACKOFF_MS: [i32; 5] = [200, 500, 1000, 2000, 5000];
 18 
 19 /// The locker to run. Mirrors `cce_cloud_cmd()`: prefer the installed path,
 20 /// fall back to the bare name on PATH.
 21 fn cce_lock_cmd() -> String {
 22     if let Ok(home) = std::env::var("HOME") {
 23         let path = format!("{}/.local/bin/cce-lock", home);
 24         if std::path::Path::new(&path).exists() {
 25             return path;
 26         }
 27     }
 28     "cce-lock".to_string()
 29 }
 30 
 31 pub struct LockManager {
 32     pub wlr_manager: *mut ffi::wlr_session_lock_manager_v1,
 33     pub state: LockState,
 34     pub lock: *mut ffi::wlr_session_lock_v1,
 35     pub lock_surfaces_timer: *mut ffi::wl_event_source,
 36     /// Fires to bring a locker back after one died mid-lock; see
 37     /// [`LockManager::schedule_locker_respawn`].
 38     pub respawn_timer: *mut ffi::wl_event_source,
 39     /// Respawns since the last locker that got as far as drawing. Indexes
 40     /// [`RESPAWN_BACKOFF_MS`]; past its end, we stop trying.
 41     pub respawn_attempts: usize,
 42     /// Replies owed to `ccectl lock` callers (the lock-before-sleep thread
 43     /// among them), sent once the session is `Locked`. `Option` because the
 44     /// struct is zero-initialised: `None` is the null niche, an empty `Vec`
 45     /// is not.
 46     pub lock_waiters: Option<Vec<std::sync::mpsc::Sender<String>>>,
 47     pub server: *mut Server,
 48 
 49     pub new_lock: ffi::wl_listener,
 50     pub unlock: ffi::wl_listener,
 51     pub destroy: ffi::wl_listener,
 52     pub new_surface: ffi::wl_listener,
 53 }
 54 
 55 #[derive(Debug, Clone, Copy, PartialEq)]
 56 pub enum LockState {
 57     Unlocked,
 58     Locked,
 59     WaitingForBlank,
 60     WaitingForLockSurfaces,
 61 }
 62 
 63 impl Default for LockManager {
 64     fn default() -> Self {
 65         unsafe { std::mem::zeroed() }
 66     }
 67 }
 68 
 69 impl LockManager {
 70     pub unsafe fn init(&mut self, server: *mut Server) -> Result<(), &'static str> {
 71         self.server = server;
 72         self.state = LockState::Unlocked;
 73         std::ptr::write(&mut self.lock_waiters, None);
 74 
 75         let wlr_manager = ffi::wlr_session_lock_manager_v1_create((*server).wl_server);
 76         if wlr_manager.is_null() {
 77             return Err("Failed to create wlr_session_lock_manager_v1");
 78         }
 79         self.wlr_manager = wlr_manager;
 80 
 81         let event_loop = ffi::wl_display_get_event_loop((*server).wl_server);
 82         let timer = ffi::wl_event_loop_add_timer(
 83             event_loop,
 84             Some(handle_lock_surfaces_timeout),
 85             self as *mut LockManager as *mut _,
 86         );
 87         if timer.is_null() {
 88             return Err("Failed to create lock surfaces timer");
 89         }
 90         self.lock_surfaces_timer = timer;
 91 
 92         let respawn_timer = ffi::wl_event_loop_add_timer(
 93             event_loop,
 94             Some(handle_respawn_timeout),
 95             self as *mut LockManager as *mut _,
 96         );
 97         if respawn_timer.is_null() {
 98             return Err("Failed to create the locker respawn timer");
 99         }
100         self.respawn_timer = respawn_timer;
101 
102         let new_lock_ptr = &mut self.new_lock as *mut ffi::wl_listener as *mut WlListener;
103         (*new_lock_ptr).notify = Some(handle_new_lock);
104         wl_signal_add(&mut (*self.wlr_manager).events.new_lock, &mut self.new_lock);
105 
106         Ok(())
107     }
108 
109     pub unsafe fn deinit(&mut self) {
110         if !self.lock_surfaces_timer.is_null() {
111             ffi::wl_event_source_remove(self.lock_surfaces_timer);
112             self.lock_surfaces_timer = std::ptr::null_mut();
113         }
114         if !self.respawn_timer.is_null() {
115             ffi::wl_event_source_remove(self.respawn_timer);
116             self.respawn_timer = std::ptr::null_mut();
117         }
118         wl_listener_remove(&mut self.new_lock);
119     }
120 
121     pub unsafe fn lock_surface_from_output(
122         &self,
123         output: *mut crate::output::Output,
124     ) -> Option<*mut LockSurface> {
125         if self.lock.is_null() {
126             return None;
127         }
128 
129         let surfaces_head = &mut (*self.lock).surfaces as *mut ffi::wl_list as *mut WlList;
130         let mut curr = (*surfaces_head).next;
131         while curr != surfaces_head {
132             let next = (*curr).next;
133             let wlr_lock_surface = crate::container_of!(curr, ffi::wlr_session_lock_surface_v1, link);
134             let lock_surface = (*wlr_lock_surface).data as *mut LockSurface;
135             if !lock_surface.is_null() && (*lock_surface).get_output() == output {
136                 return Some(lock_surface);
137             }
138             curr = next;
139         }
140 
141         None
142     }
143 
144     pub unsafe fn maybe_lock(&mut self) {
145         let mut all_outputs_blanked = true;
146         let mut all_outputs_rendered_lock_surface = true;
147 
148         let outputs_head = &mut (*self.server).om.outputs as *mut ffi::wl_list as *mut WlList;
149         let mut curr = (*outputs_head).next;
150         while curr != outputs_head {
151             let next = (*curr).next;
152             let output = crate::container_of!(curr, crate::output::Output, link);
153             let wlr_output = (*output).wlr_output;
154             if !wlr_output.is_null() && ffi::river_wlr_output_get_enabled(wlr_output) {
155                 match (*output).lock_render_state {
156                     crate::output::LockRenderState::PendingUnlock
157                     | crate::output::LockRenderState::Unlocked
158                     | crate::output::LockRenderState::PendingBlank
159                     | crate::output::LockRenderState::PendingLockSurface => {
160                         all_outputs_blanked = false;
161                         all_outputs_rendered_lock_surface = false;
162                     }
163                     crate::output::LockRenderState::Blanked => {
164                         all_outputs_rendered_lock_surface = false;
165                     }
166                     crate::output::LockRenderState::LockSurface => {}
167                 }
168             }
169             curr = next;
170         }
171 
172         match self.state {
173             LockState::WaitingForLockSurfaces => {
174                 if all_outputs_rendered_lock_surface {
175                     self.send_locked();
176                     ffi::wlr_scene_node_set_enabled((*self.server).scene.normal_tree as *mut ffi::wlr_scene_node, false);
177                     ffi::wl_event_source_timer_update(self.lock_surfaces_timer, 0);
178                 }
179             }
180             LockState::WaitingForBlank => {
181                 if all_outputs_blanked {
182                     self.send_locked();
183                 }
184             }
185             _ => {}
186         }
187     }
188 
189     pub unsafe fn send_locked(&mut self) {
190         log::info!("session locked");
191         if !self.lock.is_null() {
192             ffi::wlr_session_lock_v1_send_locked(self.lock);
193         }
194         self.state = LockState::Locked;
195         for tx in self.lock_waiters.take().unwrap_or_default() {
196             let _ = tx.send("ok locked\n".to_string());
197         }
198         (*self.server).idle.on_locked();
199         (*self.server).wm.dirty_windowing();
200     }
201 
202     /// Lock the session from the compositor's side, with no lock client yet.
203     ///
204     /// Until 2026-10-01 only a client could lock — the protocol's `lock()` —
205     /// and nothing ever started one: the idle timeout and a lid close both
206     /// suspended an UNLOCKED session, so whoever opened the lid had the
207     /// desktop. Now the compositor hides the desktop at once (the normal tree
208     /// off, every output rendering the blank locked tree) and then starts
209     /// `cce-lock`, which binds through `handle_new_lock`'s "already locked
210     /// session" branch and puts the prompt up. This is the state a crashed
211     /// locker leaves behind, so the respawn timer is what starts it, with the
212     /// same backoff and the same fail-closed end: a locker that never comes
213     /// leaves the session locked, not open.
214     ///
215     /// No-op when the session is already locked or locking.
216     pub unsafe fn lock_now(&mut self) {
217         if self.state != LockState::Unlocked {
218             return;
219         }
220         log::info!("locking the session (compositor-initiated)");
221         self.state = LockState::WaitingForBlank;
222         let scene = &(*self.server).scene;
223         ffi::wlr_scene_node_set_enabled(scene.locked_tree as *mut ffi::wlr_scene_node, true);
224         ffi::wlr_scene_node_set_enabled(scene.normal_tree as *mut ffi::wlr_scene_node, false);
225 
226         let seats_head = &mut (*self.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
227         let mut curr = (*seats_head).next;
228         while curr != seats_head {
229             let next = (*curr).next;
230             let seat = crate::container_of!(curr, crate::seat::Seat, link);
231             (*seat).focus(Focus::None);
232             curr = next;
233         }
234 
235         // Each enabled output reports `Blanked` after its next frame, and the
236         // last of them completes the lock in `maybe_lock`. With none enabled
237         // (the idle timeout darkened them all) there is nothing to wait for,
238         // and this call completes it now.
239         let outputs_head = &mut (*self.server).om.outputs as *mut ffi::wl_list as *mut WlList;
240         let mut curr = (*outputs_head).next;
241         while curr != outputs_head {
242             let next = (*curr).next;
243             let output = crate::container_of!(curr, crate::output::Output, link);
244             if !(*output).wlr_output.is_null() && ffi::river_wlr_output_get_enabled((*output).wlr_output) {
245                 ffi::wlr_output_schedule_frame((*output).wlr_output);
246             }
247             curr = next;
248         }
249         (*self.server).wm.dirty_windowing();
250         self.maybe_lock();
251 
252         self.respawn_attempts = 0;
253         if !self.respawn_timer.is_null() {
254             ffi::wl_event_source_timer_update(self.respawn_timer, 1);
255         }
256     }
257 
258     /// Answer `tx` once the session is locked: now if it is, else from
259     /// `send_locked`.
260     pub fn reply_when_locked(&mut self, tx: std::sync::mpsc::Sender<String>) {
261         if self.state == LockState::Locked {
262             let _ = tx.send("ok locked\n".to_string());
263         } else {
264             self.lock_waiters.get_or_insert_with(Vec::new).push(tx);
265         }
266     }
267 
268     /// Bring a locker back after the one holding the session went away
269     /// without unlocking.
270     ///
271     /// The session stays locked when a locker dies — that is the protocol's
272     /// guarantee and this does not weaken it. What it fixes is that the
273     /// screen was then a dead end: locked, blank, with no process left to
274     /// type a password into, so the only way back into the session was a TTY
275     /// and a kill. `handle_new_lock` already knows how to hand an
276     /// already-locked session to a fresh client ("given control of already
277     /// locked session"); nothing ever started one.
278     ///
279     /// Backed off and capped by [`RESPAWN_BACKOFF_MS`]. Exhausting it leaves
280     /// the session exactly as it is now — locked — which is why giving up is
281     /// an acceptable outcome and looping forever is not.
282     unsafe fn schedule_locker_respawn(&mut self) {
283         if self.respawn_timer.is_null() {
284             return;
285         }
286         let Some(&delay) = RESPAWN_BACKOFF_MS.get(self.respawn_attempts) else {
287             log::error!(
288                 "the locker died {} times without drawing; giving up. The session \
289                  STAYS LOCKED and there is no prompt to type into — switch to a TTY \
290                  (ctrl+alt+F2), log in, and run `cce-lock` against this display, or \
291                  kill the session.",
292                 self.respawn_attempts
293             );
294             return;
295         };
296         self.respawn_attempts += 1;
297         // Worded for both callers: the one after a locker vanished, and the
298         // one right after a spawn that arms this as a "did it take?" check.
299         // The check is the common case and is cancelled by `handle_new_lock`
300         // without ever firing, so this must not promise a respawn outright.
301         log::warn!(
302             "no lock client for a locked session; starting one in {}ms unless one \
303              binds first (attempt {} of {})",
304             delay,
305             self.respawn_attempts,
306             RESPAWN_BACKOFF_MS.len()
307         );
308         ffi::wl_event_source_timer_update(self.respawn_timer, delay);
309     }
310 
311     /// Stop a respawn that is armed but no longer wanted — a locker is here.
312     /// Without this, a timer armed during the gap could fire after the
313     /// session was unlocked and lock it again out of nowhere.
314     unsafe fn cancel_locker_respawn(&mut self) {
315         if !self.respawn_timer.is_null() {
316             ffi::wl_event_source_timer_update(self.respawn_timer, 0);
317         }
318     }
319 }
320 
321 pub struct LockSurface {
322     pub tree: *mut ffi::wlr_scene_tree,
323     pub wlr_lock_surface: *mut ffi::wlr_session_lock_surface_v1,
324     pub lock: *mut ffi::wlr_session_lock_v1,
325     pub manager: *mut LockManager,
326 
327     pub idle_update_focus: *mut ffi::wl_event_source,
328 
329     pub map: ffi::wl_listener,
330     pub surface_destroy: ffi::wl_listener,
331 }
332 
333 impl LockSurface {
334     pub unsafe fn create(
335         wlr_lock_surface: *mut ffi::wlr_session_lock_surface_v1,
336         lock: *mut ffi::wlr_session_lock_v1,
337         manager: *mut LockManager,
338     ) -> Result<*mut Self, &'static str> {
339         let tree = ffi::wlr_scene_subsurface_tree_create(
340             (*(*manager).server).scene.locked_tree,
341             (*wlr_lock_surface).surface,
342         );
343         if tree.is_null() {
344             return Err("Failed to create subsurface tree for lock surface");
345         }
346 
347         let lock_surface = Box::into_raw(Box::new(Self {
348             tree,
349             wlr_lock_surface,
350             lock,
351             manager,
352             idle_update_focus: std::ptr::null_mut(),
353             map: std::mem::zeroed(),
354             surface_destroy: std::mem::zeroed(),
355         }));
356 
357         (*wlr_lock_surface).data = lock_surface as *mut _;
358 
359         SceneNodeData::attach(tree as *mut ffi::wlr_scene_node, SceneNodeDataVal::LockSurface(lock_surface));
360         ffi::river_wlr_surface_set_data((*wlr_lock_surface).surface, tree as *mut ffi::wlr_scene_node as *mut _);
361 
362         let map_ptr = &mut (*lock_surface).map as *mut ffi::wl_listener as *mut WlListener;
363         (*map_ptr).notify = Some(handle_lock_surface_map);
364         wl_signal_add(
365             ffi::river_wlr_surface_get_map_signal((*wlr_lock_surface).surface),
366             &mut (*lock_surface).map,
367         );
368 
369         let destroy_ptr = &mut (*lock_surface).surface_destroy as *mut ffi::wl_listener as *mut WlListener;
370         (*destroy_ptr).notify = Some(handle_lock_surface_destroy);
371         wl_signal_add(
372             &mut (*wlr_lock_surface).events.destroy,
373             &mut (*lock_surface).surface_destroy,
374         );
375 
376         (*lock_surface).configure();
377 
378         Ok(lock_surface)
379     }
380 
381     pub unsafe fn destroy(lock_surface: *mut Self) {
382         let mut new_focus = Focus::None;
383         let surfaces_head = &mut (*(*lock_surface).lock).surfaces as *mut ffi::wl_list as *mut WlList;
384         let mut curr = (*surfaces_head).next;
385         while curr != surfaces_head {
386             let next = (*curr).next;
387             let wlr_lock_surface = crate::container_of!(curr, ffi::wlr_session_lock_surface_v1, link);
388             if wlr_lock_surface != (*lock_surface).wlr_lock_surface {
389                 let other_surf = (*wlr_lock_surface).data as *mut LockSurface;
390                 if !other_surf.is_null() {
391                     new_focus = Focus::LockSurface(other_surf);
392                     break;
393                 }
394             }
395             curr = next;
396         }
397 
398         let server = (*(*lock_surface).manager).server;
399         let seats_head = &mut (*server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
400         let mut curr = (*seats_head).next;
401         while curr != seats_head {
402             let next = (*curr).next;
403             let seat = crate::container_of!(curr, crate::seat::Seat, link);
404             if let Focus::LockSurface(focused_surf) = (*seat).focused {
405                 if focused_surf == lock_surface {
406                     (*seat).focus(new_focus);
407                 }
408             }
409             (*seat).cursor.update_state();
410             curr = next;
411         }
412 
413         if !(*lock_surface).idle_update_focus.is_null() {
414             ffi::wl_event_source_remove((*lock_surface).idle_update_focus);
415         }
416 
417         wl_listener_remove(&mut (*lock_surface).map);
418         wl_listener_remove(&mut (*lock_surface).surface_destroy);
419 
420         ffi::river_wlr_surface_set_data((*(*lock_surface).wlr_lock_surface).surface, std::ptr::null_mut());
421 
422         let _ = Box::from_raw(lock_surface);
423     }
424 
425     pub unsafe fn get_output(&self) -> *mut crate::output::Output {
426         ffi::river_wlr_output_get_data((*self.wlr_lock_surface).output) as *mut crate::output::Output
427     }
428 
429     pub unsafe fn configure(&self) {
430         let mut width: i32 = 0;
431         let mut height: i32 = 0;
432         ffi::wlr_output_effective_resolution((*self.wlr_lock_surface).output, &mut width, &mut height);
433         ffi::wlr_session_lock_surface_v1_configure(self.wlr_lock_surface, width as u32, height as u32);
434     }
435 }
436 
437 unsafe extern "C" fn handle_lock_surfaces_timeout(data: *mut std::ffi::c_void) -> std::os::raw::c_int {
438     let manager = &mut *(data as *mut LockManager);
439     log::error!("waiting for lock surfaces timed out, imperfect frames may be shown");
440 
441     assert!(manager.state == LockState::WaitingForLockSurfaces);
442     manager.state = LockState::WaitingForBlank;
443 
444     ffi::wlr_scene_node_set_enabled((*manager.server).scene.normal_tree as *mut ffi::wlr_scene_node, false);
445 
446     manager.maybe_lock();
447 
448     0
449 }
450 
451 /// Start a locker for a session that is locked and has none.
452 ///
453 /// Forked and detached like every other client the compositor starts (the
454 /// server's SIGCHLD source reaps it). The new process calls
455 /// `ext_session_lock_manager_v1.lock()` and `handle_new_lock` hands it the
456 /// session that is already locked, so the screen never unlocks across the
457 /// gap — the user just gets a prompt back.
458 unsafe extern "C" fn handle_respawn_timeout(data: *mut std::ffi::c_void) -> std::os::raw::c_int {
459     let manager = &mut *(data as *mut LockManager);
460 
461     // The world may have moved while the timer was armed: a locker of the
462     // user's own may have attached, or the session may be unlocked. Either
463     // way, spawning now would seize a session nobody asked us to.
464     if manager.state == LockState::Unlocked || !manager.lock.is_null() {
465         return 0;
466     }
467 
468     let cmd = cce_lock_cmd();
469     log::info!("starting the locker: {}", cmd);
470     match nix::unistd::fork() {
471         Ok(nix::unistd::ForkResult::Child) => {
472             crate::process::cleanup_child();
473             let sh = std::ffi::CString::new("/bin/sh").unwrap();
474             let dash_c = std::ffi::CString::new("-c").unwrap();
475             let cmd_c = std::ffi::CString::new(cmd)
476                 .unwrap_or_else(|_| std::ffi::CString::new("true").unwrap());
477             let args = [sh.as_c_str(), dash_c.as_c_str(), cmd_c.as_c_str()];
478             let _ = nix::unistd::execv(&sh, &args);
479             std::process::exit(1);
480         }
481         Ok(nix::unistd::ForkResult::Parent { .. }) => {
482             // Arm the NEXT step as a "did it take?" check, and let
483             // `handle_new_lock` cancel it when the new locker binds. A
484             // forked child proves nothing: the likeliest real failure is a
485             // locker that cannot start at all — no PAM stack, no GPU, binary
486             // missing — and that one dies without ever creating a
487             // `wlr_session_lock_v1`, so no destroy event is coming to
488             // trigger another attempt. Without this, the single attempt
489             // failed silently and the user stayed locked out with nothing in
490             // the log to say why.
491             manager.schedule_locker_respawn();
492         }
493         Err(e) => {
494             log::error!("failed to fork the locker respawn: {}", e);
495             manager.schedule_locker_respawn();
496         }
497     }
498 
499     0
500 }
501 
502 unsafe extern "C" fn handle_new_lock(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
503     let manager = &mut *crate::container_of!(listener, LockManager, new_lock);
504     let lock = data as *mut ffi::wlr_session_lock_v1;
505 
506     log::debug!("session lock client made lock request");
507 
508     if !manager.lock.is_null() {
509         log::info!("denying new session lock client, an active one already exists");
510         ffi::wlr_session_lock_v1_destroy(lock);
511         return;
512     }
513 
514     manager.lock = lock;
515 
516     // Someone is holding the session now — whether that is the respawn we
517     // asked for or a locker the user started themselves, we must not spawn
518     // another on top of it.
519     manager.cancel_locker_respawn();
520 
521     if manager.state == LockState::Unlocked {
522         manager.state = LockState::WaitingForLockSurfaces;
523 
524         ffi::wlr_scene_node_set_enabled((*manager.server).scene.locked_tree as *mut ffi::wlr_scene_node, true);
525 
526         ffi::wl_event_source_timer_update(manager.lock_surfaces_timer, 200);
527 
528         let seats_head = &mut (*manager.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
529         let mut curr = (*seats_head).next;
530         while curr != seats_head {
531             let next = (*curr).next;
532             let seat = crate::container_of!(curr, crate::seat::Seat, link);
533             (*seat).focus(Focus::None);
534             curr = next;
535         }
536     } else {
537         if manager.state == LockState::Locked {
538             ffi::wlr_session_lock_v1_send_locked(lock);
539         }
540         log::info!("new session lock client given control of already locked session");
541     }
542 
543     let unlock_ptr = &mut manager.unlock as *mut ffi::wl_listener as *mut WlListener;
544     (*unlock_ptr).notify = Some(handle_unlock);
545     wl_signal_add(&mut (*lock).events.unlock, &mut manager.unlock);
546 
547     let destroy_ptr = &mut manager.destroy as *mut ffi::wl_listener as *mut WlListener;
548     (*destroy_ptr).notify = Some(handle_destroy);
549     wl_signal_add(&mut (*lock).events.destroy, &mut manager.destroy);
550 
551     let new_surface_ptr = &mut manager.new_surface as *mut ffi::wl_listener as *mut WlListener;
552     (*new_surface_ptr).notify = Some(handle_surface);
553     wl_signal_add(&mut (*lock).events.new_surface, &mut manager.new_surface);
554 }
555 
556 unsafe extern "C" fn handle_unlock(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
557     let manager = &mut *crate::container_of!(listener, LockManager, unlock);
558 
559     manager.state = LockState::Unlocked;
560     log::info!("session unlocked");
561     // Nobody is waiting for a lock that is over.
562     manager.lock_waiters = None;
563 
564     // The session is going away legitimately: no respawn is wanted, and the
565     // next lock starts with a full budget.
566     manager.cancel_locker_respawn();
567     manager.respawn_attempts = 0;
568 
569     ffi::wlr_scene_node_set_enabled((*manager.server).scene.normal_tree as *mut ffi::wlr_scene_node, true);
570     ffi::wlr_scene_node_set_enabled((*manager.server).scene.locked_tree as *mut ffi::wlr_scene_node, false);
571 
572     let seats_head = &mut (*manager.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
573     let mut curr = (*seats_head).next;
574     while curr != seats_head {
575         let next = (*curr).next;
576         let seat = crate::container_of!(curr, crate::seat::Seat, link);
577         (*seat).focus(Focus::None);
578         curr = next;
579     }
580 
581     handle_destroy(&mut manager.destroy, std::ptr::null_mut());
582 
583     (*manager.server).wm.dirty_windowing();
584 }
585 
586 unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
587     let manager = &mut *crate::container_of!(listener, LockManager, destroy);
588 
589     log::debug!("ext_session_lock_v1 destroyed");
590 
591     wl_listener_remove(&mut manager.new_surface);
592     wl_listener_remove(&mut manager.unlock);
593     wl_listener_remove(&mut manager.destroy);
594 
595     manager.lock = std::ptr::null_mut();
596     if manager.state == LockState::WaitingForLockSurfaces {
597         manager.state = LockState::WaitingForBlank;
598         ffi::wl_event_source_timer_update(manager.lock_surfaces_timer, 0);
599     }
600 
601     // Reached two ways: from `handle_unlock`, which has already set the state
602     // to Unlocked and is just tearing down; or from wlroots because the lock
603     // client died. Only the second leaves the user facing a locked screen
604     // with nothing to authenticate against.
605     if manager.state != LockState::Unlocked {
606         manager.schedule_locker_respawn();
607     }
608 }
609 
610 unsafe extern "C" fn handle_surface(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
611     let manager = &mut *crate::container_of!(listener, LockManager, new_surface);
612     let wlr_lock_surface = data as *mut ffi::wlr_session_lock_surface_v1;
613 
614     log::debug!("new ext_session_lock_surface_v1 created");
615 
616     // Far enough to put something on screen, so this is not the startup crash
617     // loop the cap exists for: give the next failure a full budget again.
618     manager.respawn_attempts = 0;
619 
620     assert!(manager.state != LockState::Unlocked);
621     assert!(!manager.lock.is_null());
622 
623     if LockSurface::create(wlr_lock_surface, manager.lock, manager).is_err() {
624         log::error!("out of memory");
625         ffi::wl_resource_post_no_memory((*wlr_lock_surface).resource);
626     }
627 }
628 
629 unsafe extern "C" fn update_focus(data: *mut std::ffi::c_void) {
630     let lock_surface = data as *mut LockSurface;
631     let manager = (*lock_surface).manager;
632 
633     let seats_head = &mut (*(*manager).server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
634     let mut curr = (*seats_head).next;
635     while curr != seats_head {
636         let next = (*curr).next;
637         let seat = crate::container_of!(curr, crate::seat::Seat, link);
638         if !matches!((*seat).focused, Focus::LockSurface(s) if s == lock_surface) {
639             (*seat).focus(Focus::LockSurface(lock_surface));
640         }
641         (*seat).cursor.update_state();
642         curr = next;
643     }
644 
645     (*lock_surface).idle_update_focus = std::ptr::null_mut();
646 }
647 
648 unsafe extern "C" fn handle_lock_surface_map(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
649     let lock_surface = crate::container_of!(listener, LockSurface, map);
650 
651     let output = (*lock_surface).get_output();
652     let x = (*output).sent.x;
653     let y = (*output).sent.y;
654     ffi::wlr_scene_node_set_position((*lock_surface).tree as *mut ffi::wlr_scene_node, x, y);
655 
656     let server = (*(*lock_surface).manager).server;
657     let event_loop = ffi::wl_display_get_event_loop((*server).wl_server);
658     assert!((*lock_surface).idle_update_focus.is_null());
659 
660     let idle = ffi::wl_event_loop_add_idle(
661         event_loop,
662         Some(update_focus),
663         lock_surface as *mut _,
664     );
665     if idle.is_null() {
666         log::error!("Failed to create idle update focus event source");
667         return;
668     }
669     (*lock_surface).idle_update_focus = idle;
670 }
671 
672 unsafe extern "C" fn handle_lock_surface_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
673     let lock_surface = crate::container_of!(listener, LockSurface, surface_destroy);
674     LockSurface::destroy(lock_surface);
675 }