Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
src/server/input_manager.rs (18.9K)
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, wl_signal_add, WlListener, wl_listener_remove, WlList, wl_list_insert, wl_list_remove};
6 use crate::seat::Seat;
7
8 pub struct InputManager {
9 pub server: *mut Server,
10 pub default_seat: *mut Seat,
11 pub seats: ffi::wl_list,
12 pub devices: ffi::wl_list, // list of InputDevice
13 pub objects: ffi::wl_list, // list of InputManagerObject
14 pub global: *mut ffi::wl_global,
15
16 pub idle_notifier: *mut ffi::wlr_idle_notifier_v1,
17 pub relative_pointer_manager: *mut ffi::wlr_relative_pointer_manager_v1,
18 pub pointer_gestures: *mut ffi::wlr_pointer_gestures_v1,
19 pub virtual_pointer_manager: *mut ffi::wlr_virtual_pointer_manager_v1,
20 pub virtual_keyboard_manager: *mut ffi::wlr_virtual_keyboard_manager_v1,
21 pub pointer_constraints: *mut ffi::wlr_pointer_constraints_v1,
22 pub input_method_manager: *mut ffi::wlr_input_method_manager_v2,
23 pub text_input_manager: *mut ffi::wlr_text_input_manager_v3,
24 pub tablet_manager: *mut ffi::wlr_tablet_manager_v2,
25
26 pub new_input_listener: ffi::wl_listener,
27 pub new_text_input: ffi::wl_listener,
28 pub new_input_method: ffi::wl_listener,
29 pub new_virtual_pointer_listener: ffi::wl_listener,
30 pub new_virtual_keyboard_listener: ffi::wl_listener,
31
32 /// Pending deferred pointer-focus re-evaluation (see
33 /// [`InputManager::schedule_pointer_refresh`]); null when none. One idle
34 /// source coalesces every scene-mapping change of a dispatch.
35 pub pointer_refresh_idle: *mut ffi::wl_event_source,
36 }
37
38 pub struct InputManagerObject {
39 pub manager: *mut InputManager,
40 pub resource: *mut ffi::wl_resource,
41 pub link: ffi::wl_list,
42 }
43
44 impl Default for InputManager {
45 fn default() -> Self {
46 unsafe { std::mem::zeroed() }
47 }
48 }
49
50 impl InputManager {
51 pub unsafe fn init(&mut self, server: *mut Server) -> Result<(), &'static str> {
52 self.server = server;
53 let wl_server = (*server).wl_server;
54
55 ffi::wl_list_init(&mut self.seats);
56 ffi::wl_list_init(&mut self.devices);
57 ffi::wl_list_init(&mut self.objects);
58
59 self.idle_notifier = ffi::wlr_idle_notifier_v1_create(wl_server);
60 self.relative_pointer_manager = ffi::wlr_relative_pointer_manager_v1_create(wl_server);
61 self.pointer_gestures = ffi::wlr_pointer_gestures_v1_create(wl_server);
62 self.virtual_pointer_manager = ffi::wlr_virtual_pointer_manager_v1_create(wl_server);
63 self.virtual_keyboard_manager = ffi::wlr_virtual_keyboard_manager_v1_create(wl_server);
64 self.pointer_constraints = ffi::wlr_pointer_constraints_v1_create(wl_server);
65 self.input_method_manager = ffi::wlr_input_method_manager_v2_create(wl_server);
66 self.text_input_manager = ffi::wlr_text_input_manager_v3_create(wl_server);
67 self.tablet_manager = ffi::wlr_tablet_v2_create(wl_server);
68
69 // Create default seat
70 self.default_seat = Seat::create(server, "default")?;
71
72 if !(*server).xwayland.is_null() {
73 ffi::wlr_xwayland_set_seat((*server).xwayland, (*self.default_seat).wlr_seat);
74 }
75
76 self.global = ffi::wl_global_create(
77 wl_server,
78 &ffi::river_input_manager_v1_interface,
79 2,
80 self as *mut InputManager as *mut _,
81 Some(bind_input_manager),
82 );
83 if self.global.is_null() {
84 return Err("Failed to create river_input_manager_v1 global");
85 }
86
87 let new_input_ptr = &mut self.new_input_listener as *mut ffi::wl_listener as *mut WlListener;
88 (*new_input_ptr).notify = Some(handle_new_input);
89
90 let signal = ffi::river_wlr_backend_get_new_input_signal((*server).backend);
91 wl_signal_add(signal, &mut self.new_input_listener);
92
93 // Connect new_text_input listener
94 let new_text_input_ptr = &mut self.new_text_input as *mut ffi::wl_listener as *mut WlListener;
95 (*new_text_input_ptr).notify = Some(handle_new_text_input);
96 wl_signal_add(&mut (*self.text_input_manager).events.new_text_input, &mut self.new_text_input);
97
98 // Connect new_input_method listener
99 let new_input_method_ptr = &mut self.new_input_method as *mut ffi::wl_listener as *mut WlListener;
100 (*new_input_method_ptr).notify = Some(handle_new_input_method);
101 wl_signal_add(&mut (*self.input_method_manager).events.new_input_method, &mut self.new_input_method);
102
103 // Connect new_virtual_pointer listener
104 let new_virtual_pointer_ptr = &mut self.new_virtual_pointer_listener as *mut ffi::wl_listener as *mut WlListener;
105 (*new_virtual_pointer_ptr).notify = Some(handle_new_virtual_pointer);
106 wl_signal_add(&mut (*self.virtual_pointer_manager).events.new_virtual_pointer, &mut self.new_virtual_pointer_listener);
107
108 // Connect new_virtual_keyboard listener
109 let new_virtual_keyboard_ptr = &mut self.new_virtual_keyboard_listener as *mut ffi::wl_listener as *mut WlListener;
110 (*new_virtual_keyboard_ptr).notify = Some(handle_new_virtual_keyboard);
111 wl_signal_add(&mut (*self.virtual_keyboard_manager).events.new_virtual_keyboard, &mut self.new_virtual_keyboard_listener);
112
113 Ok(())
114 }
115
116 /// Schedule a pointer-focus re-evaluation for every seat, deferred to an
117 /// idle callback and coalesced (one source no matter how many commits
118 /// land in a dispatch). Used when a commit changes the surface↔frame
119 /// mapping under a stationary cursor — geometry re-anchor or surface
120 /// extent change. Deferred, NOT inline: wlroots' own scene commit
121 /// listeners re-anchor the surface tree AFTER the compositor's toplevel
122 /// commit handler runs, so an inline refresh would query the stale
123 /// mapping.
124 pub unsafe fn schedule_pointer_refresh(&mut self) {
125 if !self.pointer_refresh_idle.is_null() {
126 return;
127 }
128 let event_loop = ffi::wl_display_get_event_loop((*self.server).wl_server);
129 let idle = ffi::wl_event_loop_add_idle(
130 event_loop,
131 Some(handle_pointer_refresh_idle),
132 self as *mut InputManager as *mut _,
133 );
134 if idle.is_null() {
135 log::error!("Failed to schedule pointer-refresh idle source");
136 return;
137 }
138 self.pointer_refresh_idle = idle;
139 }
140
141 pub unsafe fn deinit(&mut self) {
142 log::info!("[deinit] InputManager::deinit started");
143 if !self.pointer_refresh_idle.is_null() {
144 ffi::wl_event_source_remove(self.pointer_refresh_idle);
145 self.pointer_refresh_idle = std::ptr::null_mut();
146 }
147 if !self.global.is_null() {
148 log::info!("[deinit] destroying input manager global");
149 ffi::wl_global_destroy(self.global);
150 self.global = std::ptr::null_mut();
151 }
152
153 let objects_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
154 let mut curr = (*objects_head).next;
155 while curr != objects_head {
156 let next = (*curr).next;
157 let obj = crate::container_of!(curr, InputManagerObject, link);
158 ffi::wl_resource_destroy((*obj).resource);
159 curr = next;
160 }
161
162 // Detach all devices from their seats and set their seat pointer to null
163 // so they do not attempt to access a freed seat during backend destruction.
164 log::info!("[deinit] detaching devices");
165 let devices_head = &mut self.devices as *mut ffi::wl_list as *mut WlList;
166 let mut curr_dev = (*devices_head).next;
167 while curr_dev != devices_head {
168 let next_dev = (*curr_dev).next;
169 let device = crate::container_of!(curr_dev, crate::input_device::InputDevice, link);
170 if !(*device).seat.is_null() {
171 log::info!("[deinit] detaching device from seat");
172 (*(*device).seat).detach_device(device);
173 (*device).seat = std::ptr::null_mut();
174 }
175 curr_dev = next_dev;
176 }
177
178 // Destroy all seats
179 log::info!("[deinit] destroying seats");
180 let seats_head = &mut self.seats as *mut ffi::wl_list as *mut WlList;
181 let mut curr_seat = (*seats_head).next;
182 while curr_seat != seats_head {
183 let next_seat = (*curr_seat).next;
184 let seat = crate::container_of!(curr_seat, Seat, link);
185 log::info!("[deinit] calling Seat::destroy for {:?}", (*seat).wlr_seat);
186 Seat::destroy(seat);
187 curr_seat = next_seat;
188 }
189 self.default_seat = std::ptr::null_mut();
190 log::info!("[deinit] seats destroyed");
191
192 log::info!("[deinit] removing input manager listeners");
193 wl_listener_remove(&mut self.new_input_listener);
194 wl_listener_remove(&mut self.new_text_input);
195 wl_listener_remove(&mut self.new_input_method);
196 wl_listener_remove(&mut self.new_virtual_pointer_listener);
197 wl_listener_remove(&mut self.new_virtual_keyboard_listener);
198 log::info!("[deinit] InputManager::deinit finished");
199 }
200 }
201
202 unsafe extern "C" fn bind_input_manager(
203 client: *mut ffi::wl_client,
204 data: *mut std::ffi::c_void,
205 version: u32,
206 id: u32,
207 ) {
208 let im = data as *mut InputManager;
209 let resource = ffi::wl_resource_create(client, &ffi::river_input_manager_v1_interface, version as i32, id);
210 if resource.is_null() {
211 ffi::wl_client_post_no_memory(client);
212 return;
213 }
214
215 let obj = Box::into_raw(Box::new(InputManagerObject {
216 manager: im,
217 resource,
218 link: std::mem::zeroed(),
219 }));
220
221 ffi::wl_resource_set_implementation(
222 resource,
223 &INPUT_MANAGER_INTERFACE as *const _ as *const _,
224 obj as *mut _,
225 Some(handle_manager_object_destroy),
226 );
227
228 let list_head = &mut (*im).objects as *mut ffi::wl_list as *mut WlList;
229 wl_list_insert((*list_head).prev, &mut (*obj).link as *mut ffi::wl_list as *mut WlList);
230
231 // Send existing devices to the client
232 let devices_head = &mut (*im).devices as *mut ffi::wl_list as *mut WlList;
233 let mut curr = (*devices_head).next;
234 while curr != devices_head {
235 let next = (*curr).next;
236 let device = crate::container_of!(curr, crate::input_device::InputDevice, link);
237 if !(*device).virtual_device {
238 (*device).create_object(resource);
239 }
240 curr = next;
241 }
242 }
243
244 unsafe extern "C" fn handle_manager_object_destroy(resource: *mut ffi::wl_resource) {
245 let obj = ffi::wl_resource_get_user_data(resource) as *mut InputManagerObject;
246 if !obj.is_null() {
247 wl_list_remove(&mut (*obj).link as *mut ffi::wl_list as *mut WlList);
248 let _ = Box::from_raw(obj);
249 }
250 }
251
252 static INPUT_MANAGER_INTERFACE: ffi::river_input_manager_v1_interface = ffi::river_input_manager_v1_interface {
253 stop: Some(input_manager_stop),
254 destroy: Some(input_manager_destroy),
255 create_seat: Some(input_manager_create_seat),
256 destroy_seat: Some(input_manager_destroy_seat),
257 };
258
259 static INPUT_MANAGER_INERT_INTERFACE: ffi::river_input_manager_v1_interface = ffi::river_input_manager_v1_interface {
260 stop: None,
261 destroy: Some(input_manager_inert_destroy),
262 create_seat: None,
263 destroy_seat: None,
264 };
265
266 unsafe extern "C" fn input_manager_stop(
267 _client: *mut ffi::wl_client,
268 resource: *mut ffi::wl_resource,
269 ) {
270 let obj = ffi::wl_resource_get_user_data(resource) as *mut InputManagerObject;
271 if obj.is_null() {
272 return;
273 }
274
275 wl_list_remove(&mut (*obj).link as *mut ffi::wl_list as *mut WlList);
276 ffi::wl_list_init(&mut (*obj).link);
277
278 ffi::wl_resource_post_event(resource, 0); // finished event
279
280 ffi::wl_resource_set_implementation(
281 resource,
282 &INPUT_MANAGER_INERT_INTERFACE as *const _ as *const _,
283 obj as *mut _,
284 Some(handle_manager_object_destroy),
285 );
286 }
287
288 unsafe extern "C" fn input_manager_destroy(
289 _client: *mut ffi::wl_client,
290 resource: *mut ffi::wl_resource,
291 ) {
292 ffi::wl_resource_post_error(
293 resource,
294 ffi::river_input_manager_v1_error_RIVER_INPUT_MANAGER_V1_ERROR_INVALID_DESTROY,
295 b"destroy before finished event sent\0".as_ptr() as *const _,
296 );
297 }
298
299 unsafe extern "C" fn input_manager_inert_destroy(
300 _client: *mut ffi::wl_client,
301 resource: *mut ffi::wl_resource,
302 ) {
303 ffi::wl_resource_destroy(resource);
304 }
305
306 unsafe extern "C" fn input_manager_create_seat(
307 _client: *mut ffi::wl_client,
308 resource: *mut ffi::wl_resource,
309 name: *const std::os::raw::c_char,
310 ) {
311 let obj = ffi::wl_resource_get_user_data(resource) as *mut InputManagerObject;
312 if obj.is_null() || name.is_null() {
313 return;
314 }
315 let im = (*obj).manager;
316 let name_str = std::ffi::CStr::from_ptr(name).to_string_lossy();
317
318 // Check if seat already exists
319 let seats_head = &mut (*im).seats as *mut ffi::wl_list as *mut WlList;
320 let mut curr = (*seats_head).next;
321 let mut exists = false;
322 while curr != seats_head {
323 let next = (*curr).next;
324 let seat = crate::container_of!(curr, Seat, link);
325 let seat_name = std::ffi::CStr::from_ptr(ffi::river_wlr_seat_get_name((*seat).wlr_seat)).to_string_lossy();
326 if seat_name == name_str {
327 exists = true;
328 break;
329 }
330 curr = next;
331 }
332
333 if !exists {
334 let server = (*im).server;
335 if let Err(e) = Seat::create(server, &name_str) {
336 log::error!("failed to create seat '{}': {}", name_str, e);
337 ffi::wl_resource_post_no_memory(resource);
338 }
339 }
340 }
341
342 unsafe extern "C" fn input_manager_destroy_seat(
343 _client: *mut ffi::wl_client,
344 resource: *mut ffi::wl_resource,
345 name: *const std::os::raw::c_char,
346 ) {
347 let obj = ffi::wl_resource_get_user_data(resource) as *mut InputManagerObject;
348 if obj.is_null() || name.is_null() {
349 return;
350 }
351 let im = (*obj).manager;
352 let name_str = std::ffi::CStr::from_ptr(name).to_string_lossy();
353
354 if name_str == "default" {
355 return;
356 }
357
358 let seats_head = &mut (*im).seats as *mut ffi::wl_list as *mut WlList;
359 let mut curr = (*seats_head).next;
360 // Skip default seat (which is the first seat in the list)
361 curr = (*curr).next;
362 while curr != seats_head {
363 let next = (*curr).next;
364 let seat = crate::container_of!(curr, Seat, link);
365 let seat_name = std::ffi::CStr::from_ptr(ffi::river_wlr_seat_get_name((*seat).wlr_seat)).to_string_lossy();
366 if seat_name == name_str {
367 (*seat).destroying = true;
368 (*(*im).server).wm.dirty_windowing();
369 break;
370 }
371 curr = next;
372 }
373 }
374
375 unsafe extern "C" fn handle_new_input(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
376 let im = &mut *crate::container_of!(listener, InputManager, new_input_listener);
377 let wlr_device = data as *mut ffi::wlr_input_device;
378
379 log::info!("new input device connected");
380
381 let device = crate::input_device::InputDevice::new(im.default_seat, wlr_device, false);
382
383 let name_ptr = ffi::river_wlr_input_device_get_name(wlr_device);
384 if !name_ptr.is_null() {
385 let name = std::ffi::CStr::from_ptr(name_ptr).to_string_lossy();
386 let wm = &mut (*im.server).wm;
387 for rule in &wm.input_rules {
388 if rule.name == "*" || name.contains(&rule.name) {
389 if let Some(factor) = rule.scroll_factor {
390 (*device).config.scroll_factor = factor;
391 }
392 }
393 }
394 }
395
396 let dev_type = ffi::river_wlr_input_device_get_type(wlr_device);
397 if dev_type == ffi::wlr_input_device_type_WLR_INPUT_DEVICE_KEYBOARD {
398 crate::keyboard::Keyboard::create(device);
399 } else if dev_type == ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TABLET {
400 let _ = crate::tablet::Tablet::create(im.default_seat, wlr_device, false);
401 }
402
403 // Attach device to the default seat
404 (*im.default_seat).attach_device(device);
405 }
406
407 unsafe extern "C" fn handle_new_text_input(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
408 let _im = crate::container_of!(listener, InputManager, new_text_input);
409 let wlr_text_input = data as *mut ffi::wlr_text_input_v3;
410
411 if let Err(e) = crate::text_input::TextInput::create(wlr_text_input) {
412 log::error!("failed to create text input: {}", e);
413 ffi::wl_resource_post_no_memory((*wlr_text_input).resource);
414 }
415 }
416
417 unsafe extern "C" fn handle_new_input_method(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
418 let im = &mut *crate::container_of!(listener, InputManager, new_input_method);
419 let input_method = data as *mut ffi::wlr_input_method_v2;
420 let seat = ffi::river_wlr_seat_get_data((*input_method).seat) as *mut Seat;
421 if !seat.is_null() {
422 (*seat).relay.new_input_method(input_method);
423 } else {
424 // Fallback to default seat if input_method.seat has no data set yet
425 (*im.default_seat).relay.new_input_method(input_method);
426 }
427 }
428
429 unsafe extern "C" fn handle_new_virtual_pointer(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
430 let im = &mut *crate::container_of!(listener, InputManager, new_virtual_pointer_listener);
431 let event = data as *mut ffi::wlr_virtual_pointer_v1_new_pointer_event;
432
433 log::info!("new virtual pointer device connected");
434
435 let virtual_pointer = (*event).new_pointer;
436 let wlr_device = &mut (*virtual_pointer).pointer.base as *mut ffi::wlr_input_device;
437
438 let device = crate::input_device::InputDevice::new(im.default_seat, wlr_device, true);
439
440 // Attach device to the default seat
441 (*im.default_seat).attach_device(device);
442 }
443
444 unsafe extern "C" fn handle_new_virtual_keyboard(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
445 let im = &mut *crate::container_of!(listener, InputManager, new_virtual_keyboard_listener);
446 let virtual_keyboard = data as *mut ffi::wlr_virtual_keyboard_v1;
447
448 log::info!("new virtual keyboard device connected");
449
450 let wlr_device = &mut (*virtual_keyboard).keyboard.base as *mut ffi::wlr_input_device;
451
452 // Honor the seat the client bound the virtual keyboard to, like input
453 // methods do; fall back to the default seat if it carries no Seat data.
454 let mut seat = ffi::river_wlr_seat_get_data((*virtual_keyboard).seat) as *mut Seat;
455 if seat.is_null() {
456 seat = im.default_seat;
457 }
458
459 let device = crate::input_device::InputDevice::new(seat, wlr_device, true);
460 crate::keyboard::Keyboard::create(device);
461
462 // Same path a hardware keyboard takes: attach_device puts the keyboard in
463 // a (private, virtual) KeyboardGroup, so compositor keybindings apply.
464 (*seat).attach_device(device);
465 }
466
467 unsafe extern "C" fn handle_pointer_refresh_idle(data: *mut std::ffi::c_void) {
468 let manager = data as *mut InputManager;
469 (*manager).pointer_refresh_idle = std::ptr::null_mut();
470 let seats_head = &mut (*manager).seats as *mut ffi::wl_list as *mut WlList;
471 let mut curr = (*seats_head).next;
472 while curr != seats_head {
473 let next = (*curr).next;
474 let seat = crate::container_of!(curr, Seat, link);
475 (*seat).cursor.refresh_after_scene_change();
476 curr = next;
477 }
478 }