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

commit05d60a1bea6747c23b50bae3afd5cbbeba2e1a0c
parent2cf8d9eb8a
authorLucas Galante <[email protected]>
date2026-06-15 12:49
feat: parse and apply touchpad/mouse input settings in monolithic server

 src/server/config.rs          | 28 ++++++++++++++-
 src/server/input_device.rs    |  5 ++-
 src/server/libinput_device.rs | 81 +++++++++++++++++++++++++++++++++++++++++++
 src/server/window_manager.rs  | 19 ++++++++++
 4 files changed, 131 insertions(+), 2 deletions(-)

diff --git a/src/server/config.rs b/src/server/config.rs
index fde12a7..74456c1 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -177,6 +177,18 @@ pub struct InputDeviceConfigRule {
     pub scroll_factor: Option<f64>,
 }
 
+#[derive(Debug, Deserialize, Clone, Default)]
+pub struct InputConfig {
+    pub tap_to_click: Option<bool>,
+    pub accel_speed: Option<f64>,
+    pub accel_profile: Option<String>,
+    pub natural_scroll: Option<bool>,
+    pub dwt: Option<bool>,
+    pub dwtp: Option<bool>,
+    pub trackpoint_accel_speed: Option<f64>,
+    pub trackpoint_accel_profile: Option<String>,
+}
+
 #[derive(Debug, Deserialize)]
 pub struct Config {
     #[serde(default)]
@@ -197,6 +209,8 @@ pub struct Config {
     pub output: Option<OutputConfig>,
     #[serde(default)]
     pub device: Vec<InputDeviceConfigRule>,
+    #[serde(default)]
+    pub input: Option<InputConfig>,
 }
 
 #[derive(Debug, Deserialize)]
@@ -607,7 +621,11 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
     }
 
     state.input_rules = config.device.clone();
-    unsafe { state.apply_input_rules(); }
+    state.input_config = config.input.clone().unwrap_or_default();
+    unsafe {
+        state.apply_input_rules();
+        state.apply_input_config();
+    }
 
     state.keybinds.clear();
     for kb in &config.keybind {
@@ -707,6 +725,14 @@ mod tests {
             println!("TEST_WM_STARTUP: {:?}", server.wm.startup);
             println!("TEST_WM_PATH: {:?}", std::env::var("PATH"));
             assert!(!server.wm.startup.is_empty(), "Startup list should not be empty!");
+            assert_eq!(server.wm.input_config.tap_to_click, Some(true));
+            assert_eq!(server.wm.input_config.accel_speed, Some(0.2));
+            assert_eq!(server.wm.input_config.accel_profile, Some("adaptive".to_string()));
+            assert_eq!(server.wm.input_config.natural_scroll, Some(true));
+            assert_eq!(server.wm.input_config.dwt, Some(true));
+            assert_eq!(server.wm.input_config.dwtp, Some(true));
+            assert_eq!(server.wm.input_config.trackpoint_accel_speed, Some(0.6));
+            assert_eq!(server.wm.input_config.trackpoint_accel_profile, Some("flat".to_string()));
         }
     }
 }
diff --git a/src/server/input_device.rs b/src/server/input_device.rs
index bc8e8eb..c3dd1b6 100644
--- a/src/server/input_device.rs
+++ b/src/server/input_device.rs
@@ -89,7 +89,10 @@ impl InputDevice {
                 std::ptr::null_mut()
             };
             if !handle.is_null() {
-                (*device).libinput = Some(crate::libinput_device::LibinputDevice::init(device, handle));
+                let libinput_dev = crate::libinput_device::LibinputDevice::init(device, handle);
+                let wm = &(*(*seat).server).wm;
+                libinput_dev.apply_config(&wm.input_config);
+                (*device).libinput = Some(libinput_dev);
             }
 
             let dev_type = ffi::river_wlr_input_device_get_type(wlr_device);
diff --git a/src/server/libinput_device.rs b/src/server/libinput_device.rs
index d5642c4..d0e0b0d 100644
--- a/src/server/libinput_device.rs
+++ b/src/server/libinput_device.rs
@@ -49,6 +49,87 @@ impl LibinputDevice {
         dev
     }
 
