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 }