system settings
git clone https://git.lucas.co/cce-system-interface.git
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));
+ }
+}
+