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

src/server/idle_inhibit_manager.rs (7.4K)

  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};
  6 use crate::scene_node_data::SceneNodeData;
  7 
  8 pub struct IdleInhibitManager {
  9     pub wlr_manager: *mut ffi::wlr_idle_inhibit_manager_v1,
 10     pub new_idle_inhibitor: ffi::wl_listener,
 11     pub inhibitors: ffi::wl_list,
 12     pub server: *mut Server,
 13 }
 14 
 15 impl IdleInhibitManager {
 16     pub unsafe fn init(&mut self, server: *mut Server) -> Result<(), &'static str> {
 17         self.server = server;
 18         ffi::wl_list_init(&mut self.inhibitors);
 19 
 20         let wlr_manager = ffi::wlr_idle_inhibit_v1_create((*server).wl_server);
 21         if wlr_manager.is_null() {
 22             return Err("Failed to create wlr_idle_inhibit_manager_v1");
 23         }
 24         self.wlr_manager = wlr_manager;
 25 
 26         let new_inhibitor_listener = &mut self.new_idle_inhibitor as *mut ffi::wl_listener as *mut WlListener;
 27         (*new_inhibitor_listener).notify = Some(handle_new_idle_inhibitor);
 28         wl_signal_add(
 29             &mut (*self.wlr_manager).events.new_inhibitor,
 30             &mut self.new_idle_inhibitor,
 31         );
 32 
 33         Ok(())
 34     }
 35 
 36     pub unsafe fn deinit(&mut self) {
 37         wl_listener_remove(&mut self.new_idle_inhibitor);
 38         
 39         let inhibitors_head = &mut self.inhibitors as *mut ffi::wl_list as *mut crate::server::WlList;
 40         let mut curr = (*inhibitors_head).next;
 41         while curr != inhibitors_head {
 42             let next = (*curr).next;
 43             let inhibitor = crate::container_of!(curr, IdleInhibitor, link);
 44             IdleInhibitor::destroy(inhibitor);
 45             curr = next;
 46         }
 47     }
 48 
 49     /// Recount the live inhibitors and hand the idle manager the list of who
 50     /// holds one, so `ccectl idle status` can name them. Every inhibitor is
 51     /// visited (no early exit): two apps holding one are both reported.
 52     pub unsafe fn check_active(&self) {
 53         let mut names: Vec<String> = Vec::new();
 54 
 55         let inhibitors_head = &self.inhibitors as *const ffi::wl_list as *mut crate::server::WlList;
 56         let mut curr = (*inhibitors_head).next;
 57         while curr != inhibitors_head {
 58             let next = (*curr).next;
 59             let inhibitor = crate::container_of!(curr, IdleInhibitor, link);
 60 
 61             let surface = (*(*inhibitor).wlr_inhibitor).surface;
 62             if let Some(node_data) = SceneNodeData::from_surface(surface) {
 63                 use crate::scene_node_data::SceneNodeDataVal::*;
 64                 let name = match node_data.data {
 65                     Window(w) => inhibitor_name(
 66                         "window",
 67                         (*w).get_app_id_string().as_deref(),
 68                         (*w).get_title_string().as_deref(),
 69                     ),
 70                     ShellSurface(_) => inhibitor_name("shell-surface", None, None),
 71                     LockSurface(_) => inhibitor_name("lock-surface", None, None),
 72                     LayerSurface(l) => {
 73                         let ns = (*(*l).wlr_layer_surface).namespace;
 74                         let ns = (!ns.is_null())
 75                             .then(|| std::ffi::CStr::from_ptr(ns).to_string_lossy().into_owned());
 76                         inhibitor_name("layer", ns.as_deref(), None)
 77                     }
 78                     OverrideRedirect(_) => inhibitor_name("xwayland-override-redirect", None, None),
 79                 };
 80                 if !names.contains(&name) {
 81                     names.push(name);
 82                 }
 83             }
 84             curr = next;
 85         }
 86 
 87         let inhibited = !names.is_empty();
 88         let notifier = (*self.server).input_manager.idle_notifier;
 89         if !notifier.is_null() {
 90             ffi::wlr_idle_notifier_v1_set_inhibited(notifier, inhibited);
 91         }
 92         (*self.server).idle.set_inhibitors(names);
 93     }
 94 }
 95 
 96 /// What the status line calls one inhibitor: a window's app id, its title
 97 /// when the client never set one, and the bare kind when it set neither; a
 98 /// layer surface shows as `layer:<namespace>`. Kinds are stable words, so a
 99 /// script can tell a browser holding the display from the lock screen.