+    pub unsafe fn apply_config(&self, config: &crate::config::InputConfig) {
+        let handle = self.libinput;
+        if handle.is_null() {
+            return;
+        }
+
+        // Tap to click
+        if let Some(tap) = config.tap_to_click {
+            if ffi::libinput_device_config_tap_get_finger_count(handle) > 0 {
+                let state = if tap { 1 } else { 0 };
+                ffi::libinput_device_config_tap_set_enabled(handle, state);
+            }
+        }
+
+        // Natural scroll
+        if let Some(natural) = config.natural_scroll {
+            if ffi::libinput_device_config_scroll_has_natural_scroll(handle) != 0 {
+                let state = if natural { 1 } else { 0 };
+                ffi::libinput_device_config_scroll_set_natural_scroll_enabled(handle, state);
+            }
+        }
+
+        // Dwt (Disable while typing)
+        if let Some(dwt) = config.dwt {
+            if ffi::libinput_device_config_dwt_is_available(handle) != 0 {
+                let state = if dwt { 1 } else { 0 };
+                ffi::libinput_device_config_dwt_set_enabled(handle, state);
+            }
+        }
+
+        // Dwtp (Disable while trackpointing)
+        if let Some(dwtp) = config.dwtp {
+            if ffi::libinput_device_config_dwtp_is_available(handle) != 0 {
+                let state = if dwtp { 1 } else { 0 };
+                ffi::libinput_device_config_dwtp_set_enabled(handle, state);
+            }
+        }
+
+        // Check if device name contains "trackpoint" (case-insensitive)
+        let name_ptr = ffi::river_wlr_input_device_get_name((*self.parent_device).wlr_device);
+        let name = if !name_ptr.is_null() {
+            std::ffi::CStr::from_ptr(name_ptr).to_string_lossy().to_lowercase()
+        } else {
+            String::new()
+        };
+        let is_trackpoint = name.contains("trackpoint");
+
+        // Acceleration Speed
+        let speed = if is_trackpoint {
+            config.trackpoint_accel_speed.or(config.accel_speed)
+        } else {
+            config.accel_speed
+        };
+        if let Some(s) = speed {
+            if ffi::libinput_device_config_accel_get_profiles(handle) != 0 {
+                ffi::libinput_device_config_accel_set_speed(handle, s);
+            }
+        }
+
+        // Acceleration Profile
+        let profile_str = if is_trackpoint {
+            config.trackpoint_accel_profile.as_ref().or(config.accel_profile.as_ref())
+        } else {
+            config.accel_profile.as_ref()
+        };
+        if let Some(ref p_str) = profile_str {
+            if ffi::libinput_device_config_accel_get_profiles(handle) != 0 {
+                let profile = match p_str.as_str() {
+                    "flat" => Some(ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT),
+                    "adaptive" => Some(ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE),
+                    "none" => Some(ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_NONE),
+                    "custom" => Some(ffi::libinput_config_accel_profile_LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM),
+                    _ => None,
+                };
+                if let Some(p) = profile {
+                    ffi::libinput_device_config_accel_set_profile(handle, p);
+                }
+            }
+        }
+    }
+
     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;
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 1f3a741..c705c96 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -68,6 +68,7 @@ pub struct WindowManager {
     pub status_sender: Option<crate::status_server::StatusSender>,
     pub output_scale: f32,
     pub input_rules: Vec<crate::config::InputDeviceConfigRule>,
+    pub input_config: crate::config::InputConfig,
 }
 
 impl WindowManager {
@@ -79,6 +80,7 @@ impl WindowManager {
         self.sent.output_config = std::ptr::null_mut();
         self.output_scale = 1.0;
         self.input_rules = Vec::new();
+        self.input_config = crate::config::InputConfig::default();
         Ok(())
     }
 
@@ -121,6 +123,7 @@ impl WindowManager {
         self.startup = Vec::new();
         self.status_sender = None;
         self.input_rules = Vec::new();
+        self.input_config = crate::config::InputConfig::default();
 
         ffi::wl_list_init(&mut self.sent.outputs);
         ffi::wl_list_init(&mut self.sent.seats);
@@ -1630,6 +1633,22 @@ impl WindowManager {
             curr = next;
         }
     }
+
+    pub unsafe fn apply_input_config(&mut self) {
+        if self.server.is_null() {
+            return;
+        }
+        let devices_head = &mut (*self.server).input_manager.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 let Some(ref mut libinput) = (*device).libinput {
+                libinput.apply_config(&self.input_config);
+            }
+            curr = next;
+        }
+    }
 }
 
 unsafe extern "C" fn handle_ipc_timer(data: *mut std::ffi::c_void) -> std::os::raw::c_int {