git.lucas.co / cce-compositor
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 }