100 pub fn inhibitor_name(kind: &str, id: Option<&str>, title: Option<&str>) -> String {
101     let id = id.filter(|s| !s.is_empty());
102     let title = title.filter(|s| !s.is_empty());
103     match (kind, id, title) {
104         ("window", Some(id), _) => id.to_string(),
105         ("window", None, Some(title)) => format!("window:{}", title),
106         (kind, Some(id), _) => format!("{}:{}", kind, id),
107         (kind, None, _) => kind.to_string(),
108     }
109 }
110 
111 #[cfg(test)]
112 mod tests {
113     use super::inhibitor_name;
114 
115     #[test]
116     fn a_window_is_its_app_id_then_its_title_then_the_kind() {
117         assert_eq!(inhibitor_name("window", Some("cce-browser"), Some("Video")), "cce-browser");
118         assert_eq!(inhibitor_name("window", None, Some("Video")), "window:Video");
119         assert_eq!(inhibitor_name("window", Some(""), None), "window");
120     }
121 
122     #[test]
123     fn other_surfaces_are_their_kind_with_an_optional_id() {
124         assert_eq!(inhibitor_name("layer", Some("bar"), None), "layer:bar");
125         assert_eq!(inhibitor_name("layer", None, None), "layer");
126         assert_eq!(inhibitor_name("lock-surface", None, None), "lock-surface");
127     }
128 }
129 
130 pub struct IdleInhibitor {
131     pub inhibit_manager: *mut IdleInhibitManager,
132     pub wlr_inhibitor: *mut ffi::wlr_idle_inhibitor_v1,
133     pub listen_destroy: ffi::wl_listener,
134     pub link: ffi::wl_list,
135 }
136 
137 impl IdleInhibitor {
138     pub unsafe fn create(
139         wlr_inhibitor: *mut ffi::wlr_idle_inhibitor_v1,
140         inhibit_manager: *mut IdleInhibitManager,
141     ) -> Result<*mut Self, &'static str> {
142         let inhibitor = Box::into_raw(Box::new(Self {
143             inhibit_manager,
144             wlr_inhibitor,
145             listen_destroy: std::mem::zeroed(),
146             link: std::mem::zeroed(),
147         }));
148 
149         let destroy_listener = &mut (*inhibitor).listen_destroy as *mut ffi::wl_listener as *mut WlListener;
150         (*destroy_listener).notify = Some(handle_inhibitor_destroy);
151         wl_signal_add(
152             &mut (*wlr_inhibitor).events.destroy,
153             &mut (*inhibitor).listen_destroy,
154         );
155 
156         let list_head = &mut (*inhibit_manager).inhibitors as *mut ffi::wl_list as *mut crate::server::WlList;
157         crate::server::wl_list_insert((*list_head).prev, &mut (*inhibitor).link as *mut ffi::wl_list as *mut crate::server::WlList);
158 
159         (*inhibit_manager).check_active();
160 
161         Ok(inhibitor)
162     }
163 
164     pub unsafe fn destroy(inhibitor: *mut Self) {
165         wl_listener_remove(&mut (*inhibitor).listen_destroy);
166         crate::server::wl_list_remove(&mut (*inhibitor).link as *mut ffi::wl_list as *mut crate::server::WlList);
167         
168         let manager = (*inhibitor).inhibit_manager;
169         let _boxed = Box::from_raw(inhibitor);
170         
171         (*manager).check_active();
172     }
173 }
174 
175 unsafe extern "C" fn handle_new_idle_inhibitor(
176     listener: *mut ffi::wl_listener,
177     data: *mut std::ffi::c_void,
178 ) {
179     let manager = &mut *crate::container_of!(listener, IdleInhibitManager, new_idle_inhibitor);
180     let inhibitor = data as *mut ffi::wlr_idle_inhibitor_v1;
181 
182     if IdleInhibitor::create(inhibitor, manager).is_err() {
183         log::error!("failed to create idle inhibitor");
184     }
185 }
186 
187 unsafe extern "C" fn handle_inhibitor_destroy(
188     listener: *mut ffi::wl_listener,
189     _data: *mut std::ffi::c_void,
190 ) {
191     let inhibitor = crate::container_of!(listener, IdleInhibitor, listen_destroy);
192     IdleInhibitor::destroy(inhibitor);
193 }