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 }