diff --git a/src/config/handler.rs b/src/config/handler.rs index a9a54e48..6da5c2d5 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -478,7 +478,7 @@ impl ConfigProxyHandler { } else { Some(self.get_keymap(keymap)?) }; - dev.set_keymap(map); + dev.set_keymap(&self.state, map); Ok(()) } @@ -532,13 +532,13 @@ impl ConfigProxyHandler { ) -> Result<(), CphError> { let dev = self.get_device_handler_data(input_device)?; let output = self.get_output_node(connector)?; - dev.set_output(Some(&output.global)); + dev.set_output(&self.state, Some(&output.global)); Ok(()) } fn handle_remove_input_mapping(&self, input_device: InputDevice) -> Result<(), CphError> { let dev = self.get_device_handler_data(input_device)?; - dev.set_output(None); + dev.set_output(&self.state, None); Ok(()) } @@ -788,7 +788,7 @@ impl ConfigProxyHandler { Some(self.get_seat(seat)?) }; let dev = self.get_device_handler_data(device)?; - dev.set_seat(seat); + dev.set_seat(&self.state, seat); Ok(()) } @@ -798,7 +798,7 @@ impl ConfigProxyHandler { left_handed: bool, ) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.set_left_handed(left_handed); + dev.set_left_handed(&self.state, left_handed); Ok(()) } @@ -813,31 +813,31 @@ impl ConfigProxyHandler { ACCEL_PROFILE_ADAPTIVE => InputDeviceAccelProfile::Adaptive, _ => return Err(CphError::UnknownAccelProfile(accel_profile)), }; - dev.set_accel_profile(profile); + dev.set_accel_profile(&self.state, profile); Ok(()) } fn handle_set_accel_speed(&self, device: InputDevice, speed: f64) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.set_accel_speed(speed); + dev.set_accel_speed(&self.state, speed); Ok(()) } fn handle_set_px_per_wheel_scroll(&self, device: InputDevice, px: f64) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.set_px_per_scroll_wheel(px); + dev.set_px_per_scroll_wheel(&self.state, px); Ok(()) } fn handle_set_tap_enabled(&self, device: InputDevice, enabled: bool) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.set_tap_enabled(enabled); + dev.set_tap_enabled(&self.state, enabled); Ok(()) } fn handle_set_drag_enabled(&self, device: InputDevice, enabled: bool) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.set_drag_enabled(enabled); + dev.set_drag_enabled(&self.state, enabled); Ok(()) } @@ -847,7 +847,7 @@ impl ConfigProxyHandler { enabled: bool, ) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.set_natural_scrolling_enabled(enabled); + dev.set_natural_scrolling_enabled(&self.state, enabled); Ok(()) } @@ -857,7 +857,7 @@ impl ConfigProxyHandler { enabled: bool, ) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.set_drag_lock_enabled(enabled); + dev.set_drag_lock_enabled(&self.state, enabled); Ok(()) } @@ -867,7 +867,7 @@ impl ConfigProxyHandler { matrix: [[f64; 2]; 2], ) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.set_transform_matrix(matrix); + dev.set_transform_matrix(&self.state, matrix); Ok(()) } @@ -877,7 +877,7 @@ impl ConfigProxyHandler { matrix: [[f32; 3]; 2], ) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.set_calibration_matrix(matrix); + dev.set_calibration_matrix(&self.state, matrix); Ok(()) } @@ -893,7 +893,7 @@ impl ConfigProxyHandler { CLICK_METHOD_CLICKFINGER => InputDeviceClickMethod::Clickfinger, _ => return Err(CphError::UnknownClickMethod(click_method)), }; - dev.set_click_method(method); + dev.set_click_method(&self.state, method); Ok(()) } @@ -903,7 +903,7 @@ impl ConfigProxyHandler { enabled: bool, ) -> Result<(), CphError> { let dev = self.get_device_handler_data(device)?; - dev.set_middle_button_emulation_enabled(enabled); + dev.set_middle_button_emulation_enabled(&self.state, enabled); Ok(()) } diff --git a/src/control_center.rs b/src/control_center.rs index 0d332ccb..f698b992 100644 --- a/src/control_center.rs +++ b/src/control_center.rs @@ -2,7 +2,7 @@ use { crate::{ control_center::{ cc_color_management::ColorManagementPane, cc_compositor::CompositorPane, - cc_gpus::GpusPane, cc_idle::IdlePane, cc_outputs::OutputsPane, + cc_gpus::GpusPane, cc_idle::IdlePane, cc_input::InputPane, cc_outputs::OutputsPane, cc_xwayland::XwaylandPane, }, egui_adapter::egui_platform::{ @@ -38,6 +38,7 @@ mod cc_color_management; mod cc_compositor; mod cc_gpus; mod cc_idle; +mod cc_input; mod cc_outputs; mod cc_sidebar; mod cc_xwayland; @@ -79,6 +80,7 @@ bitflags! { CCI_XWAYLAND, CCI_OUTPUTS, CCI_GPUS, + CCI_INPUT, } pub struct ControlCenter { @@ -125,6 +127,7 @@ enum PaneType { Xwayland(XwaylandPane), Outputs(Box), GPUs(GpusPane), + Input(InputPane), } struct CcBehavior<'a> { @@ -149,6 +152,7 @@ impl Pane { PaneType::Xwayland(v) => v.title(res), PaneType::Outputs(v) => v.title(res), PaneType::GPUs(v) => v.title(res), + PaneType::Input(v) => v.title(res), } } @@ -160,6 +164,7 @@ impl Pane { PaneType::Xwayland(p) => p.show(behavior, ui), PaneType::Outputs(p) => p.show(&mut self.ps, ui), PaneType::GPUs(p) => p.show(ui), + PaneType::Input(p) => p.show(&mut self.ps, ui), } } } @@ -173,6 +178,7 @@ impl PaneType { PaneType::Xwayland(_) => CCI_XWAYLAND, PaneType::Outputs(_) => CCI_OUTPUTS, PaneType::GPUs(_) => CCI_GPUS, + PaneType::Input(_) => CCI_INPUT, } } } @@ -442,7 +448,6 @@ fn tip(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui)) { icon_label(ICON_INFO).ui(ui).on_hover_ui(add_contents); } -#[expect(dead_code)] fn text_edit(ui: &mut Ui, v: &mut dyn TextBuffer) -> Response { TextEdit::singleline(v) .clip_text(false) @@ -564,7 +569,6 @@ fn combo_box_ui( }); } -#[expect(dead_code)] fn drag_value( ui: &mut Ui, name: &str, diff --git a/src/control_center/cc_input.rs b/src/control_center/cc_input.rs new file mode 100644 index 00000000..40ed942f --- /dev/null +++ b/src/control_center/cc_input.rs @@ -0,0 +1,679 @@ +use { + crate::{ + backend::{InputDeviceCapability, InputDeviceId}, + control_center::{ + ControlCenterInner, GridExt, PaneState, bool, bool_ui, combo_box, combo_box_ui, + drag_value, drag_value_ui, grid, grid_label, grid_label_ui, label, text_edit, tip, + }, + egui_adapter::egui_platform::icons::ICON_PENDING, + ifs::{ + wl_output::WlOutputGlobal, + wl_seat::{SeatId, WlSeatGlobal}, + }, + kbvm::KbvmMap, + state::{DeviceHandlerData, State}, + utils::{errorfmt::ErrorFmt, static_text::StaticText}, + }, + ahash::AHashMap, + egui::{ + CollapsingHeader, ComboBox, DragValue, Event, Grid, Id, TextBuffer, TextFormat, Ui, + UiBuilder, ViewportCommand, Widget, emath::Numeric, text::LayoutJob, + }, + isnt::std_1::string::IsntStringExt, + jay_config::keyboard::syms::KeySym, + kbvm::Keysym, + linearize::LinearizeExt, + rand::random, + std::{mem, rc::Rc}, +}; + +pub struct InputPane { + state: Rc, + paste_requested: Option, + keymaps: AHashMap, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +enum Key { + Seat(SeatId), + Dev(InputDeviceId), +} + +struct KeymapState { + seed: u64, + rules_default: bool, + rules: String, + model_default: bool, + model: String, + layouts: String, + variants: String, + options: String, + backup: Option>, + pointer_revert_key: Keysym, + pointer_revert_key_str: Option, + unknown_pointer_revert_key: bool, +} + +impl Default for KeymapState { + fn default() -> Self { + Self { + seed: random(), + rules_default: true, + rules: Default::default(), + model_default: true, + model: Default::default(), + layouts: Default::default(), + variants: Default::default(), + options: Default::default(), + backup: Default::default(), + pointer_revert_key: Default::default(), + pointer_revert_key_str: None, + unknown_pointer_revert_key: false, + } + } +} + +impl ControlCenterInner { + pub fn create_input_pane(self: &Rc) -> InputPane { + InputPane { + state: self.state.clone(), + paste_requested: Default::default(), + keymaps: Default::default(), + } + } +} + +impl InputPane { + pub fn title(&self, res: &mut String) { + res.push_str("Input"); + } + + pub fn show(&mut self, ps: &mut PaneState, ui: &mut Ui) { + let state = self.state.clone(); + let seats = state.globals.seats.lock(); + let mut seats: Vec<_> = seats.values().collect(); + seats.sort_by_key(|d| d.seat_name()); + for seat in &seats { + self.show_seat(ps, ui, seat); + } + let outputs = state.globals.outputs.lock(); + let mut outputs: Vec<_> = outputs.values().collect(); + outputs.sort_by_key(|o| &o.connector.name); + let dev = &*state.input_device_handlers.borrow(); + let mut dev: Vec<_> = dev.values().collect(); + dev.sort_by_key(|d| d.data.device.name()); + for dev in dev { + self.show_device(ps, ui, &dev.data, &seats, &outputs); + } + } + + fn show_seat(&mut self, ps: &mut PaneState, ui: &mut Ui, seat: &Rc) { + let mut layout_job = LayoutJob::default(); + layout_job.append( + "Seat", + 0.0, + TextFormat { + color: ui.style().visuals.widgets.inactive.text_color(), + ..Default::default() + }, + ); + layout_job.append( + seat.seat_name(), + 10.0, + TextFormat { + color: ui.style().visuals.widgets.active.text_color(), + ..Default::default() + }, + ); + let ks = self.keymaps.entry(Key::Seat(seat.id())).or_default(); + CollapsingHeader::new(layout_job) + .id_salt(("seat", seat.id())) + .show(ui, |ui| { + grid(ui, ("seat", seat.id()), |ui| { + let mut dv = |name: &str, get: &dyn Fn(&mut (i32, i32)) -> &mut i32| { + let ui = &mut *ui.row(); + grid_label(ui, name); + let mut v = seat.get_rate(); + let old = v; + ui.horizontal(|ui| { + let v = get(&mut v); + DragValue::new(v).range(0..=i32::MAX).ui(ui); + if ui.button("-20").clicked() { + *v = v.saturating_sub(20).max(0) + } + if ui.button("+20").clicked() { + *v = v.saturating_add(20); + } + }); + if v != old { + seat.set_rate(v.0, v.1); + } + }; + dv("Repeat Rate", &|v| &mut v.0); + dv("Repeat Delay", &|v| &mut v.1); + drag_value( + ui, + "Cursor Size", + seat.cursor_group().cursor_size(), + 0..=u32::MAX, + 1.0, + |v| seat.cursor_group().set_cursor_size(v), + ); + bool_ui( + ui, + "Simple IM", + |ui| { + tip(ui, |ui| { + ui.label("A simple input method based on Xcompose files."); + ui.label(concat!( + "If you're not using another input method, you should ", + "leave this enabled as it will work for sandboxed ", + "applications, which regular Xcompose will not.", + )); + ui.label(concat!( + "The `enable-unicode-input` action can be used to input ", + "characters by their unicode value.", + )); + }); + }, + seat.simple_im_enabled(), + |b| seat.set_simple_im_enabled(b), + ); + bool_ui( + ui, + "Hardware Cursor", + |ui| { + tip(ui, |ui| { + ui.label( + "Allow this seat to use the hardware cursor, if available.", + ); + ui.label("Only one seat can use the hardware cursor at a time."); + }); + }, + seat.cursor_group().hardware_cursor(), + |b| seat.cursor_group().set_hardware_cursor(b), + ); + { + let ui = &mut *ui.row(); + let v = seat.pointer_revert_key(); + let v = Keysym(v.0); + if mem::replace(&mut ks.pointer_revert_key, v) != v { + ks.pointer_revert_key_str = None; + } + let name = ks + .pointer_revert_key_str + .get_or_insert_with(|| v.name().unwrap_or_default().to_string()); + grid_label_ui(ui, |ui| { + ui.label("Pointer Revert Key"); + tip(ui, |ui| { + ui.label(concat!( + "Pressing this key reverts the pointer to the default state, ", + "breaking grabs, drags, etc.", + )); + ui.label( + "Setting this to `NoSymbol` effectively disables this feature.", + ); + }); + }); + ui.horizontal(|ui| { + if ui.text_edit_singleline(name).changed() { + let v = Keysym::from_str(name); + ks.unknown_pointer_revert_key = v.is_none(); + if let Some(v) = v { + ks.pointer_revert_key = v; + seat.set_pointer_revert_key(KeySym(v.0)); + } + } + if ks.unknown_pointer_revert_key { + ui.label("Error: Unknown key"); + } + }); + } + bool(ui, "Focus Follows Mouse", seat.focus_follows_mouse(), |v| { + seat.set_focus_follows_mouse(v); + }); + combo_box_ui( + ui, + "Fallback Output Mode", + |ui| { + tip(ui, |ui| { + ui.label(concat!( + "This determines the output to use in operations where no ", + "output is explicitly specified.", + )); + ui.label(concat!( + "For example, when a new window is opened, this determines ", + "where the window will be opened.", + )); + ui.label("`Cursor` refers to the output that contains the cursor."); + ui.label( + "`Focus` refers to the output that has the keyboard focus.", + ); + }); + }, + seat.fallback_output_mode(), + |v| seat.set_fallback_output_mode(v), + ); + }); + ui.label("Focus History"); + ui.indent("focus-history", |ui| { + let mut v = seat.focus_history_visible(); + if ui.checkbox(&mut v, "Only Visible").changed() { + seat.focus_history_set_visible(v); + } + let mut v = seat.focus_history_same_workspace(); + if ui.checkbox(&mut v, "Same Workspace").changed() { + seat.focus_history_set_same_workspace(v); + } + }); + if ui.button("Reload Simple IM").clicked() { + seat.reload_simple_im(); + } + show_keymap( + &self.state, + ps, + &mut self.paste_requested, + ks, + ui, + Some(&seat.keymap()), + |m| seat.set_seat_keymap(m), + ); + }); + } + + fn show_device( + &mut self, + ps: &mut PaneState, + ui: &mut Ui, + dev: &Rc, + seats: &[&Rc], + outputs: &[&Rc], + ) { + let mut layout_job = LayoutJob::default(); + layout_job.append( + "Device", + 0.0, + TextFormat { + color: ui.style().visuals.widgets.inactive.text_color(), + ..Default::default() + }, + ); + layout_job.append( + &dev.device.name(), + 10.0, + TextFormat { + color: ui.style().visuals.widgets.active.text_color(), + ..Default::default() + }, + ); + let dev_id = dev.device.id(); + CollapsingHeader::new(layout_job) + .id_salt(("device", dev_id)) + .show(ui, |ui| { + grid(ui, ("device", dev_id), |ui| { + { + let old = dev.seat.get(); + let ui = &mut *ui.row(); + grid_label(ui, "Seat"); + let mut seat = old.as_ref(); + ui.horizontal(|ui| { + let mut cb = ComboBox::from_id_salt("seat"); + if let Some(seat) = seat { + cb = cb.selected_text(seat.seat_name()); + } + cb.show_ui(ui, |ui| { + for s in seats { + ui.selectable_value(&mut seat, Some(s), s.seat_name()); + } + }); + if ui.button("Detach").clicked() { + seat = None; + } + }); + if seat != old.as_ref() { + dev.set_seat(&self.state, seat.cloned()); + } + } + macro_rules! string { + ($field:ident, $name:expr) => { + if let Some(v) = &dev.$field { + label(ui, $name, v); + } + }; + } + string!(syspath, "Syspath"); + string!(devnode, "Devnode"); + { + let ui = &mut *ui.row(); + grid_label(ui, "Capabilities"); + let mut s = String::new(); + for cap in InputDeviceCapability::variants() { + if dev.device.has_capability(cap) { + if s.is_not_empty() { + s.push_str(" | "); + } + s.push_str(cap.text()); + } + } + ui.label(s); + } + if let Some(old) = dev.device.natural_scrolling_enabled() { + bool(ui, "Natural Scrolling", old, |v| { + dev.set_natural_scrolling_enabled(&self.state, v) + }); + } + if dev.device.has_capability(InputDeviceCapability::Pointer) { + drag_value_ui( + ui, + "Scroll Distance (px)", + |ui| { + tip(ui, |ui| { + ui.label(concat!( + "This only applies to applications that use the ", + "legacy px scrolling events.", + )); + }); + }, + dev.px_per_scroll_wheel.get(), + -f64::INFINITY..=f64::INFINITY, + 0.1, + |v| dev.set_px_per_scroll_wheel(&self.state, v), + ); + } + if let Some(old) = dev.device.accel_profile() { + combo_box(ui, "Accel Profile", old, |v| { + dev.set_accel_profile(&self.state, v) + }); + } + if let Some(old) = dev.device.accel_speed() { + drag_value(ui, "Accel Speed", old, 0.0..=1.0, 0.01, |v| { + dev.set_accel_speed(&self.state, v) + }); + } + if let Some(old) = dev.device.click_method() { + combo_box(ui, "Click Method", old, |v| { + dev.set_click_method(&self.state, v) + }); + } + if let Some(old) = dev.device.tap_enabled() { + bool(ui, "Tap Enabled", old, |v| { + dev.set_tap_enabled(&self.state, v) + }); + } + if let Some(old) = dev.device.drag_enabled() { + bool(ui, "Tap Drag Enabled", old, |v| { + dev.set_drag_enabled(&self.state, v) + }); + } + if let Some(old) = dev.device.drag_lock_enabled() { + bool(ui, "Tap Drag Lock Enabled", old, |v| { + dev.set_drag_lock_enabled(&self.state, v) + }); + } + if let Some(old) = dev.device.left_handed() { + bool(ui, "Left Handed", old, |v| { + dev.set_left_handed(&self.state, v) + }); + } + if let Some(old) = dev.device.middle_button_emulation_enabled() { + bool(ui, "Middle Button Emulation", old, |v| { + dev.set_middle_button_emulation_enabled(&self.state, v) + }); + } + { + let ui = &mut *ui.row(); + grid_label_ui(ui, |ui| { + ui.label("Output"); + tip(ui, |ui| { + ui.label("This applies to touch and tablet input."); + }); + }); + ui.horizontal(|ui| { + let old = dev.output.get().and_then(|v| v.global.get()); + let old = old.as_ref(); + let mut v = old; + let mut cb = ComboBox::from_id_salt("output"); + if let Some(v) = v { + cb = cb.selected_text(&*v.connector.name); + } + cb.show_ui(ui, |ui| { + for &output in outputs { + ui.selectable_value( + &mut v, + Some(output), + &*output.connector.name, + ); + } + }); + if v != old { + dev.set_output(&self.state, v.map(|v| &**v)); + } + if ui.button("Detach").clicked() { + dev.set_output(&self.state, None); + } + }); + } + matrix_ui( + ui, + "Transform Matrix", + |ui| { + tip(ui, |ui| { + ui.label("This matrix is applied to relative pointer movements."); + }); + }, + dev.device + .has_capability(InputDeviceCapability::Pointer) + .then(|| { + dev.device + .transform_matrix() + .unwrap_or([[1.0, 0.0], [0.0, 1.0]]) + }), + |v| dev.set_transform_matrix(&self.state, v), + ); + matrix( + ui, + "Calibration Matrix", + dev.device.calibration_matrix(), + |v| dev.set_calibration_matrix(&self.state, v), + ); + }); + if dev.device.has_capability(InputDeviceCapability::Keyboard) { + ui.collapsing("Device Keymap", |ui| { + let ks = self.keymaps.entry(Key::Dev(dev_id)).or_default(); + let map = dev.keymap.get(); + ui.add_enabled_ui(map.is_some(), |ui| { + if ui.button("Use Seat Keymap").clicked() { + ks.backup(map.as_ref()); + dev.set_keymap(&self.state, None); + } + }); + show_keymap( + &self.state, + ps, + &mut self.paste_requested, + ks, + ui, + map.as_ref(), + |m| { + dev.set_keymap(&self.state, Some(m.clone())); + }, + ); + }); + } + }); + } +} + +impl KeymapState { + fn backup(&mut self, map: Option<&Rc>) { + if self.backup.is_none() + && let Some(map) = map + { + self.backup = Some(map.clone()); + } + } +} + +fn show_keymap( + state: &State, + ps: &mut PaneState, + paste_requested: &mut Option, + ks: &mut KeymapState, + ui: &mut Ui, + map: Option<&Rc>, + set_map: impl Fn(&Rc), +) { + ui.scope_builder(UiBuilder::new().id(("keymap-settings", ks.seed)), |ui| { + ui.add_enabled_ui(map.is_some(), |ui| { + if ui.button("Copy Keymap").clicked() + && let Some(map) = map + { + ui.ctx().copy_text(map.map_text.clone()); + } + }); + let backup = |ks: &mut KeymapState| { + ks.backup(map); + }; + if ui.button("Load Default Keymap").clicked() { + backup(ks); + set_map(&state.default_keymap); + } + ui.horizontal(|ui| { + ui.add_enabled_ui(map.is_some(), |ui| { + if ui.button("Backup Keymap").clicked() { + ks.backup = None; + backup(ks); + } + }); + if let Some(backup) = &ks.backup + && ui.button("Restore Keymap").clicked() + { + set_map(backup); + ks.backup = None; + } + }); + let mut label = "Load Keymap from Clipboard".to_string(); + if *paste_requested == Some(ui.id()) { + label.push_str(" "); + label.push_str(ICON_PENDING); + } + let button = ui.button(label); + if button.clicked() { + *paste_requested = Some(ui.id()); + button.request_focus(); + ui.ctx().send_viewport_cmd(ViewportCommand::RequestPaste); + } else if *paste_requested == Some(ui.id()) && button.has_focus() { + ui.input(|e| { + let map = e + .events + .iter() + .filter_map(|e| match e { + Event::Paste(s) => Some(s), + _ => None, + }) + .next(); + let Some(map) = map else { + return; + }; + *paste_requested = None; + let map = match state.kb_ctx.parse_keymap(map.as_bytes()) { + Ok(m) => m, + Err(e) => { + let error = format!("Could not parse keymap: {}", ErrorFmt(e)); + ps.errors.push(error); + return; + } + }; + backup(ks); + set_map(&map); + }); + } else if *paste_requested == Some(ui.id()) { + *paste_requested = None; + } + ui.collapsing("Create Keymap from Names", |ui| { + grid(ui, ("keymap-from-names", ui.id()), |ui| { + let defaulted = + |ui: &mut Ui, name: &str, default: &mut bool, text: &mut dyn TextBuffer| { + let ui = &mut *ui.row(); + grid_label(ui, name); + ui.add_enabled_ui(!*default, |ui| { + text_edit(ui, text); + }); + ui.checkbox(default, "Default"); + }; + let required = |ui: &mut Ui, name, text| { + let ui = &mut *ui.row(); + grid_label(ui, name); + text_edit(ui, text); + }; + defaulted(ui, "Rules", &mut ks.rules_default, &mut ks.rules); + defaulted(ui, "Model", &mut ks.model_default, &mut ks.model); + required(ui, "Layouts", &mut ks.layouts); + required(ui, "Variants", &mut ks.variants); + required(ui, "Options", &mut ks.options); + }); + if ui.button("Load").clicked() { + 'set_map: { + let map = state.kb_ctx.keymap_from_rmlvo( + (!ks.rules_default).then_some(&ks.rules), + (!ks.model_default).then_some(&ks.model), + Some(&ks.layouts), + Some(&ks.variants), + Some(&ks.options), + ); + let map = match map { + Ok(map) => map, + Err(e) => { + let error = format!("Could not parse keymap: {}", ErrorFmt(e)); + ps.errors.push(error); + break 'set_map; + } + }; + backup(ks); + set_map(&map); + } + } + }); + }); +} + +fn matrix( + ui: &mut Ui, + name: &str, + old: Option<[[T; W]; 2]>, + set: impl FnOnce([[T; W]; 2]), +) where + T: Numeric, +{ + matrix_ui(ui, name, |_| (), old, set); +} + +fn matrix_ui( + ui: &mut Ui, + name: &str, + label: impl FnOnce(&mut Ui) -> R, + old: Option<[[T; W]; 2]>, + set: impl FnOnce([[T; W]; 2]), +) where + T: Numeric, +{ + if let Some(mut m) = old { + let old = m; + let ui = &mut *ui.row(); + grid_label_ui(ui, |ui| { + ui.label(name); + label(ui); + }); + Grid::new(name).show(ui, |ui| { + for row in &mut m { + for cell in row { + DragValue::new(cell).speed(0.01).ui(ui); + } + ui.end_row(); + } + }); + if old != m { + set(m); + } + } +} diff --git a/src/control_center/cc_sidebar.rs b/src/control_center/cc_sidebar.rs index e276fcf8..22183ba4 100644 --- a/src/control_center/cc_sidebar.rs +++ b/src/control_center/cc_sidebar.rs @@ -14,6 +14,7 @@ enum PaneName { Xwayland, Outputs, GPUs, + Input, } impl PaneName { @@ -25,6 +26,7 @@ impl PaneName { PaneName::Xwayland => "Xwayland", PaneName::Outputs => "Outputs", PaneName::GPUs => "GPUs", + PaneName::Input => "Input", } } } @@ -63,6 +65,7 @@ impl ControlCenterInner { PaneType::Outputs(Box::new(self.create_outputs_pane())) } PaneName::GPUs => PaneType::GPUs(self.create_gpus_pane()), + PaneName::Input => PaneType::Input(self.create_input_pane()), }; self.open(tree, ty); ui.ctx().request_repaint(); diff --git a/src/cursor_user.rs b/src/cursor_user.rs index f7ee78b3..e79beade 100644 --- a/src/cursor_user.rs +++ b/src/cursor_user.rs @@ -1,6 +1,7 @@ use { crate::{ backend::HardwareCursorUpdate, + control_center::CCI_INPUT, cursor::{Cursor, DEFAULT_CURSOR_SIZE, KnownCursor}, fixed::Fixed, gfx_api::{AcquireSync, ReleaseSync}, @@ -183,6 +184,7 @@ impl CursorUserGroup { self.remove_hardware_cursor(); self.state.cursor_user_group_hardware_cursor.take(); } + self.state.trigger_cci(CCI_INPUT); } pub fn hardware_cursor(&self) -> bool { @@ -195,10 +197,10 @@ impl CursorUserGroup { self.state.remove_cursor_size(old); self.state.add_cursor_size(size); self.reload_known_cursor(); + self.state.trigger_cci(CCI_INPUT); } } - #[expect(dead_code)] pub fn cursor_size(&self) -> u32 { self.size.get() } diff --git a/src/egui_adapter/egui_platform.rs b/src/egui_adapter/egui_platform.rs index e51b27c4..920ea679 100644 --- a/src/egui_adapter/egui_platform.rs +++ b/src/egui_adapter/egui_platform.rs @@ -116,7 +116,6 @@ pub mod icons { pub const ICON_INFO: &str = "\u{e88e}"; #[expect(dead_code)] pub const ICON_OPEN_IN_NEW: &str = "\u{e89e}"; - #[expect(dead_code)] pub const ICON_PENDING: &str = "\u{ef64}"; pub const ICON_REMOVE: &str = "\u{e15b}"; } diff --git a/src/ifs/jay_input.rs b/src/ifs/jay_input.rs index 9fdbac8a..26a3bcec 100644 --- a/src/ifs/jay_input.rs +++ b/src/ifs/jay_input.rs @@ -14,7 +14,7 @@ use { LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER, LIBINPUT_CONFIG_CLICK_METHOD_NONE, }, object::{Object, Version}, - state::{DeviceHandlerData, InputDeviceData}, + state::{DeviceHandlerData, InputDeviceData, State}, utils::errorfmt::ErrorFmt, wire::{JayInputId, jay_input::*}, }, @@ -28,6 +28,7 @@ use { pub struct JayInput { pub id: JayInputId, pub client: Rc, + pub state: Rc, pub tracker: Tracker, pub version: Version, } @@ -41,6 +42,7 @@ impl JayInput { Self { id, client: client.clone(), + state: client.state.clone(), tracker: Default::default(), version, } @@ -309,7 +311,7 @@ impl JayInputRequestHandler for JayInput { LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE => InputDeviceAccelProfile::Adaptive, _ => return Err(JayInputError::UnknownAccelerationProfile(req.profile)), }; - dev.set_accel_profile(profile); + dev.set_accel_profile(&self.state, profile); Ok(()) }) } @@ -317,7 +319,7 @@ impl JayInputRequestHandler for JayInput { fn set_accel_speed(&self, req: SetAccelSpeed, _slf: &Rc) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.set_accel_speed(req.speed); + dev.set_accel_speed(&self.state, req.speed); Ok(()) }) } @@ -325,7 +327,7 @@ impl JayInputRequestHandler for JayInput { fn set_tap_enabled(&self, req: SetTapEnabled, _slf: &Rc) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.set_tap_enabled(req.enabled != 0); + dev.set_tap_enabled(&self.state, req.enabled != 0); Ok(()) }) } @@ -337,7 +339,7 @@ impl JayInputRequestHandler for JayInput { ) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.set_drag_enabled(req.enabled != 0); + dev.set_drag_enabled(&self.state, req.enabled != 0); Ok(()) }) } @@ -349,7 +351,7 @@ impl JayInputRequestHandler for JayInput { ) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.set_drag_lock_enabled(req.enabled != 0); + dev.set_drag_lock_enabled(&self.state, req.enabled != 0); Ok(()) }) } @@ -357,7 +359,7 @@ impl JayInputRequestHandler for JayInput { fn set_left_handed(&self, req: SetLeftHanded, _slf: &Rc) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.set_left_handed(req.enabled != 0); + dev.set_left_handed(&self.state, req.enabled != 0); Ok(()) }) } @@ -369,7 +371,7 @@ impl JayInputRequestHandler for JayInput { ) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.set_natural_scrolling_enabled(req.enabled != 0); + dev.set_natural_scrolling_enabled(&self.state, req.enabled != 0); Ok(()) }) } @@ -381,7 +383,7 @@ impl JayInputRequestHandler for JayInput { ) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.set_px_per_scroll_wheel(req.px); + dev.set_px_per_scroll_wheel(&self.state, req.px); Ok(()) }) } @@ -393,7 +395,7 @@ impl JayInputRequestHandler for JayInput { ) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.set_transform_matrix([[req.m11, req.m12], [req.m21, req.m22]]); + dev.set_transform_matrix(&self.state, [[req.m11, req.m12], [req.m21, req.m22]]); Ok(()) }) } @@ -410,7 +412,7 @@ impl JayInputRequestHandler for JayInput { self.or_error(|| { let seat = self.seat(req.seat)?; let dev = self.device(req.id)?; - dev.set_seat(Some(seat)); + dev.set_seat(&self.state, Some(seat)); Ok(()) }) } @@ -418,7 +420,7 @@ impl JayInputRequestHandler for JayInput { fn detach(&self, req: Detach, _slf: &Rc) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.set_seat(None); + dev.set_seat(&self.state, None); Ok(()) }) } @@ -459,7 +461,7 @@ impl JayInputRequestHandler for JayInput { fn set_device_keymap(&self, req: SetDeviceKeymap, _slf: &Rc) -> Result<(), Self::Error> { self.set_keymap_impl(&req.keymap, req.keymap_len, |map| { let dev = self.device(req.id)?; - dev.set_keymap(Some(map.clone())); + dev.set_keymap(&self.state, Some(map.clone())); Ok(()) }) } @@ -490,11 +492,11 @@ impl JayInputRequestHandler for JayInput { .find(|c| c.global.connector.name.to_ascii_lowercase() == namelc) .cloned(); match c { - Some(c) => dev.set_output(Some(&c.global)), + Some(c) => dev.set_output(&self.state, Some(&c.global)), _ => return Err(JayInputError::OutputNotConnected), } } - _ => dev.set_output(None), + _ => dev.set_output(&self.state, None), } Ok(()) }) @@ -507,7 +509,10 @@ impl JayInputRequestHandler for JayInput { ) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.set_calibration_matrix([[req.m00, req.m01, req.m02], [req.m10, req.m11, req.m12]]); + dev.set_calibration_matrix( + &self.state, + [[req.m00, req.m01, req.m02], [req.m10, req.m11, req.m12]], + ); Ok(()) }) } @@ -521,7 +526,7 @@ impl JayInputRequestHandler for JayInput { LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER => InputDeviceClickMethod::Clickfinger, _ => return Err(JayInputError::UnknownClickMethod(req.method)), }; - dev.set_click_method(method); + dev.set_click_method(&self.state, method); Ok(()) }) } @@ -533,7 +538,7 @@ impl JayInputRequestHandler for JayInput { ) -> Result<(), Self::Error> { self.or_error(|| { let dev = self.device(req.id)?; - dev.set_middle_button_emulation_enabled(req.enabled != 0); + dev.set_middle_button_emulation_enabled(&self.state, req.enabled != 0); Ok(()) }) } @@ -594,7 +599,7 @@ impl JayInputRequestHandler for JayInput { req.options, |map| { let dev = self.device(req.id)?; - dev.set_keymap(Some(map.clone())); + dev.set_keymap(&self.state, Some(map.clone())); Ok(()) }, ) diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 1fa99506..dc94c098 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -28,6 +28,7 @@ use { ButtonState, InputDeviceAccelProfile, InputDeviceClickMethod, Leds, TransformMatrix, }, client::{Client, ClientError, ClientId}, + control_center::CCI_INPUT, cursor_user::{CursorUser, CursorUserGroup, CursorUserOwner}, ei::ei_ifs::ei_seat::EiSeat, fixed::Fixed, @@ -98,6 +99,7 @@ use { numcell::NumCell, rc_eq::{rc_eq, rc_weak_eq}, smallmap::SmallMap, + static_text::StaticText, }, wire::{ ExtIdleNotificationV1Id, WlDataDeviceId, WlKeyboardId, WlPointerId, WlSeatId, @@ -276,6 +278,15 @@ pub enum FallbackOutputMode { Focus, } +impl StaticText for FallbackOutputMode { + fn text(&self) -> &'static str { + match self { + FallbackOutputMode::Cursor => "Cursor", + FallbackOutputMode::Focus => "Focus", + } + } +} + impl TryFrom for FallbackOutputMode { type Error = (); @@ -768,6 +779,7 @@ impl WlSeatGlobal { if let Some(grab) = self.input_method_grab.get() { grab.on_repeat_info(); } + self.state.trigger_cci(CCI_INPUT); } pub fn close(self: &Rc) { @@ -963,18 +975,18 @@ impl WlSeatGlobal { pub fn focus_history_set_visible(&self, visible: bool) { self.focus_history_visible_only.set(visible); + self.state.trigger_cci(CCI_INPUT); } - #[expect(dead_code)] pub fn focus_history_visible(&self) -> bool { self.focus_history_visible_only.get() } pub fn focus_history_set_same_workspace(&self, same_workspace: bool) { self.focus_history_same_workspace.set(same_workspace); + self.state.trigger_cci(CCI_INPUT); } - #[expect(dead_code)] pub fn focus_history_same_workspace(&self) -> bool { self.focus_history_same_workspace.get() } @@ -1479,18 +1491,18 @@ impl WlSeatGlobal { pub fn set_focus_follows_mouse(&self, focus_follows_mouse: bool) { self.focus_follows_mouse.set(focus_follows_mouse); + self.state.trigger_cci(CCI_INPUT); } - #[expect(dead_code)] pub fn focus_follows_mouse(&self) -> bool { self.focus_follows_mouse.get() } pub fn set_fallback_output_mode(&self, fallback_output_mode: FallbackOutputMode) { self.fallback_output_mode.set(fallback_output_mode); + self.state.trigger_cci(CCI_INPUT); } - #[expect(dead_code)] pub fn fallback_output_mode(&self) -> FallbackOutputMode { self.fallback_output_mode.get() } @@ -1610,9 +1622,9 @@ impl WlSeatGlobal { pub fn set_pointer_revert_key(&self, key: KeySym) { self.revert_key.set(key); + self.state.trigger_cci(CCI_INPUT); } - #[expect(dead_code)] pub fn pointer_revert_key(&self) -> KeySym { self.revert_key.get() } @@ -1809,7 +1821,7 @@ pub fn collect_kb_foci(node: Rc) -> SmallVec<[Rc; 3]> { } impl DeviceHandlerData { - pub fn set_seat(&self, seat: Option>) { + pub fn set_seat(&self, state: &State, seat: Option>) { if let Some(new) = &seat { if let Some(old) = self.seat.get() && old.id() == new.id() @@ -1848,6 +1860,7 @@ impl DeviceHandlerData { } } self.attach_event_listeners(); + state.trigger_cci(CCI_INPUT); } fn destroy_physical_keyboard_state(&self) { @@ -1869,13 +1882,14 @@ impl DeviceHandlerData { }; } - pub fn set_keymap(&self, keymap: Option>) { + pub fn set_keymap(&self, state: &State, keymap: Option>) { self.destroy_physical_keyboard_state(); self.keymap.set(keymap); self.attach_event_listeners(); + state.trigger_cci(CCI_INPUT); } - pub fn set_output(&self, output: Option<&WlOutputGlobal>) { + pub fn set_output(&self, state: &State, output: Option<&WlOutputGlobal>) { match output { None => { log::info!("Removing output mapping of {}", self.device.name()); @@ -1886,6 +1900,7 @@ impl DeviceHandlerData { self.output.set(Some(o.opt.clone())); } } + state.trigger_cci(CCI_INPUT); } pub fn get_rect(&self, state: &State) -> Rect { @@ -1897,52 +1912,64 @@ impl DeviceHandlerData { state.root.extents.get() } - pub fn set_accel_profile(&self, v: InputDeviceAccelProfile) { + pub fn set_accel_profile(&self, state: &State, v: InputDeviceAccelProfile) { self.device.set_accel_profile(v); + state.trigger_cci(CCI_INPUT); } - pub fn set_accel_speed(&self, v: f64) { + pub fn set_accel_speed(&self, state: &State, v: f64) { self.device.set_accel_speed(v); + state.trigger_cci(CCI_INPUT); } - pub fn set_tap_enabled(&self, v: bool) { + pub fn set_tap_enabled(&self, state: &State, v: bool) { self.device.set_tap_enabled(v); + state.trigger_cci(CCI_INPUT); } - pub fn set_drag_enabled(&self, v: bool) { + pub fn set_drag_enabled(&self, state: &State, v: bool) { self.device.set_drag_enabled(v); + state.trigger_cci(CCI_INPUT); } - pub fn set_drag_lock_enabled(&self, v: bool) { + pub fn set_drag_lock_enabled(&self, state: &State, v: bool) { self.device.set_drag_lock_enabled(v); + state.trigger_cci(CCI_INPUT); } - pub fn set_left_handed(&self, v: bool) { + pub fn set_left_handed(&self, state: &State, v: bool) { self.device.set_left_handed(v); + state.trigger_cci(CCI_INPUT); } - pub fn set_natural_scrolling_enabled(&self, v: bool) { + pub fn set_natural_scrolling_enabled(&self, state: &State, v: bool) { self.device.set_natural_scrolling_enabled(v); + state.trigger_cci(CCI_INPUT); } - pub fn set_px_per_scroll_wheel(&self, v: f64) { + pub fn set_px_per_scroll_wheel(&self, state: &State, v: f64) { self.px_per_scroll_wheel.set(v); + state.trigger_cci(CCI_INPUT); } - pub fn set_transform_matrix(&self, v: TransformMatrix) { + pub fn set_transform_matrix(&self, state: &State, v: TransformMatrix) { self.device.set_transform_matrix(v); + state.trigger_cci(CCI_INPUT); } - pub fn set_calibration_matrix(&self, v: [[f32; 3]; 2]) { + pub fn set_calibration_matrix(&self, state: &State, v: [[f32; 3]; 2]) { self.device.set_calibration_matrix(v); + state.trigger_cci(CCI_INPUT); } - pub fn set_click_method(&self, v: InputDeviceClickMethod) { + pub fn set_click_method(&self, state: &State, v: InputDeviceClickMethod) { self.device.set_click_method(v); + state.trigger_cci(CCI_INPUT); } - pub fn set_middle_button_emulation_enabled(&self, v: bool) { + pub fn set_middle_button_emulation_enabled(&self, state: &State, v: bool) { self.device.set_middle_button_emulation_enabled(v); + state.trigger_cci(CCI_INPUT); } } diff --git a/src/ifs/wl_seat/text_input.rs b/src/ifs/wl_seat/text_input.rs index 51cea1d3..77d2d09e 100644 --- a/src/ifs/wl_seat/text_input.rs +++ b/src/ifs/wl_seat/text_input.rs @@ -1,6 +1,7 @@ use { crate::{ backend::KeyState, + control_center::CCI_INPUT, ifs::{ wl_seat::{ WlSeatGlobal, @@ -89,6 +90,7 @@ impl WlSeatGlobal { im.cancel_simple(self); } } + self.state.trigger_cci(CCI_INPUT); } pub fn simple_im_enabled(&self) -> bool { diff --git a/src/kbvm.rs b/src/kbvm.rs index b7026ae6..db16b124 100644 --- a/src/kbvm.rs +++ b/src/kbvm.rs @@ -52,7 +52,6 @@ pub struct KbvmMap { pub id: KbvmMapId, pub state_machine: StateMachine, pub lookup_table: LookupTable, - #[expect(dead_code)] pub map_text: String, pub map: KeymapFd, pub xwayland_map: KeymapFd, diff --git a/src/tasks/input_device.rs b/src/tasks/input_device.rs index f9bf3f5a..61550def 100644 --- a/src/tasks/input_device.rs +++ b/src/tasks/input_device.rs @@ -67,7 +67,7 @@ impl DeviceHandler { } for seat in self.state.globals.seats.lock().values() { if seat.seat_name() == DEFAULT_SEAT_NAME { - self.data.set_seat(Some(seat.clone())); + self.data.set_seat(&self.state, Some(seat.clone())); break; } } @@ -102,6 +102,6 @@ impl DeviceHandler { .input_device_handlers .borrow_mut() .remove(&self.dev.id()); - self.data.set_seat(None); + self.data.set_seat(&self.state, None); } }