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

src/server/libinput_device.rs (46.4K)

   1 // SPDX-FileCopyrightText: © 2026 The River Developers
   2 // SPDX-License-Identifier: GPL-3.0-only
   3 
   4 use crate::ffi;
   5 use crate::input_device::InputDevice;
   6 use crate::server::WlList;
   7 
   8 pub struct LibinputDevice {
   9     pub parent_device: *mut InputDevice,
  10     pub libinput: *mut ffi::libinput_device,
  11     pub objects: ffi::wl_list, // list of LibinputDeviceObject
  12     pub link: ffi::wl_list,    // link inside LibinputConfig::devices
  13 }
  14 
  15 pub struct LibinputDeviceObject {
  16     pub device: *mut LibinputDevice,
  17     pub resource: *mut ffi::wl_resource,
  18     pub link: ffi::wl_list,
  19 }
  20 
  21 impl LibinputDevice {
  22     pub unsafe fn init(
  23         parent_device: *mut InputDevice,
  24         handle: *mut ffi::libinput_device,
  25     ) -> Box<Self> {
  26         let mut dev = Box::new(Self {
  27             parent_device,
  28             libinput: handle,
  29             objects: std::mem::zeroed(),
  30             link: std::mem::zeroed(),
  31         });
  32 
  33         ffi::wl_list_init(&mut dev.objects);
  34         ffi::wl_list_init(&mut dev.link);
  35 
  36         let server = (*(*parent_device).seat).server;
  37         let devices_head = &mut (*server).libinput_config.devices as *mut ffi::wl_list as *mut WlList;
  38         crate::server::wl_list_insert((*devices_head).prev, &mut dev.link as *mut ffi::wl_list as *mut WlList);
  39 
  40         let config_objects = &mut (*server).libinput_config.objects as *mut ffi::wl_list as *mut WlList;
  41         let mut curr = (*config_objects).next;
  42         while curr != config_objects {
  43             let next = (*curr).next;
  44             let config_obj = crate::container_of!(curr, crate::libinput_config::LibinputConfigObject, link);
  45             dev.create_object((*config_obj).resource);
  46             curr = next;
  47         }
  48 
  49         dev
  50     }
  51 
  52     pub unsafe fn apply_config(&self, config: &crate::config::InputConfig) {
  53         let handle = self.libinput;
  54         if handle.is_null() {
  55             return;
  56         }
  57 
  58         // Tap to click
  59         if let Some(tap) = config.touchpad.as_ref().and_then(|t| t.tap_to_click) {
  60             if ffi::libinput_device_config_tap_get_finger_count(handle) > 0 {
  61                 let state = if tap { 1 } else { 0 };
  62                 ffi::libinput_device_config_tap_set_enabled(handle, state);
  63             }
  64         }
  65 
  66         // Natural scroll
  67         if let Some(natural) = config.touchpad.as_ref().and_then(|t| t.natural_scroll) {
  68             if ffi::libinput_device_config_scroll_has_natural_scroll(handle) != 0 {
  69                 let state = if natural { 1 } else { 0 };
  70                 ffi::libinput_device_config_scroll_set_natural_scroll_enabled(handle, state);
  71             }
  72         }
  73 
  74         // Dwt (Disable while typing)
  75         if let Some(dwt) = config.touchpad.as_ref().and_then(|t| t.dwt) {
  76             if ffi::libinput_device_config_dwt_is_available(handle) != 0 {
  77                 let state = if dwt { 1 } else { 0 };
  78                 ffi::libinput_device_config_dwt_set_enabled(handle, state);
  79             }
  80         }
  81 
  82         // Dwtp (Disable while trackpointing)
  83         if let Some(dwtp) = config.touchpad.as_ref().and_then(|t| t.dwtp) {
  84             if ffi::libinput_device_config_dwtp_is_available(handle) != 0 {
  85                 let state = if dwtp { 1 } else { 0 };
  86                 ffi::libinput_device_config_dwtp_set_enabled(handle, state);
  87             }
  88         }
  89 
  90         // Device class: trackpoint by name, touchpad by tap capability,
  91         // mouse otherwise. Per-class config blocks override the top-level
  92         // values for devices of that class.
  93         let name_ptr = ffi::river_wlr_input_device_get_name((*self.parent_device).wlr_device);
  94         let name = if !name_ptr.is_null() {
  95             std::ffi::CStr::from_ptr(name_ptr).to_string_lossy().to_lowercase()
  96         } else {
  97             String::new()
  98         };
  99         let is_trackpoint = name.contains("trackpoint");
 100         let is_touchpad = !is_trackpoint && ffi::libinput_device_config_tap_get_finger_count(handle) > 0;
 101 
 102         // Acceleration Speed
 103         let class_speed = if is_trackpoint {
 104             config.trackpoint.as_ref().and_then(|t| t.accel_speed)
 105         } else if is_touchpad {
 106             config.touchpad.as_ref().and_then(|t| t.accel_speed)
 107         } else {
 108             config.mouse.as_ref().and_then(|m| m.accel_speed)
 109         };
 110         if let Some(s) = class_speed.or(config.accel_speed) {
 111             if ffi::libinput_device_config_accel_get_profiles(handle) != 0 {
 112                 ffi::libinput_device_config_accel_set_speed(handle, s);
 113             }
 114         }
 115 
 116         // Scroll factor (cce's own wheel-delta scaling, not libinput):
 117         // class value -> top-level value. Name-based `device` rules run
 118         // after this and stay the most specific override.
 119         let class_scroll = if is_trackpoint {
 120             config.trackpoint.as_ref().and_then(|t| t.scroll_factor)
 121         } else if is_touchpad {
 122             config.touchpad.as_ref().and_then(|t| t.scroll_factor)
 123         } else {
 124             config.mouse.as_ref().and_then(|m| m.scroll_factor)
 125         };
 126         if let Some(f) = class_scroll.or(config.scroll_factor) {
 127             if f.is_finite() && f > 0.0 {
 128                 (*self.parent_device).config.scroll_factor = f;
 129             }
 130         }
 131 
 132         // Acceleration Profile
 133         let class_profile = if is_trackpoint {
 134             config.trackpoint.as_ref().and_then(|t| t.accel_profile.as_ref())
 135         } else if is_touchpad {
 136             config.touchpad.as_ref().and_then(|t| t.accel_profile.as_ref())
 137         } else {
 138             config.mouse.as_ref().and_then(|m| m.accel_profile.as_ref())
 139         };
 140         let profile_str = class_profile.or(config.accel_profile.as_ref());
 141         if let Some(ref p_str) = profile_str {
 142             if ffi::libinput_device_config_accel_get_profiles(handle) != 0 {
 143                 let profile = match p_str.as_str() {
 144                     "flat" => Some(ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT),
 145                     "adaptive" => Some(ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE),
 146                     "none" => Some(ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_NONE),
 147                     "custom" => Some(ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM),
 148                     _ => None,
 149                 };
 150                 if let Some(p) = profile {
 151                     ffi::libinput_device_config_accel_set_profile(handle, p);
 152                 }
 153             }
 154         }
 155 
 156         // Scroll method. Only the trackpoint and mouse classes carry it: a
 157         // touchpad's two-finger default is what everyone wants, and a
 158         // pointing stick's default -- scroll on middle-button-down -- is
 159         // what nobody clicking a middle button wants (see TrackpointConfig).
 160         let class_method = if is_trackpoint {
 161             config.trackpoint.as_ref().and_then(|t| t.scroll_method.as_ref())
 162         } else if is_touchpad {
 163             None
 164         } else {
 165             config.mouse.as_ref().and_then(|m| m.scroll_method.as_ref())
 166         };
 167         if let Some(m_str) = class_method {
 168             let method = match m_str.as_str() {
 169                 "none" => Some(ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_NO_SCROLL),
 170                 "button" | "on_button_down" => Some(ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN),
 171                 "two_finger" | "2fg" => Some(ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_2FG),
 172                 "edge" => Some(ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_EDGE),
 173                 other => {
 174                     log::warn!("input: unknown scroll_method {:?} (none, button, two_finger, edge)", other);
 175                     None
 176                 }
 177             };
 178             if let Some(m) = method {
 179                 let supported = ffi::libinput_device_config_scroll_get_methods(handle);
 180                 if m == ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_NO_SCROLL || (supported & m) != 0 {
 181                     ffi::libinput_device_config_scroll_set_method(handle, m);
 182                 } else {
 183                     log::warn!("input: {} does not support scroll_method {:?}", name, m_str);
 184                 }
 185             }
 186         }
 187     }
 188 
 189     pub unsafe fn deinit(&mut self) {
 190         let objects_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
 191         let mut curr = (*objects_head).next;
 192         while curr != objects_head {
 193             let next = (*curr).next;
 194             let obj = crate::container_of!(curr, LibinputDeviceObject, link);
 195 
 196             crate::server::wl_list_remove(curr);
 197             ffi::wl_resource_post_event((*obj).resource, ffi::RIVER_LIBINPUT_DEVICE_V1_REMOVED);
 198 
 199             // Mark the object as inert by setting device to NULL, but do NOT set the resource's
 200             // implementation to NULL or free the `obj` memory here. Let handle_device_object_destroy
 201             // do the cleanup when the resource is eventually destroyed by the client/server.
 202             (*obj).device = std::ptr::null_mut();
 203 
 204             // Re-initialize the link so it doesn't point to the freed self.objects list.
 205             ffi::wl_list_init(&mut (*obj).link);
 206 
 207             curr = next;
 208         }
 209 
 210         crate::server::wl_list_remove(&mut self.link as *mut ffi::wl_list as *mut WlList);
 211     }
 212 
 213     pub unsafe fn create_object(&mut self, config_v1_resource: *mut ffi::wl_resource) {
 214         let client = ffi::wl_resource_get_client(config_v1_resource);
 215         let version = ffi::wl_resource_get_version(config_v1_resource);
 216 
 217         let resource = ffi::wl_resource_create(
 218             client,
 219             &ffi::river_libinput_device_v1_interface,
 220             version,
 221             0,
 222         );
 223         if resource.is_null() {
 224             log::error!("out of memory creating river_libinput_device_v1");
 225             ffi::wl_client_post_no_memory(client);
 226             return;
 227         }
 228 
 229         let obj = Box::into_raw(Box::new(LibinputDeviceObject {
 230             device: self,
 231             resource,
 232             link: std::mem::zeroed(),
 233         }));
 234 
 235         ffi::wl_resource_set_implementation(
 236             resource,
 237             &LIBINPUT_DEVICE_INTERFACE as *const _ as *const _,
 238             obj as *mut _,
 239             Some(handle_device_object_destroy),
 240         );
 241 
 242         let objects_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
 243         let link_ptr = &mut (*obj).link as *mut ffi::wl_list as *mut WlList;
 244         crate::server::wl_list_insert(objects_head, link_ptr);
 245 
 246         // Send libinput_device event
 247         ffi::wl_resource_post_event(config_v1_resource, ffi::RIVER_LIBINPUT_CONFIG_V1_LIBINPUT_DEVICE, resource);
 248 
 249         // Send corresponding river input device
 250         let input_dev_objects = &mut (*self.parent_device).objects as *mut ffi::wl_list as *mut WlList;
 251         let mut curr = (*input_dev_objects).next;
 252         while curr != input_dev_objects {
 253             let next = (*curr).next;
 254             let input_dev_obj = crate::container_of!(curr, crate::input_device::InputDeviceObject, link);
 255             if ffi::wl_resource_get_client((*input_dev_obj).resource) == client {
 256                 ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_INPUT_DEVICE, (*input_dev_obj).resource);
 257             }
 258             curr = next;
 259         }
 260 
 261         // SendEvents support and defaults
 262         let send_events_modes = ffi::libinput_device_config_send_events_get_modes(self.libinput);
 263         ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SEND_EVENTS_SUPPORT, send_events_modes);
 264         let send_events_default = ffi::libinput_device_config_send_events_get_default_mode(self.libinput);
 265         ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SEND_EVENTS_DEFAULT, send_events_default);
 266         let send_events_current = ffi::libinput_device_config_send_events_get_mode(self.libinput);
 267         ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SEND_EVENTS_CURRENT, send_events_current);
 268 
 269         // Tap support and defaults
 270         let tap_finger_count = ffi::libinput_device_config_tap_get_finger_count(self.libinput);
 271         ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_TAP_SUPPORT, tap_finger_count as i32);
 272         if tap_finger_count > 0 {
 273             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_TAP_DEFAULT, ffi::libinput_device_config_tap_get_default_enabled(self.libinput));
 274             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_TAP_CURRENT, ffi::libinput_device_config_tap_get_enabled(self.libinput));
 275             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_TAP_BUTTON_MAP_DEFAULT, ffi::libinput_device_config_tap_get_default_button_map(self.libinput));
 276             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_TAP_BUTTON_MAP_CURRENT, ffi::libinput_device_config_tap_get_button_map(self.libinput));
 277             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DRAG_DEFAULT, ffi::libinput_device_config_tap_get_default_drag_enabled(self.libinput));
 278             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DRAG_CURRENT, ffi::libinput_device_config_tap_get_drag_enabled(self.libinput));
 279             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DRAG_LOCK_DEFAULT, ffi::libinput_device_config_tap_get_default_drag_lock_enabled(self.libinput));
 280             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DRAG_LOCK_CURRENT, ffi::libinput_device_config_tap_get_drag_lock_enabled(self.libinput));
 281         }
 282 
 283         // Three finger drag
 284         let three_finger_drag_finger_count = ffi::libinput_device_config_3fg_drag_get_finger_count(self.libinput);
 285         ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_THREE_FINGER_DRAG_SUPPORT, three_finger_drag_finger_count as i32);
 286         if three_finger_drag_finger_count >= 3 {
 287             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_THREE_FINGER_DRAG_DEFAULT, ffi::libinput_device_config_3fg_drag_get_default_enabled(self.libinput));
 288             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_THREE_FINGER_DRAG_CURRENT, ffi::libinput_device_config_3fg_drag_get_enabled(self.libinput));
 289         }
 290 
 291         // Calibration matrix
 292         let has_matrix = ffi::libinput_device_config_calibration_has_matrix(self.libinput);
 293         ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CALIBRATION_MATRIX_SUPPORT, has_matrix as i32);
 294         if has_matrix != 0 {
 295             let mut matrix = [0.0f32; 6];
 296             ffi::libinput_device_config_calibration_get_default_matrix(self.libinput, matrix.as_mut_ptr());
 297             let mut arr_default = ffi::wl_array {
 298                 size: std::mem::size_of_val(&matrix),
 299                 alloc: std::mem::size_of_val(&matrix),
 300                 data: matrix.as_mut_ptr() as *mut _,
 301             };
 302             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CALIBRATION_MATRIX_DEFAULT, &mut arr_default as *mut _);
 303 
 304             ffi::libinput_device_config_calibration_get_matrix(self.libinput, matrix.as_mut_ptr());
 305             let mut arr_current = ffi::wl_array {
 306                 size: std::mem::size_of_val(&matrix),
 307                 alloc: std::mem::size_of_val(&matrix),
 308                 data: matrix.as_mut_ptr() as *mut _,
 309             };
 310             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CALIBRATION_MATRIX_CURRENT, &mut arr_current as *mut _);
 311         }
 312 
 313         // Acceleration configs
 314         let profiles = ffi::libinput_device_config_accel_get_profiles(self.libinput);
 315         ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_PROFILES_SUPPORT, profiles);
 316         if profiles != 0 {
 317             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_PROFILE_DEFAULT, ffi::libinput_device_config_accel_get_default_profile(self.libinput));
 318             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_PROFILE_CURRENT, ffi::libinput_device_config_accel_get_profile(self.libinput));
 319 
 320             let default_speed = ffi::libinput_device_config_accel_get_default_speed(self.libinput);
 321             let mut arr_default = ffi::wl_array {
 322                 size: std::mem::size_of::<f64>(),
 323                 alloc: std::mem::size_of::<f64>(),
 324                 data: &default_speed as *const f64 as *mut _,
 325             };
 326             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_SPEED_DEFAULT, &mut arr_default as *mut _);
 327 
 328             let current_speed = ffi::libinput_device_config_accel_get_speed(self.libinput);
 329             let mut arr_current = ffi::wl_array {
 330                 size: std::mem::size_of::<f64>(),
 331                 alloc: std::mem::size_of::<f64>(),
 332                 data: &current_speed as *const f64 as *mut _,
 333             };
 334             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_SPEED_CURRENT, &mut arr_current as *mut _);
 335         }
 336 
 337         // Natural scroll
 338         let natural_scroll = ffi::libinput_device_config_scroll_has_natural_scroll(self.libinput);
 339         ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_NATURAL_SCROLL_SUPPORT, natural_scroll as i32);
 340         if natural_scroll != 0 {
 341             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_NATURAL_SCROLL_DEFAULT, ffi::libinput_device_config_scroll_get_default_natural_scroll_enabled(self.libinput) as u32);
 342             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_NATURAL_SCROLL_CURRENT, ffi::libinput_device_config_scroll_get_natural_scroll_enabled(self.libinput) as u32);
 343         }
 344 
 345         // Left handed mode
 346         let left_handed = ffi::libinput_device_config_left_handed_is_available(self.libinput);
 347         ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_LEFT_HANDED_SUPPORT, left_handed as i32);
 348         if left_handed != 0 {
 349             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_LEFT_HANDED_DEFAULT, ffi::libinput_device_config_left_handed_get_default(self.libinput) as u32);
 350             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_LEFT_HANDED_CURRENT, ffi::libinput_device_config_left_handed_get(self.libinput) as u32);
 351         }
 352 
 353         // Click methods
 354         let click_methods = ffi::libinput_device_config_click_get_methods(self.libinput);
 355         ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CLICK_METHOD_SUPPORT, click_methods);
 356         if click_methods != 0 {
 357             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CLICK_METHOD_DEFAULT, ffi::libinput_device_config_click_get_default_method(self.libinput));
 358             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CLICK_METHOD_CURRENT, ffi::libinput_device_config_click_get_method(self.libinput));
 359             if (click_methods & ffi::libinput_config_click_method_LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER) != 0 {
 360                 ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CLICKFINGER_BUTTON_MAP_DEFAULT, ffi::libinput_device_config_click_get_default_clickfinger_button_map(self.libinput));
 361                 ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CLICKFINGER_BUTTON_MAP_CURRENT, ffi::libinput_device_config_click_get_clickfinger_button_map(self.libinput));
 362             }
 363         }
 364 
 365         // Middle mouse emulation
 366         let middle_emulation = ffi::libinput_device_config_middle_emulation_is_available(self.libinput);
 367         ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_MIDDLE_EMULATION_SUPPORT, middle_emulation as i32);
 368         if middle_emulation != 0 {
 369             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_MIDDLE_EMULATION_DEFAULT, ffi::libinput_device_config_middle_emulation_get_default_enabled(self.libinput));
 370             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_MIDDLE_EMULATION_CURRENT, ffi::libinput_device_config_middle_emulation_get_enabled(self.libinput));
 371         }
 372 
 373         // Scroll methods
 374         let scroll_methods = ffi::libinput_device_config_scroll_get_methods(self.libinput);
 375         ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_METHOD_SUPPORT, scroll_methods);
 376         if scroll_methods != 0 {
 377             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_METHOD_DEFAULT, ffi::libinput_device_config_scroll_get_default_method(self.libinput));
 378             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_METHOD_CURRENT, ffi::libinput_device_config_scroll_get_method(self.libinput));
 379             if (scroll_methods & ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) != 0 {
 380                 ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_BUTTON_DEFAULT, ffi::libinput_device_config_scroll_get_default_button(self.libinput));
 381                 ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_BUTTON_CURRENT, ffi::libinput_device_config_scroll_get_button(self.libinput));
 382                 ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_BUTTON_LOCK_DEFAULT, ffi::libinput_device_config_scroll_get_default_button_lock(self.libinput));
 383                 ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_BUTTON_LOCK_CURRENT, ffi::libinput_device_config_scroll_get_button_lock(self.libinput));
 384             }
 385         }
 386 
 387         // Disable While Typing (DWT)
 388         let dwt = ffi::libinput_device_config_dwt_is_available(self.libinput);
 389         ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DWT_SUPPORT, dwt as i32);
 390         if dwt != 0 {
 391             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DWT_DEFAULT, ffi::libinput_device_config_dwt_get_default_enabled(self.libinput) as u32);
 392             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DWT_CURRENT, ffi::libinput_device_config_dwt_get_enabled(self.libinput) as u32);
 393         }
 394 
 395         // Disable While Trackpointing (DWTP)
 396         let dwtp = ffi::libinput_device_config_dwtp_is_available(self.libinput);
 397         ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DWTP_SUPPORT, dwtp as i32);
 398         if dwtp != 0 {
 399             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DWTP_DEFAULT, ffi::libinput_device_config_dwtp_get_default_enabled(self.libinput) as u32);
 400             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DWTP_CURRENT, ffi::libinput_device_config_dwtp_get_enabled(self.libinput) as u32);
 401         }
 402 
 403         // Rotation
 404         let rotation = ffi::libinput_device_config_rotation_is_available(self.libinput);
 405         ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ROTATION_SUPPORT, rotation as i32);
 406         if rotation != 0 {
 407             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ROTATION_DEFAULT, ffi::libinput_device_config_rotation_get_default_angle(self.libinput) as u32);
 408             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ROTATION_CURRENT, ffi::libinput_device_config_rotation_get_angle(self.libinput) as u32);
 409         }
 410 
 411         if version >= 2 {
 412             ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DONE);
 413         }
 414     }
 415 }
 416 
 417 unsafe extern "C" fn handle_device_object_destroy(resource: *mut ffi::wl_resource) {
 418     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 419     if !obj.is_null() {
 420         if !(*obj).device.is_null() {
 421             crate::server::wl_list_remove(&mut (*obj).link as *mut ffi::wl_list as *mut WlList);
 422         }
 423         let _ = Box::from_raw(obj);
 424     }
 425 }
 426 
 427 static LIBINPUT_DEVICE_INTERFACE: ffi::river_libinput_device_v1_interface = ffi::river_libinput_device_v1_interface {
 428     destroy: Some(device_destroy),
 429     set_send_events: Some(device_set_send_events),
 430     set_tap: Some(device_set_tap),
 431     set_tap_button_map: Some(device_set_tap_button_map),
 432     set_drag: Some(device_set_drag),
 433     set_drag_lock: Some(device_set_drag_lock),
 434     set_three_finger_drag: Some(device_set_three_finger_drag),
 435     set_calibration_matrix: Some(device_set_calibration_matrix),
 436     set_accel_profile: Some(device_set_accel_profile),
 437     set_accel_speed: Some(device_set_accel_speed),
 438     apply_accel_config: Some(device_apply_accel_config),
 439     set_natural_scroll: Some(device_set_natural_scroll),
 440     set_left_handed: Some(device_set_left_handed),
 441     set_click_method: Some(device_set_click_method),
 442     set_clickfinger_button_map: Some(device_set_clickfinger_button_map),
 443     set_middle_emulation: Some(device_set_middle_emulation),
 444     set_scroll_method: Some(device_set_scroll_method),
 445     set_scroll_button: Some(device_set_scroll_button),
 446     set_scroll_button_lock: Some(device_set_scroll_button_lock),
 447     set_dwt: Some(device_set_dwt),
 448     set_dwtp: Some(device_set_dwtp),
 449     set_rotation: Some(device_set_rotation),
 450 };
 451 
 452 unsafe fn make_result(
 453     client: *mut ffi::wl_client,
 454     resource: *mut ffi::wl_resource,
 455     result_id: u32,
 456     status: ffi::libinput_config_status,
 457 ) -> bool {
 458     let result_res = ffi::wl_resource_create(
 459         client,
 460         &ffi::river_libinput_result_v1_interface,
 461         ffi::wl_resource_get_version(resource),
 462         result_id,
 463     );
 464     if result_res.is_null() {
 465         ffi::wl_client_post_no_memory(client);
 466         return false;
 467     }
 468 
 469     match status {
 470         ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => {
 471             ffi::wl_resource_post_event(result_res, 0); // success
 472         }
 473         ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
 474             ffi::wl_resource_post_event(result_res, 1); // unsupported
 475         }
 476         _ => {
 477             ffi::wl_resource_post_event(result_res, 2); // invalid
 478         }
 479     }
 480     ffi::wl_resource_destroy(result_res);
 481     status == ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS
 482 }
 483 
 484 unsafe fn broadcast_update(dev: *mut LibinputDevice, opcode: u32, arg: u32) {
 485     let objects_head = &mut (*dev).objects as *mut ffi::wl_list as *mut WlList;
 486     let mut curr = (*objects_head).next;
 487     while curr != objects_head {
 488         let next = (*curr).next;
 489         let obj = crate::container_of!(curr, LibinputDeviceObject, link);
 490         ffi::wl_resource_post_event((*obj).resource, opcode, arg);
 491         if ffi::wl_resource_get_version((*obj).resource) >= 2 {
 492             ffi::wl_resource_post_event((*obj).resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DONE);
 493         }
 494         curr = next;
 495     }
 496 }
 497 
 498 unsafe extern "C" fn device_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
 499     ffi::wl_resource_destroy(resource);
 500 }
 501 
 502 unsafe extern "C" fn device_set_send_events(
 503     client: *mut ffi::wl_client,
 504     resource: *mut ffi::wl_resource,
 505     result_id: u32,
 506     mode: u32,
 507 ) {
 508     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 509     if obj.is_null() { return; }
 510     let dev = (*obj).device;
 511     if dev.is_null() {
 512         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 513         return;
 514     }
 515 
 516     let status = ffi::libinput_device_config_send_events_set_mode((*dev).libinput, mode);
 517     if make_result(client, resource, result_id, status) {
 518         let current = ffi::libinput_device_config_send_events_get_mode((*dev).libinput);
 519         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_SEND_EVENTS_CURRENT, current);
 520     }
 521 }
 522 
 523 unsafe extern "C" fn device_set_tap(
 524     client: *mut ffi::wl_client,
 525     resource: *mut ffi::wl_resource,
 526     result_id: u32,
 527     state: u32,
 528 ) {
 529     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 530     if obj.is_null() { return; }
 531     let dev = (*obj).device;
 532     if dev.is_null() {
 533         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 534         return;
 535     }
 536 
 537     let status = ffi::libinput_device_config_tap_set_enabled((*dev).libinput, state);
 538     if make_result(client, resource, result_id, status) {
 539         let current = ffi::libinput_device_config_tap_get_enabled((*dev).libinput);
 540         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_TAP_CURRENT, current);
 541     }
 542 }
 543 
 544 unsafe extern "C" fn device_set_tap_button_map(
 545     client: *mut ffi::wl_client,
 546     resource: *mut ffi::wl_resource,
 547     result_id: u32,
 548     button_map: u32,
 549 ) {
 550     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 551     if obj.is_null() { return; }
 552     let dev = (*obj).device;
 553     if dev.is_null() {
 554         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 555         return;
 556     }
 557 
 558     let status = ffi::libinput_device_config_tap_set_button_map((*dev).libinput, button_map);
 559     if make_result(client, resource, result_id, status) {
 560         let current = ffi::libinput_device_config_tap_get_button_map((*dev).libinput);
 561         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_TAP_BUTTON_MAP_CURRENT, current);
 562     }
 563 }
 564 
 565 unsafe extern "C" fn device_set_drag(
 566     client: *mut ffi::wl_client,
 567     resource: *mut ffi::wl_resource,
 568     result_id: u32,
 569     state: u32,
 570 ) {
 571     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 572     if obj.is_null() { return; }
 573     let dev = (*obj).device;
 574     if dev.is_null() {
 575         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 576         return;
 577     }
 578 
 579     let status = ffi::libinput_device_config_tap_set_drag_enabled((*dev).libinput, state);
 580     if make_result(client, resource, result_id, status) {
 581         let current = ffi::libinput_device_config_tap_get_drag_enabled((*dev).libinput);
 582         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_DRAG_CURRENT, current);
 583     }
 584 }
 585 
 586 unsafe extern "C" fn device_set_drag_lock(
 587     client: *mut ffi::wl_client,
 588     resource: *mut ffi::wl_resource,
 589     result_id: u32,
 590     state: u32,
 591 ) {
 592     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 593     if obj.is_null() { return; }
 594     let dev = (*obj).device;
 595     if dev.is_null() {
 596         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 597         return;
 598     }
 599 
 600     let status = ffi::libinput_device_config_tap_set_drag_lock_enabled((*dev).libinput, state);
 601     if make_result(client, resource, result_id, status) {
 602         let current = ffi::libinput_device_config_tap_get_drag_lock_enabled((*dev).libinput);
 603         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_DRAG_LOCK_CURRENT, current);
 604     }
 605 }
 606 
 607 unsafe extern "C" fn device_set_three_finger_drag(
 608     client: *mut ffi::wl_client,
 609     resource: *mut ffi::wl_resource,
 610     result_id: u32,
 611     state: u32,
 612 ) {
 613     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 614     if obj.is_null() { return; }
 615     let dev = (*obj).device;
 616     if dev.is_null() {
 617         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 618         return;
 619     }
 620 
 621     let status = ffi::libinput_device_config_3fg_drag_set_enabled((*dev).libinput, state);
 622     if make_result(client, resource, result_id, status) {
 623         let current = ffi::libinput_device_config_3fg_drag_get_enabled((*dev).libinput);
 624         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_THREE_FINGER_DRAG_CURRENT, current);
 625     }
 626 }
 627 
 628 unsafe extern "C" fn device_set_calibration_matrix(
 629     client: *mut ffi::wl_client,
 630     resource: *mut ffi::wl_resource,
 631     result_id: u32,
 632     matrix_arr: *mut ffi::wl_array,
 633 ) {
 634     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 635     if obj.is_null() { return; }
 636     let dev = (*obj).device;
 637     if dev.is_null() {
 638         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 639         return;
 640     }
 641 
 642     if (*matrix_arr).size != std::mem::size_of::<[f32; 6]>() {
 643         ffi::wl_resource_post_error(
 644             resource,
 645             ffi::river_libinput_device_v1_error_RIVER_LIBINPUT_DEVICE_V1_ERROR_INVALID_ARG,
 646             b"invalid calibration matrix\0".as_ptr() as *const _,
 647         );
 648         return;
 649     }
 650 
 651     let matrix_ptr = (*matrix_arr).data as *const f32;
 652     let status = ffi::libinput_device_config_calibration_set_matrix((*dev).libinput, matrix_ptr);
 653     if make_result(client, resource, result_id, status) {
 654         let mut current = [0.0f32; 6];
 655         ffi::libinput_device_config_calibration_get_matrix((*dev).libinput, current.as_mut_ptr());
 656         let mut arr = ffi::wl_array {
 657             size: std::mem::size_of_val(&current),
 658             alloc: std::mem::size_of_val(&current),
 659             data: current.as_mut_ptr() as *mut _,
 660         };
 661 
 662         let objects_head = &mut (*dev).objects as *mut ffi::wl_list as *mut WlList;
 663         let mut curr = (*objects_head).next;
 664         while curr != objects_head {
 665             let next = (*curr).next;
 666             let dest_obj = crate::container_of!(curr, LibinputDeviceObject, link);
 667             ffi::wl_resource_post_event((*dest_obj).resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CALIBRATION_MATRIX_CURRENT, &mut arr as *mut _);
 668             if ffi::wl_resource_get_version((*dest_obj).resource) >= 2 {
 669                 ffi::wl_resource_post_event((*dest_obj).resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DONE);
 670             }
 671             curr = next;
 672         }
 673     }
 674 }
 675 
 676 unsafe extern "C" fn device_set_accel_profile(
 677     client: *mut ffi::wl_client,
 678     resource: *mut ffi::wl_resource,
 679     result_id: u32,
 680     profile: u32,
 681 ) {
 682     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 683     if obj.is_null() { return; }
 684     let dev = (*obj).device;
 685     if dev.is_null() {
 686         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 687         return;
 688     }
 689 
 690     let status = ffi::libinput_device_config_accel_set_profile((*dev).libinput, profile);
 691     if make_result(client, resource, result_id, status) {
 692         let current = ffi::libinput_device_config_accel_get_profile((*dev).libinput);
 693         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_PROFILE_CURRENT, current);
 694     }
 695 }
 696 
 697 unsafe extern "C" fn device_set_accel_speed(
 698     client: *mut ffi::wl_client,
 699     resource: *mut ffi::wl_resource,
 700     result_id: u32,
 701     speed_arr: *mut ffi::wl_array,
 702 ) {
 703     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 704     if obj.is_null() { return; }
 705     let dev = (*obj).device;
 706     if dev.is_null() {
 707         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 708         return;
 709     }
 710 
 711     if (*speed_arr).size != std::mem::size_of::<f64>() {
 712         ffi::wl_resource_post_error(
 713             resource,
 714             ffi::river_libinput_device_v1_error_RIVER_LIBINPUT_DEVICE_V1_ERROR_INVALID_ARG,
 715             b"invalid accel speed\0".as_ptr() as *const _,
 716         );
 717         return;
 718     }
 719 
 720     let speed = *((*speed_arr).data as *const f64);
 721     let status = ffi::libinput_device_config_accel_set_speed((*dev).libinput, speed);
 722     if make_result(client, resource, result_id, status) {
 723         let current = ffi::libinput_device_config_accel_get_speed((*dev).libinput);
 724         let mut arr = ffi::wl_array {
 725             size: std::mem::size_of::<f64>(),
 726             alloc: std::mem::size_of::<f64>(),
 727             data: &current as *const f64 as *mut _,
 728         };
 729 
 730         let objects_head = &mut (*dev).objects as *mut ffi::wl_list as *mut WlList;
 731         let mut curr = (*objects_head).next;
 732         while curr != objects_head {
 733             let next = (*curr).next;
 734             let dest_obj = crate::container_of!(curr, LibinputDeviceObject, link);
 735             ffi::wl_resource_post_event((*dest_obj).resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_SPEED_CURRENT, &mut arr as *mut _);
 736             if ffi::wl_resource_get_version((*dest_obj).resource) >= 2 {
 737                 ffi::wl_resource_post_event((*dest_obj).resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DONE);
 738             }
 739             curr = next;
 740         }
 741     }
 742 }
 743 
 744 unsafe extern "C" fn device_apply_accel_config(
 745     client: *mut ffi::wl_client,
 746     resource: *mut ffi::wl_resource,
 747     result_id: u32,
 748     config_res: *mut ffi::wl_resource,
 749 ) {
 750     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 751     if obj.is_null() { return; }
 752     let dev = (*obj).device;
 753 
 754     let result_res = ffi::wl_resource_create(
 755         client,
 756         &ffi::river_libinput_result_v1_interface,
 757         ffi::wl_resource_get_version(resource),
 758         result_id,
 759     );
 760     if result_res.is_null() {
 761         ffi::wl_client_post_no_memory(client);
 762         return;
 763     }
 764 
 765     if dev.is_null() {
 766         ffi::wl_resource_post_event(result_res, 1); // unsupported
 767         ffi::wl_resource_destroy(result_res);
 768         return;
 769     }
 770 
 771     let accel_config = ffi::wl_resource_get_user_data(config_res) as *mut crate::libinput_accel_config::LibinputAccelConfig;
 772     let config = if !accel_config.is_null() {
 773         (*accel_config).libinput
 774     } else {
 775         std::ptr::null_mut()
 776     };
 777 
 778     if config.is_null() {
 779         ffi::wl_resource_post_event(result_res, 2); // invalid
 780         ffi::wl_resource_destroy(result_res);
 781         return;
 782     }
 783 
 784     let status = ffi::libinput_device_config_accel_apply((*dev).libinput, config);
 785 
 786     let success = match status {
 787         ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => {
 788             ffi::wl_resource_post_event(result_res, 0); // success
 789             true
 790         }
 791         ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
 792             ffi::wl_resource_post_event(result_res, 1); // unsupported
 793             false
 794         }
 795         _ => {
 796             ffi::wl_resource_post_event(result_res, 2); // invalid
 797             false
 798         }
 799     };
 800 
 801     ffi::wl_resource_destroy(result_res);
 802 
 803     if success {
 804         let current = ffi::libinput_device_config_accel_get_profile((*dev).libinput);
 805         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_PROFILE_CURRENT, current);
 806     }
 807 }
 808 
 809 unsafe extern "C" fn device_set_natural_scroll(
 810     client: *mut ffi::wl_client,
 811     resource: *mut ffi::wl_resource,
 812     result_id: u32,
 813     state: u32,
 814 ) {
 815     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 816     if obj.is_null() { return; }
 817     let dev = (*obj).device;
 818     if dev.is_null() {
 819         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 820         return;
 821     }
 822 
 823     let status = ffi::libinput_device_config_scroll_set_natural_scroll_enabled((*dev).libinput, state as i32);
 824     if make_result(client, resource, result_id, status) {
 825         let current = ffi::libinput_device_config_scroll_get_natural_scroll_enabled((*dev).libinput);
 826         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_NATURAL_SCROLL_CURRENT, current as u32);
 827     }
 828 }
 829 
 830 unsafe extern "C" fn device_set_left_handed(
 831     client: *mut ffi::wl_client,
 832     resource: *mut ffi::wl_resource,
 833     result_id: u32,
 834     state: u32,
 835 ) {
 836     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 837     if obj.is_null() { return; }
 838     let dev = (*obj).device;
 839     if dev.is_null() {
 840         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 841         return;
 842     }
 843 
 844     let status = ffi::libinput_device_config_left_handed_set((*dev).libinput, state as i32);
 845     if make_result(client, resource, result_id, status) {
 846         let current = ffi::libinput_device_config_left_handed_get((*dev).libinput);
 847         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_LEFT_HANDED_CURRENT, current as u32);
 848     }
 849 }
 850 
 851 unsafe extern "C" fn device_set_click_method(
 852     client: *mut ffi::wl_client,
 853     resource: *mut ffi::wl_resource,
 854     result_id: u32,
 855     method: u32,
 856 ) {
 857     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 858     if obj.is_null() { return; }
 859     let dev = (*obj).device;
 860     if dev.is_null() {
 861         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 862         return;
 863     }
 864 
 865     let status = ffi::libinput_device_config_click_set_method((*dev).libinput, method);
 866     if make_result(client, resource, result_id, status) {
 867         let current = ffi::libinput_device_config_click_get_method((*dev).libinput);
 868         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_CLICK_METHOD_CURRENT, current);
 869     }
 870 }
 871 
 872 unsafe extern "C" fn device_set_clickfinger_button_map(
 873     client: *mut ffi::wl_client,
 874     resource: *mut ffi::wl_resource,
 875     result_id: u32,
 876     button_map: u32,
 877 ) {
 878     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 879     if obj.is_null() { return; }
 880     let dev = (*obj).device;
 881     if dev.is_null() {
 882         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 883         return;
 884     }
 885 
 886     let status = ffi::libinput_device_config_click_set_clickfinger_button_map((*dev).libinput, button_map);
 887     if make_result(client, resource, result_id, status) {
 888         let current = ffi::libinput_device_config_click_get_clickfinger_button_map((*dev).libinput);
 889         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_CLICKFINGER_BUTTON_MAP_CURRENT, current);
 890     }
 891 }
 892 
 893 unsafe extern "C" fn device_set_middle_emulation(
 894     client: *mut ffi::wl_client,
 895     resource: *mut ffi::wl_resource,
 896     result_id: u32,
 897     state: u32,
 898 ) {
 899     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 900     if obj.is_null() { return; }
 901     let dev = (*obj).device;
 902     if dev.is_null() {
 903         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 904         return;
 905     }
 906 
 907     let status = ffi::libinput_device_config_middle_emulation_set_enabled((*dev).libinput, state);
 908     if make_result(client, resource, result_id, status) {
 909         let current = ffi::libinput_device_config_middle_emulation_get_enabled((*dev).libinput);
 910         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_MIDDLE_EMULATION_CURRENT, current);
 911     }
 912 }
 913 
 914 unsafe extern "C" fn device_set_scroll_method(
 915     client: *mut ffi::wl_client,
 916     resource: *mut ffi::wl_resource,
 917     result_id: u32,
 918     method: u32,
 919 ) {
 920     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 921     if obj.is_null() { return; }
 922     let dev = (*obj).device;
 923     if dev.is_null() {
 924         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 925         return;
 926     }
 927 
 928     let status = ffi::libinput_device_config_scroll_set_method((*dev).libinput, method);
 929     if make_result(client, resource, result_id, status) {
 930         let current = ffi::libinput_device_config_scroll_get_method((*dev).libinput);
 931         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_METHOD_CURRENT, current);
 932     }
 933 }
 934 
 935 unsafe extern "C" fn device_set_scroll_button(
 936     client: *mut ffi::wl_client,
 937     resource: *mut ffi::wl_resource,
 938     result_id: u32,
 939     button: u32,
 940 ) {
 941     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 942     if obj.is_null() { return; }
 943     let dev = (*obj).device;
 944     if dev.is_null() {
 945         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 946         return;
 947     }
 948 
 949     let status = ffi::libinput_device_config_scroll_set_button((*dev).libinput, button);
 950     if make_result(client, resource, result_id, status) {
 951         let current = ffi::libinput_device_config_scroll_get_button((*dev).libinput);
 952         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_BUTTON_CURRENT, current);
 953     }
 954 }
 955 
 956 unsafe extern "C" fn device_set_scroll_button_lock(
 957     client: *mut ffi::wl_client,
 958     resource: *mut ffi::wl_resource,
 959     result_id: u32,
 960     state: u32,
 961 ) {
 962     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 963     if obj.is_null() { return; }
 964     let dev = (*obj).device;
 965     if dev.is_null() {
 966         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 967         return;
 968     }
 969 
 970     let status = ffi::libinput_device_config_scroll_set_button_lock((*dev).libinput, state);
 971     if make_result(client, resource, result_id, status) {
 972         let current = ffi::libinput_device_config_scroll_get_button_lock((*dev).libinput);
 973         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_BUTTON_LOCK_CURRENT, current);
 974     }
 975 }
 976 
 977 unsafe extern "C" fn device_set_dwt(
 978     client: *mut ffi::wl_client,
 979     resource: *mut ffi::wl_resource,
 980     result_id: u32,
 981     state: u32,
 982 ) {
 983     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
 984     if obj.is_null() { return; }
 985     let dev = (*obj).device;
 986     if dev.is_null() {
 987         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
 988         return;
 989     }
 990 
 991     let status = ffi::libinput_device_config_dwt_set_enabled((*dev).libinput, state);
 992     if make_result(client, resource, result_id, status) {
 993         let current = ffi::libinput_device_config_dwt_get_enabled((*dev).libinput);
 994         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_DWT_CURRENT, current as u32);
 995     }
 996 }
 997 
 998 unsafe extern "C" fn device_set_dwtp(
 999     client: *mut ffi::wl_client,
1000     resource: *mut ffi::wl_resource,
1001     result_id: u32,
1002     state: u32,
1003 ) {
1004     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
1005     if obj.is_null() { return; }
1006     let dev = (*obj).device;
1007     if dev.is_null() {
1008         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
1009         return;
1010     }
1011 
1012     let status = ffi::libinput_device_config_dwtp_set_enabled((*dev).libinput, state);
1013     if make_result(client, resource, result_id, status) {
1014         let current = ffi::libinput_device_config_dwtp_get_enabled((*dev).libinput);
1015         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_DWTP_CURRENT, current as u32);
1016     }
1017 }
1018 
1019 unsafe extern "C" fn device_set_rotation(
1020     client: *mut ffi::wl_client,
1021     resource: *mut ffi::wl_resource,
1022     result_id: u32,
1023     angle: u32,
1024 ) {
1025     let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
1026     if obj.is_null() { return; }
1027     let dev = (*obj).device;
1028     if dev.is_null() {
1029         make_result(client, resource, result_id, ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED);
1030         return;
1031     }
1032 
1033     let status = ffi::libinput_device_config_rotation_set_angle((*dev).libinput, angle);
1034     if make_result(client, resource, result_id, status) {
1035         let current = ffi::libinput_device_config_rotation_get_angle((*dev).libinput);
1036         broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_ROTATION_CURRENT, current);
1037     }
1038 }