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