git.lucas.co / cce-system-interface
system settings
git clone https://git.lucas.co/cce-system-interface.git

commite5bfb8e26764b306fa89e08d2ebc68dc1c497845
parent65c78832ff
authorLucas Galante <[email protected]>
date2026-05-26 21:18
Update system configuration and interface modules

 src/main.rs        |  62 ++++++++++++++++++--
 src/pages/input.rs | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 217 insertions(+), 12 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index cc16a3a..d4ac2b8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -685,6 +685,8 @@ impl SystemInterface {
         self.app.input.scroll_friction_spinbox.clear_children(); self.app.input.scroll_friction_spinbox.set_parent(None);
         self.app.input.pointer_friction_spinbox.clear_children(); self.app.input.pointer_friction_spinbox.set_parent(None);
         self.app.input.trackpad_friction_spinbox.clear_children(); self.app.input.trackpad_friction_spinbox.set_parent(None);
+        self.app.input.trackpoint_accel_speed_spinbox.clear_children(); self.app.input.trackpoint_accel_speed_spinbox.set_parent(None);
+        self.app.input.trackpoint_accel_profile_menu.clear_children(); self.app.input.trackpoint_accel_profile_menu.set_parent(None);
 
         for sb in &mut self.app.audio.sink_spinboxes {
             sb.clear_children();
@@ -771,16 +773,20 @@ impl SystemInterface {
                 link_parent_child(&mut self.page_root_container, &mut self.app.notifications.duration_spinbox);
             }
             Page::Input => {
-                self.page_sec_containers.resize_with(2, clear_ui::widget::Container::new);
+                self.page_sec_containers.resize_with(3, clear_ui::widget::Container::new);
                 link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[0]);
                 link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[1]);
+                link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[2]);
+                
+                link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.trackpoint_accel_speed_spinbox);
+                link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.trackpoint_accel_profile_menu);
                 
-                link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.rate_spinbox);
-                link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.delay_spinbox);
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.input.rate_spinbox);
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.input.delay_spinbox);
                 
