Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
src/server/input_device.rs (18.2K)
1 // SPDX-FileCopyrightText: © 2026 The River Developers
2 // SPDX-License-Identifier: GPL-3.0-only
3
4 use crate::ffi;
5 use crate::seat::Seat;
6 use crate::server::{WlListener, wl_listener_remove, WlList, wl_list_insert, wl_list_remove};
7
8 pub struct InputDeviceConfig {
9 pub scroll_factor: f64,
10 pub map_to_output: *mut ffi::wlr_output,
11 pub map_to_rectangle: ffi::wlr_box,
12 }
13
14 pub struct InputDevice {
15 pub seat: *mut Seat,
16 pub wlr_device: *mut ffi::wlr_input_device,
17 pub virtual_device: bool,
18 pub destroy_listener: ffi::wl_listener,
19 pub config: InputDeviceConfig,
20 pub destroy_fn: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
21 pub destroy_data: *mut std::ffi::c_void,
22
23 pub objects: ffi::wl_list, // list of InputDeviceObject
24 pub link: ffi::wl_list, // link inside InputManager::devices
25 pub libinput: Option<Box<crate::libinput_device::LibinputDevice>>,
26 pub xkb_keyboard: Option<Box<crate::xkb_keyboard::XkbKeyboard>>,
27 }
28
29 pub struct InputDeviceObject {
30 pub device: *mut InputDevice,
31 pub resource: *mut ffi::wl_resource,
32 pub link: ffi::wl_list,
33 }
34
35 impl InputDevice {
36 pub unsafe fn new(
37 seat: *mut Seat,
38 wlr_device: *mut ffi::wlr_input_device,
39 virtual_device: bool,
40 ) -> *mut Self {
41 let server = (*seat).server;
42
43 let device = Box::into_raw(Box::new(Self {
44 seat,
45 wlr_device,
46 virtual_device,
47 destroy_listener: std::mem::zeroed(),
48 config: InputDeviceConfig {
49 scroll_factor: 1.0,
50 map_to_output: std::ptr::null_mut(),
51 map_to_rectangle: ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 },
52 },
53 destroy_fn: None,
54 destroy_data: std::ptr::null_mut(),
55 objects: std::mem::zeroed(),
56 link: std::mem::zeroed(),
57 libinput: None,
58 xkb_keyboard: None,
59 }));
60
61 ffi::wl_list_init(&mut (*device).objects);
62 ffi::wl_list_init(&mut (*device).link);
63
64 // Insert into InputManager::devices
65 let manager_devices_head = &mut (*server).input_manager.devices as *mut ffi::wl_list as *mut WlList;
66 wl_list_insert(manager_devices_head, &mut (*device).link as *mut ffi::wl_list as *mut WlList);
67
68 ffi::river_wlr_input_device_set_data(wlr_device, device as *mut _);
69
70 let destroy_listener_ptr = &mut (*device).destroy_listener as *mut ffi::wl_listener as *mut WlListener;
71 (*destroy_listener_ptr).notify = Some(handle_device_destroy);
72
73 let destroy_signal = ffi::river_wlr_input_device_get_destroy_signal(wlr_device);
74 crate::server::wl_signal_add(destroy_signal, &mut (*device).destroy_listener);
75
76 if !virtual_device {
77 let config_objects = &mut (*server).input_manager.objects as *mut ffi::wl_list as *mut WlList;
78 let mut curr = (*config_objects).next;
79 while curr != config_objects {
80 let next = (*curr).next;
81 let im_obj = crate::container_of!(curr, crate::input_manager::InputManagerObject, link);
82 (*device).create_object((*im_obj).resource);
83 curr = next;
84 }
85
86 let handle = if ffi::wlr_input_device_is_libinput(wlr_device) {
87 ffi::wlr_libinput_get_device_handle(wlr_device)
88 } else {
89 std::ptr::null_mut()
90 };
91 if !handle.is_null() {
92 let libinput_dev = crate::libinput_device::LibinputDevice::init(device, handle);
93 let wm = &(*(*seat).server).wm;
94 libinput_dev.apply_config(&wm.input_config);
95 (*device).libinput = Some(libinput_dev);
96 }
97
98 let dev_type = ffi::river_wlr_input_device_get_type(wlr_device);
99 if dev_type == ffi::wlr_input_device_type_WLR_INPUT_DEVICE_KEYBOARD {
100 (*device).xkb_keyboard = Some(crate::xkb_keyboard::XkbKeyboard::init(device));
101 }
102 }
103
104 // Output mapping logic for pointers and touch screens (as done in Zig version)
105 let output_name = match ffi::river_wlr_input_device_get_type(wlr_device) {
106 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_POINTER => {
107 let ptr = ffi::wlr_pointer_from_input_device(wlr_device);
108 if !ptr.is_null() && !(*ptr).output_name.is_null() {
109 Some(std::ffi::CStr::from_ptr((*ptr).output_name))
110 } else {
111 None
112 }
113 }
114 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TOUCH => {
115 let touch = ffi::wlr_touch_from_input_device(wlr_device);
116 if !touch.is_null() && !(*touch).output_name.is_null() {
117 Some(std::ffi::CStr::from_ptr((*touch).output_name))
118 } else {
119 None
120 }
121 }
122 _ => None,
123 };
124
125 if let Some(name) = output_name {
126 let outputs_head = &mut (*server).om.outputs as *mut ffi::wl_list as *mut WlList;
127 let mut curr = (*outputs_head).next;
128 while curr != outputs_head {
129 let next = (*curr).next;
130 let output = crate::container_of!(curr, crate::output::Output, link);
131 let wlr_output = (*output).wlr_output;
132 if !wlr_output.is_null() {
133 let wlr_name = std::ffi::CStr::from_ptr(ffi::river_wlr_output_get_name(wlr_output));
134 if wlr_name == name {
135 (*device).config.map_to_output = wlr_output;
136 break;
137 }
138 }
139 curr = next;
140 }
141 }
142
143 device
144 }
145
146 pub unsafe fn assign_to_seat(&mut self, new_seat: *mut Seat) {
147 let old_seat = self.seat;
148 if old_seat == new_seat {
149 return;
150 }
151 if !old_seat.is_null() {
152 (*old_seat).detach_device(self);
153 }
154 self.seat = new_seat;
155 if !new_seat.is_null() {
156 (*new_seat).attach_device(self);
157 }
158 if !old_seat.is_null() {
159 (*old_seat).update_capabilities();
160 }
161 if !new_seat.is_null() {
162 (*new_seat).update_capabilities();
163 }
164 }
165
166 pub unsafe fn active_mapping(&self) -> ffi::wlr_box {
167 let mut mapping = self.config.map_to_rectangle;
168 if !ffi::wlr_box_empty(&mapping) {
169 return mapping;
170 }
171 if !self.config.map_to_output.is_null() {
172 let server = (*self.seat).server;
173 ffi::wlr_output_layout_get_box((*server).om.output_layout, self.config.map_to_output, &mut mapping);
174 }
175 mapping
176 }
177
178 pub unsafe fn create_object(&mut self, im_v1_resource: *mut ffi::wl_resource) {
179 if self.virtual_device {
180 return;
181 }
182
183 let dev_type = ffi::river_wlr_input_device_get_type(self.wlr_device);
184 let proto_type = match dev_type {
185 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_KEYBOARD => 0, // keyboard
186 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_POINTER => 1, // pointer
187 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TOUCH => 2, // touch
188 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TABLET => 3, // tablet
189 _ => return,
190 };
191
192 let client = ffi::wl_resource_get_client(im_v1_resource);
193 let version = ffi::wl_resource_get_version(im_v1_resource);
194
195 let resource = ffi::wl_resource_create(
196 client,
197 &ffi::river_input_device_v1_interface,
198 version,
199 0,
200 );
201 if resource.is_null() {
202 log::error!("out of memory creating river_input_device_v1");
203 ffi::wl_client_post_no_memory(client);
204 return;
205 }
206
207 let obj = Box::into_raw(Box::new(InputDeviceObject {
208 device: self,
209 resource,
210 link: std::mem::zeroed(),
211 }));
212
213 ffi::wl_resource_set_implementation(
214 resource,
215 &INPUT_DEVICE_INTERFACE as *const _ as *const _,
216 obj as *mut _,
217 Some(handle_device_object_destroy),
218 );
219
220 // Insert into self.objects
221 let list_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
222 wl_list_insert(list_head, &mut (*obj).link as *mut ffi::wl_list as *mut WlList);
223
224 // Send input_device event to the manager resource
225 ffi::wl_resource_post_event(im_v1_resource, 1, resource); // opcode 1 is input_device in river_input_manager_v1
226
227 // Send type and name to client
228 ffi::wl_resource_post_event(resource, 1, proto_type); // type event
229
230 let name = ffi::river_wlr_input_device_get_name(self.wlr_device);
231 let name_ptr = if !name.is_null() { name } else { b"\0".as_ptr() as *const _ };
232 ffi::wl_resource_post_event(resource, 2, name_ptr); // name event
233
234 if version >= 2 {
235 ffi::wl_resource_post_event(resource, 3); // done event
236 }
237
238 // Connect client-side libinput device if matching exists
239 if let Some(ref mut libinput) = self.libinput {
240 let libinput_config_objects = &mut (*(*self.seat).server).libinput_config.objects as *mut ffi::wl_list as *mut WlList;
241 let mut curr = (*libinput_config_objects).next;
242 while curr != libinput_config_objects {
243 let next = (*curr).next;
244 let config_obj = crate::container_of!(curr, crate::libinput_config::LibinputConfigObject, link);
245 if ffi::wl_resource_get_client((*config_obj).resource) == client {
246 libinput.create_object((*config_obj).resource);
247 }
248 curr = next;
249 }
250 }
251 }
252 }
253
254 unsafe extern "C" fn handle_device_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
255 let device_ptr = crate::container_of!(listener, InputDevice, destroy_listener) as *mut InputDevice;
256 let device = &mut *device_ptr;
257
258 log::debug!(
259 "removed input device: {:?}",
260 ffi::river_wlr_input_device_get_type(device.wlr_device)
261 );
262
263 // Detach from seat if attached
264 if !device.seat.is_null() {
265 (*device.seat).detach_device(device);
266 (*device.seat).update_capabilities();
267 }
268
269 // Free objects and set inert
270 let objects_head = &mut device.objects as *mut ffi::wl_list as *mut WlList;
271 let mut curr = (*objects_head).next;
272 while curr != objects_head {
273 let next = (*curr).next;
274 let obj = crate::container_of!(curr, InputDeviceObject, link);
275
276 wl_list_remove(curr);
277 ffi::wl_list_init(curr as *mut ffi::wl_list);
278
279 ffi::wl_resource_post_event((*obj).resource, 0); // removed event
280
281 ffi::wl_resource_set_implementation(
282 (*obj).resource,
283 &INPUT_DEVICE_INERT_INTERFACE as *const _ as *const _,
284 obj as *mut _,
285 Some(handle_device_object_destroy),
286 );
287
288 curr = next;
289 }
290
291 if let Some(mut libinput) = device.libinput.take() {
292 libinput.deinit();
293 }
294
295 if let Some(mut xkb_kbd) = device.xkb_keyboard.take() {
296 xkb_kbd.deinit();
297 }
298
299 // Call custom destroy callback if set (e.g. to clean up Tablet/Keyboard wrappers)
300 if let Some(destroy_fn) = device.destroy_fn {
301 destroy_fn(device.destroy_data);
302 }
303
304 // Remove destroy listener
305 wl_listener_remove(&mut device.destroy_listener);
306
307 // Remove from InputManager::devices
308 wl_list_remove(&mut device.link as *mut ffi::wl_list as *mut WlList);
309
310 // Free wrapper memory
311 let _boxed = Box::from_raw(device_ptr);
312 }
313
314 unsafe extern "C" fn handle_device_object_destroy(resource: *mut ffi::wl_resource) {
315 let obj = ffi::wl_resource_get_user_data(resource) as *mut InputDeviceObject;
316 if !obj.is_null() {
317 wl_list_remove(&mut (*obj).link as *mut ffi::wl_list as *mut WlList);
318 let _ = Box::from_raw(obj);
319 }
320 }
321
322 static INPUT_DEVICE_INTERFACE: ffi::river_input_device_v1_interface = ffi::river_input_device_v1_interface {
323 destroy: Some(input_device_destroy),
324 assign_to_seat: Some(input_device_assign_to_seat),
325 set_repeat_info: Some(input_device_set_repeat_info),
326 set_scroll_factor: Some(input_device_set_scroll_factor),
327 map_to_output: Some(input_device_map_to_output),
328 map_to_rectangle: Some(input_device_map_to_rectangle),
329 };
330
331 static INPUT_DEVICE_INERT_INTERFACE: ffi::river_input_device_v1_interface = ffi::river_input_device_v1_interface {
332 destroy: Some(input_device_destroy),
333 assign_to_seat: None,
334 set_repeat_info: None,
335 set_scroll_factor: None,
336 map_to_output: None,
337 map_to_rectangle: None,
338 };
339
340 unsafe extern "C" fn input_device_destroy(
341 _client: *mut ffi::wl_client,
342 resource: *mut ffi::wl_resource,
343 ) {
344 ffi::wl_resource_destroy(resource);
345 }
346
347 unsafe extern "C" fn input_device_assign_to_seat(
348 _client: *mut ffi::wl_client,
349 resource: *mut ffi::wl_resource,
350 name: *const std::os::raw::c_char,
351 ) {
352 let obj = ffi::wl_resource_get_user_data(resource) as *mut InputDeviceObject;
353 if obj.is_null() {
354 return;
355 }
356 let device = (*obj).device;
357 if name.is_null() {
358 return;
359 }
360 let server = (*(*device).seat).server;
361 let seats_head = &mut (*server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
362 let mut curr = (*seats_head).next;
363 let mut found = false;
364 let name_str = std::ffi::CStr::from_ptr(name).to_string_lossy();
365 while curr != seats_head {
366 let next = (*curr).next;
367 let seat = crate::container_of!(curr, Seat, link);
368 let seat_name = std::ffi::CStr::from_ptr(ffi::river_wlr_seat_get_name((*seat).wlr_seat)).to_string_lossy();
369 if seat_name == name_str {
370 (*device).assign_to_seat(seat);
371 found = true;
372 break;
373 }
374 curr = next;
375 }
376 if !found {
377 log::info!("client requested input device be assigned to non-existent seat '{}'", name_str);
378 }
379 }
380
381 unsafe extern "C" fn input_device_set_repeat_info(
382 _client: *mut ffi::wl_client,
383 resource: *mut ffi::wl_resource,
384 rate: i32,
385 delay: i32,
386 ) {
387 let obj = ffi::wl_resource_get_user_data(resource) as *mut InputDeviceObject;
388 if obj.is_null() {
389 return;
390 }
391 let device = (*obj).device;
392 if rate < 0 || delay < 0 {
393 ffi::wl_resource_post_error(
394 resource,
395 ffi::river_input_device_v1_error_RIVER_INPUT_DEVICE_V1_ERROR_INVALID_REPEAT_INFO,
396 b"negative rate/delay\0".as_ptr() as *const _,
397 );
398 return;
399 }
400 let dev_type = ffi::river_wlr_input_device_get_type((*device).wlr_device);
401 if dev_type == ffi::wlr_input_device_type_WLR_INPUT_DEVICE_KEYBOARD {
402 let keyboard = (*device).destroy_data as *mut crate::keyboard::Keyboard;
403 if !keyboard.is_null() {
404 (*keyboard).set_repeat_info(rate, delay);
405 }
406 }
407 }
408
409 unsafe extern "C" fn input_device_set_scroll_factor(
410 _client: *mut ffi::wl_client,
411 resource: *mut ffi::wl_resource,
412 factor_raw: ffi::wl_fixed_t,
413 ) {
414 let obj = ffi::wl_resource_get_user_data(resource) as *mut InputDeviceObject;
415 if obj.is_null() {
416 return;
417 }
418 let device = (*obj).device;
419 let factor = factor_raw as f64 / 256.0;
420 if factor < 0.0 {
421 ffi::wl_resource_post_error(
422 resource,
423 ffi::river_input_device_v1_error_RIVER_INPUT_DEVICE_V1_ERROR_INVALID_SCROLL_FACTOR,
424 b"negative scroll factor\0".as_ptr() as *const _,
425 );
426 return;
427 }
428 (*device).config.scroll_factor = factor;
429 }
430
431 unsafe extern "C" fn input_device_map_to_output(
432 _client: *mut ffi::wl_client,
433 resource: *mut ffi::wl_resource,
434 output_resource: *mut ffi::wl_resource,
435 ) {
436 let obj = ffi::wl_resource_get_user_data(resource) as *mut InputDeviceObject;
437 if obj.is_null() {
438 return;
439 }
440 let device = (*obj).device;
441
442 let dev_type = ffi::river_wlr_input_device_get_type((*device).wlr_device);
443 match dev_type {
444 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_POINTER |
445 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TOUCH |
446 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TABLET => {},
447 _ => return,
448 }
449
450 let wlr_output = if !output_resource.is_null() {
451 let out = ffi::wlr_output_from_resource(output_resource);
452 if out.is_null() {
453 return;
454 }
455 out
456 } else {
457 std::ptr::null_mut()
458 };
459
460 (*device).config.map_to_output = wlr_output;
461
462 match dev_type {
463 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TOUCH |
464 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TABLET => {
465 if !(*device).seat.is_null() {
466 let cursor = &mut (*(*device).seat).cursor;
467 ffi::wlr_cursor_map_input_to_output(cursor.wlr_cursor, (*device).wlr_device, wlr_output);
468 }
469 }
470 _ => {}
471 }
472 }
473
474 unsafe extern "C" fn input_device_map_to_rectangle(
475 _client: *mut ffi::wl_client,
476 resource: *mut ffi::wl_resource,
477 x: i32,
478 y: i32,
479 width: i32,
480 height: i32,
481 ) {
482 let obj = ffi::wl_resource_get_user_data(resource) as *mut InputDeviceObject;
483 if obj.is_null() {
484 return;
485 }
486 let device = (*obj).device;
487
488 if width < 0 || height < 0 {
489 ffi::wl_resource_post_error(
490 resource,
491 ffi::river_input_device_v1_error_RIVER_INPUT_DEVICE_V1_ERROR_INVALID_MAP_TO_RECTANGLE,
492 b"negative rectangle width/height\0".as_ptr() as *const _,
493 );
494 return;
495 }
496
497 let dev_type = ffi::river_wlr_input_device_get_type((*device).wlr_device);
498 match dev_type {
499 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_POINTER |
500 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TOUCH |
501 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TABLET => {},
502 _ => return,
503 }
504
505 (*device).config.map_to_rectangle = ffi::wlr_box {
506 x,
507 y,
508 width,
509 height,
510 };
511
512 match dev_type {
513 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TOUCH |
514 ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TABLET => {
515 if !(*device).seat.is_null() {
516 let cursor = &mut (*(*device).seat).cursor;
517 ffi::wlr_cursor_map_input_to_region(
518 cursor.wlr_cursor,
519 (*device).wlr_device,
520 &mut (*device).config.map_to_rectangle,
521 );
522 }
523 }
524 _ => {}
525 }
526 }