Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Implement libinput physical device configuration and river_input_management_v1 integration
src/input_device.rs | 418 +++++++++++++++++++++-
src/input_manager.rs | 216 +++++++++++-
src/libinput_accel_config.rs | 157 +++++++++
src/libinput_config.rs | 64 +---
src/libinput_device.rs | 813 +++++++++++++++++++++++++++++++++++++++++++
src/main.rs | 2 +
src/seat.rs | 2 +-
wrapper.h | 1 +
8 files changed, 1619 insertions(+), 54 deletions(-)
diff --git a/src/input_device.rs b/src/input_device.rs
index 40d455e..1ca38f8 100644
--- a/src/input_device.rs
+++ b/src/input_device.rs
@@ -1,6 +1,9 @@
+// SPDX-FileCopyrightText: © 2026 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
use crate::ffi;
use crate::seat::Seat;
-use crate::server::{WlListener, wl_listener_remove};
+use crate::server::{WlListener, wl_listener_remove, WlList, wl_list_insert, wl_list_remove};
pub struct InputDeviceConfig {
pub scroll_factor: f64,
@@ -16,6 +19,16 @@ pub struct InputDevice {
pub config: InputDeviceConfig,
pub destroy_fn: Option<unsafe extern "C" fn(*mut std::ffi::c_void)>,
pub destroy_data: *mut std::ffi::c_void,
+
+ pub objects: ffi::wl_list, // list of InputDeviceObject
+ pub link: ffi::wl_list, // link inside InputManager::devices
+ pub libinput: Option<Box<crate::libinput_device::LibinputDevice>>,
+}
+
+pub struct InputDeviceObject {
+ pub device: *mut InputDevice,
+ pub resource: *mut ffi::wl_resource,
+ pub link: ffi::wl_list,
}
impl InputDevice {
@@ -24,6 +37,8 @@ impl InputDevice {
wlr_device: *mut ffi::wlr_input_device,
virtual_device: bool,
) -> *mut Self {
+ let server = (*seat).server;
+
let device = Box::into_raw(Box::new(Self {
seat,
wlr_device,
@@ -36,8 +51,18 @@ impl InputDevice {
},
destroy_fn: None,
destroy_data: std::ptr::null_mut(),
+ objects: std::mem::zeroed(),
+ link: std::mem::zeroed(),
+ libinput: None,
}));
+ ffi::wl_list_init(&mut (*device).objects);
+ ffi::wl_list_init(&mut (*device).link);
+
+ // Insert into InputManager::devices
+ let manager_devices_head = &mut (*server).input_manager.devices as *mut ffi::wl_list as *mut WlList;
+ wl_list_insert(manager_devices_head, &mut (*device).link as *mut ffi::wl_list as *mut WlList);
+
ffi::river_wlr_input_device_set_data(wlr_device, device as *mut _);
let destroy_listener_ptr = &mut (*device).destroy_listener as *mut ffi::wl_listener as *mut WlListener;
@@ -46,19 +71,81 @@ impl InputDevice {
let destroy_signal = ffi::river_wlr_input_device_get_destroy_signal(wlr_device);
crate::server::wl_signal_add(destroy_signal, &mut (*device).destroy_listener);
+ if !virtual_device {
+ let config_objects = &mut (*server).input_manager.objects as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*config_objects).next;
+ while curr != config_objects {
+ let next = (*curr).next;
+ let im_obj = crate::container_of!(curr, crate::input_manager::InputManagerObject, link);
+ (*device).create_object((*im_obj).resource);
+ curr = next;
+ }
+
+ let handle = ffi::wlr_libinput_get_device_handle(wlr_device);
+ if !handle.is_null() {
+ (*device).libinput = Some(crate::libinput_device::LibinputDevice::init(device, handle));
+ }
+ }
+
+ // Output mapping logic for pointers and touch screens (as done in Zig version)
+ let output_name = match ffi::river_wlr_input_device_get_type(wlr_device) {
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_POINTER => {
+ let ptr = ffi::wlr_pointer_from_input_device(wlr_device);
+ if !ptr.is_null() && !(*ptr).output_name.is_null() {
+ Some(std::ffi::CStr::from_ptr((*ptr).output_name))
+ } else {
+ None
+ }
+ }
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TOUCH => {
+ let touch = ffi::wlr_touch_from_input_device(wlr_device);
+ if !touch.is_null() && !(*touch).output_name.is_null() {
+ Some(std::ffi::CStr::from_ptr((*touch).output_name))
+ } else {
+ None
+ }
+ }
+ _ => None,
+ };
+
+ if let Some(name) = output_name {
+ let outputs_head = &mut (*server).om.outputs as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*outputs_head).next;
+ while curr != outputs_head {
+ let next = (*curr).next;
+ let output = crate::container_of!(curr, crate::output::Output, link);
+ let wlr_output = (*output).wlr_output;
+ if !wlr_output.is_null() {
+ let wlr_name = std::ffi::CStr::from_ptr(ffi::river_wlr_output_get_name(wlr_output));
+ if wlr_name == name {
+ (*device).config.map_to_output = wlr_output;
+ break;
+ }
+ }
+ curr = next;
+ }
+ }
+
device
}
pub unsafe fn assign_to_seat(&mut self, new_seat: *mut Seat) {
- if self.seat == new_seat {
+ let old_seat = self.seat;
+ if old_seat == new_seat {
return;
}
- if !self.seat.is_null() {
- (*self.seat).detach_device(self);
+ if !old_seat.is_null() {
+ (*old_seat).detach_device(self);
}
self.seat = new_seat;
- if !self.seat.is_null() {
- (*self.seat).attach_device(self);
+ if !new_seat.is_null() {
+ (*new_seat).attach_device(self);
+ }
+ if !old_seat.is_null() {
+ (*old_seat).update_capabilities();
+ }
+ if !new_seat.is_null() {
+ (*new_seat).update_capabilities();
}
}
@@ -73,6 +160,81 @@ impl InputDevice {
}
mapping
}
+
+ pub unsafe fn create_object(&mut self, im_v1_resource: *mut ffi::wl_resource) {
+ if self.virtual_device {
+ return;
+ }
+
+ let dev_type = ffi::river_wlr_input_device_get_type(self.wlr_device);
+ let proto_type = match dev_type {
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_KEYBOARD => 0, // keyboard
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_POINTER => 1, // pointer
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TOUCH => 2, // touch
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TABLET => 3, // tablet
+ _ => return,
+ };
+
+ let client = ffi::wl_resource_get_client(im_v1_resource);
+ let version = ffi::wl_resource_get_version(im_v1_resource);
+
+ let resource = ffi::wl_resource_create(
+ client,
+ &ffi::river_input_device_v1_interface,
+ version,
+ 0,
+ );
+ if resource.is_null() {
+ log::error!("out of memory creating river_input_device_v1");
+ ffi::wl_client_post_no_memory(client);
+ return;
+ }
+
+ let obj = Box::into_raw(Box::new(InputDeviceObject {
+ device: self,
+ resource,
+ link: std::mem::zeroed(),
+ }));
+
+ ffi::wl_resource_set_implementation(
+ resource,
+ &INPUT_DEVICE_INTERFACE as *const _ as *const _,
+ obj as *mut _,
+ Some(handle_device_object_destroy),
+ );
+
+ // Insert into self.objects
+ let list_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
+ wl_list_insert(list_head, &mut (*obj).link as *mut ffi::wl_list as *mut WlList);
+
+ // Send input_device event to the manager resource
+ ffi::wl_resource_post_event(im_v1_resource, 0, resource); // opcode 0 is input_device in river_input_manager_v1
+
+ // Send type and name to client
+ ffi::wl_resource_post_event(resource, 1, proto_type); // type event
+
+ let name = ffi::river_wlr_input_device_get_name(self.wlr_device);
+ let name_ptr = if !name.is_null() { name } else { b"\0".as_ptr() as *const _ };
+ ffi::wl_resource_post_event(resource, 2, name_ptr); // name event
+
+ if version >= 2 {
+ ffi::wl_resource_post_event(resource, 3); // done event
+ }
+
+ // Connect client-side libinput device if matching exists
+ if let Some(ref mut libinput) = self.libinput {
+ let libinput_config_objects = &mut (*(*self.seat).server).libinput_config.objects as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*libinput_config_objects).next;
+ while curr != libinput_config_objects {
+ let next = (*curr).next;
+ let config_obj = crate::container_of!(curr, crate::libinput_config::LibinputConfigObject, link);
+ if ffi::wl_resource_get_client((*config_obj).resource) == client {
+ libinput.create_object((*config_obj).resource);
+ }
+ curr = next;
+ }
+ }
+ }
}
unsafe extern "C" fn handle_device_destroy(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
@@ -84,6 +246,32 @@ unsafe extern "C" fn handle_device_destroy(listener: *mut ffi::wl_listener, _dat
ffi::river_wlr_input_device_get_type(device.wlr_device)
);
+ // Free objects and set inert
+ let objects_head = &mut device.objects as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*objects_head).next;
+ while curr != objects_head {
+ let next = (*curr).next;
+ let obj = crate::container_of!(curr, InputDeviceObject, link);
+
+ wl_list_remove(curr);
+ ffi::wl_list_init(curr as *mut ffi::wl_list);
+
+ ffi::wl_resource_post_event((*obj).resource, 0); // removed event
+
+ ffi::wl_resource_set_implementation(
+ (*obj).resource,
+ &INPUT_DEVICE_INERT_INTERFACE as *const _ as *const _,
+ obj as *mut _,
+ Some(handle_device_object_destroy),
+ );
+
+ curr = next;
+ }
+
+ if let Some(mut libinput) = device.libinput.take() {
+ libinput.deinit();
+ }
+
// Call custom destroy callback if set (e.g. to clean up Tablet/Keyboard wrappers)
if let Some(destroy_fn) = device.destroy_fn {
destroy_fn(device.destroy_data);
@@ -92,11 +280,229 @@ unsafe extern "C" fn handle_device_destroy(listener: *mut ffi::wl_listener, _dat
// Remove destroy listener
wl_listener_remove(&mut device.destroy_listener);
+ // Remove from InputManager::devices
+ wl_list_remove(&mut device.link as *mut ffi::wl_list as *mut WlList);
+
// Detach from seat if attached
if !device.seat.is_null() {
(*device.seat).detach_device(device);
+ (*device.seat).update_capabilities();
}
// Free wrapper memory
let _boxed = Box::from_raw(device_ptr);
}
+
+unsafe extern "C" fn handle_device_object_destroy(resource: *mut ffi::wl_resource) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut InputDeviceObject;
+ if !obj.is_null() {
+ wl_list_remove(&mut (*obj).link as *mut ffi::wl_list as *mut WlList);
+ let _ = Box::from_raw(obj);
+ }
+}
+
+static INPUT_DEVICE_INTERFACE: ffi::river_input_device_v1_interface = ffi::river_input_device_v1_interface {
+ destroy: Some(input_device_destroy),
+ assign_to_seat: Some(input_device_assign_to_seat),
+ set_repeat_info: Some(input_device_set_repeat_info),
+ set_scroll_factor: Some(input_device_set_scroll_factor),
+ map_to_output: Some(input_device_map_to_output),
+ map_to_rectangle: Some(input_device_map_to_rectangle),
+};
+
+static INPUT_DEVICE_INERT_INTERFACE: ffi::river_input_device_v1_interface = ffi::river_input_device_v1_interface {
+ destroy: Some(input_device_destroy),
+ assign_to_seat: None,
+ set_repeat_info: None,
+ set_scroll_factor: None,
+ map_to_output: None,
+ map_to_rectangle: None,
+};
+
+unsafe extern "C" fn input_device_destroy(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn input_device_assign_to_seat(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ name: *const std::os::raw::c_char,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut InputDeviceObject;
+ if obj.is_null() {
+ return;
+ }
+ let device = (*obj).device;
+ if name.is_null() {
+ return;
+ }
+ let server = (*(*device).seat).server;
+ let seats_head = &mut (*server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats_head).next;
+ let mut found = false;
+ let name_str = std::ffi::CStr::from_ptr(name).to_string_lossy();
+ while curr != seats_head {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, Seat, link);
+ let seat_name = std::ffi::CStr::from_ptr(ffi::river_wlr_seat_get_name((*seat).wlr_seat)).to_string_lossy();
+ if seat_name == name_str {
+ (*device).assign_to_seat(seat);
+ found = true;
+ break;
+ }
+ curr = next;
+ }
+ if !found {
+ log::info!("client requested input device be assigned to non-existent seat '{}'", name_str);
+ }
+}
+
+unsafe extern "C" fn input_device_set_repeat_info(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ rate: i32,
+ delay: i32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut InputDeviceObject;
+ if obj.is_null() {
+ return;
+ }
+ let device = (*obj).device;
+ if rate < 0 || delay < 0 {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_input_device_v1_error_RIVER_INPUT_DEVICE_V1_ERROR_INVALID_REPEAT_INFO,
+ b"negative rate/delay\0".as_ptr() as *const _,
+ );
+ return;
+ }
+ let dev_type = ffi::river_wlr_input_device_get_type((*device).wlr_device);
+ if dev_type == ffi::wlr_input_device_type_WLR_INPUT_DEVICE_KEYBOARD {
+ let keyboard = (*device).destroy_data as *mut crate::keyboard::Keyboard;
+ if !keyboard.is_null() {
+ (*keyboard).set_repeat_info(rate, delay);
+ }
+ }
+}
+
+unsafe extern "C" fn input_device_set_scroll_factor(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ factor_raw: ffi::wl_fixed_t,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut InputDeviceObject;
+ if obj.is_null() {
+ return;
+ }
+ let device = (*obj).device;
+ let factor = factor_raw as f64 / 256.0;
+ if factor < 0.0 {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_input_device_v1_error_RIVER_INPUT_DEVICE_V1_ERROR_INVALID_SCROLL_FACTOR,
+ b"negative scroll factor\0".as_ptr() as *const _,
+ );
+ return;
+ }
+ (*device).config.scroll_factor = factor;
+}
+
+unsafe extern "C" fn input_device_map_to_output(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ output_resource: *mut ffi::wl_resource,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut InputDeviceObject;
+ if obj.is_null() {
+ return;
+ }
+ let device = (*obj).device;
+
+ let dev_type = ffi::river_wlr_input_device_get_type((*device).wlr_device);
+ match dev_type {
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_POINTER |
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TOUCH |
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TABLET => {},
+ _ => return,
+ }
+
+ let wlr_output = if !output_resource.is_null() {
+ let out = ffi::wlr_output_from_resource(output_resource);
+ if out.is_null() {
+ return;
+ }
+ out
+ } else {
+ std::ptr::null_mut()
+ };
+
+ (*device).config.map_to_output = wlr_output;
+
+ match dev_type {
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TOUCH |
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TABLET => {
+ if !(*device).seat.is_null() {
+ let cursor = &mut (*(*device).seat).cursor;
+ ffi::wlr_cursor_map_input_to_output(cursor.wlr_cursor, (*device).wlr_device, wlr_output);
+ }
+ }
+ _ => {}
+ }
+}
+
+unsafe extern "C" fn input_device_map_to_rectangle(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ x: i32,
+ y: i32,
+ width: i32,
+ height: i32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut InputDeviceObject;
+ if obj.is_null() {
+ return;
+ }
+ let device = (*obj).device;
+
+ if width < 0 || height < 0 {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_input_device_v1_error_RIVER_INPUT_DEVICE_V1_ERROR_INVALID_MAP_TO_RECTANGLE,
+ b"negative rectangle width/height\0".as_ptr() as *const _,
+ );
+ return;
+ }
+
+ let dev_type = ffi::river_wlr_input_device_get_type((*device).wlr_device);
+ match dev_type {
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_POINTER |
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TOUCH |
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TABLET => {},
+ _ => return,
+ }
+
+ (*device).config.map_to_rectangle = ffi::wlr_box {
+ x,
+ y,
+ width,
+ height,
+ };
+
+ match dev_type {
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TOUCH |
+ ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TABLET => {
+ if !(*device).seat.is_null() {
+ let cursor = &mut (*(*device).seat).cursor;
+ ffi::wlr_cursor_map_input_to_region(
+ cursor.wlr_cursor,
+ (*device).wlr_device,
+ &mut (*device).config.map_to_rectangle,
+ );
+ }
+ }
+ _ => {}
+ }
+}
diff --git a/src/input_manager.rs b/src/input_manager.rs
index b6db300..8841715 100644
--- a/src/input_manager.rs
+++ b/src/input_manager.rs
@@ -1,14 +1,17 @@
-// SPDX-FileCopyrightText: © 2020 The River Developers
+// SPDX-FileCopyrightText: © 2026 The River Developers
// SPDX-License-Identifier: GPL-3.0-only
use crate::ffi;
-use crate::server::{Server, wl_signal_add, WlListener, wl_listener_remove};
+use crate::server::{Server, wl_signal_add, WlListener, wl_listener_remove, WlList, wl_list_insert, wl_list_remove};
use crate::seat::Seat;
pub struct InputManager {
pub server: *mut Server,
pub default_seat: *mut Seat,
pub seats: ffi::wl_list,
+ pub devices: ffi::wl_list, // list of InputDevice
+ pub objects: ffi::wl_list, // list of InputManagerObject
+ pub global: *mut ffi::wl_global,
pub idle_notifier: *mut ffi::wlr_idle_notifier_v1,
pub relative_pointer_manager: *mut ffi::wlr_relative_pointer_manager_v1,
@@ -25,6 +28,12 @@ pub struct InputManager {
pub new_input_method: ffi::wl_listener,
}
+pub struct InputManagerObject {
+ pub manager: *mut InputManager,
+ pub resource: *mut ffi::wl_resource,
+ pub link: ffi::wl_list,
+}
+
impl Default for InputManager {
fn default() -> Self {
unsafe { std::mem::zeroed() }
@@ -37,6 +46,8 @@ impl InputManager {
let wl_server = (*server).wl_server;
ffi::wl_list_init(&mut self.seats);
+ ffi::wl_list_init(&mut self.devices);
+ ffi::wl_list_init(&mut self.objects);
self.idle_notifier = ffi::wlr_idle_notifier_v1_create(wl_server);
self.relative_pointer_manager = ffi::wlr_relative_pointer_manager_v1_create(wl_server);
@@ -51,6 +62,17 @@ impl InputManager {
// Create default seat
self.default_seat = Seat::create(server, "default")?;
+ self.global = ffi::wl_global_create(
+ wl_server,
+ &ffi::river_input_manager_v1_interface,
+ 2,
+ self as *mut InputManager as *mut _,
+ Some(bind_input_manager),
+ );
+ if self.global.is_null() {
+ return Err("Failed to create river_input_manager_v1 global");
+ }
+
let new_input_ptr = &mut self.new_input_listener as *mut ffi::wl_listener as *mut WlListener;
(*new_input_ptr).notify = Some(handle_new_input);
@@ -71,6 +93,20 @@ impl InputManager {
}
pub unsafe fn deinit(&mut self) {
+ if !self.global.is_null() {
+ ffi::wl_global_destroy(self.global);
+ self.global = std::ptr::null_mut();
+ }
+
+ let objects_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*objects_head).next;
+ while curr != objects_head {
+ let next = (*curr).next;
+ let obj = crate::container_of!(curr, InputManagerObject, link);
+ ffi::wl_resource_destroy((*obj).resource);
+ curr = next;
+ }
+
if !self.default_seat.is_null() {
Seat::destroy(self.default_seat);
self.default_seat = std::ptr::null_mut();
@@ -81,6 +117,179 @@ impl InputManager {
}
}
+unsafe extern "C" fn bind_input_manager(
+ client: *mut ffi::wl_client,
+ data: *mut std::ffi::c_void,
+ version: u32,
+ id: u32,
+) {
+ let im = data as *mut InputManager;
+ let resource = ffi::wl_resource_create(client, &ffi::river_input_manager_v1_interface, version as i32, id);
+ if resource.is_null() {
+ ffi::wl_client_post_no_memory(client);
+ return;
+ }
+
+ let obj = Box::into_raw(Box::new(InputManagerObject {
+ manager: im,
+ resource,
+ link: std::mem::zeroed(),
+ }));
+
+ ffi::wl_resource_set_implementation(
+ resource,
+ &INPUT_MANAGER_INTERFACE as *const _ as *const _,
+ obj as *mut _,
+ Some(handle_manager_object_destroy),
+ );
+
+ let list_head = &mut (*im).objects as *mut ffi::wl_list as *mut WlList;
+ wl_list_insert((*list_head).prev, &mut (*obj).link as *mut ffi::wl_list as *mut WlList);
+
+ // Send existing devices to the client
+ let devices_head = &mut (*im).devices as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*devices_head).next;
+ while curr != devices_head {
+ let next = (*curr).next;
+ let device = crate::container_of!(curr, crate::input_device::InputDevice, link);
+ if !(*device).virtual_device {
+ (*device).create_object(resource);
+ }
+ curr = next;
+ }
+}
+
+unsafe extern "C" fn handle_manager_object_destroy(resource: *mut ffi::wl_resource) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut InputManagerObject;
+ if !obj.is_null() {
+ wl_list_remove(&mut (*obj).link as *mut ffi::wl_list as *mut WlList);
+ let _ = Box::from_raw(obj);
+ }
+}
+
+static INPUT_MANAGER_INTERFACE: ffi::river_input_manager_v1_interface = ffi::river_input_manager_v1_interface {
+ stop: Some(input_manager_stop),
+ destroy: Some(input_manager_destroy),
+ create_seat: Some(input_manager_create_seat),
+ destroy_seat: Some(input_manager_destroy_seat),
+};
+
+static INPUT_MANAGER_INERT_INTERFACE: ffi::river_input_manager_v1_interface = ffi::river_input_manager_v1_interface {
+ stop: None,
+ destroy: Some(input_manager_inert_destroy),
+ create_seat: None,
+ destroy_seat: None,
+};
+
+unsafe extern "C" fn input_manager_stop(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut InputManagerObject;
+ if obj.is_null() {
+ return;
+ }
+
+ wl_list_remove(&mut (*obj).link as *mut ffi::wl_list as *mut WlList);
+ ffi::wl_list_init(&mut (*obj).link);
+
+ ffi::wl_resource_post_event(resource, 0); // finished event
+
+ ffi::wl_resource_set_implementation(
+ resource,
+ &INPUT_MANAGER_INERT_INTERFACE as *const _ as *const _,
+ obj as *mut _,
+ Some(handle_manager_object_destroy),
+ );
+}
+
+unsafe extern "C" fn input_manager_destroy(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_input_manager_v1_error_RIVER_INPUT_MANAGER_V1_ERROR_INVALID_DESTROY,
+ b"destroy before finished event sent\0".as_ptr() as *const _,
+ );
+}
+
+unsafe extern "C" fn input_manager_inert_destroy(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+) {
+ ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn input_manager_create_seat(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ name: *const std::os::raw::c_char,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut InputManagerObject;
+ if obj.is_null() || name.is_null() {
+ return;
+ }
+ let im = (*obj).manager;
+ let name_str = std::ffi::CStr::from_ptr(name).to_string_lossy();
+
+ // Check if seat already exists
+ let seats_head = &mut (*im).seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats_head).next;
+ let mut exists = false;
+ while curr != seats_head {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, Seat, link);
+ let seat_name = std::ffi::CStr::from_ptr(ffi::river_wlr_seat_get_name((*seat).wlr_seat)).to_string_lossy();
+ if seat_name == name_str {
+ exists = true;
+ break;
+ }
+ curr = next;
+ }
+
+ if !exists {
+ let server = (*im).server;
+ if let Err(e) = Seat::create(server, &name_str) {
+ log::error!("failed to create seat '{}': {}", name_str, e);
+ ffi::wl_resource_post_no_memory(resource);
+ }
+ }
+}
+
+unsafe extern "C" fn input_manager_destroy_seat(
+ _client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ name: *const std::os::raw::c_char,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut InputManagerObject;
+ if obj.is_null() || name.is_null() {
+ return;
+ }
+ let im = (*obj).manager;
+ let name_str = std::ffi::CStr::from_ptr(name).to_string_lossy();
+
+ if name_str == "default" {
+ return;
+ }
+
+ let seats_head = &mut (*im).seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats_head).next;
+ // Skip default seat (which is the first seat in the list)
+ curr = (*curr).next;
+ while curr != seats_head {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, Seat, link);
+ let seat_name = std::ffi::CStr::from_ptr(ffi::river_wlr_seat_get_name((*seat).wlr_seat)).to_string_lossy();
+ if seat_name == name_str {
+ (*seat).destroying = true;
+ (*(*im).server).wm.dirty_windowing();
+ break;
+ }
+ curr = next;
+ }
+}
+
unsafe extern "C" fn handle_new_input(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
let im = &mut *crate::container_of!(listener, InputManager, new_input_listener);
let wlr_device = data as *mut ffi::wlr_input_device;
@@ -95,6 +304,9 @@ unsafe extern "C" fn handle_new_input(listener: *mut ffi::wl_listener, data: *mu
} else if dev_type == ffi::wlr_input_device_type_WLR_INPUT_DEVICE_TABLET {
let _ = crate::tablet::Tablet::create(im.default_seat, wlr_device, false);
}
+
+ // Attach device to the default seat
+ (*im.default_seat).attach_device(device);
}
unsafe extern "C" fn handle_new_text_input(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
diff --git a/src/libinput_accel_config.rs b/src/libinput_accel_config.rs
new file mode 100644
index 0000000..2dff102
--- /dev/null
+++ b/src/libinput_accel_config.rs
@@ -0,0 +1,157 @@
+// SPDX-FileCopyrightText: © 2026 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+
+pub struct LibinputAccelConfig {
+ pub resource: *mut ffi::wl_resource,
+ pub libinput: *mut ffi::libinput_config_accel,
+}
+
+impl LibinputAccelConfig {
+ pub unsafe fn create(
+ client: *mut ffi::wl_client,
+ version: u32,
+ id: u32,
+ profile: u32,
+ ) -> Result<*mut Self, &'static str> {
+ let libinput = ffi::libinput_config_accel_create(profile);
+ if libinput.is_null() {
+ return Err("Failed to create libinput_config_accel");
+ }
+
+ let resource = ffi::wl_resource_create(
+ client,
+ &ffi::river_libinput_accel_config_v1_interface,
+ version as i32,
+ id,
+ );
+ if resource.is_null() {
+ ffi::libinput_config_accel_destroy(libinput);
+ return Err("Failed to create river_libinput_accel_config_v1 resource");
+ }
+
+ let accel_config = Box::into_raw(Box::new(Self {
+ resource,
+ libinput,
+ }));
+
+ ffi::wl_resource_set_implementation(
+ resource,
+ &ACCEL_CONFIG_INTERFACE as *const _ as *const _,
+ accel_config as *mut _,
+ Some(handle_accel_config_destroy_resource),
+ );
+
+ Ok(accel_config)
+ }
+}
+
+unsafe extern "C" fn handle_accel_config_destroy_resource(resource: *mut ffi::wl_resource) {
+ let accel_config = ffi::wl_resource_get_user_data(resource) as *mut LibinputAccelConfig;
+ if !accel_config.is_null() {
+ if !(*accel_config).libinput.is_null() {
+ ffi::libinput_config_accel_destroy((*accel_config).libinput);
+ }
+ let _ = Box::from_raw(accel_config);
+ }
+}
+
+static ACCEL_CONFIG_INTERFACE: ffi::river_libinput_accel_config_v1_interface = ffi::river_libinput_accel_config_v1_interface {
+ destroy: Some(accel_config_destroy),
+ set_points: Some(accel_config_set_points),
+};
+
+unsafe extern "C" fn accel_config_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn accel_config_set_points(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ accel_type_raw: u32,
+ step_arr: *mut ffi::wl_array,
+ points_arr: *mut ffi::wl_array,
+) {
+ let accel_config = ffi::wl_resource_get_user_data(resource) as *mut LibinputAccelConfig;
+ if accel_config.is_null() {
+ return;
+ }
+
+ let accel_type = match accel_type_raw {
+ 0 => ffi::libinput_config_accel_type_LIBINPUT_ACCEL_TYPE_FALLBACK,
+ 1 => ffi::libinput_config_accel_type_LIBINPUT_ACCEL_TYPE_MOTION,
+ 2 => ffi::libinput_config_accel_type_LIBINPUT_ACCEL_TYPE_SCROLL,
+ _ => {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_libinput_accel_config_v1_error_RIVER_LIBINPUT_ACCEL_CONFIG_V1_ERROR_INVALID_ARG,
+ b"invalid accel_type enum value\0".as_ptr() as *const _,
+ );
+ return;
+ }
+ };
+
+ if (*step_arr).size != std::mem::size_of::<f64>() as usize {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_libinput_accel_config_v1_error_RIVER_LIBINPUT_ACCEL_CONFIG_V1_ERROR_INVALID_ARG,
+ b"invalid step argument\0".as_ptr() as *const _,
+ );
+ return;
+ }
+
+ let step = *((*step_arr).data as *const f64);
+
+ if (*points_arr).size == 0 || (*points_arr).size % std::mem::size_of::<f64>() as usize != 0 {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_libinput_accel_config_v1_error_RIVER_LIBINPUT_ACCEL_CONFIG_V1_ERROR_INVALID_ARG,
+ b"invalid points argument\0".as_ptr() as *const _,
+ );
+ return;
+ }
+
+ let points_count = (*points_arr).size / std::mem::size_of::<f64>();
+ let points_ptr = (*points_arr).data as *const f64;
+
+ let result_res = ffi::wl_resource_create(
+ client,
+ &ffi::river_libinput_result_v1_interface,
+ ffi::wl_resource_get_version(resource),
+ result_id,
+ );
+ if result_res.is_null() {
+ ffi::wl_client_post_no_memory(client);
+ return;
+ }
+
+ let libinput = (*accel_config).libinput;
+ if libinput.is_null() {
+ ffi::wl_resource_post_event(result_res, 2); // invalid
+ ffi::wl_resource_destroy(result_res);
+ return;
+ }
+
+ let status = ffi::libinput_config_accel_set_points(
+ libinput,
+ accel_type,
+ step,
+ points_count,
+ points_ptr,
+ );
+
+ match status {
+ ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => {
+ ffi::wl_resource_post_event(result_res, 0); // success
+ }
+ ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
+ ffi::wl_resource_post_event(result_res, 1); // unsupported
+ }
+ _ => {
+ ffi::wl_resource_post_event(result_res, 2); // invalid
+ }
+ }
+ ffi::wl_resource_destroy(result_res);
+}
diff --git a/src/libinput_config.rs b/src/libinput_config.rs
index 653ec9a..494a33d 100644
--- a/src/libinput_config.rs
+++ b/src/libinput_config.rs
@@ -2,12 +2,13 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::ffi;
-use crate::server::{Server, WlListener, wl_signal_add, wl_listener_remove, WlList, wl_list_insert, wl_list_remove};
+use crate::server::{Server, WlList, wl_list_insert, wl_list_remove};
pub struct LibinputConfig {
pub server: *mut Server,
pub global: *mut ffi::wl_global,
pub objects: ffi::wl_list,
+ pub devices: ffi::wl_list,
}
pub struct LibinputConfigObject {
@@ -26,6 +27,7 @@ impl LibinputConfig {
pub unsafe fn init(&mut self, server: *mut Server) -> Result<(), &'static str> {
self.server = server;
ffi::wl_list_init(&mut self.objects);
+ ffi::wl_list_init(&mut self.devices);
self.global = ffi::wl_global_create(
(*server).wl_server,
@@ -86,6 +88,15 @@ unsafe extern "C" fn bind_libinput_config(
let list_head = &mut (*config).objects as *mut ffi::wl_list as *mut WlList;
wl_list_insert((*list_head).prev, &mut (*obj).link as *mut ffi::wl_list as *mut WlList);
+
+ let devices_head = &mut (*config).devices as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*devices_head).next;
+ while curr != devices_head {
+ let next = (*curr).next;
+ let dev = crate::container_of!(curr, crate::libinput_device::LibinputDevice, link);
+ (*dev).create_object(resource);
+ curr = next;
+ }
}
unsafe extern "C" fn handle_object_destroy(resource: *mut ffi::wl_resource) {
@@ -143,52 +154,15 @@ unsafe extern "C" fn libinput_config_create_accel_config(
client: *mut ffi::wl_client,
resource: *mut ffi::wl_resource,
id: u32,
- _profile: u32,
+ profile: u32,
) {
- let accel_res = ffi::wl_resource_create(
+ if let Err(e) = crate::libinput_accel_config::LibinputAccelConfig::create(
client,
- &ffi::river_libinput_accel_config_v1_interface,
- ffi::wl_resource_get_version(resource),
+ ffi::wl_resource_get_version(resource) as u32,
id,
- );
- if accel_res.is_null() {
- ffi::wl_client_post_no_memory(client);
- return;
- }
-
- static LIBINPUT_ACCEL_CONFIG_INTERFACE: ffi::river_libinput_accel_config_v1_interface = ffi::river_libinput_accel_config_v1_interface {
- destroy: Some(libinput_accel_config_destroy),
- set_points: Some(libinput_accel_config_set_points),
- };
-
- ffi::wl_resource_set_implementation(
- accel_res,
- &LIBINPUT_ACCEL_CONFIG_INTERFACE as *const _ as *const _,
- std::ptr::null_mut(),
- None,
- );
-}
-
-unsafe extern "C" fn libinput_accel_config_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
- ffi::wl_resource_destroy(resource);
-}
-
-unsafe extern "C" fn libinput_accel_config_set_points(
- client: *mut ffi::wl_client,
- resource: *mut ffi::wl_resource,
- result_id: u32,
- _accel_type: u32,
- _step: *mut ffi::wl_array,
- _points: *mut ffi::wl_array,
-) {
- let result_res = ffi::wl_resource_create(
- client,
- &ffi::river_libinput_result_v1_interface,
- ffi::wl_resource_get_version(resource),
- result_id,
- );
- if !result_res.is_null() {
- ffi::wl_resource_post_event(result_res, 1); // unsupported event
- ffi::wl_resource_destroy(result_res);
+ profile,
+ ) {
+ log::error!("Failed to create LibinputAccelConfig: {}", e);
+ ffi::wl_resource_post_no_memory(resource);
}
}
diff --git a/src/libinput_device.rs b/src/libinput_device.rs
new file mode 100644
index 0000000..7dbd900
--- /dev/null
+++ b/src/libinput_device.rs
@@ -0,0 +1,813 @@
+// SPDX-FileCopyrightText: © 2026 The River Developers
+// SPDX-License-Identifier: GPL-3.0-only
+
+use crate::ffi;
+use crate::input_device::InputDevice;
+use crate::server::WlList;
+
+pub struct LibinputDevice {
+ pub parent_device: *mut InputDevice,
+ pub libinput: *mut ffi::libinput_device,
+ pub objects: ffi::wl_list, // list of LibinputDeviceObject
+ pub link: ffi::wl_list, // link inside LibinputConfig::devices
+}
+
+pub struct LibinputDeviceObject {
+ pub device: *mut LibinputDevice,
+ pub resource: *mut ffi::wl_resource,
+ pub link: ffi::wl_list,
+}
+
+impl LibinputDevice {
+ pub unsafe fn init(
+ parent_device: *mut InputDevice,
+ handle: *mut ffi::libinput_device,
+ ) -> Box<Self> {
+ let mut dev = Box::new(Self {
+ parent_device,
+ libinput: handle,
+ objects: std::mem::zeroed(),
+ link: std::mem::zeroed(),
+ });
+
+ ffi::wl_list_init(&mut dev.objects);
+ ffi::wl_list_init(&mut dev.link);
+
+ let server = (*(*parent_device).seat).server;
+ let devices_head = &mut (*server).libinput_config.devices as *mut ffi::wl_list as *mut WlList;
+ crate::server::wl_list_insert((*devices_head).prev, &mut dev.link as *mut ffi::wl_list as *mut WlList);
+
+ let config_objects = &mut (*server).libinput_config.objects as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*config_objects).next;
+ while curr != config_objects {
+ let next = (*curr).next;
+ let config_obj = crate::container_of!(curr, crate::libinput_config::LibinputConfigObject, link);
+ dev.create_object((*config_obj).resource);
+ curr = next;
+ }
+
+ dev
+ }
+
+ pub unsafe fn deinit(&mut self) {
+ let objects_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*objects_head).next;
+ while curr != objects_head {
+ let next = (*curr).next;
+ let obj = crate::container_of!(curr, LibinputDeviceObject, link);
+
+ crate::server::wl_list_remove(curr);
+ ffi::wl_resource_post_event((*obj).resource, ffi::RIVER_LIBINPUT_DEVICE_V1_REMOVED);
+
+ ffi::wl_resource_set_implementation(
+ (*obj).resource,
+ std::ptr::null(),
+ std::ptr::null_mut(),
+ None,
+ );
+
+ let _ = Box::from_raw(obj);
+ curr = next;
+ }
+
+ crate::server::wl_list_remove(&mut self.link as *mut ffi::wl_list as *mut WlList);
+ }
+
+ pub unsafe fn create_object(&mut self, config_v1_resource: *mut ffi::wl_resource) {
+ let client = ffi::wl_resource_get_client(config_v1_resource);
+ let version = ffi::wl_resource_get_version(config_v1_resource);
+
+ let resource = ffi::wl_resource_create(
+ client,
+ &ffi::river_libinput_device_v1_interface,
+ version,
+ 0,
+ );
+ if resource.is_null() {
+ log::error!("out of memory creating river_libinput_device_v1");
+ ffi::wl_client_post_no_memory(client);
+ return;
+ }
+
+ let obj = Box::into_raw(Box::new(LibinputDeviceObject {
+ device: self,
+ resource,
+ link: std::mem::zeroed(),
+ }));
+
+ ffi::wl_resource_set_implementation(
+ resource,
+ &LIBINPUT_DEVICE_INTERFACE as *const _ as *const _,
+ obj as *mut _,
+ Some(handle_device_object_destroy),
+ );
+
+ let objects_head = &mut self.objects as *mut ffi::wl_list as *mut WlList;
+ let link_ptr = &mut (*obj).link as *mut ffi::wl_list as *mut WlList;
+ crate::server::wl_list_insert(objects_head, link_ptr);
+
+ // Send libinput_device event
+ ffi::wl_resource_post_event(config_v1_resource, ffi::RIVER_LIBINPUT_CONFIG_V1_LIBINPUT_DEVICE, resource);
+
+ // Send corresponding river input device
+ let input_dev_objects = &mut (*self.parent_device).objects as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*input_dev_objects).next;
+ while curr != input_dev_objects {
+ let next = (*curr).next;
+ let input_dev_obj = crate::container_of!(curr, crate::input_device::InputDeviceObject, link);
+ if ffi::wl_resource_get_client((*input_dev_obj).resource) == client {
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_INPUT_DEVICE, (*input_dev_obj).resource);
+ }
+ curr = next;
+ }
+
+ // SendEvents support and defaults
+ let send_events_modes = ffi::libinput_device_config_send_events_get_modes(self.libinput);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SEND_EVENTS_SUPPORT, send_events_modes);
+ let send_events_default = ffi::libinput_device_config_send_events_get_default_mode(self.libinput);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SEND_EVENTS_DEFAULT, send_events_default);
+ let send_events_current = ffi::libinput_device_config_send_events_get_mode(self.libinput);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SEND_EVENTS_CURRENT, send_events_current);
+
+ // Tap support and defaults
+ let tap_finger_count = ffi::libinput_device_config_tap_get_finger_count(self.libinput);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_TAP_SUPPORT, tap_finger_count as i32);
+ if tap_finger_count > 0 {
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_TAP_DEFAULT, ffi::libinput_device_config_tap_get_default_enabled(self.libinput));
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_TAP_CURRENT, ffi::libinput_device_config_tap_get_enabled(self.libinput));
+ 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));
+ 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));
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DRAG_DEFAULT, ffi::libinput_device_config_tap_get_default_drag_enabled(self.libinput));
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DRAG_CURRENT, ffi::libinput_device_config_tap_get_drag_enabled(self.libinput));
+ 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));
+ 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));
+ }
+
+ // Three finger drag
+ let three_finger_drag_finger_count = ffi::libinput_device_config_3fg_drag_get_finger_count(self.libinput);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_THREE_FINGER_DRAG_SUPPORT, three_finger_drag_finger_count as i32);
+ if three_finger_drag_finger_count >= 3 {
+ 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));
+ 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));
+ }
+
+ // Calibration matrix
+ let has_matrix = ffi::libinput_device_config_calibration_has_matrix(self.libinput);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CALIBRATION_MATRIX_SUPPORT, has_matrix as i32);
+ if has_matrix != 0 {
+ let mut matrix = [0.0f32; 6];
+ ffi::libinput_device_config_calibration_get_default_matrix(self.libinput, matrix.as_mut_ptr());
+ let mut arr_default = ffi::wl_array {
+ size: std::mem::size_of_val(&matrix),
+ alloc: std::mem::size_of_val(&matrix),
+ data: matrix.as_mut_ptr() as *mut _,
+ };
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CALIBRATION_MATRIX_DEFAULT, &mut arr_default as *mut _);
+
+ ffi::libinput_device_config_calibration_get_matrix(self.libinput, matrix.as_mut_ptr());
+ let mut arr_current = ffi::wl_array {
+ size: std::mem::size_of_val(&matrix),
+ alloc: std::mem::size_of_val(&matrix),
+ data: matrix.as_mut_ptr() as *mut _,
+ };
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CALIBRATION_MATRIX_CURRENT, &mut arr_current as *mut _);
+ }
+
+ // Acceleration configs
+ let profiles = ffi::libinput_device_config_accel_get_profiles(self.libinput);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_PROFILES_SUPPORT, profiles);
+ if profiles != 0 {
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_PROFILE_DEFAULT, ffi::libinput_device_config_accel_get_default_profile(self.libinput));
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_PROFILE_CURRENT, ffi::libinput_device_config_accel_get_profile(self.libinput));
+
+ let default_speed = ffi::libinput_device_config_accel_get_default_speed(self.libinput);
+ let mut arr_default = ffi::wl_array {
+ size: std::mem::size_of::<f64>(),
+ alloc: std::mem::size_of::<f64>(),
+ data: &default_speed as *const f64 as *mut _,
+ };
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_SPEED_DEFAULT, &mut arr_default as *mut _);
+
+ let current_speed = ffi::libinput_device_config_accel_get_speed(self.libinput);
+ let mut arr_current = ffi::wl_array {
+ size: std::mem::size_of::<f64>(),
+ alloc: std::mem::size_of::<f64>(),
+ data: ¤t_speed as *const f64 as *mut _,
+ };
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_SPEED_CURRENT, &mut arr_current as *mut _);
+ }
+
+ // Natural scroll
+ let natural_scroll = ffi::libinput_device_config_scroll_has_natural_scroll(self.libinput);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_NATURAL_SCROLL_SUPPORT, natural_scroll as i32);
+ if natural_scroll != 0 {
+ 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);
+ 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);
+ }
+
+ // Left handed mode
+ let left_handed = ffi::libinput_device_config_left_handed_is_available(self.libinput);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_LEFT_HANDED_SUPPORT, left_handed as i32);
+ if left_handed != 0 {
+ 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);
+ 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);
+ }
+
+ // Click methods
+ let click_methods = ffi::libinput_device_config_click_get_methods(self.libinput);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CLICK_METHOD_SUPPORT, click_methods);
+ if click_methods != 0 {
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CLICK_METHOD_DEFAULT, ffi::libinput_device_config_click_get_default_method(self.libinput));
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CLICK_METHOD_CURRENT, ffi::libinput_device_config_click_get_method(self.libinput));
+ if (click_methods & ffi::libinput_config_click_method_LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER) != 0 {
+ 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));
+ 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));
+ }
+ }
+
+ // Middle mouse emulation
+ let middle_emulation = ffi::libinput_device_config_middle_emulation_is_available(self.libinput);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_MIDDLE_EMULATION_SUPPORT, middle_emulation as i32);
+ if middle_emulation != 0 {
+ 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));
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_MIDDLE_EMULATION_CURRENT, ffi::libinput_device_config_middle_emulation_get_enabled(self.libinput));
+ }
+
+ // Scroll methods
+ let scroll_methods = ffi::libinput_device_config_scroll_get_methods(self.libinput);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_METHOD_SUPPORT, scroll_methods);
+ if scroll_methods != 0 {
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_METHOD_DEFAULT, ffi::libinput_device_config_scroll_get_default_method(self.libinput));
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_METHOD_CURRENT, ffi::libinput_device_config_scroll_get_method(self.libinput));
+ if (scroll_methods & ffi::libinput_config_scroll_method_LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN) != 0 {
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_BUTTON_DEFAULT, ffi::libinput_device_config_scroll_get_default_button(self.libinput));
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_BUTTON_CURRENT, ffi::libinput_device_config_scroll_get_button(self.libinput));
+ 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));
+ 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));
+ }
+ }
+
+ // Disable While Typing (DWT)
+ let dwt = ffi::libinput_device_config_dwt_is_available(self.libinput);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DWT_SUPPORT, dwt as i32);
+ if dwt != 0 {
+ 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);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DWT_CURRENT, ffi::libinput_device_config_dwt_get_enabled(self.libinput) as u32);
+ }
+
+ // Disable While Trackpointing (DWTP)
+ let dwtp = ffi::libinput_device_config_dwtp_is_available(self.libinput);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DWTP_SUPPORT, dwtp as i32);
+ if dwtp != 0 {
+ 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);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DWTP_CURRENT, ffi::libinput_device_config_dwtp_get_enabled(self.libinput) as u32);
+ }
+
+ // Rotation
+ let rotation = ffi::libinput_device_config_rotation_is_available(self.libinput);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ROTATION_SUPPORT, rotation as i32);
+ if rotation != 0 {
+ 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);
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ROTATION_CURRENT, ffi::libinput_device_config_rotation_get_angle(self.libinput) as u32);
+ }
+
+ if version >= 2 {
+ ffi::wl_resource_post_event(resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DONE);
+ }
+ }
+}
+
+unsafe extern "C" fn handle_device_object_destroy(resource: *mut ffi::wl_resource) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if !obj.is_null() {
+ crate::server::wl_list_remove(&mut (*obj).link as *mut ffi::wl_list as *mut WlList);
+ let _ = Box::from_raw(obj);
+ }
+}
+
+static LIBINPUT_DEVICE_INTERFACE: ffi::river_libinput_device_v1_interface = ffi::river_libinput_device_v1_interface {
+ destroy: Some(device_destroy),
+ set_send_events: Some(device_set_send_events),
+ set_tap: Some(device_set_tap),
+ set_tap_button_map: Some(device_set_tap_button_map),
+ set_drag: Some(device_set_drag),
+ set_drag_lock: Some(device_set_drag_lock),
+ set_three_finger_drag: Some(device_set_three_finger_drag),
+ set_calibration_matrix: Some(device_set_calibration_matrix),
+ set_accel_profile: Some(device_set_accel_profile),
+ set_accel_speed: Some(device_set_accel_speed),
+ apply_accel_config: Some(device_apply_accel_config),
+ set_natural_scroll: Some(device_set_natural_scroll),
+ set_left_handed: Some(device_set_left_handed),
+ set_click_method: Some(device_set_click_method),
+ set_clickfinger_button_map: Some(device_set_clickfinger_button_map),
+ set_middle_emulation: Some(device_set_middle_emulation),
+ set_scroll_method: Some(device_set_scroll_method),
+ set_scroll_button: Some(device_set_scroll_button),
+ set_scroll_button_lock: Some(device_set_scroll_button_lock),
+ set_dwt: Some(device_set_dwt),
+ set_dwtp: Some(device_set_dwtp),
+ set_rotation: Some(device_set_rotation),
+};
+
+unsafe fn make_result(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ status: ffi::libinput_config_status,
+) -> bool {
+ let result_res = ffi::wl_resource_create(
+ client,
+ &ffi::river_libinput_result_v1_interface,
+ ffi::wl_resource_get_version(resource),
+ result_id,
+ );
+ if result_res.is_null() {
+ ffi::wl_client_post_no_memory(client);
+ return false;
+ }
+
+ match status {
+ ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => {
+ ffi::wl_resource_post_event(result_res, 0); // success
+ }
+ ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
+ ffi::wl_resource_post_event(result_res, 1); // unsupported
+ }
+ _ => {
+ ffi::wl_resource_post_event(result_res, 2); // invalid
+ }
+ }
+ ffi::wl_resource_destroy(result_res);
+ status == ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS
+}
+
+unsafe fn broadcast_update(dev: *mut LibinputDevice, opcode: u32, arg: u32) {
+ let objects_head = &mut (*dev).objects as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*objects_head).next;
+ while curr != objects_head {
+ let next = (*curr).next;
+ let obj = crate::container_of!(curr, LibinputDeviceObject, link);
+ ffi::wl_resource_post_event((*obj).resource, opcode, arg);
+ if ffi::wl_resource_get_version((*obj).resource) >= 2 {
+ ffi::wl_resource_post_event((*obj).resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DONE);
+ }
+ curr = next;
+ }
+}
+
+unsafe extern "C" fn device_destroy(_client: *mut ffi::wl_client, resource: *mut ffi::wl_resource) {
+ ffi::wl_resource_destroy(resource);
+}
+
+unsafe extern "C" fn device_set_send_events(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ mode: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_send_events_set_mode((*dev).libinput, mode);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_send_events_get_mode((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_SEND_EVENTS_CURRENT, current);
+ }
+}
+
+unsafe extern "C" fn device_set_tap(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ state: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_tap_set_enabled((*dev).libinput, state);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_tap_get_enabled((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_TAP_CURRENT, current);
+ }
+}
+
+unsafe extern "C" fn device_set_tap_button_map(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ button_map: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_tap_set_button_map((*dev).libinput, button_map);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_tap_get_button_map((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_TAP_BUTTON_MAP_CURRENT, current);
+ }
+}
+
+unsafe extern "C" fn device_set_drag(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ state: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_tap_set_drag_enabled((*dev).libinput, state);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_tap_get_drag_enabled((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_DRAG_CURRENT, current);
+ }
+}
+
+unsafe extern "C" fn device_set_drag_lock(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ state: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_tap_set_drag_lock_enabled((*dev).libinput, state);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_tap_get_drag_lock_enabled((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_DRAG_LOCK_CURRENT, current);
+ }
+}
+
+unsafe extern "C" fn device_set_three_finger_drag(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ state: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_3fg_drag_set_enabled((*dev).libinput, state);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_3fg_drag_get_enabled((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_THREE_FINGER_DRAG_CURRENT, current);
+ }
+}
+
+unsafe extern "C" fn device_set_calibration_matrix(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ matrix_arr: *mut ffi::wl_array,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ if (*matrix_arr).size != std::mem::size_of::<[f32; 6]>() {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_libinput_device_v1_error_RIVER_LIBINPUT_DEVICE_V1_ERROR_INVALID_ARG,
+ b"invalid calibration matrix\0".as_ptr() as *const _,
+ );
+ return;
+ }
+
+ let matrix_ptr = (*matrix_arr).data as *const f32;
+ let status = ffi::libinput_device_config_calibration_set_matrix((*dev).libinput, matrix_ptr);
+ if make_result(client, resource, result_id, status) {
+ let mut current = [0.0f32; 6];
+ ffi::libinput_device_config_calibration_get_matrix((*dev).libinput, current.as_mut_ptr());
+ let mut arr = ffi::wl_array {
+ size: std::mem::size_of_val(¤t),
+ alloc: std::mem::size_of_val(¤t),
+ data: current.as_mut_ptr() as *mut _,
+ };
+
+ let objects_head = &mut (*dev).objects as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*objects_head).next;
+ while curr != objects_head {
+ let next = (*curr).next;
+ let dest_obj = crate::container_of!(curr, LibinputDeviceObject, link);
+ ffi::wl_resource_post_event((*dest_obj).resource, ffi::RIVER_LIBINPUT_DEVICE_V1_CALIBRATION_MATRIX_CURRENT, &mut arr as *mut _);
+ if ffi::wl_resource_get_version((*dest_obj).resource) >= 2 {
+ ffi::wl_resource_post_event((*dest_obj).resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DONE);
+ }
+ curr = next;
+ }
+ }
+}
+
+unsafe extern "C" fn device_set_accel_profile(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ profile: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_accel_set_profile((*dev).libinput, profile);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_accel_get_profile((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_PROFILE_CURRENT, current);
+ }
+}
+
+unsafe extern "C" fn device_set_accel_speed(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ speed_arr: *mut ffi::wl_array,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ if (*speed_arr).size != std::mem::size_of::<f64>() {
+ ffi::wl_resource_post_error(
+ resource,
+ ffi::river_libinput_device_v1_error_RIVER_LIBINPUT_DEVICE_V1_ERROR_INVALID_ARG,
+ b"invalid accel speed\0".as_ptr() as *const _,
+ );
+ return;
+ }
+
+ let speed = *((*speed_arr).data as *const f64);
+ let status = ffi::libinput_device_config_accel_set_speed((*dev).libinput, speed);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_accel_get_speed((*dev).libinput);
+ let mut arr = ffi::wl_array {
+ size: std::mem::size_of::<f64>(),
+ alloc: std::mem::size_of::<f64>(),
+ data: ¤t as *const f64 as *mut _,
+ };
+
+ let objects_head = &mut (*dev).objects as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*objects_head).next;
+ while curr != objects_head {
+ let next = (*curr).next;
+ let dest_obj = crate::container_of!(curr, LibinputDeviceObject, link);
+ ffi::wl_resource_post_event((*dest_obj).resource, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_SPEED_CURRENT, &mut arr as *mut _);
+ if ffi::wl_resource_get_version((*dest_obj).resource) >= 2 {
+ ffi::wl_resource_post_event((*dest_obj).resource, ffi::RIVER_LIBINPUT_DEVICE_V1_DONE);
+ }
+ curr = next;
+ }
+ }
+}
+
+unsafe extern "C" fn device_apply_accel_config(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ config_res: *mut ffi::wl_resource,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let accel_config = ffi::wl_resource_get_user_data(config_res) as *mut crate::libinput_accel_config::LibinputAccelConfig;
+ let config = if !accel_config.is_null() {
+ (*accel_config).libinput
+ } else {
+ std::ptr::null_mut()
+ };
+
+ let result_res = ffi::wl_resource_create(
+ client,
+ &ffi::river_libinput_result_v1_interface,
+ ffi::wl_resource_get_version(resource),
+ result_id,
+ );
+ if result_res.is_null() {
+ ffi::wl_client_post_no_memory(client);
+ return;
+ }
+
+ if config.is_null() {
+ ffi::wl_resource_post_event(result_res, 2); // invalid
+ ffi::wl_resource_destroy(result_res);
+ return;
+ }
+
+ let status = ffi::libinput_device_config_accel_apply((*dev).libinput, config);
+
+ let success = match status {
+ ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_SUCCESS => {
+ ffi::wl_resource_post_event(result_res, 0); // success
+ true
+ }
+ ffi::libinput_config_status_LIBINPUT_CONFIG_STATUS_UNSUPPORTED => {
+ ffi::wl_resource_post_event(result_res, 1); // unsupported
+ false
+ }
+ _ => {
+ ffi::wl_resource_post_event(result_res, 2); // invalid
+ false
+ }
+ };
+
+ ffi::wl_resource_destroy(result_res);
+
+ if success {
+ let current = ffi::libinput_device_config_accel_get_profile((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_ACCEL_PROFILE_CURRENT, current);
+ }
+}
+
+unsafe extern "C" fn device_set_natural_scroll(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ state: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_scroll_set_natural_scroll_enabled((*dev).libinput, state as i32);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_scroll_get_natural_scroll_enabled((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_NATURAL_SCROLL_CURRENT, current as u32);
+ }
+}
+
+unsafe extern "C" fn device_set_left_handed(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ state: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_left_handed_set((*dev).libinput, state as i32);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_left_handed_get((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_LEFT_HANDED_CURRENT, current as u32);
+ }
+}
+
+unsafe extern "C" fn device_set_click_method(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ method: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_click_set_method((*dev).libinput, method);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_click_get_method((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_CLICK_METHOD_CURRENT, current);
+ }
+}
+
+unsafe extern "C" fn device_set_clickfinger_button_map(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ button_map: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_click_set_clickfinger_button_map((*dev).libinput, button_map);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_click_get_clickfinger_button_map((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_CLICKFINGER_BUTTON_MAP_CURRENT, current);
+ }
+}
+
+unsafe extern "C" fn device_set_middle_emulation(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ state: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_middle_emulation_set_enabled((*dev).libinput, state);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_middle_emulation_get_enabled((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_MIDDLE_EMULATION_CURRENT, current);
+ }
+}
+
+unsafe extern "C" fn device_set_scroll_method(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ method: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_scroll_set_method((*dev).libinput, method);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_scroll_get_method((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_METHOD_CURRENT, current);
+ }
+}
+
+unsafe extern "C" fn device_set_scroll_button(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ button: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_scroll_set_button((*dev).libinput, button);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_scroll_get_button((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_BUTTON_CURRENT, current);
+ }
+}
+
+unsafe extern "C" fn device_set_scroll_button_lock(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ state: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_scroll_set_button_lock((*dev).libinput, state);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_scroll_get_button_lock((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_SCROLL_BUTTON_LOCK_CURRENT, current);
+ }
+}
+
+unsafe extern "C" fn device_set_dwt(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ state: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_dwt_set_enabled((*dev).libinput, state);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_dwt_get_enabled((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_DWT_CURRENT, current as u32);
+ }
+}
+
+unsafe extern "C" fn device_set_dwtp(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ state: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_dwtp_set_enabled((*dev).libinput, state);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_dwtp_get_enabled((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_DWTP_CURRENT, current as u32);
+ }
+}
+
+unsafe extern "C" fn device_set_rotation(
+ client: *mut ffi::wl_client,
+ resource: *mut ffi::wl_resource,
+ result_id: u32,
+ angle: u32,
+) {
+ let obj = ffi::wl_resource_get_user_data(resource) as *mut LibinputDeviceObject;
+ if obj.is_null() { return; }
+ let dev = (*obj).device;
+
+ let status = ffi::libinput_device_config_rotation_set_angle((*dev).libinput, angle);
+ if make_result(client, resource, result_id, status) {
+ let current = ffi::libinput_device_config_rotation_get_angle((*dev).libinput);
+ broadcast_update(dev, ffi::RIVER_LIBINPUT_DEVICE_V1_ROTATION_CURRENT, current);
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 780802a..58be2a4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -15,6 +15,8 @@ mod output;
mod output_manager;
mod input_manager;
mod libinput_config;
+pub mod libinput_device;
+pub mod libinput_accel_config;
mod xkb_config;
mod idle_inhibit_manager;
mod lock_manager;
diff --git a/src/seat.rs b/src/seat.rs
index 8614c8c..63e179d 100644
--- a/src/seat.rs
+++ b/src/seat.rs
@@ -225,7 +225,7 @@ impl Seat {
let dev_type = ffi::river_wlr_input_device_get_type((*device).wlr_device);
match dev_type {
ffi::wlr_input_device_type_WLR_INPUT_DEVICE_KEYBOARD => {
- let keyboard = ffi::river_wlr_input_device_get_data((*device).wlr_device) as *mut crate::keyboard::Keyboard;
+ let keyboard = (*device).destroy_data as *mut crate::keyboard::Keyboard;
if !keyboard.is_null() {
(*keyboard).set_group();
if !(*keyboard).group.is_null() {
diff --git a/wrapper.h b/wrapper.h
index 2a030bd..35d94c7 100644
--- a/wrapper.h
+++ b/wrapper.h
@@ -97,6 +97,7 @@
#include "river-libinput-config-v1-protocol.h"
#include "river-xkb-config-v1-protocol.h"
#include "virtual-keyboard-unstable-v1-protocol.h"
+#include <wlr/backend/libinput.h>
// Custom FFI helpers defined in wlroots_log_wrapper.c
struct wlr_surface *river_scene_node_get_surface(struct wlr_scene_node *node);