Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
src/server/lock_manager.rs (23.3K)
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 pub server: *mut Server,
43
44 pub new_lock: ffi::wl_listener,
45 pub unlock: ffi::wl_listener,
46 pub destroy: ffi::wl_listener,
47 pub new_surface: ffi::wl_listener,
48 }
49
50 #[derive(Debug, Clone, Copy, PartialEq)]
51 pub enum LockState {
52 Unlocked,
53 Locked,
54 WaitingForBlank,
55 WaitingForLockSurfaces,
56 }
57
58 impl Default for LockManager {
59 fn default() -> Self {
60 unsafe { std::mem::zeroed() }
61 }
62 }
63
64 impl LockManager {
65 pub unsafe fn init(&mut self, server: *mut Server) -> Result<(), &'static str> {
66 self.server = server;
67 self.state = LockState::Unlocked;
68
69 let wlr_manager = ffi::wlr_session_lock_manager_v1_create((*server).wl_server);
70 if wlr_manager.is_null() {
71 return Err("Failed to create wlr_session_lock_manager_v1");
72 }
73 self.wlr_manager = wlr_manager;
74
75 let event_loop = ffi::wl_display_get_event_loop((*server).wl_server);
76 let timer = ffi::wl_event_loop_add_timer(
77 event_loop,
78 Some(handle_lock_surfaces_timeout),
79 self as *mut LockManager as *mut _,
80 );
81 if timer.is_null() {
82 return Err("Failed to create lock surfaces timer");
83 }
84 self.lock_surfaces_timer = timer;
85
86 let respawn_timer = ffi::wl_event_loop_add_timer(
87 event_loop,
88 Some(handle_respawn_timeout),
89 self as *mut LockManager as *mut _,
90 );
91 if respawn_timer.is_null() {
92 return Err("Failed to create the locker respawn timer");
93 }
94 self.respawn_timer = respawn_timer;
95
96 let new_lock_ptr = &mut self.new_lock as *mut ffi::wl_listener as *mut WlListener;
97 (*new_lock_ptr).notify = Some(handle_new_lock);
98 wl_signal_add(&mut (*self.wlr_manager).events.new_lock, &mut self.new_lock);
99
100 Ok(())
101 }
102
103 pub unsafe fn deinit(&mut self) {
104 if !self.lock_surfaces_timer.is_null() {
105 ffi::wl_event_source_remove(self.lock_surfaces_timer);
106 self.lock_surfaces_timer = std::ptr::null_mut();
107 }
108 if !self.respawn_timer.is_null() {
109 ffi::wl_event_source_remove(self.respawn_timer);
110 self.respawn_timer = std::ptr::null_mut();
111 }
112 wl_listener_remove(&mut self.new_lock);
113 }
114
115 pub unsafe fn lock_surface_from_output(
116 &self,
117 output: *mut crate::output::Output,
118 ) -> Option<*mut LockSurface> {
119 if self.lock.is_null() {
120 return None;
121 }
122
123 let surfaces_head = &mut (*self.lock).surfaces as *mut ffi::wl_list as *mut WlList;
124 let mut curr = (*surfaces_head).next;
125 while curr != surfaces_head {
126 let next = (*curr).next;
127 let wlr_lock_surface = crate::container_of!(curr, ffi::wlr_session_lock_surface_v1, link);
128 let lock_surface = (*wlr_lock_surface).data as *mut LockSurface;
129 if !lock_surface.is_null() && (*lock_surface).get_output() == output {
130 return Some(lock_surface);
131 }
132 curr = next;
133 }
134
135 None
136 }
137
138 pub unsafe fn maybe_lock(&mut self) {
139 let mut all_outputs_blanked = true;
140 let mut all_outputs_rendered_lock_surface = true;
141
142 let outputs_head = &mut (*self.server).om.outputs as *mut ffi::wl_list as *mut WlList;
143 let mut curr = (*outputs_head).next;
144 while curr != outputs_head {
145 let next = (*curr).next;
146 let output = crate::container_of!(curr, crate::output::Output, link);
147 let wlr_output = (*output).wlr_output;
148 if !wlr_output.is_null() && ffi::river_wlr_output_get_enabled(wlr_output) {
149 match (*output).lock_render_state {
150 crate::output::LockRenderState::PendingUnlock
151 | crate::output::LockRenderState::Unlocked
152 | crate::output::LockRenderState::PendingBlank
153 | crate::output::LockRenderState::PendingLockSurface => {
154 all_outputs_blanked = false;
155 all_outputs_rendered_lock_surface = false;
156 }
157 crate::output::LockRenderState::Blanked => {
158 all_outputs_rendered_lock_surface = false;
159 }
160 crate::output::LockRenderState::LockSurface => {}
161 }
162 }
163 curr = next;
164 }
165
166 match self.state {
167 LockState::WaitingForLockSurfaces => {
168 if all_outputs_rendered_lock_surface {
169 self.send_locked();
170 ffi::wlr_scene_node_set_enabled((*self.server).scene.normal_tree as *mut ffi::wlr_scene_node, false);
171 ffi::wl_event_source_timer_update(self.lock_surfaces_timer, 0);
172 }
173 }
174 LockState::WaitingForBlank => {
175 if all_outputs_blanked {
176 self.send_locked();
177 }
178 }
179 _ => {}
180 }
181 }
182
183 pub unsafe fn send_locked(&mut self) {
184 log::info!("session locked");
185 if !self.lock.is_null() {
186 ffi::wlr_session_lock_v1_send_locked(self.lock);
187 }
188 self.state = LockState::Locked;
189 (*self.server).wm.dirty_windowing();
190 }
191
192 /// Bring a locker back after the one holding the session went away
193 /// without unlocking.
194 ///
195 /// The session stays locked when a locker dies — that is the protocol's
196 /// guarantee and this does not weaken it. What it fixes is that the
197 /// screen was then a dead end: locked, blank, with no process left to
198 /// type a password into, so the only way back into the session was a TTY
199 /// and a kill. `handle_new_lock` already knows how to hand an
200 /// already-locked session to a fresh client ("given control of already
201 /// locked session"); nothing ever started one.
202 ///
203 /// Backed off and capped by [`RESPAWN_BACKOFF_MS`]. Exhausting it leaves
204 /// the session exactly as it is now — locked — which is why giving up is
205 /// an acceptable outcome and looping forever is not.
206 unsafe fn schedule_locker_respawn(&mut self) {
207 if self.respawn_timer.is_null() {
208 return;
209 }
210 let Some(&delay) = RESPAWN_BACKOFF_MS.get(self.respawn_attempts) else {
211 log::error!(
212 "the locker died {} times without drawing; giving up. The session \
213 STAYS LOCKED and there is no prompt to type into — switch to a TTY \
214 (ctrl+alt+F2), log in, and run `cce-lock` against this display, or \
215 kill the session.",
216 self.respawn_attempts
217 );
218 return;
219 };
220 self.respawn_attempts += 1;
221 // Worded for both callers: the one after a locker vanished, and the
222 // one right after a spawn that arms this as a "did it take?" check.
223 // The check is the common case and is cancelled by `handle_new_lock`
224 // without ever firing, so this must not promise a respawn outright.
225 log::warn!(
226 "no lock client for a locked session; starting one in {}ms unless one \
227 binds first (attempt {} of {})",
228 delay,
229 self.respawn_attempts,
230 RESPAWN_BACKOFF_MS.len()
231 );
232 ffi::wl_event_source_timer_update(self.respawn_timer, delay);
233 }
234
235 /// Stop a respawn that is armed but no longer wanted — a locker is here.
236 /// Without this, a timer armed during the gap could fire after the
237 /// session was unlocked and lock it again out of nowhere.
238 unsafe fn cancel_locker_respawn(&mut self) {
239 if !self.respawn_timer.is_null() {
240 ffi::wl_event_source_timer_update(self.respawn_timer, 0);
241 }
242 }
243 }
244
245 pub struct LockSurface {
246 pub tree: *mut ffi::wlr_scene_tree,
247 pub wlr_lock_surface: *mut ffi::wlr_session_lock_surface_v1,
248 pub lock: *mut ffi::wlr_session_lock_v1,
249 pub manager: *mut LockManager,
250
251 pub idle_update_focus: *mut ffi::wl_event_source,
252
253 pub map: ffi::wl_listener,
254 pub surface_destroy: ffi::wl_listener,
255 }
256
257 impl LockSurface {
258 pub unsafe fn create(
259 wlr_lock_surface: *mut ffi::wlr_session_lock_surface_v1,
260 lock: *mut ffi::wlr_session_lock_v1,
261 manager: *mut LockManager,
262 ) -> Result<*mut Self, &'static str> {
263 let tree = ffi::wlr_scene_subsurface_tree_create(
264 (*(*manager).server).scene.locked_tree,
265 (*wlr_lock_surface).surface,
266 );
267 if tree.is_null() {
268 return Err("Failed to create subsurface tree for lock surface");
269 }
270
271 let lock_surface = Box::into_raw(Box::new(Self {
272 tree,
273 wlr_lock_surface,
274 lock,
275 manager,
276 idle_update_focus: std::ptr::null_mut(),
277 map: std::mem::zeroed(),
278 surface_destroy: std::mem::zeroed(),
279 }));
280
281 (*wlr_lock_surface).data = lock_surface as *mut _;
282
283 SceneNodeData::attach(tree as *mut ffi::wlr_scene_node, SceneNodeDataVal::LockSurface(lock_surface));
284 ffi::river_wlr_surface_set_data((*wlr_lock_surface).surface, tree as *mut ffi::wlr_scene_node as *mut _);
285
286 let map_ptr = &mut (*lock_surface).map as *mut ffi::wl_listener as *mut WlListener;
287 (*map_ptr).notify = Some(handle_lock_surface_map);
288 wl_signal_add(
289 ffi::river_wlr_surface_get_map_signal((*wlr_lock_surface).surface),
290 &mut (*lock_surface).map,
291 );
292
293 let destroy_ptr = &mut (*lock_surface).surface_destroy as *mut ffi::wl_listener as *mut WlListener;
294 (*destroy_ptr).notify = Some(handle_lock_surface_destroy);
295 wl_signal_add(
296 &mut (*wlr_lock_surface).events.destroy,
297 &mut (*lock_surface).surface_destroy,
298 );
299
300 (*lock_surface).configure();
301
302 Ok(lock_surface)
303 }
304
305 pub unsafe fn destroy(lock_surface: *mut Self) {
306 let mut new_focus = Focus::None;
307 let surfaces_head = &mut (*(*lock_surface).lock).surfaces as *mut ffi::wl_list as *mut WlList;
308 let mut curr = (*surfaces_head).next;
309 while curr != surfaces_head {
310 let next = (*curr).next;
311 let wlr_lock_surface = crate::container_of!(curr, ffi::wlr_session_lock_surface_v1, link);
312 if wlr_lock_surface != (*lock_surface).wlr_lock_surface {
313 let other_surf = (*wlr_lock_surface).data as *mut LockSurface;
314 if !other_surf.is_null() {
315 new_focus = Focus::LockSurface(other_surf);
316 break;
317 }
318 }
319 curr = next;
320 }
321
322 let server = (*(*lock_surface).manager).server;
323 let seats_head = &mut (*server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
324 let mut curr = (*seats_head).next;
325 while curr != seats_head {
326 let next = (*curr).next;
327 let seat = crate::container_of!(curr, crate::seat::Seat, link);
328 if let Focus::LockSurface(focused_surf) = (*seat).focused {
329 if focused_surf == lock_surface {
330 (*seat).focus(new_focus);
331 }
332 }
333 (*seat).cursor.update_state();
334 curr = next;
335 }
336
337 if !(*lock_surface).idle_update_focus.is_null() {
338 ffi::wl_event_source_remove((*lock_surface).idle_update_focus);
339 }
340
341 wl_listener_remove(&mut (*lock_surface).map);
342 wl_listener_remove(&mut (*lock_surface).surface_destroy);
343
344 ffi::river_wlr_surface_set_data((*(*lock_surface).wlr_lock_surface).surface, std::ptr::null_mut());
345
346 let _ = Box::from_raw(lock_surface);
347 }
348
349 pub unsafe fn get_output(&self) -> *mut crate::output::Output {
350 ffi::river_wlr_output_get_data((*self.wlr_lock_surface).output) as *mut crate::output::Output
351 }
352
353 pub unsafe fn configure(&self) {
354 let mut width: i32 = 0;
355 let mut height: i32 = 0;
356 ffi::wlr_output_effective_resolution((*self.wlr_lock_surface).output, &mut width, &mut height);
357 ffi::wlr_session_lock_surface_v1_configure(self.wlr_lock_surface, width as u32, height as u32);
358 }
359 }
360
361 unsafe extern "C" fn handle_lock_surfaces_timeout(data: *mut std::ffi::c_void) -> std::os::raw::c_int {
362 let manager = &mut *(data as *mut LockManager);
363 log::error!("waiting for lock surfaces timed out, imperfect frames may be shown");
364
365 assert!(manager.state == LockState::WaitingForLockSurfaces);
366 manager.state = LockState::WaitingForBlank;
367
368 ffi::wlr_scene_node_set_enabled((*manager.server).scene.normal_tree as *mut ffi::wlr_scene_node, false);
369
370 manager.maybe_lock();
371
372 0
373 }
374
375 /// Start a locker for a session that is locked and has none.
376 ///
377 /// Forked and detached like every other client the compositor starts (the
378 /// server's SIGCHLD source reaps it). The new process calls
379 /// `ext_session_lock_manager_v1.lock()` and `handle_new_lock` hands it the
380 /// session that is already locked, so the screen never unlocks across the
381 /// gap — the user just gets a prompt back.
382 unsafe extern "C" fn handle_respawn_timeout(data: *mut std::ffi::c_void) -> std::os::raw::c_int {
383 let manager = &mut *(data as *mut LockManager);
384
385 // The world may have moved while the timer was armed: a locker of the
386 // user's own may have attached, or the session may be unlocked. Either
387 // way, spawning now would seize a session nobody asked us to.
388 if manager.state == LockState::Unlocked || !manager.lock.is_null() {
389 return 0;
390 }
391
392 let cmd = cce_lock_cmd();
393 log::warn!("respawning the locker: {}", cmd);
394 match nix::unistd::fork() {
395 Ok(nix::unistd::ForkResult::Child) => {
396 crate::process::cleanup_child();
397 let sh = std::ffi::CString::new("/bin/sh").unwrap();
398 let dash_c = std::ffi::CString::new("-c").unwrap();
399 let cmd_c = std::ffi::CString::new(cmd)
400 .unwrap_or_else(|_| std::ffi::CString::new("true").unwrap());
401 let args = [sh.as_c_str(), dash_c.as_c_str(), cmd_c.as_c_str()];
402 let _ = nix::unistd::execv(&sh, &args);
403 std::process::exit(1);
404 }
405 Ok(nix::unistd::ForkResult::Parent { .. }) => {
406 // Arm the NEXT step as a "did it take?" check, and let
407 // `handle_new_lock` cancel it when the new locker binds. A
408 // forked child proves nothing: the likeliest real failure is a
409 // locker that cannot start at all — no PAM stack, no GPU, binary
410 // missing — and that one dies without ever creating a
411 // `wlr_session_lock_v1`, so no destroy event is coming to
412 // trigger another attempt. Without this, the single attempt
413 // failed silently and the user stayed locked out with nothing in
414 // the log to say why.
415 manager.schedule_locker_respawn();
416 }
417 Err(e) => {
418 log::error!("failed to fork the locker respawn: {}", e);
419 manager.schedule_locker_respawn();
420 }
421 }
422
423 0
424 }
425
426 unsafe extern "C" fn handle_new_lock(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
427 let manager = &mut *crate::container_of!(listener, LockManager, new_lock);
428 let lock = data as *mut ffi::wlr_session_lock_v1;
429
430 log::debug!("session lock client made lock request");
431
432 if !manager.lock.is_null() {
433 log::info!("denying new session lock client, an active one already exists");
434 ffi::wlr_session_lock_v1_destroy(lock);
435 return;
436 }
437
438 manager.lock = lock;
439
440 // Someone is holding the session now — whether that is the respawn we
441 // asked for or a locker the user started themselves, we must not spawn
442 // another on top of it.
443 manager.cancel_locker_respawn();
444
445 if manager.state == LockState::Unlocked {
446 manager.state = LockState::WaitingForLockSurfaces;
447
448 ffi::wlr_scene_node_set_enabled((*manager.server).scene.locked_tree as *mut ffi::wlr_scene_node, true);
449
450 ffi::wl_event_source_timer_update(manager.lock_surfaces_timer, 200);
451
452 let seats_head = &mut (*manager.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
453 let mut curr = (*seats_head).next;
454 while curr != seats_head {
455 let next = (*curr).next;
456 let seat = crate::container_of!(curr, crate::seat::Seat, link);
457 (*seat).focus(Focus::None);
458 curr = next;
459 }
460 } else {
461 if manager.state == LockState::Locked {
462 ffi::wlr_session_lock_v1_send_locked(lock);
463 }
464 log::info!("new session lock client given control of already locked session");
465 }
466
467 let unlock_ptr = &mut manager.unlock as *mut ffi::wl_listener as *mut WlListener;
468 (*unlock_ptr).notify = Some(handle_unlock);
469 wl_signal_add(&mut (*lock).events.unlock, &mut manager.unlock);
470
471 let destroy_ptr = &mut manager.destroy as *mut ffi::wl_listener as *mut WlListener;
472 (*destroy_ptr).notify = Some(handle_destroy);
473 wl_signal_add(&mut (*lock).events.destroy, &mut manager.destroy);
474
475 let new_surface_ptr = &mut manager.new_surface as *mut ffi::wl_listener as *mut WlListener;
476 (*new_surface_ptr).notify = Some(handle_surface);
477 wl_signal_add(&mut (*lock).events.new_surface, &mut manager.new_surface);
478 }
479
480 unsafe extern "C" fn handle_unlock(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
481 let manager = &mut *crate::container_of!(listener, LockManager, unlock);
482
483 manager.state = LockState::Unlocked;
484 log::info!("session unlocked");
485
486 // The session is going away legitimately: no respawn is wanted, and the
487 // next lock starts with a full budget.
488 manager.cancel_locker_respawn();
489 manager.respawn_attempts = 0;
490
491 ffi::wlr_scene_node_set_enabled((*manager.server).scene.normal_tree as *mut ffi::wlr_scene_node, true);
492 ffi::wlr_scene_node_set_enabled((*manager.server).scene.locked_tree as *mut ffi::wlr_scene_node, false);
493
494 let seats_head = &mut (*manager.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
495 let mut curr = (*seats_head).next;
496 while curr != seats_head {
497 let next = (*curr).next;
498 let seat = crate::container_of!(curr, crate::seat::Seat, link);
499 (*seat).focus(Focus::None);
500 curr = next;
501 }
502
503 handle_destroy(&mut manager.destroy, std::ptr::null_mut());
504
505 (*manager.server).wm.dirty_windowing();
506 }
507
508 unsafe extern "C" fn handle_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
509 let manager = &mut *crate::container_of!(listener, LockManager, destroy);
510
511 log::debug!("ext_session_lock_v1 destroyed");
512
513 wl_listener_remove(&mut manager.new_surface);
514 wl_listener_remove(&mut manager.unlock);
515 wl_listener_remove(&mut manager.destroy);
516
517 manager.lock = std::ptr::null_mut();
518 if manager.state == LockState::WaitingForLockSurfaces {
519 manager.state = LockState::WaitingForBlank;
520 ffi::wl_event_source_timer_update(manager.lock_surfaces_timer, 0);
521 }
522
523 // Reached two ways: from `handle_unlock`, which has already set the state
524 // to Unlocked and is just tearing down; or from wlroots because the lock
525 // client died. Only the second leaves the user facing a locked screen
526 // with nothing to authenticate against.
527 if manager.state != LockState::Unlocked {
528 manager.schedule_locker_respawn();
529 }
530 }
531
532 unsafe extern "C" fn handle_surface(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
533 let manager = &mut *crate::container_of!(listener, LockManager, new_surface);
534 let wlr_lock_surface = data as *mut ffi::wlr_session_lock_surface_v1;
535
536 log::debug!("new ext_session_lock_surface_v1 created");
537
538 // Far enough to put something on screen, so this is not the startup crash
539 // loop the cap exists for: give the next failure a full budget again.
540 manager.respawn_attempts = 0;
541
542 assert!(manager.state != LockState::Unlocked);
543 assert!(!manager.lock.is_null());
544
545 if LockSurface::create(wlr_lock_surface, manager.lock, manager).is_err() {
546 log::error!("out of memory");
547 ffi::wl_resource_post_no_memory((*wlr_lock_surface).resource);
548 }
549 }
550
551 unsafe extern "C" fn update_focus(data: *mut std::ffi::c_void) {
552 let lock_surface = data as *mut LockSurface;
553 let manager = (*lock_surface).manager;
554
555 let seats_head = &mut (*(*manager).server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
556 let mut curr = (*seats_head).next;
557 while curr != seats_head {
558 let next = (*curr).next;
559 let seat = crate::container_of!(curr, crate::seat::Seat, link);
560 if !matches!((*seat).focused, Focus::LockSurface(s) if s == lock_surface) {
561 (*seat).focus(Focus::LockSurface(lock_surface));
562 }
563 (*seat).cursor.update_state();
564 curr = next;
565 }
566
567 (*lock_surface).idle_update_focus = std::ptr::null_mut();
568 }
569
570 unsafe extern "C" fn handle_lock_surface_map(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
571 let lock_surface = crate::container_of!(listener, LockSurface, map);
572
573 let output = (*lock_surface).get_output();
574 let x = (*output).sent.x;
575 let y = (*output).sent.y;
576 ffi::wlr_scene_node_set_position((*lock_surface).tree as *mut ffi::wlr_scene_node, x, y);
577
578 let server = (*(*lock_surface).manager).server;
579 let event_loop = ffi::wl_display_get_event_loop((*server).wl_server);
580 assert!((*lock_surface).idle_update_focus.is_null());
581
582 let idle = ffi::wl_event_loop_add_idle(
583 event_loop,
584 Some(update_focus),
585 lock_surface as *mut _,
586 );
587 if idle.is_null() {
588 log::error!("Failed to create idle update focus event source");
589 return;
590 }
591 (*lock_surface).idle_update_focus = idle;
592 }
593
594 unsafe extern "C" fn handle_lock_surface_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
595 let lock_surface = crate::container_of!(listener, LockSurface, surface_destroy);
596 LockSurface::destroy(lock_surface);
597 }