-                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.input.scroll_friction_spinbox);
-                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.input.pointer_friction_spinbox);
-                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.input.trackpad_friction_spinbox);
+                link_parent_child(&mut self.page_sec_containers[2], &mut self.app.input.scroll_friction_spinbox);
+                link_parent_child(&mut self.page_sec_containers[2], &mut self.app.input.pointer_friction_spinbox);
+                link_parent_child(&mut self.page_sec_containers[2], &mut self.app.input.trackpad_friction_spinbox);
             }
             Page::Audio => {
                 self.page_sec_containers.resize_with(2, clear_ui::widget::Container::new);
@@ -1132,6 +1138,15 @@ impl SystemInterface {
             if self.app.input.trackpad_friction_spinbox.cursor_moved(lx, ly) {
                 changed = true;
             }
+            if self.app.input.dwtp_toggle.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.input.trackpoint_accel_speed_spinbox.cursor_moved(lx, ly) {
+                changed = true;
+            }
+            if self.app.input.trackpoint_accel_profile_menu.cursor_moved(lx, ly) {
+                changed = true;
+            }
         }
         if self.app.current_page == Page::Audio {
             for sb in &mut self.app.audio.sink_spinboxes {
@@ -1325,6 +1340,8 @@ impl SystemInterface {
                     if self.app.input.scroll_friction_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
                     if self.app.input.pointer_friction_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
                     if self.app.input.trackpad_friction_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if self.app.input.trackpoint_accel_speed_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if self.app.input.trackpoint_accel_profile_menu.hit_test(lx, ly) { clicked_any_focusable = true; }
                 }
                 Page::Notifications => {
                     if self.app.notifications.duration_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
@@ -1479,6 +1496,12 @@ impl SystemInterface {
             if sb.mouse_input(button, state, lx, ly) && sb.value != old {
                 actions.push(AppAction::Input(pages::input::InputMessage::ApplyTrackpadFriction));
             }
+            let sb = &mut self.app.input.trackpoint_accel_speed_spinbox;
+            if !sb.hit_test(lx, ly) { sb.unfocus(); }
+            let old = sb.value;
+            if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+                actions.push(AppAction::Input(pages::input::InputMessage::ApplyTrackpointAccelSpeed));
+            }
         }
         if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Notifications {
             let sb = &mut self.app.notifications.duration_spinbox;
@@ -1509,6 +1532,19 @@ impl SystemInterface {
             if toggle.take_click() {
                 actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialTrackpad));
             }
+            let toggle = &mut self.app.input.dwtp_toggle;
+            toggle.mouse_input(button, state, lx, ly);
+            if toggle.take_click() {
+                actions.push(AppAction::Input(pages::input::InputMessage::ToggleDwtp));
+            }
+            let menu = &mut self.app.input.trackpoint_accel_profile_menu;
+            if state == clear_ui::widget::ElementState::Pressed && !menu.hit_test(lx, ly) { menu.unfocus(); }
+            if menu.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
+            if state == clear_ui::widget::ElementState::Pressed && menu.take_change() {
+                actions.push(AppAction::Input(pages::input::InputMessage::ApplyTrackpointAccelProfile(menu.selected)));
+            }
         }
         if self.app.current_page == Page::Notifications {
             let toggle = &mut self.app.notifications.enable_toggle;
@@ -1769,6 +1805,15 @@ impl SystemInterface {
             let lx = self.cursor_x / s;
             let ly = self.cursor_y / s + self.scroll_y;
             
+            if self.app.current_page == Page::Input {
+                let input = &self.app.input;
+                if lx >= input.trackpad_x && lx <= input.trackpad_x + input.trackpad_w
+                    && ly >= input.trackpad_y && ly <= input.trackpad_y + input.trackpad_h
+                {
+                    return true;
+                }
+            }
+
             if self.app.current_page == Page::Typefaces {
                 let tf = &mut self.app.typeface;
                 if tf.list_box.mouse_wheel(delta, lx, ly) {
@@ -1972,6 +2017,11 @@ impl SystemInterface {
                 self.needs_rebuild = true;
                 return true;
             }
+            if self.app.input.trackpoint_accel_speed_spinbox.keyboard_input(event) {
+                self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyTrackpointAccelSpeed));
+                self.needs_rebuild = true;
+                return true;
+            }
         }
         if self.app.current_page == Page::Audio {
             let mut actions = Vec::new();
diff --git a/src/pages/input.rs b/src/pages/input.rs
index 247658c..c3bbcc4 100644
--- a/src/pages/input.rs
+++ b/src/pages/input.rs
@@ -3,7 +3,7 @@ use std::io::Write;
 
 use crate::app::PageContent;
 use clear_ui::layout::Section;
-use clear_ui::widget::{Spinbox, Toggle};
+use clear_ui::widget::{Dropdown, Spinbox, Toggle, Widget};
 
 const CONFIG_PATH: &str = "/home/lsgalante/.config/clearwm/config.toml";
 const CLEARWM_SOCK: &str = "/tmp/clearwm.sock";
@@ -33,6 +33,10 @@ pub struct InputState {
     pub tap_toggle: Toggle,
     pub keybinds: Vec<Keybind>,
     pub fingers: Vec<Finger>,
+    pub trackpad_x: f32,
+    pub trackpad_y: f32,
+    pub trackpad_w: f32,
+    pub trackpad_h: f32,
 
     // Inertial settings
     pub inertial_scroll: bool,
@@ -48,6 +52,15 @@ pub struct InputState {
     pub pointer_friction_spinbox: Spinbox,
     pub trackpad_toggle: Toggle,
     pub trackpad_friction_spinbox: Spinbox,
+
+    // Trackpoint settings
+    pub dwtp: bool,
+    pub trackpoint_accel_speed: f32,
+    pub trackpoint_accel_profile: String,
+
+    pub dwtp_toggle: Toggle,
+    pub trackpoint_accel_speed_spinbox: Spinbox,
+    pub trackpoint_accel_profile_menu: Dropdown,
 }
 
 impl Default for InputState {
@@ -61,6 +74,10 @@ impl Default for InputState {
             tap_toggle: Toggle::new().with_label("Tap to Click"),
             keybinds: Vec::new(),
             fingers: Vec::new(),
+            trackpad_x: 0.0,
+            trackpad_y: 0.0,
+            trackpad_w: 0.0,
+            trackpad_h: 0.0,
 
             inertial_scroll: true,
             scroll_friction: 90,
@@ -75,10 +92,25 @@ impl Default for InputState {
             pointer_friction_spinbox: Spinbox::new(95, 50, 99, 1).with_label("Pointer Friction").with_unit("%"),
             trackpad_toggle: Toggle::new().with_label("Inertial Pointer (Trackpad)"),
             trackpad_friction_spinbox: Spinbox::new(95, 50, 99, 1).with_label("Trackpad Friction").with_unit("%"),
+
+            dwtp: true,
+            trackpoint_accel_speed: 0.5,
+            trackpoint_accel_profile: "flat".to_string(),
+
+            dwtp_toggle: Toggle::new().with_label("Disable While Trackpointing"),
+            trackpoint_accel_speed_spinbox: Spinbox::new(5, -10, 10, 1).with_label("Acceleration Speed").with_decimals(1),
+            trackpoint_accel_profile_menu: Dropdown::new(vec!["flat".to_string(), "adaptive".to_string()], 0).with_label("Acceleration Profile"),
         }
     }
 }
 
+impl InputState {
+    pub fn is_over_trackpad(&self, lx: f32, ly: f32) -> bool {
+        lx >= self.trackpad_x && lx <= self.trackpad_x + self.trackpad_w
+            && ly >= self.trackpad_y && ly <= self.trackpad_y + self.trackpad_h
+    }
+}
+
 #[derive(Debug, Clone)]
 pub enum InputMessage {
     ToggleTapToClick,
@@ -92,6 +124,10 @@ pub enum InputMessage {
     ApplyPointerFriction,
     ToggleInertialTrackpad,
     ApplyTrackpadFriction,
+
+    ToggleDwtp,
+    ApplyTrackpointAccelSpeed,
+    ApplyTrackpointAccelProfile(usize),
 }
 
 pub fn read_input_config() -> InputState {
@@ -107,6 +143,13 @@ pub fn read_input_config() -> InputState {
     let inertial_trackpad = parse_bool_from_default(&content, "inertial_trackpad", false);
     let trackpad_friction = parse_u16_key(&content, "trackpad_friction", 95);
 
+    let dwtp = parse_bool_from_default(&content, "dwtp", true);
+    let trackpoint_accel_speed = parse_f32_key(&content, "trackpoint_accel_speed", 0.5);
+    let trackpoint_accel_profile = parse_string_key(&content, "trackpoint_accel_profile", "flat");
+
+    let speed_val = (trackpoint_accel_speed * 10.0).round() as i32;
+    let profile_idx = if trackpoint_accel_profile == "adaptive" { 1 } else { 0 };
+
     InputState {
         tap_to_click: tap,
         repeat_rate: rate,
@@ -116,6 +159,10 @@ pub fn read_input_config() -> InputState {
         tap_toggle: Toggle::new().with_label("Tap to Click"),
         keybinds: parse_keybinds(&content),
         fingers: Vec::new(),
+        trackpad_x: 0.0,
+        trackpad_y: 0.0,
+        trackpad_w: 0.0,
+        trackpad_h: 0.0,
 
         inertial_scroll,
         scroll_friction,
@@ -130,6 +177,14 @@ pub fn read_input_config() -> InputState {
         pointer_friction_spinbox: Spinbox::new(pointer_friction as i32, 50, 99, 1).with_label("Pointer Friction").with_unit("%"),
         trackpad_toggle: Toggle::new().with_label("Inertial Pointer (Trackpad)"),
         trackpad_friction_spinbox: Spinbox::new(trackpad_friction as i32, 50, 99, 1).with_label("Trackpad Friction").with_unit("%"),
+
+        dwtp,
+        trackpoint_accel_speed,
+        trackpoint_accel_profile,
+
+        dwtp_toggle: Toggle::new().with_label("Disable While Trackpointing"),
+        trackpoint_accel_speed_spinbox: Spinbox::new(speed_val, -10, 10, 1).with_label("Acceleration Speed").with_decimals(1),
+        trackpoint_accel_profile_menu: Dropdown::new(vec!["flat".to_string(), "adaptive".to_string()], profile_idx).with_label("Acceleration Profile"),
     }
 }
 
@@ -154,6 +209,20 @@ fn parse_u16_key(content: &str, key: &str, default: u16) -> u16 {
         .unwrap_or(default)
 }
 
+fn parse_f32_key(content: &str, key: &str, default: f32) -> f32 {
+    content.lines().find(|l| l.trim().starts_with(key))
+        .and_then(|l| l.split('=').nth(1))
+        .and_then(|v| v.trim().parse::<f32>().ok())
+        .unwrap_or(default)
+}
+
+fn parse_string_key(content: &str, key: &str, default: &str) -> String {
+    content.lines().find(|l| l.trim().starts_with(key))
+        .and_then(|l| l.split('=').nth(1))
+        .map(|v| v.trim().trim_matches('"').to_string())
+        .unwrap_or_else(|| default.to_string())
+}
+
 fn parse_keybinds(content: &str) -> Vec<Keybind> {
     let mut keybinds = Vec::new();
     let mut current: Option<Keybind> = None;
@@ -200,7 +269,8 @@ fn write_config_value(key: &str, value: &str) {
         .join("\n");
 
     if !found {
-        let section = if key == "tap_to_click" {
+        let section = if key == "tap_to_click" || key == "dwtp"
+                || key == "trackpoint_accel_speed" || key == "trackpoint_accel_profile" {
             "[input]"
         } else if key == "inertial_scroll" || key == "scroll_friction"
                || key == "inertial_pointer" || key == "pointer_friction"
@@ -260,12 +330,17 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_foc
     sec.widget(&mut pc, &mut state.tap_toggle, 14.0, toggle_w, toggle_h);
     sec.spacing(8.0);
 
-    // Centered trackpad visualizer box
+    // Left-aligned trackpad visualizer box
     let pad_w = 280.0;
     let pad_h = 140.0;
-    let pad_x = sec.ax((cw - 16.0 - pad_w) / 2.0);
+    let pad_x = sec.ax(14.0);
     let pad_y = sec.ay();
 
+    state.trackpad_x = pad_x;
+    state.trackpad_y = pad_y;
+    state.trackpad_w = pad_w;
+    state.trackpad_h = pad_h;
+
     // Background of trackpad: sleek dark translucent blue/grey
     pc.rect([0.11, 0.11, 0.16, 0.85], pad_x, pad_y, pad_w, pad_h);
 
@@ -296,6 +371,20 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_foc
     sec.spacing(pad_h + 12.0);
     y = sec.finish(&mut pc);
 
+    // ── Trackpoint ──
+    let mut sec = Section::new(&mut pc, cx, y, cw, "Trackpoint");
+
+    state.dwtp_toggle.set_toggled(state.dwtp);
+    sec.widget(&mut pc, &mut state.dwtp_toggle, 14.0, toggle_w, toggle_h);
+    sec.spacing(12.0);
+
+    sec.widget(&mut pc, &mut state.trackpoint_accel_speed_spinbox, 14.0, 200.0, 26.0);
+    sec.spacing(12.0);
+
+    sec.widget(&mut pc, &mut state.trackpoint_accel_profile_menu, 14.0, 200.0, 26.0);
+    sec.spacing(8.0);
+    y = sec.finish_focused(&mut pc, sec_focused.get(0).copied().unwrap_or(false));
+
     // ── Keyboard ──
     let mut sec = Section::new(&mut pc, cx, y, cw, "Keyboard");
 
@@ -304,7 +393,7 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_foc
 
     sec.widget(&mut pc, &mut state.delay_spinbox, 14.0, 200.0, 26.0);
     sec.spacing(8.0);
-    y = sec.finish_focused(&mut pc, sec_focused.get(0).copied().unwrap_or(false));
+    y = sec.finish_focused(&mut pc, sec_focused.get(1).copied().unwrap_or(false));
 
     // ── Inertial Input ──
     let mut sec = Section::new(&mut pc, cx, y, cw, "Inertial Input");
@@ -330,7 +419,7 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_foc
     sec.widget(&mut pc, &mut state.trackpad_friction_spinbox, 14.0, 200.0, 26.0);
     sec.spacing(8.0);
 
-    y = sec.finish_focused(&mut pc, sec_focused.get(1).copied().unwrap_or(false));
+    y = sec.finish_focused(&mut pc, sec_focused.get(2).copied().unwrap_or(false));
 
     // ── Keybindings ──
     let mut sec = Section::new(&mut pc, cx, y, cw, "Keyboard Bindings");
@@ -353,6 +442,26 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_foc
     }
     sec.finish(&mut pc);
 
+    // Filter out base text items covered by any open popover to prevent showing through
+    let mut popovers = Vec::new();
+    if state.trackpoint_accel_profile_menu.open {
+        let (x, y, w, h) = state.trackpoint_accel_profile_menu.rect();
+        popovers.push((x, y + h, w, state.trackpoint_accel_profile_menu.options.len() as f32 * 24.0));
+    }
+
+    if !popovers.is_empty() {
+        pc.texts.retain(|(_, _, tx, ty, _, _)| {
+            for &(px, py, pw, ph) in &popovers {
+                if *tx >= px && *tx <= px + pw && *ty >= py && *ty <= py + ph {
+                    return false;
+                }
+            }
+            true
+        });
+    }
+
+    state.trackpoint_accel_profile_menu.render_popover(&mut pc);
+
     pc
 }
 
@@ -396,6 +505,24 @@ pub fn update(state: &mut InputState, msg: InputMessage) {
             state.trackpad_friction = friction;
             write_config_value("trackpad_friction", &friction.to_string());
         }
+        InputMessage::ToggleDwtp => {
+            state.dwtp = !state.dwtp;
+            write_config_value("dwtp", &state.dwtp.to_string());
+            send_ipc_command(&format!("input dwtp {}", state.dwtp));
+        }
+        InputMessage::ApplyTrackpointAccelSpeed => {
+            let val = state.trackpoint_accel_speed_spinbox.value as f32 / 10.0;
+            state.trackpoint_accel_speed = val;
+            write_config_value("trackpoint_accel_speed", &val.to_string());
+            send_ipc_command(&format!("input trackpoint-accel-speed {}", val));
+        }
+        InputMessage::ApplyTrackpointAccelProfile(idx) => {
+            let profile = if idx == 1 { "adaptive" } else { "flat" };
+            state.trackpoint_accel_profile = profile.to_string();
+            state.trackpoint_accel_profile_menu.selected = idx;
+            write_config_value("trackpoint_accel_profile", &format!("\"{}\"", profile));
+            send_ipc_command(&format!("input trackpoint-accel-profile {}", profile));
+        }
         InputMessage::Refreshed(new) => {
             let fingers = state.fingers.clone();
             *state = new;
@@ -406,3 +533,31 @@ pub fn update(state: &mut InputState, msg: InputMessage) {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_is_over_trackpad() {
+        let mut state = InputState::default();
+        state.trackpad_x = 100.0;
+        state.trackpad_y = 200.0;
+        state.trackpad_w = 300.0;
+        state.trackpad_h = 150.0;
+
+        // Inside
+        assert!(state.is_over_trackpad(150.0, 250.0));
+        assert!(state.is_over_trackpad(100.0, 200.0));
+        assert!(state.is_over_trackpad(400.0, 350.0));
+
+        // Outside X
+        assert!(!state.is_over_trackpad(99.0, 250.0));
+        assert!(!state.is_over_trackpad(401.0, 250.0));
+
+        // Outside Y
+        assert!(!state.is_over_trackpad(150.0, 199.0));
+        assert!(!state.is_over_trackpad(150.0, 351.0));
+    }
+}
+