From a5e7fc4a1a5ca5f75cc6db245430074e40b704ed Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 27 Jul 2023 14:06:43 -0700 Subject: [PATCH 1/7] config: Move key binding code to a seperate file --- src/config/key_bindings.rs | 295 +++++++++++++++++++++++++++++++++++++ src/config/mod.rs | 283 +---------------------------------- 2 files changed, 301 insertions(+), 277 deletions(-) create mode 100644 src/config/key_bindings.rs diff --git a/src/config/key_bindings.rs b/src/config/key_bindings.rs new file mode 100644 index 00000000..b74c240f --- /dev/null +++ b/src/config/key_bindings.rs @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::shell::{ + focus::FocusDirection, grabs::ResizeEdge, layout::tiling::Direction, ResizeDirection, +}; +use serde::Deserialize; +use smithay::{ + backend::input::KeyState, + input::keyboard::{keysyms as KeySyms, xkb::keysym_get_name, ModifiersState}, +}; +use std::collections::HashMap; + +use super::{types::*, WorkspaceLayout}; + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub enum KeyModifier { + Ctrl, + Alt, + Shift, + Super, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] +pub struct KeyModifiers { + pub ctrl: bool, + pub alt: bool, + pub shift: bool, + pub logo: bool, +} + +impl PartialEq for KeyModifiers { + fn eq(&self, other: &ModifiersState) -> bool { + self.ctrl == other.ctrl + && self.alt == other.alt + && self.shift == other.shift + && self.logo == other.logo + } +} + +impl Into for ModifiersState { + fn into(self) -> KeyModifiers { + KeyModifiers { + ctrl: self.ctrl, + alt: self.alt, + shift: self.shift, + logo: self.logo, + } + } +} + +impl std::ops::AddAssign for KeyModifiers { + fn add_assign(&mut self, rhs: KeyModifier) { + match rhs { + KeyModifier::Ctrl => self.ctrl = true, + KeyModifier::Alt => self.alt = true, + KeyModifier::Shift => self.shift = true, + KeyModifier::Super => self.logo = true, + }; + } +} + +impl std::ops::BitOr for KeyModifier { + type Output = KeyModifiers; + + fn bitor(self, rhs: KeyModifier) -> Self::Output { + let mut modifiers = self.into(); + modifiers += rhs; + modifiers + } +} + +impl Into for KeyModifier { + fn into(self) -> KeyModifiers { + let mut modifiers = KeyModifiers { + ctrl: false, + alt: false, + shift: false, + logo: false, + }; + modifiers += self; + modifiers + } +} + +/// Describtion of a key combination that might be +/// handled by the compositor. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Hash)] +#[serde(deny_unknown_fields)] +pub struct KeyPattern { + /// What modifiers are expected to be pressed alongside the key + #[serde(deserialize_with = "deserialize_KeyModifiers")] + pub modifiers: KeyModifiers, + /// The actual key, that was pressed + #[serde(deserialize_with = "deserialize_Keysym")] + pub key: u32, +} + +impl KeyPattern { + pub fn new(modifiers: impl Into, key: u32) -> KeyPattern { + KeyPattern { + modifiers: modifiers.into(), + key, + } + } +} + +impl ToString for KeyPattern { + fn to_string(&self) -> String { + let mut result = String::new(); + if self.modifiers.logo { + result += "Super+"; + } + if self.modifiers.ctrl { + result += "Ctrl+"; + } + if self.modifiers.alt { + result += "Alt+"; + } + if self.modifiers.shift { + result += "Shift+"; + } + result += &keysym_get_name(self.key); + result + } +} + +#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +pub enum Action { + Terminate, + Debug, + Close, + + Workspace(u8), + NextWorkspace, + PreviousWorkspace, + LastWorkspace, + MoveToWorkspace(u8), + MoveToNextWorkspace, + MoveToPreviousWorkspace, + MoveToLastWorkspace, + SendToWorkspace(u8), + SendToNextWorkspace, + SendToPreviousWorkspace, + SendToLastWorkspace, + + NextOutput, + PreviousOutput, + MoveToNextOutput, + MoveToPreviousOutput, + SendToNextOutput, + SendToPreviousOutput, + + Focus(FocusDirection), + Move(Direction), + + ToggleOrientation, + Orientation(crate::shell::layout::Orientation), + + ToggleStacking, + + ToggleTiling, + ToggleWindowFloating, + + Resizing(ResizeDirection), + #[serde(skip)] + _ResizingInternal(ResizeDirection, ResizeEdge, KeyState), + Maximize, + Spawn(String), +} + +fn insert_binding( + key_bindings: &mut HashMap, + modifiers: KeyModifiers, + keys: impl Iterator, + action: Action, +) { + if !key_bindings.values().any(|a| a == &action) { + for key in keys { + let pattern = KeyPattern { + modifiers: modifiers.clone(), + key, + }; + if !key_bindings.contains_key(&pattern) { + key_bindings.insert(pattern, action.clone()); + } + } + } +} + +pub fn add_default_bindings( + key_bindings: &mut HashMap, + workspace_layout: WorkspaceLayout, +) { + let (workspace_previous, workspace_next, output_previous, output_next) = match workspace_layout + { + WorkspaceLayout::Horizontal => ( + [KeySyms::KEY_Left, KeySyms::KEY_h], + [KeySyms::KEY_Right, KeySyms::KEY_j], + [KeySyms::KEY_Up, KeySyms::KEY_k], + [KeySyms::KEY_Down, KeySyms::KEY_j], + ), + WorkspaceLayout::Vertical => ( + [KeySyms::KEY_Up, KeySyms::KEY_k], + [KeySyms::KEY_Down, KeySyms::KEY_j], + [KeySyms::KEY_Left, KeySyms::KEY_h], + [KeySyms::KEY_Right, KeySyms::KEY_j], + ), + }; + + insert_binding( + key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + ..Default::default() + }, + workspace_previous.iter().copied(), + Action::PreviousWorkspace, + ); + insert_binding( + key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + ..Default::default() + }, + workspace_next.iter().copied(), + Action::NextWorkspace, + ); + insert_binding( + key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + shift: true, + ..Default::default() + }, + workspace_previous.iter().copied(), + Action::MoveToPreviousWorkspace, + ); + insert_binding( + key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + shift: true, + ..Default::default() + }, + workspace_next.iter().copied(), + Action::MoveToNextWorkspace, + ); + + insert_binding( + key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + ..Default::default() + }, + output_previous.iter().copied(), + Action::PreviousOutput, + ); + insert_binding( + key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + ..Default::default() + }, + output_next.iter().copied(), + Action::NextOutput, + ); + insert_binding( + key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + shift: true, + ..Default::default() + }, + output_previous.iter().copied(), + Action::MoveToPreviousOutput, + ); + insert_binding( + key_bindings, + KeyModifiers { + logo: true, + ctrl: true, + shift: true, + ..Default::default() + }, + output_next.iter().copied(), + Action::MoveToNextOutput, + ); +} diff --git a/src/config/mod.rs b/src/config/mod.rs index 663b4e62..2a663ddd 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -2,14 +2,13 @@ use crate::{ shell::{ - focus::FocusDirection, grabs::ResizeEdge, layout::tiling::Direction, ResizeDirection, Shell, WorkspaceAmount, }, state::{BackendData, Data, State}, wayland::protocols::output_configuration::OutputConfigurationState, }; use serde::{Deserialize, Serialize}; -use smithay::input::{keyboard::xkb::keysym_get_name, Seat}; +use smithay::input::Seat; pub use smithay::{ backend::input::KeyState, input::keyboard::{keysyms as KeySyms, Keysym, ModifiersState}, @@ -26,6 +25,8 @@ pub use smithay::{ use std::{cell::RefCell, collections::HashMap, fs::OpenOptions, path::PathBuf}; use tracing::{debug, error, info, warn}; +mod key_bindings; +pub use key_bindings::{Action, KeyModifier, KeyModifiers, KeyPattern}; mod types; pub use self::types::*; @@ -37,7 +38,7 @@ pub struct Config { #[derive(Debug, Deserialize)] pub struct StaticConfig { - pub key_bindings: HashMap, + pub key_bindings: HashMap, pub workspace_mode: WorkspaceMode, pub workspace_amount: WorkspaceAmount, #[serde(default = "default_workspace_layout")] @@ -247,125 +248,9 @@ impl Config { ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) .expect("Malformed config file"); - let (workspace_previous, workspace_next, output_previous, output_next) = - match config.workspace_layout { - WorkspaceLayout::Horizontal => ( - [KeySyms::KEY_Left, KeySyms::KEY_h], - [KeySyms::KEY_Right, KeySyms::KEY_j], - [KeySyms::KEY_Up, KeySyms::KEY_k], - [KeySyms::KEY_Down, KeySyms::KEY_j], - ), - WorkspaceLayout::Vertical => ( - [KeySyms::KEY_Up, KeySyms::KEY_k], - [KeySyms::KEY_Down, KeySyms::KEY_j], - [KeySyms::KEY_Left, KeySyms::KEY_h], - [KeySyms::KEY_Right, KeySyms::KEY_j], - ), - }; - - fn insert_binding( - key_bindings: &mut HashMap, - modifiers: KeyModifiers, - keys: impl Iterator, - action: Action, - ) { - if !key_bindings.values().any(|a| a == &action) { - for key in keys { - let pattern = KeyPattern { - modifiers: modifiers.clone(), - key, - }; - if !key_bindings.contains_key(&pattern) { - key_bindings.insert(pattern, action.clone()); - } - } - } - } - - insert_binding( + key_bindings::add_default_bindings( &mut config.key_bindings, - KeyModifiers { - logo: true, - ctrl: true, - ..Default::default() - }, - workspace_previous.iter().copied(), - Action::PreviousWorkspace, - ); - insert_binding( - &mut config.key_bindings, - KeyModifiers { - logo: true, - ctrl: true, - ..Default::default() - }, - workspace_next.iter().copied(), - Action::NextWorkspace, - ); - insert_binding( - &mut config.key_bindings, - KeyModifiers { - logo: true, - ctrl: true, - shift: true, - ..Default::default() - }, - workspace_previous.iter().copied(), - Action::MoveToPreviousWorkspace, - ); - insert_binding( - &mut config.key_bindings, - KeyModifiers { - logo: true, - ctrl: true, - shift: true, - ..Default::default() - }, - workspace_next.iter().copied(), - Action::MoveToNextWorkspace, - ); - - insert_binding( - &mut config.key_bindings, - KeyModifiers { - logo: true, - ctrl: true, - ..Default::default() - }, - output_previous.iter().copied(), - Action::PreviousOutput, - ); - insert_binding( - &mut config.key_bindings, - KeyModifiers { - logo: true, - ctrl: true, - ..Default::default() - }, - output_next.iter().copied(), - Action::NextOutput, - ); - insert_binding( - &mut config.key_bindings, - KeyModifiers { - logo: true, - ctrl: true, - shift: true, - ..Default::default() - }, - output_previous.iter().copied(), - Action::MoveToPreviousOutput, - ); - insert_binding( - &mut config.key_bindings, - KeyModifiers { - logo: true, - ctrl: true, - shift: true, - ..Default::default() - }, - output_next.iter().copied(), - Action::MoveToNextOutput, + config.workspace_layout, ); return config; @@ -901,159 +786,3 @@ impl DynamicConfig { PersistenceGuard(self.inputs.0.clone(), &mut self.inputs.1) } } - -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub enum KeyModifier { - Ctrl, - Alt, - Shift, - Super, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] -pub struct KeyModifiers { - pub ctrl: bool, - pub alt: bool, - pub shift: bool, - pub logo: bool, -} - -impl PartialEq for KeyModifiers { - fn eq(&self, other: &ModifiersState) -> bool { - self.ctrl == other.ctrl - && self.alt == other.alt - && self.shift == other.shift - && self.logo == other.logo - } -} - -impl Into for ModifiersState { - fn into(self) -> KeyModifiers { - KeyModifiers { - ctrl: self.ctrl, - alt: self.alt, - shift: self.shift, - logo: self.logo, - } - } -} - -impl std::ops::AddAssign for KeyModifiers { - fn add_assign(&mut self, rhs: KeyModifier) { - match rhs { - KeyModifier::Ctrl => self.ctrl = true, - KeyModifier::Alt => self.alt = true, - KeyModifier::Shift => self.shift = true, - KeyModifier::Super => self.logo = true, - }; - } -} - -impl std::ops::BitOr for KeyModifier { - type Output = KeyModifiers; - - fn bitor(self, rhs: KeyModifier) -> Self::Output { - let mut modifiers = self.into(); - modifiers += rhs; - modifiers - } -} - -impl Into for KeyModifier { - fn into(self) -> KeyModifiers { - let mut modifiers = KeyModifiers { - ctrl: false, - alt: false, - shift: false, - logo: false, - }; - modifiers += self; - modifiers - } -} - -/// Describtion of a key combination that might be -/// handled by the compositor. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Hash)] -#[serde(deny_unknown_fields)] -pub struct KeyPattern { - /// What modifiers are expected to be pressed alongside the key - #[serde(deserialize_with = "deserialize_KeyModifiers")] - pub modifiers: KeyModifiers, - /// The actual key, that was pressed - #[serde(deserialize_with = "deserialize_Keysym")] - pub key: u32, -} - -impl KeyPattern { - pub fn new(modifiers: impl Into, key: u32) -> KeyPattern { - KeyPattern { - modifiers: modifiers.into(), - key, - } - } -} - -impl ToString for KeyPattern { - fn to_string(&self) -> String { - let mut result = String::new(); - if self.modifiers.logo { - result += "Super+"; - } - if self.modifiers.ctrl { - result += "Ctrl+"; - } - if self.modifiers.alt { - result += "Alt+"; - } - if self.modifiers.shift { - result += "Shift+"; - } - result += &keysym_get_name(self.key); - result - } -} - -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] -pub enum Action { - Terminate, - Debug, - Close, - - Workspace(u8), - NextWorkspace, - PreviousWorkspace, - LastWorkspace, - MoveToWorkspace(u8), - MoveToNextWorkspace, - MoveToPreviousWorkspace, - MoveToLastWorkspace, - SendToWorkspace(u8), - SendToNextWorkspace, - SendToPreviousWorkspace, - SendToLastWorkspace, - - NextOutput, - PreviousOutput, - MoveToNextOutput, - MoveToPreviousOutput, - SendToNextOutput, - SendToPreviousOutput, - - Focus(FocusDirection), - Move(Direction), - - ToggleOrientation, - Orientation(crate::shell::layout::Orientation), - - ToggleStacking, - - ToggleTiling, - ToggleWindowFloating, - - Resizing(ResizeDirection), - #[serde(skip)] - _ResizingInternal(ResizeDirection, ResizeEdge, KeyState), - Maximize, - Spawn(String), -} From 75912df2701f8e91158f4dcd121c59e9002532ba Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 27 Jul 2023 14:22:46 -0700 Subject: [PATCH 2/7] config: Move input config handling into seperate file --- src/config/input_config.rs | 315 +++++++++++++++++++++++++++++++++++++ src/config/mod.rs | 304 +---------------------------------- 2 files changed, 320 insertions(+), 299 deletions(-) create mode 100644 src/config/input_config.rs diff --git a/src/config/input_config.rs b/src/config/input_config.rs new file mode 100644 index 00000000..86a01140 --- /dev/null +++ b/src/config/input_config.rs @@ -0,0 +1,315 @@ +use serde::{Deserialize, Serialize}; +pub use smithay::{ + backend::input::KeyState, + input::keyboard::{keysyms as KeySyms, Keysym, ModifiersState}, + output::{Mode, Output}, + reexports::{ + calloop::LoopHandle, + input::{ + AccelProfile, ClickMethod, Device as InputDevice, ScrollMethod, SendEventsMode, + TapButtonMap, + }, + }, + utils::{Logical, Physical, Point, Size, Transform}, +}; +use tracing::warn; + +use super::types::*; + +#[derive(Debug, Deserialize, Serialize)] +pub struct InputConfig { + state: DeviceState, + #[serde(skip_serializing_if = "Option::is_none", default)] + acceleration: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + calibration: Option<[f32; 6]>, + #[serde(with = "ClickMethodDef")] + #[serde(skip_serializing_if = "Option::is_none", default)] + click_method: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + disable_while_typing: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + left_handed: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + middle_button_emulation: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + rotation_angle: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + scroll_config: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + tap_config: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AccelConfig { + #[serde(with = "AccelProfileDef")] + profile: Option, + speed: f64, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ScrollConfig { + #[serde(with = "ScrollMethodDef")] + method: Option, + natural_scroll: Option, + scroll_button: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum DeviceState { + Enabled, + Disabled, + DisabledOnExternalMouse, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct TapConfig { + enabled: bool, + #[serde(with = "TapButtonMapDef")] + button_map: Option, + drag: bool, + drag_lock: bool, +} + +impl InputConfig { + pub fn for_device(device: &InputDevice) -> Self { + InputConfig { + state: match device.config_send_events_mode() { + x if x.contains(SendEventsMode::ENABLED) => DeviceState::Enabled, + x if x.contains(SendEventsMode::DISABLED_ON_EXTERNAL_MOUSE) => { + DeviceState::DisabledOnExternalMouse + } + x if x.contains(SendEventsMode::DISABLED) => DeviceState::Disabled, + _ => DeviceState::Disabled, + }, + acceleration: if device.config_accel_is_available() { + Some(AccelConfig { + profile: device.config_accel_profile(), + speed: device.config_accel_speed(), + }) + } else { + None + }, + calibration: device.config_calibration_matrix(), + click_method: device.config_click_method(), + disable_while_typing: if device.config_dwt_is_available() { + Some(device.config_dwt_enabled()) + } else { + None + }, + left_handed: if device.config_left_handed_is_available() { + Some(device.config_left_handed()) + } else { + None + }, + middle_button_emulation: if device.config_middle_emulation_is_available() { + Some(device.config_middle_emulation_enabled()) + } else { + None + }, + rotation_angle: if device.config_rotation_is_available() { + Some(device.config_rotation_angle()) + } else { + None + }, + scroll_config: if device + .config_scroll_methods() + .iter() + .any(|x| *x != ScrollMethod::NoScroll) + { + Some(ScrollConfig { + method: device.config_scroll_method(), + natural_scroll: if device.config_scroll_has_natural_scroll() { + Some(device.config_scroll_natural_scroll_enabled()) + } else { + None + }, + scroll_button: if device.config_scroll_method() + == Some(ScrollMethod::OnButtonDown) + { + Some(device.config_scroll_button()) + } else { + None + }, + }) + } else { + None + }, + tap_config: if device.config_tap_finger_count() > 0 { + Some(TapConfig { + enabled: device.config_tap_enabled(), + button_map: device.config_tap_button_map(), + drag: device.config_tap_drag_enabled(), + drag_lock: device.config_tap_drag_lock_enabled(), + }) + } else { + None + }, + } + } + + pub fn update_device(&self, device: &mut InputDevice) { + if let Err(err) = match self.state { + DeviceState::Enabled => device.config_send_events_set_mode(SendEventsMode::ENABLED), + DeviceState::Disabled => device.config_send_events_set_mode(SendEventsMode::DISABLED), + DeviceState::DisabledOnExternalMouse => { + device.config_send_events_set_mode(SendEventsMode::DISABLED_ON_EXTERNAL_MOUSE) + } + } { + warn!( + ?err, + "Failed to apply mode {:?} for device {:?}.", + self.state, + device.name(), + ); + } + if let Some(accel) = self.acceleration.as_ref() { + if let Some(profile) = accel.profile { + if let Err(err) = device.config_accel_set_profile(profile) { + warn!( + ?err, + "Failed to apply acceleration profile {:?} for device {:?}.", + profile, + device.name(), + ); + } + } + if let Err(err) = device.config_accel_set_speed(accel.speed) { + warn!( + ?err, + "Failed to apply acceleration speed {:?} for device {:?}.", + accel.speed, + device.name(), + ); + } + } + if let Some(matrix) = self.calibration { + if let Err(err) = device.config_calibration_set_matrix(matrix) { + warn!( + ?err, + "Failed to apply calibration matrix {:?} for device {:?}.", + matrix, + device.name(), + ); + } + } + if let Some(method) = self.click_method { + if let Err(err) = device.config_click_set_method(method) { + warn!( + ?err, + "Failed to apply click method {:?} for device {:?}.", + method, + device.name(), + ); + } + } + if let Some(dwt) = self.disable_while_typing { + if let Err(err) = device.config_dwt_set_enabled(dwt) { + warn!( + ?err, + "Failed to apply disable-while-typing {:?} for device {:?}.", + dwt, + device.name(), + ); + } + } + if let Some(left) = self.left_handed { + if let Err(err) = device.config_left_handed_set(left) { + warn!( + ?err, + "Failed to apply left-handed {:?} for device {:?}.", + left, + device.name(), + ); + } + } + if let Some(middle) = self.middle_button_emulation { + if let Err(err) = device.config_middle_emulation_set_enabled(middle) { + warn!( + ?err, + "Failed to apply middle-button-emulation {:?} for device {:?}.", + middle, + device.name(), + ); + } + } + if let Some(angle) = self.rotation_angle { + if let Err(err) = device.config_rotation_set_angle(angle) { + warn!( + ?err, + "Failed to apply rotation-angle {:?} for device {:?}", + angle, + device.name(), + ); + } + } + if let Some(scroll) = self.scroll_config.as_ref() { + if let Some(method) = scroll.method { + if let Err(err) = device.config_scroll_set_method(method) { + warn!( + ?err, + "Failed to apply scroll method {:?} for device {:?}.", + method, + device.name(), + ); + } + } + if let Some(natural) = scroll.natural_scroll { + if let Err(err) = device.config_scroll_set_natural_scroll_enabled(natural) { + warn!( + ?err, + "Failed to apply natural scrolling {:?} for device {:?}.", + natural, + device.name(), + ); + } + } + if let Some(button) = scroll.scroll_button { + if let Err(err) = device.config_scroll_set_button(button) { + warn!( + ?err, + "Failed to apply scroll button {:?} for device {:?}.", + button, + device.name(), + ); + } + } + } + if let Some(tap) = self.tap_config.as_ref() { + if let Err(err) = device.config_tap_set_enabled(tap.enabled) { + warn!( + ?err, + "Failed to apply tap-to-click {:?} for device {:?}.", + tap.enabled, + device.name(), + ); + } + if let Some(button_map) = tap.button_map { + if let Err(err) = device.config_tap_set_button_map(button_map) { + warn!( + ?err, + "Failed to apply button map {:?} for device {:?}.", + button_map, + device.name(), + ); + } + } + if let Err(err) = device.config_tap_set_drag_enabled(tap.drag) { + warn!( + ?err, + "Failed to apply tap-drag {:?} for device {:?}.", + tap.drag, + device.name(), + ); + } + if let Err(err) = device.config_tap_set_drag_lock_enabled(tap.drag_lock) { + warn!( + ?err, + "Failed to apply tap-drag-lock {:?} for device {:?}.", + tap.drag_lock, + device.name(), + ); + } + } + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs index 2a663ddd..832724a9 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,9 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - shell::{ - Shell, WorkspaceAmount, - }, + shell::{Shell, WorkspaceAmount}, state::{BackendData, Data, State}, wayland::protocols::output_configuration::OutputConfigurationState, }; @@ -25,6 +23,8 @@ pub use smithay::{ use std::{cell::RefCell, collections::HashMap, fs::OpenOptions, path::PathBuf}; use tracing::{debug, error, info, warn}; +mod input_config; +use input_config::InputConfig; mod key_bindings; pub use key_bindings::{Action, KeyModifier, KeyModifiers, KeyPattern}; mod types; @@ -158,61 +158,6 @@ pub struct InputsConfig { devices: HashMap, } -#[derive(Debug, Deserialize, Serialize)] -pub struct InputConfig { - state: DeviceState, - #[serde(skip_serializing_if = "Option::is_none", default)] - acceleration: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - calibration: Option<[f32; 6]>, - #[serde(with = "ClickMethodDef")] - #[serde(skip_serializing_if = "Option::is_none", default)] - click_method: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - disable_while_typing: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - left_handed: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - middle_button_emulation: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - rotation_angle: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - scroll_config: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - tap_config: Option, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct AccelConfig { - #[serde(with = "AccelProfileDef")] - profile: Option, - speed: f64, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct ScrollConfig { - #[serde(with = "ScrollMethodDef")] - method: Option, - natural_scroll: Option, - scroll_button: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum DeviceState { - Enabled, - Disabled, - DisabledOnExternalMouse, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct TapConfig { - enabled: bool, - #[serde(with = "TapButtonMapDef")] - button_map: Option, - drag: bool, - drag_lock: bool, -} - impl Config { pub fn load() -> Config { let xdg = xdg::BaseDirectories::new().ok(); @@ -485,248 +430,9 @@ impl Config { let mut inputs = self.dynamic_conf.inputs_mut(); match inputs.devices.entry(device.name().into()) { - Entry::Occupied(entry) => { - let config = entry.get(); - if let Err(err) = match config.state { - DeviceState::Enabled => { - device.config_send_events_set_mode(SendEventsMode::ENABLED) - } - DeviceState::Disabled => { - device.config_send_events_set_mode(SendEventsMode::DISABLED) - } - DeviceState::DisabledOnExternalMouse => device - .config_send_events_set_mode(SendEventsMode::DISABLED_ON_EXTERNAL_MOUSE), - } { - warn!( - ?err, - "Failed to apply mode {:?} for device {:?}.", - config.state, - device.name(), - ); - } - if let Some(accel) = config.acceleration.as_ref() { - if let Some(profile) = accel.profile { - if let Err(err) = device.config_accel_set_profile(profile) { - warn!( - ?err, - "Failed to apply acceleration profile {:?} for device {:?}.", - profile, - device.name(), - ); - } - } - if let Err(err) = device.config_accel_set_speed(accel.speed) { - warn!( - ?err, - "Failed to apply acceleration speed {:?} for device {:?}.", - accel.speed, - device.name(), - ); - } - } - if let Some(matrix) = config.calibration { - if let Err(err) = device.config_calibration_set_matrix(matrix) { - warn!( - ?err, - "Failed to apply calibration matrix {:?} for device {:?}.", - matrix, - device.name(), - ); - } - } - if let Some(method) = config.click_method { - if let Err(err) = device.config_click_set_method(method) { - warn!( - ?err, - "Failed to apply click method {:?} for device {:?}.", - method, - device.name(), - ); - } - } - if let Some(dwt) = config.disable_while_typing { - if let Err(err) = device.config_dwt_set_enabled(dwt) { - warn!( - ?err, - "Failed to apply disable-while-typing {:?} for device {:?}.", - dwt, - device.name(), - ); - } - } - if let Some(left) = config.left_handed { - if let Err(err) = device.config_left_handed_set(left) { - warn!( - ?err, - "Failed to apply left-handed {:?} for device {:?}.", - left, - device.name(), - ); - } - } - if let Some(middle) = config.middle_button_emulation { - if let Err(err) = device.config_middle_emulation_set_enabled(middle) { - warn!( - ?err, - "Failed to apply middle-button-emulation {:?} for device {:?}.", - middle, - device.name(), - ); - } - } - if let Some(angle) = config.rotation_angle { - if let Err(err) = device.config_rotation_set_angle(angle) { - warn!( - ?err, - "Failed to apply rotation-angle {:?} for device {:?}", - angle, - device.name(), - ); - } - } - if let Some(scroll) = config.scroll_config.as_ref() { - if let Some(method) = scroll.method { - if let Err(err) = device.config_scroll_set_method(method) { - warn!( - ?err, - "Failed to apply scroll method {:?} for device {:?}.", - method, - device.name(), - ); - } - } - if let Some(natural) = scroll.natural_scroll { - if let Err(err) = device.config_scroll_set_natural_scroll_enabled(natural) { - warn!( - ?err, - "Failed to apply natural scrolling {:?} for device {:?}.", - natural, - device.name(), - ); - } - } - if let Some(button) = scroll.scroll_button { - if let Err(err) = device.config_scroll_set_button(button) { - warn!( - ?err, - "Failed to apply scroll button {:?} for device {:?}.", - button, - device.name(), - ); - } - } - } - if let Some(tap) = config.tap_config.as_ref() { - if let Err(err) = device.config_tap_set_enabled(tap.enabled) { - warn!( - ?err, - "Failed to apply tap-to-click {:?} for device {:?}.", - tap.enabled, - device.name(), - ); - } - if let Some(button_map) = tap.button_map { - if let Err(err) = device.config_tap_set_button_map(button_map) { - warn!( - ?err, - "Failed to apply button map {:?} for device {:?}.", - button_map, - device.name(), - ); - } - } - if let Err(err) = device.config_tap_set_drag_enabled(tap.drag) { - warn!( - ?err, - "Failed to apply tap-drag {:?} for device {:?}.", - tap.drag, - device.name(), - ); - } - if let Err(err) = device.config_tap_set_drag_lock_enabled(tap.drag_lock) { - warn!( - ?err, - "Failed to apply tap-drag-lock {:?} for device {:?}.", - tap.drag_lock, - device.name(), - ); - } - } - } + Entry::Occupied(entry) => entry.get().update_device(device), Entry::Vacant(entry) => { - entry.insert(InputConfig { - state: match device.config_send_events_mode() { - x if x.contains(SendEventsMode::ENABLED) => DeviceState::Enabled, - x if x.contains(SendEventsMode::DISABLED_ON_EXTERNAL_MOUSE) => { - DeviceState::DisabledOnExternalMouse - } - x if x.contains(SendEventsMode::DISABLED) => DeviceState::Disabled, - _ => DeviceState::Disabled, - }, - acceleration: if device.config_accel_is_available() { - Some(AccelConfig { - profile: device.config_accel_profile(), - speed: device.config_accel_speed(), - }) - } else { - None - }, - calibration: device.config_calibration_matrix(), - click_method: device.config_click_method(), - disable_while_typing: if device.config_dwt_is_available() { - Some(device.config_dwt_enabled()) - } else { - None - }, - left_handed: if device.config_left_handed_is_available() { - Some(device.config_left_handed()) - } else { - None - }, - middle_button_emulation: if device.config_middle_emulation_is_available() { - Some(device.config_middle_emulation_enabled()) - } else { - None - }, - rotation_angle: if device.config_rotation_is_available() { - Some(device.config_rotation_angle()) - } else { - None - }, - scroll_config: if device - .config_scroll_methods() - .iter() - .any(|x| *x != ScrollMethod::NoScroll) - { - Some(ScrollConfig { - method: device.config_scroll_method(), - natural_scroll: if device.config_scroll_has_natural_scroll() { - Some(device.config_scroll_natural_scroll_enabled()) - } else { - None - }, - scroll_button: if device.config_scroll_method() - == Some(ScrollMethod::OnButtonDown) - { - Some(device.config_scroll_button()) - } else { - None - }, - }) - } else { - None - }, - tap_config: if device.config_tap_finger_count() > 0 { - Some(TapConfig { - enabled: device.config_tap_enabled(), - button_map: device.config_tap_button_map(), - drag: device.config_tap_drag_enabled(), - drag_lock: device.config_tap_drag_lock_enabled(), - }) - } else { - None - }, - }); + entry.insert(InputConfig::for_device(device)); } } } From 0f5d654535d0a7308443f40302801caaac02ba1a Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 28 Jul 2023 12:54:02 -0700 Subject: [PATCH 3/7] Use `cosmic-config` for input configuration; allow dynamic changes --- Cargo.lock | 31 +++++++------ Cargo.toml | 5 +- src/backend/kms/mod.rs | 13 +++++- src/config/mod.rs | 103 +++++++++++++++++++++++++---------------- src/state.rs | 2 +- 5 files changed, 94 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75fe423d..e51b8f51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,6 +481,7 @@ dependencies = [ "bitflags 1.3.2", "bytemuck", "calloop", + "cosmic-config", "cosmic-protocols", "edid-rs", "egui", @@ -524,9 +525,10 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/?rev=42d7baf#42d7baf0d5cb14ab476120be9dfcaea9bd1d0be4" +source = "git+https://github.com/pop-os/libcosmic/?rev=4895b0c#4895b0c9bda9e46fc7db173e239d155dac957186" dependencies = [ "atomicwrites", + "calloop", "cosmic-config-derive", "dirs 5.0.1", "iced_futures", @@ -538,7 +540,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/?rev=42d7baf#42d7baf0d5cb14ab476120be9dfcaea9bd1d0be4" +source = "git+https://github.com/pop-os/libcosmic/?rev=4895b0c#4895b0c9bda9e46fc7db173e239d155dac957186" dependencies = [ "quote", "syn 1.0.109", @@ -578,7 +580,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/?rev=42d7baf#42d7baf0d5cb14ab476120be9dfcaea9bd1d0be4" +source = "git+https://github.com/pop-os/libcosmic/?rev=4895b0c#4895b0c9bda9e46fc7db173e239d155dac957186" dependencies = [ "anyhow", "cosmic-config", @@ -1746,7 +1748,7 @@ dependencies = [ [[package]] name = "iced" version = "0.9.0" -source = "git+https://github.com/pop-os/libcosmic/?rev=42d7baf#42d7baf0d5cb14ab476120be9dfcaea9bd1d0be4" +source = "git+https://github.com/pop-os/libcosmic/?rev=4895b0c#4895b0c9bda9e46fc7db173e239d155dac957186" dependencies = [ "iced_core", "iced_futures", @@ -1759,7 +1761,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.9.0" -source = "git+https://github.com/pop-os/libcosmic/?rev=42d7baf#42d7baf0d5cb14ab476120be9dfcaea9bd1d0be4" +source = "git+https://github.com/pop-os/libcosmic/?rev=4895b0c#4895b0c9bda9e46fc7db173e239d155dac957186" dependencies = [ "bitflags 1.3.2", "instant", @@ -1772,7 +1774,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.6.0" -source = "git+https://github.com/pop-os/libcosmic/?rev=42d7baf#42d7baf0d5cb14ab476120be9dfcaea9bd1d0be4" +source = "git+https://github.com/pop-os/libcosmic/?rev=4895b0c#4895b0c9bda9e46fc7db173e239d155dac957186" dependencies = [ "futures", "iced_core", @@ -1784,7 +1786,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.8.0" -source = "git+https://github.com/pop-os/libcosmic/?rev=42d7baf#42d7baf0d5cb14ab476120be9dfcaea9bd1d0be4" +source = "git+https://github.com/pop-os/libcosmic/?rev=4895b0c#4895b0c9bda9e46fc7db173e239d155dac957186" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -1801,7 +1803,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/?rev=42d7baf#42d7baf0d5cb14ab476120be9dfcaea9bd1d0be4" +source = "git+https://github.com/pop-os/libcosmic/?rev=4895b0c#4895b0c9bda9e46fc7db173e239d155dac957186" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -1813,7 +1815,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/?rev=42d7baf#42d7baf0d5cb14ab476120be9dfcaea9bd1d0be4" +source = "git+https://github.com/pop-os/libcosmic/?rev=4895b0c#4895b0c9bda9e46fc7db173e239d155dac957186" dependencies = [ "iced_core", "iced_futures", @@ -1823,7 +1825,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.8.0" -source = "git+https://github.com/pop-os/libcosmic/?rev=42d7baf#42d7baf0d5cb14ab476120be9dfcaea9bd1d0be4" +source = "git+https://github.com/pop-os/libcosmic/?rev=4895b0c#4895b0c9bda9e46fc7db173e239d155dac957186" dependencies = [ "iced_core", "once_cell", @@ -1833,7 +1835,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/?rev=42d7baf#42d7baf0d5cb14ab476120be9dfcaea9bd1d0be4" +source = "git+https://github.com/pop-os/libcosmic/?rev=4895b0c#4895b0c9bda9e46fc7db173e239d155dac957186" dependencies = [ "bytemuck", "cosmic-text", @@ -1851,7 +1853,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.10.0" -source = "git+https://github.com/pop-os/libcosmic/?rev=42d7baf#42d7baf0d5cb14ab476120be9dfcaea9bd1d0be4" +source = "git+https://github.com/pop-os/libcosmic/?rev=4895b0c#4895b0c9bda9e46fc7db173e239d155dac957186" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -1872,7 +1874,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/?rev=42d7baf#42d7baf0d5cb14ab476120be9dfcaea9bd1d0be4" +source = "git+https://github.com/pop-os/libcosmic/?rev=4895b0c#4895b0c9bda9e46fc7db173e239d155dac957186" dependencies = [ "iced_renderer", "iced_runtime", @@ -2167,7 +2169,7 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/?rev=42d7baf#42d7baf0d5cb14ab476120be9dfcaea9bd1d0be4" +source = "git+https://github.com/pop-os/libcosmic/?rev=4895b0c#4895b0c9bda9e46fc7db173e239d155dac957186" dependencies = [ "apply", "cosmic-config", @@ -2186,6 +2188,7 @@ dependencies = [ "lazy_static", "palette", "slotmap", + "thiserror", "tracing", ] diff --git a/Cargo.toml b/Cargo.toml index a5890a3c..495b9459 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,9 +30,10 @@ ron = "0.7" libsystemd = { version = "0.5", optional = true } wayland-backend = "0.1.0" wayland-scanner = "0.30.0" +cosmic-config = { git = "https://github.com/pop-os/libcosmic/", rev = "4895b0c", features = ["calloop"] } cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "main", default-features = false, features = ["server"] } -libcosmic = { git = "https://github.com/pop-os/libcosmic/", rev = "42d7baf", default-features = false } -iced_tiny_skia = { git = "https://github.com/pop-os/libcosmic/", rev = "42d7baf" } +libcosmic = { git = "https://github.com/pop-os/libcosmic/", rev = "4895b0c", default-features = false } +iced_tiny_skia = { git = "https://github.com/pop-os/libcosmic/", rev = "4895b0c" } tiny-skia = "0.9" ordered-float = "3.0" glow = "0.11.2" diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index 13026233..305baeb5 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -57,7 +57,7 @@ use smithay::{ control::{connector, crtc, Device as ControlDevice, ModeTypeFlags}, Device as _, }, - input::Libinput, + input::{self, Libinput}, nix::{fcntl::OFlag, sys::stat::dev_t}, wayland_protocols::wp::{ linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1, @@ -97,6 +97,7 @@ const MIN_RENDER_TIME: Duration = Duration::from_millis(3); #[derive(Debug)] pub struct KmsState { devices: HashMap, + pub input_devices: HashMap, pub api: GpuManager>, pub primary: DrmNode, session: LibSeatSession, @@ -172,8 +173,15 @@ pub fn init_backend( let libinput_event_source = event_loop .handle() .insert_source(libinput_backend, move |mut event, _, data| { - if let &mut InputEvent::DeviceAdded { ref mut device } = &mut event { + if let InputEvent::DeviceAdded { ref mut device } = &mut event { data.state.common.config.read_device(device); + data.state + .backend + .kms() + .input_devices + .insert(device.name().into(), device.clone()); + } else if let InputEvent::DeviceRemoved { device } = &event { + data.state.backend.kms().input_devices.remove(device.name()); } data.state.process_input_event(event, true); for output in data.state.common.shell.outputs() { @@ -363,6 +371,7 @@ pub fn init_backend( primary, session, devices: HashMap::new(), + input_devices: HashMap::new(), }); // Create relative pointer global diff --git a/src/config/mod.rs b/src/config/mod.rs index 832724a9..81dd8da4 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -5,6 +5,7 @@ use crate::{ state::{BackendData, Data, State}, wayland::protocols::output_configuration::OutputConfigurationState, }; +use cosmic_config::{ConfigGet, ConfigSet}; use serde::{Deserialize, Serialize}; use smithay::input::Seat; pub use smithay::{ @@ -34,6 +35,9 @@ pub use self::types::*; pub struct Config { pub static_conf: StaticConfig, pub dynamic_conf: DynamicConfig, + pub config: cosmic_config::Config, + pub xkb: XkbConfig, + pub input_devices: HashMap, } #[derive(Debug, Deserialize)] @@ -65,7 +69,6 @@ pub enum WorkspaceLayout { #[derive(Debug)] pub struct DynamicConfig { outputs: (Option, OutputsConfig), - inputs: (Option, InputsConfig), } #[derive(Debug, Deserialize, Serialize)] @@ -152,18 +155,22 @@ impl OutputConfig { } } -#[derive(Debug, Deserialize, Serialize)] -pub struct InputsConfig { - xkb: XkbConfig, - devices: HashMap, -} - impl Config { - pub fn load() -> Config { + pub fn load(loop_handle: &LoopHandle<'_, Data>) -> Config { + let config = cosmic_config::Config::new("com.system76.CosmicComp", 1).unwrap(); + let source = cosmic_config::calloop::ConfigWatchSource::new(&config).unwrap(); + loop_handle + .insert_source(source, |(config, keys), (), shared_data| { + config_changed(config, keys, &mut shared_data.state); + }) + .expect("Failed to add cosmic-config to the event loop"); let xdg = xdg::BaseDirectories::new().ok(); Config { static_conf: Self::load_static(xdg.as_ref()), dynamic_conf: Self::load_dynamic(xdg.as_ref()), + xkb: get_config(&config, "xkb-config"), + input_devices: get_config(&config, "input-devices"), + config, } } @@ -218,12 +225,8 @@ impl Config { xdg.and_then(|base| base.place_state_file("cosmic-comp/outputs.ron").ok()); let outputs = Self::load_outputs(&output_path); - let input_path = xdg.and_then(|base| base.place_state_file("cosmic-comp/inputs.ron").ok()); - let inputs = Self::load_inputs(&input_path); - DynamicConfig { outputs: (output_path, outputs), - inputs: (input_path, inputs), } } @@ -247,27 +250,6 @@ impl Config { } } - fn load_inputs(path: &Option) -> InputsConfig { - if let Some(path) = path.as_ref() { - if path.exists() { - match ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) { - Ok(config) => return config, - Err(err) => { - warn!(?err, "Failed to read input_config, resetting.."); - if let Err(err) = std::fs::remove_file(path) { - error!(?err, "Failed to remove input_config."); - } - } - }; - } - } - - InputsConfig { - xkb: XkbConfig::default(), - devices: HashMap::new(), - } - } - pub fn read_outputs( &mut self, output_state: &mut OutputConfigurationState, @@ -422,17 +404,23 @@ impl Config { } pub fn xkb_config(&self) -> XkbConfig { - self.dynamic_conf.inputs().xkb.clone() + self.xkb.clone() } pub fn read_device(&mut self, device: &mut InputDevice) { use std::collections::hash_map::Entry; - let mut inputs = self.dynamic_conf.inputs_mut(); - match inputs.devices.entry(device.name().into()) { + let mut config_changed = false; + match self.input_devices.entry(device.name().into()) { Entry::Occupied(entry) => entry.get().update_device(device), Entry::Vacant(entry) => { entry.insert(InputConfig::for_device(device)); + config_changed = true; + } + } + if config_changed { + if let Err(err) = self.config.set("input-devices", &self.input_devices) { + error!(?err, "Failed to write config 'input-devices'"); } } } @@ -483,12 +471,45 @@ impl DynamicConfig { pub fn outputs_mut<'a>(&'a mut self) -> PersistenceGuard<'a, OutputsConfig> { PersistenceGuard(self.outputs.0.clone(), &mut self.outputs.1) } +} - pub fn inputs(&self) -> &InputsConfig { - &self.inputs.1 - } +fn get_config( + config: &cosmic_config::Config, + key: &str, +) -> T { + config.get(key).unwrap_or_else(|err| { + error!(?err, "Failed to read config '{}'", key); + T::default() + }) +} - pub fn inputs_mut<'a>(&'a mut self) -> PersistenceGuard<'a, InputsConfig> { - PersistenceGuard(self.inputs.0.clone(), &mut self.inputs.1) +fn config_changed(config: cosmic_config::Config, keys: Vec, state: &mut State) { + for key in &keys { + match key.as_str() { + "xkb-config" => { + let value = get_config::(&config, "xkb-config"); + for seat in state.common.seats().cloned().collect::>().iter() { + if let Some(keyboard) = seat.get_keyboard() { + if let Err(err) = keyboard.set_xkb_config(state, (&value).into()) { + error!(?err, "Failed to load provided xkb config"); + // TODO Revert to default? + } + } + } + state.common.config.xkb = value; + } + "input-devices" => { + let value = get_config::>(&config, "input-devices"); + if let BackendData::Kms(ref mut kms_state) = &mut state.backend { + for (name, device) in kms_state.input_devices.iter_mut() { + if let Some(input_config) = value.get(name) { + input_config.update_device(device); + } + } + } + state.common.config.input_devices = value; + } + _ => {} + } } } diff --git a/src/state.rs b/src/state.rs index 8ba5ff4f..f1d1740e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -278,7 +278,7 @@ impl State { .unwrap(); let clock = Clock::new().expect("Failed to initialize clock"); - let config = Config::load(); + let config = Config::load(&handle); let compositor_state = CompositorState::new::(dh); let data_device_state = DataDeviceState::new::(dh); let dmabuf_state = DmabufState::new(); From 56467755a8d44cca0cb1bc03cb656d44a3fd8fc6 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 29 Aug 2023 13:49:41 -0700 Subject: [PATCH 4/7] `cosmic-comp-config` crate, and default input config This adds a `input-default` setting, which is used for input settings if a device isn't set in `input-devices`. More awkwardly, it also adds an `input-touchpad` setting, which is used instead of `input-default` for touchpad devices, so we can separate mouse and touchpad settings even though they use the same capability and settings. This no longer sets the input config, and only reads it. If we add a UI for per-device config, we'll need some IPC mechanism to list connected devices. (Assuming cosmic-settings can't use libinput directly for that.) This moves `InputConfig` and `XkbConfig` to a new `cosmic-comp-config` crate, so they can be used in `cosmic-settings`. --- Cargo.lock | 10 + Cargo.toml | 6 + cosmic-comp-config/Cargo.toml | 8 + cosmic-comp-config/src/input.rs | 209 ++++++++++++++ cosmic-comp-config/src/lib.rs | 26 ++ src/config/input_config.rs | 474 +++++++++++++++----------------- src/config/mod.rs | 70 +++-- src/config/types.rs | 175 ------------ src/input/mod.rs | 4 +- 9 files changed, 525 insertions(+), 457 deletions(-) create mode 100644 cosmic-comp-config/Cargo.toml create mode 100644 cosmic-comp-config/src/input.rs create mode 100644 cosmic-comp-config/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index e51b8f51..4e5359f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,6 +481,7 @@ dependencies = [ "bitflags 1.3.2", "bytemuck", "calloop", + "cosmic-comp-config", "cosmic-config", "cosmic-protocols", "edid-rs", @@ -522,6 +523,14 @@ dependencies = [ "xkbcommon 0.4.1", ] +[[package]] +name = "cosmic-comp-config" +version = "0.1.0" +dependencies = [ + "input", + "serde", +] + [[package]] name = "cosmic-config" version = "0.1.0" @@ -1981,6 +1990,7 @@ dependencies = [ "input-sys", "io-lifetimes", "libc", + "log", "udev", ] diff --git a/Cargo.toml b/Cargo.toml index 495b9459..8d7bec91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,11 @@ edition = "2021" license = "GPL-3.0-only" authors = ["Victoria Brekenfeld"] +[workspace] +members = [ + "cosmic-comp-config" +] + [dependencies] apply = "0.3.0" anyhow = { version = "1.0.51", features = ["backtrace"] } @@ -30,6 +35,7 @@ ron = "0.7" libsystemd = { version = "0.5", optional = true } wayland-backend = "0.1.0" wayland-scanner = "0.30.0" +cosmic-comp-config = { path = "cosmic-comp-config" } cosmic-config = { git = "https://github.com/pop-os/libcosmic/", rev = "4895b0c", features = ["calloop"] } cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", branch = "main", default-features = false, features = ["server"] } libcosmic = { git = "https://github.com/pop-os/libcosmic/", rev = "4895b0c", default-features = false } diff --git a/cosmic-comp-config/Cargo.toml b/cosmic-comp-config/Cargo.toml new file mode 100644 index 00000000..7f4f6588 --- /dev/null +++ b/cosmic-comp-config/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "cosmic-comp-config" +version = "0.1.0" +edition = "2021" + +[dependencies] +input = "0.8.3" +serde = { version = "1", features = ["derive"] } diff --git a/cosmic-comp-config/src/input.rs b/cosmic-comp-config/src/input.rs new file mode 100644 index 00000000..1d15abb0 --- /dev/null +++ b/cosmic-comp-config/src/input.rs @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-3.0-only + +#![allow(non_snake_case)] + +use input::{AccelProfile, ClickMethod, ScrollMethod, TapButtonMap}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct InputConfig { + pub state: DeviceState, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub acceleration: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub calibration: Option<[f32; 6]>, + #[serde(with = "ClickMethodDef")] + #[serde(skip_serializing_if = "Option::is_none", default)] + pub click_method: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub disable_while_typing: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub left_handed: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub middle_button_emulation: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub rotation_angle: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub scroll_config: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub tap_config: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct AccelConfig { + #[serde(with = "AccelProfileDef")] + pub profile: Option, + pub speed: f64, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ScrollConfig { + #[serde(with = "ScrollMethodDef")] + pub method: Option, + pub natural_scroll: Option, + pub scroll_button: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub enum DeviceState { + Enabled, + Disabled, + DisabledOnExternalMouse, +} + +impl Default for DeviceState { + fn default() -> Self { + Self::Enabled + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct TapConfig { + pub enabled: bool, + #[serde(with = "TapButtonMapDef")] + pub button_map: Option, + pub drag: bool, + pub drag_lock: bool, +} + +mod ClickMethodDef { + use input::ClickMethod as ClickMethodOrig; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + #[derive(Debug, Serialize, Deserialize)] + pub enum ClickMethod { + ButtonAreas, + Clickfinger, + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let o = Option::deserialize(deserializer)?; + Ok(o.map(|x| match x { + ClickMethod::ButtonAreas => ClickMethodOrig::ButtonAreas, + ClickMethod::Clickfinger => ClickMethodOrig::Clickfinger, + })) + } + + pub fn serialize(arg: &Option, ser: S) -> Result + where + S: Serializer, + { + let arg = match arg { + Some(ClickMethodOrig::ButtonAreas) => Some(ClickMethod::ButtonAreas), + Some(ClickMethodOrig::Clickfinger) => Some(ClickMethod::Clickfinger), + Some(_) | None => None, + }; + Option::serialize(&arg, ser) + } +} + +mod AccelProfileDef { + use input::AccelProfile as AccelProfileOrig; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + #[derive(Debug, Serialize, Deserialize)] + enum AccelProfile { + Flat, + Adaptive, + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let o = Option::deserialize(deserializer)?; + Ok(o.map(|x| match x { + AccelProfile::Flat => AccelProfileOrig::Flat, + AccelProfile::Adaptive => AccelProfileOrig::Adaptive, + })) + } + + pub fn serialize(arg: &Option, ser: S) -> Result + where + S: Serializer, + { + let arg = match arg { + Some(AccelProfileOrig::Flat) => Some(AccelProfile::Flat), + Some(AccelProfileOrig::Adaptive) => Some(AccelProfile::Adaptive), + Some(_) | None => None, + }; + Option::serialize(&arg, ser) + } +} + +mod ScrollMethodDef { + use input::ScrollMethod as ScrollMethodOrig; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + #[derive(Debug, Serialize, Deserialize)] + pub enum ScrollMethod { + NoScroll, + TwoFinger, + Edge, + OnButtonDown, + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let o = Option::deserialize(deserializer)?; + Ok(o.map(|x| match x { + ScrollMethod::NoScroll => ScrollMethodOrig::NoScroll, + ScrollMethod::TwoFinger => ScrollMethodOrig::TwoFinger, + ScrollMethod::Edge => ScrollMethodOrig::Edge, + ScrollMethod::OnButtonDown => ScrollMethodOrig::OnButtonDown, + })) + } + + pub fn serialize(arg: &Option, ser: S) -> Result + where + S: Serializer, + { + let arg = match arg { + Some(ScrollMethodOrig::NoScroll) => Some(ScrollMethod::NoScroll), + Some(ScrollMethodOrig::TwoFinger) => Some(ScrollMethod::TwoFinger), + Some(ScrollMethodOrig::Edge) => Some(ScrollMethod::Edge), + Some(ScrollMethodOrig::OnButtonDown) => Some(ScrollMethod::OnButtonDown), + Some(_) | None => None, + }; + Option::serialize(&arg, ser) + } +} + +mod TapButtonMapDef { + use input::TapButtonMap as TapButtonMapOrig; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + #[derive(Debug, Serialize, Deserialize)] + pub enum TapButtonMap { + LeftRightMiddle, + LeftMiddleRight, + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let o = Option::deserialize(deserializer)?; + Ok(o.map(|x| match x { + TapButtonMap::LeftRightMiddle => TapButtonMapOrig::LeftRightMiddle, + TapButtonMap::LeftMiddleRight => TapButtonMapOrig::LeftMiddleRight, + })) + } + + pub fn serialize(arg: &Option, ser: S) -> Result + where + S: Serializer, + { + let arg = match arg { + Some(TapButtonMapOrig::LeftRightMiddle) => Some(TapButtonMap::LeftRightMiddle), + Some(TapButtonMapOrig::LeftMiddleRight) => Some(TapButtonMap::LeftMiddleRight), + Some(_) | None => None, + }; + Option::serialize(&arg, ser) + } +} diff --git a/cosmic-comp-config/src/lib.rs b/cosmic-comp-config/src/lib.rs new file mode 100644 index 00000000..b65c114b --- /dev/null +++ b/cosmic-comp-config/src/lib.rs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use serde::{Deserialize, Serialize}; + +pub mod input; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct XkbConfig { + pub rules: String, + pub model: String, + pub layout: String, + pub variant: String, + pub options: Option, +} + +impl Default for XkbConfig { + fn default() -> XkbConfig { + XkbConfig { + rules: String::new(), + model: String::new(), + layout: String::new(), + variant: String::new(), + options: None, + } + } +} diff --git a/src/config/input_config.rs b/src/config/input_config.rs index 86a01140..e979ec51 100644 --- a/src/config/input_config.rs +++ b/src/config/input_config.rs @@ -1,4 +1,3 @@ -use serde::{Deserialize, Serialize}; pub use smithay::{ backend::input::KeyState, input::keyboard::{keysyms as KeySyms, Keysym, ModifiersState}, @@ -14,302 +13,269 @@ pub use smithay::{ }; use tracing::warn; -use super::types::*; +use cosmic_comp_config::input::*; -#[derive(Debug, Deserialize, Serialize)] -pub struct InputConfig { - state: DeviceState, - #[serde(skip_serializing_if = "Option::is_none", default)] - acceleration: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - calibration: Option<[f32; 6]>, - #[serde(with = "ClickMethodDef")] - #[serde(skip_serializing_if = "Option::is_none", default)] - click_method: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - disable_while_typing: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - left_handed: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - middle_button_emulation: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - rotation_angle: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - scroll_config: Option, - #[serde(skip_serializing_if = "Option::is_none", default)] - tap_config: Option, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct AccelConfig { - #[serde(with = "AccelProfileDef")] - profile: Option, - speed: f64, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct ScrollConfig { - #[serde(with = "ScrollMethodDef")] - method: Option, - natural_scroll: Option, - scroll_button: Option, +#[allow(dead_code)] +pub fn for_device(device: &InputDevice) -> InputConfig { + InputConfig { + state: match device.config_send_events_mode() { + x if x.contains(SendEventsMode::ENABLED) => DeviceState::Enabled, + x if x.contains(SendEventsMode::DISABLED_ON_EXTERNAL_MOUSE) => { + DeviceState::DisabledOnExternalMouse + } + x if x.contains(SendEventsMode::DISABLED) => DeviceState::Disabled, + _ => DeviceState::Disabled, + }, + acceleration: if device.config_accel_is_available() { + Some(AccelConfig { + profile: device.config_accel_profile(), + speed: device.config_accel_speed(), + }) + } else { + None + }, + calibration: device.config_calibration_matrix(), + click_method: device.config_click_method(), + disable_while_typing: if device.config_dwt_is_available() { + Some(device.config_dwt_enabled()) + } else { + None + }, + left_handed: if device.config_left_handed_is_available() { + Some(device.config_left_handed()) + } else { + None + }, + middle_button_emulation: if device.config_middle_emulation_is_available() { + Some(device.config_middle_emulation_enabled()) + } else { + None + }, + rotation_angle: if device.config_rotation_is_available() { + Some(device.config_rotation_angle()) + } else { + None + }, + scroll_config: if device + .config_scroll_methods() + .iter() + .any(|x| *x != ScrollMethod::NoScroll) + { + Some(ScrollConfig { + method: device.config_scroll_method(), + natural_scroll: if device.config_scroll_has_natural_scroll() { + Some(device.config_scroll_natural_scroll_enabled()) + } else { + None + }, + scroll_button: if device.config_scroll_method() == Some(ScrollMethod::OnButtonDown) + { + Some(device.config_scroll_button()) + } else { + None + }, + }) + } else { + None + }, + tap_config: if device.config_tap_finger_count() > 0 { + Some(TapConfig { + enabled: device.config_tap_enabled(), + button_map: device.config_tap_button_map(), + drag: device.config_tap_drag_enabled(), + drag_lock: device.config_tap_drag_lock_enabled(), + }) + } else { + None + }, + } } -#[derive(Debug, Serialize, Deserialize)] -pub enum DeviceState { - Enabled, - Disabled, - DisabledOnExternalMouse, +fn get_config<'a, T: 'a, F: Fn(&'a InputConfig) -> Option>( + device_config: Option<&'a InputConfig>, + default_config: &'a InputConfig, + f: F, +) -> Option { + if let Some(device_config) = device_config { + if let Some(setting) = f(device_config) { + return Some(setting); + } + } + f(default_config) } -#[derive(Debug, Serialize, Deserialize)] -pub struct TapConfig { - enabled: bool, - #[serde(with = "TapButtonMapDef")] - button_map: Option, - drag: bool, - drag_lock: bool, -} +pub fn update_device( + device: &mut InputDevice, + device_config: Option<&InputConfig>, + default_config: &InputConfig, +) { + macro_rules! config { + ($f:expr) => { + get_config(device_config, default_config, $f) + }; + } -impl InputConfig { - pub fn for_device(device: &InputDevice) -> Self { - InputConfig { - state: match device.config_send_events_mode() { - x if x.contains(SendEventsMode::ENABLED) => DeviceState::Enabled, - x if x.contains(SendEventsMode::DISABLED_ON_EXTERNAL_MOUSE) => { - DeviceState::DisabledOnExternalMouse - } - x if x.contains(SendEventsMode::DISABLED) => DeviceState::Disabled, - _ => DeviceState::Disabled, - }, - acceleration: if device.config_accel_is_available() { - Some(AccelConfig { - profile: device.config_accel_profile(), - speed: device.config_accel_speed(), - }) - } else { - None - }, - calibration: device.config_calibration_matrix(), - click_method: device.config_click_method(), - disable_while_typing: if device.config_dwt_is_available() { - Some(device.config_dwt_enabled()) - } else { - None - }, - left_handed: if device.config_left_handed_is_available() { - Some(device.config_left_handed()) - } else { - None - }, - middle_button_emulation: if device.config_middle_emulation_is_available() { - Some(device.config_middle_emulation_enabled()) - } else { - None - }, - rotation_angle: if device.config_rotation_is_available() { - Some(device.config_rotation_angle()) - } else { - None - }, - scroll_config: if device - .config_scroll_methods() - .iter() - .any(|x| *x != ScrollMethod::NoScroll) - { - Some(ScrollConfig { - method: device.config_scroll_method(), - natural_scroll: if device.config_scroll_has_natural_scroll() { - Some(device.config_scroll_natural_scroll_enabled()) - } else { - None - }, - scroll_button: if device.config_scroll_method() - == Some(ScrollMethod::OnButtonDown) - { - Some(device.config_scroll_button()) - } else { - None - }, - }) - } else { - None - }, - tap_config: if device.config_tap_finger_count() > 0 { - Some(TapConfig { - enabled: device.config_tap_enabled(), - button_map: device.config_tap_button_map(), - drag: device.config_tap_drag_enabled(), - drag_lock: device.config_tap_drag_lock_enabled(), - }) - } else { - None - }, + let state = device_config.unwrap_or(default_config).state; + if let Err(err) = match state { + DeviceState::Enabled => device.config_send_events_set_mode(SendEventsMode::ENABLED), + DeviceState::Disabled => device.config_send_events_set_mode(SendEventsMode::DISABLED), + DeviceState::DisabledOnExternalMouse => { + device.config_send_events_set_mode(SendEventsMode::DISABLED_ON_EXTERNAL_MOUSE) } + } { + warn!( + ?err, + "Failed to apply mode {:?} for device {:?}.", + state, + device.name(), + ); } - - pub fn update_device(&self, device: &mut InputDevice) { - if let Err(err) = match self.state { - DeviceState::Enabled => device.config_send_events_set_mode(SendEventsMode::ENABLED), - DeviceState::Disabled => device.config_send_events_set_mode(SendEventsMode::DISABLED), - DeviceState::DisabledOnExternalMouse => { - device.config_send_events_set_mode(SendEventsMode::DISABLED_ON_EXTERNAL_MOUSE) + if let Some(accel) = config!(|x| x.acceleration.as_ref()) { + if let Some(profile) = accel.profile { + if let Err(err) = device.config_accel_set_profile(profile) { + warn!( + ?err, + "Failed to apply acceleration profile {:?} for device {:?}.", + profile, + device.name(), + ); } - } { + } + if let Err(err) = device.config_accel_set_speed(accel.speed) { warn!( ?err, - "Failed to apply mode {:?} for device {:?}.", - self.state, + "Failed to apply acceleration speed {:?} for device {:?}.", + accel.speed, device.name(), ); } - if let Some(accel) = self.acceleration.as_ref() { - if let Some(profile) = accel.profile { - if let Err(err) = device.config_accel_set_profile(profile) { - warn!( - ?err, - "Failed to apply acceleration profile {:?} for device {:?}.", - profile, - device.name(), - ); - } - } - if let Err(err) = device.config_accel_set_speed(accel.speed) { - warn!( - ?err, - "Failed to apply acceleration speed {:?} for device {:?}.", - accel.speed, - device.name(), - ); - } + } + if let Some(matrix) = config!(|x| x.calibration) { + if let Err(err) = device.config_calibration_set_matrix(matrix) { + warn!( + ?err, + "Failed to apply calibration matrix {:?} for device {:?}.", + matrix, + device.name(), + ); } - if let Some(matrix) = self.calibration { - if let Err(err) = device.config_calibration_set_matrix(matrix) { - warn!( - ?err, - "Failed to apply calibration matrix {:?} for device {:?}.", - matrix, - device.name(), - ); - } + } + if let Some(method) = config!(|x| x.click_method) { + if let Err(err) = device.config_click_set_method(method) { + warn!( + ?err, + "Failed to apply click method {:?} for device {:?}.", + method, + device.name(), + ); } - if let Some(method) = self.click_method { - if let Err(err) = device.config_click_set_method(method) { + } + if let Some(dwt) = config!(|x| x.disable_while_typing) { + if let Err(err) = device.config_dwt_set_enabled(dwt) { + warn!( + ?err, + "Failed to apply disable-while-typing {:?} for device {:?}.", + dwt, + device.name(), + ); + } + } + if let Some(left) = config!(|x| x.left_handed) { + if let Err(err) = device.config_left_handed_set(left) { + warn!( + ?err, + "Failed to apply left-handed {:?} for device {:?}.", + left, + device.name(), + ); + } + } + if let Some(middle) = config!(|x| x.middle_button_emulation) { + if let Err(err) = device.config_middle_emulation_set_enabled(middle) { + warn!( + ?err, + "Failed to apply middle-button-emulation {:?} for device {:?}.", + middle, + device.name(), + ); + } + } + if let Some(angle) = config!(|x| x.rotation_angle) { + if let Err(err) = device.config_rotation_set_angle(angle) { + warn!( + ?err, + "Failed to apply rotation-angle {:?} for device {:?}", + angle, + device.name(), + ); + } + } + if let Some(scroll) = config!(|x| x.scroll_config.as_ref()) { + if let Some(method) = scroll.method { + if let Err(err) = device.config_scroll_set_method(method) { warn!( ?err, - "Failed to apply click method {:?} for device {:?}.", + "Failed to apply scroll method {:?} for device {:?}.", method, device.name(), ); } } - if let Some(dwt) = self.disable_while_typing { - if let Err(err) = device.config_dwt_set_enabled(dwt) { + if let Some(natural) = scroll.natural_scroll { + if let Err(err) = device.config_scroll_set_natural_scroll_enabled(natural) { warn!( ?err, - "Failed to apply disable-while-typing {:?} for device {:?}.", - dwt, + "Failed to apply natural scrolling {:?} for device {:?}.", + natural, device.name(), ); } } - if let Some(left) = self.left_handed { - if let Err(err) = device.config_left_handed_set(left) { + if let Some(button) = scroll.scroll_button { + if let Err(err) = device.config_scroll_set_button(button) { warn!( ?err, - "Failed to apply left-handed {:?} for device {:?}.", - left, + "Failed to apply scroll button {:?} for device {:?}.", + button, device.name(), ); } } - if let Some(middle) = self.middle_button_emulation { - if let Err(err) = device.config_middle_emulation_set_enabled(middle) { - warn!( - ?err, - "Failed to apply middle-button-emulation {:?} for device {:?}.", - middle, - device.name(), - ); - } + } + if let Some(tap) = config!(|x| x.tap_config.as_ref()) { + if let Err(err) = device.config_tap_set_enabled(tap.enabled) { + warn!( + ?err, + "Failed to apply tap-to-click {:?} for device {:?}.", + tap.enabled, + device.name(), + ); } - if let Some(angle) = self.rotation_angle { - if let Err(err) = device.config_rotation_set_angle(angle) { + if let Some(button_map) = tap.button_map { + if let Err(err) = device.config_tap_set_button_map(button_map) { warn!( ?err, - "Failed to apply rotation-angle {:?} for device {:?}", - angle, + "Failed to apply button map {:?} for device {:?}.", + button_map, device.name(), ); } } - if let Some(scroll) = self.scroll_config.as_ref() { - if let Some(method) = scroll.method { - if let Err(err) = device.config_scroll_set_method(method) { - warn!( - ?err, - "Failed to apply scroll method {:?} for device {:?}.", - method, - device.name(), - ); - } - } - if let Some(natural) = scroll.natural_scroll { - if let Err(err) = device.config_scroll_set_natural_scroll_enabled(natural) { - warn!( - ?err, - "Failed to apply natural scrolling {:?} for device {:?}.", - natural, - device.name(), - ); - } - } - if let Some(button) = scroll.scroll_button { - if let Err(err) = device.config_scroll_set_button(button) { - warn!( - ?err, - "Failed to apply scroll button {:?} for device {:?}.", - button, - device.name(), - ); - } - } + if let Err(err) = device.config_tap_set_drag_enabled(tap.drag) { + warn!( + ?err, + "Failed to apply tap-drag {:?} for device {:?}.", + tap.drag, + device.name(), + ); } - if let Some(tap) = self.tap_config.as_ref() { - if let Err(err) = device.config_tap_set_enabled(tap.enabled) { - warn!( - ?err, - "Failed to apply tap-to-click {:?} for device {:?}.", - tap.enabled, - device.name(), - ); - } - if let Some(button_map) = tap.button_map { - if let Err(err) = device.config_tap_set_button_map(button_map) { - warn!( - ?err, - "Failed to apply button map {:?} for device {:?}.", - button_map, - device.name(), - ); - } - } - if let Err(err) = device.config_tap_set_drag_enabled(tap.drag) { - warn!( - ?err, - "Failed to apply tap-drag {:?} for device {:?}.", - tap.drag, - device.name(), - ); - } - if let Err(err) = device.config_tap_set_drag_lock_enabled(tap.drag_lock) { - warn!( - ?err, - "Failed to apply tap-drag-lock {:?} for device {:?}.", - tap.drag_lock, - device.name(), - ); - } + if let Err(err) = device.config_tap_set_drag_lock_enabled(tap.drag_lock) { + warn!( + ?err, + "Failed to apply tap-drag-lock {:?} for device {:?}.", + tap.drag_lock, + device.name(), + ); } } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 81dd8da4..826bf220 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -5,7 +5,7 @@ use crate::{ state::{BackendData, Data, State}, wayland::protocols::output_configuration::OutputConfigurationState, }; -use cosmic_config::{ConfigGet, ConfigSet}; +use cosmic_config::ConfigGet; use serde::{Deserialize, Serialize}; use smithay::input::Seat; pub use smithay::{ @@ -25,11 +25,11 @@ use std::{cell::RefCell, collections::HashMap, fs::OpenOptions, path::PathBuf}; use tracing::{debug, error, info, warn}; mod input_config; -use input_config::InputConfig; mod key_bindings; pub use key_bindings::{Action, KeyModifier, KeyModifiers, KeyPattern}; mod types; pub use self::types::*; +use cosmic_comp_config::{input::InputConfig, XkbConfig}; #[derive(Debug)] pub struct Config { @@ -37,6 +37,8 @@ pub struct Config { pub dynamic_conf: DynamicConfig, pub config: cosmic_config::Config, pub xkb: XkbConfig, + pub input_default: InputConfig, + pub input_touchpad: InputConfig, pub input_devices: HashMap, } @@ -169,6 +171,8 @@ impl Config { static_conf: Self::load_static(xdg.as_ref()), dynamic_conf: Self::load_dynamic(xdg.as_ref()), xkb: get_config(&config, "xkb-config"), + input_default: get_config(&config, "input-default"), + input_touchpad: get_config(&config, "input-touchpad"), input_devices: get_config(&config, "input-devices"), config, } @@ -407,22 +411,14 @@ impl Config { self.xkb.clone() } - pub fn read_device(&mut self, device: &mut InputDevice) { - use std::collections::hash_map::Entry; - - let mut config_changed = false; - match self.input_devices.entry(device.name().into()) { - Entry::Occupied(entry) => entry.get().update_device(device), - Entry::Vacant(entry) => { - entry.insert(InputConfig::for_device(device)); - config_changed = true; - } - } - if config_changed { - if let Err(err) = self.config.set("input-devices", &self.input_devices) { - error!(?err, "Failed to write config 'input-devices'"); - } - } + pub fn read_device(&self, device: &mut InputDevice) { + let default_config = if device.config_tap_finger_count() > 0 { + &self.input_touchpad + } else { + &self.input_default + }; + let device_config = self.input_devices.get(device.name()); + input_config::update_device(device, device_config, default_config); } } @@ -483,6 +479,14 @@ fn get_config( }) } +fn update_input(state: &mut State) { + if let BackendData::Kms(ref mut kms_state) = &mut state.backend { + for device in kms_state.input_devices.values_mut() { + state.common.config.read_device(device); + } + } +} + fn config_changed(config: cosmic_config::Config, keys: Vec, state: &mut State) { for key in &keys { match key.as_str() { @@ -490,7 +494,7 @@ fn config_changed(config: cosmic_config::Config, keys: Vec, state: &mut let value = get_config::(&config, "xkb-config"); for seat in state.common.seats().cloned().collect::>().iter() { if let Some(keyboard) = seat.get_keyboard() { - if let Err(err) = keyboard.set_xkb_config(state, (&value).into()) { + if let Err(err) = keyboard.set_xkb_config(state, xkb_config_to_wl(&value)) { error!(?err, "Failed to load provided xkb config"); // TODO Revert to default? } @@ -498,18 +502,32 @@ fn config_changed(config: cosmic_config::Config, keys: Vec, state: &mut } state.common.config.xkb = value; } + "input-default" => { + let value = get_config::(&config, "input-default"); + state.common.config.input_default = value; + update_input(state); + } + "input-touchpad" => { + let value = get_config::(&config, "input-touchpad"); + state.common.config.input_touchpad = value; + update_input(state); + } "input-devices" => { let value = get_config::>(&config, "input-devices"); - if let BackendData::Kms(ref mut kms_state) = &mut state.backend { - for (name, device) in kms_state.input_devices.iter_mut() { - if let Some(input_config) = value.get(name) { - input_config.update_device(device); - } - } - } state.common.config.input_devices = value; + update_input(state); } _ => {} } } } + +pub fn xkb_config_to_wl(config: &XkbConfig) -> WlXkbConfig<'_> { + WlXkbConfig { + rules: &config.rules, + model: &config.model, + layout: &config.layout, + variant: &config.variant, + options: config.options.clone(), + } +} diff --git a/src/config/types.rs b/src/config/types.rs index d9f2577c..349dc216 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -13,147 +13,6 @@ pub use smithay::{ use tracing::warn; use xkbcommon::xkb; -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct XkbConfig { - pub rules: String, - pub model: String, - pub layout: String, - pub variant: String, - pub options: Option, -} - -impl Default for XkbConfig { - fn default() -> XkbConfig { - XkbConfig { - rules: String::new(), - model: String::new(), - layout: String::new(), - variant: String::new(), - options: None, - } - } -} - -impl<'a> Into> for &'a XkbConfig { - fn into(self) -> WlXkbConfig<'a> { - WlXkbConfig { - rules: &self.rules, - model: &self.model, - layout: &self.layout, - variant: &self.variant, - options: self.options.clone(), - } - } -} - -pub mod ClickMethodDef { - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - use smithay::reexports::input::ClickMethod as ClickMethodOrig; - - #[derive(Debug, Serialize, Deserialize)] - pub enum ClickMethod { - ButtonAreas, - Clickfinger, - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let o = Option::deserialize(deserializer)?; - Ok(o.map(|x| match x { - ClickMethod::ButtonAreas => ClickMethodOrig::ButtonAreas, - ClickMethod::Clickfinger => ClickMethodOrig::Clickfinger, - })) - } - - pub fn serialize(arg: &Option, ser: S) -> Result - where - S: Serializer, - { - let arg = match arg { - Some(ClickMethodOrig::ButtonAreas) => Some(ClickMethod::ButtonAreas), - Some(ClickMethodOrig::Clickfinger) => Some(ClickMethod::Clickfinger), - Some(_) | None => None, - }; - Option::serialize(&arg, ser) - } -} - -pub mod AccelProfileDef { - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - use smithay::reexports::input::AccelProfile as AccelProfileOrig; - - #[derive(Debug, Serialize, Deserialize)] - enum AccelProfile { - Flat, - Adaptive, - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let o = Option::deserialize(deserializer)?; - Ok(o.map(|x| match x { - AccelProfile::Flat => AccelProfileOrig::Flat, - AccelProfile::Adaptive => AccelProfileOrig::Adaptive, - })) - } - - pub fn serialize(arg: &Option, ser: S) -> Result - where - S: Serializer, - { - let arg = match arg { - Some(AccelProfileOrig::Flat) => Some(AccelProfile::Flat), - Some(AccelProfileOrig::Adaptive) => Some(AccelProfile::Adaptive), - Some(_) | None => None, - }; - Option::serialize(&arg, ser) - } -} - -pub mod ScrollMethodDef { - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - use smithay::reexports::input::ScrollMethod as ScrollMethodOrig; - - #[derive(Debug, Serialize, Deserialize)] - pub enum ScrollMethod { - NoScroll, - TwoFinger, - Edge, - OnButtonDown, - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let o = Option::deserialize(deserializer)?; - Ok(o.map(|x| match x { - ScrollMethod::NoScroll => ScrollMethodOrig::NoScroll, - ScrollMethod::TwoFinger => ScrollMethodOrig::TwoFinger, - ScrollMethod::Edge => ScrollMethodOrig::Edge, - ScrollMethod::OnButtonDown => ScrollMethodOrig::OnButtonDown, - })) - } - - pub fn serialize(arg: &Option, ser: S) -> Result - where - S: Serializer, - { - let arg = match arg { - Some(ScrollMethodOrig::NoScroll) => Some(ScrollMethod::NoScroll), - Some(ScrollMethodOrig::TwoFinger) => Some(ScrollMethod::TwoFinger), - Some(ScrollMethodOrig::Edge) => Some(ScrollMethod::Edge), - Some(ScrollMethodOrig::OnButtonDown) => Some(ScrollMethod::OnButtonDown), - Some(_) | None => None, - }; - Option::serialize(&arg, ser) - } -} - #[derive(Serialize, Deserialize)] #[serde(remote = "Transform")] pub enum TransformDef { @@ -167,40 +26,6 @@ pub enum TransformDef { Flipped270, } -pub mod TapButtonMapDef { - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - use smithay::reexports::input::TapButtonMap as TapButtonMapOrig; - - #[derive(Debug, Serialize, Deserialize)] - pub enum TapButtonMap { - LeftRightMiddle, - LeftMiddleRight, - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let o = Option::deserialize(deserializer)?; - Ok(o.map(|x| match x { - TapButtonMap::LeftRightMiddle => TapButtonMapOrig::LeftRightMiddle, - TapButtonMap::LeftMiddleRight => TapButtonMapOrig::LeftMiddleRight, - })) - } - - pub fn serialize(arg: &Option, ser: S) -> Result - where - S: Serializer, - { - let arg = match arg { - Some(TapButtonMapOrig::LeftRightMiddle) => Some(TapButtonMap::LeftRightMiddle), - Some(TapButtonMapOrig::LeftMiddleRight) => Some(TapButtonMap::LeftMiddleRight), - Some(_) | None => None, - }; - Option::serialize(&arg, ser) - } -} - #[derive(Deserialize)] #[serde(transparent)] pub struct KeyModifiersDef(Vec); diff --git a/src/input/mod.rs b/src/input/mod.rs index 0c647243..27ec163f 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -2,7 +2,7 @@ use crate::{ backend::render::cursor::CursorState, - config::{Action, Config, KeyPattern, WorkspaceLayout}, + config::{xkb_config_to_wl, Action, Config, KeyPattern, WorkspaceLayout}, shell::{ focus::{target::PointerFocusTarget, FocusDirection}, grabs::{ResizeEdge, SeatMoveGrabState}, @@ -153,7 +153,7 @@ pub fn add_seat( // So instead of doing the right thing (and initialize these capabilities as matching // devices appear), we have to surrender to reality and just always expose a keyboard and pointer. let conf = config.xkb_config(); - if let Err(err) = seat.add_keyboard((&conf).into(), 200, 25) { + if let Err(err) = seat.add_keyboard(xkb_config_to_wl(&conf), 200, 25) { warn!( ?err, "Failed to load provided xkb config. Trying default...", From 8c3a3a9d1aa59e6dec44c388b1da88ecd5e606c3 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 31 Aug 2023 13:57:57 -0700 Subject: [PATCH 5/7] cosmic_comp_config: Some trait implementations and re-exports --- cosmic-comp-config/src/input.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cosmic-comp-config/src/input.rs b/cosmic-comp-config/src/input.rs index 1d15abb0..d085a278 100644 --- a/cosmic-comp-config/src/input.rs +++ b/cosmic-comp-config/src/input.rs @@ -2,10 +2,10 @@ #![allow(non_snake_case)] -use input::{AccelProfile, ClickMethod, ScrollMethod, TapButtonMap}; +pub use input::{AccelProfile, ClickMethod, ScrollMethod, TapButtonMap}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct InputConfig { pub state: DeviceState, #[serde(skip_serializing_if = "Option::is_none", default)] @@ -29,14 +29,14 @@ pub struct InputConfig { pub tap_config: Option, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct AccelConfig { #[serde(with = "AccelProfileDef")] pub profile: Option, pub speed: f64, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct ScrollConfig { #[serde(with = "ScrollMethodDef")] pub method: Option, @@ -57,7 +57,7 @@ impl Default for DeviceState { } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TapConfig { pub enabled: bool, #[serde(with = "TapButtonMapDef")] From 34eba9e75b740e9ce69b99923875663e3345df06 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 31 Aug 2023 13:58:37 -0700 Subject: [PATCH 6/7] config: Ignore `DeviceConfigError::Unsupported` for defaults Also reduces duplication. --- src/config/input_config.rs | 170 ++++++++++++------------------------- 1 file changed, 53 insertions(+), 117 deletions(-) diff --git a/src/config/input_config.rs b/src/config/input_config.rs index e979ec51..463d678d 100644 --- a/src/config/input_config.rs +++ b/src/config/input_config.rs @@ -1,15 +1,5 @@ -pub use smithay::{ - backend::input::KeyState, - input::keyboard::{keysyms as KeySyms, Keysym, ModifiersState}, - output::{Mode, Output}, - reexports::{ - calloop::LoopHandle, - input::{ - AccelProfile, ClickMethod, Device as InputDevice, ScrollMethod, SendEventsMode, - TapButtonMap, - }, - }, - utils::{Logical, Physical, Point, Size, Transform}, +use smithay::reexports::input::{ + Device as InputDevice, DeviceConfigError, ScrollMethod, SendEventsMode, }; use tracing::warn; @@ -91,17 +81,38 @@ pub fn for_device(device: &InputDevice) -> InputConfig { } } +// Get setting from `device_config` if present, then `default_config` +// Returns `is_default` to indicate this is a default value. fn get_config<'a, T: 'a, F: Fn(&'a InputConfig) -> Option>( device_config: Option<&'a InputConfig>, default_config: &'a InputConfig, f: F, -) -> Option { - if let Some(device_config) = device_config { - if let Some(setting) = f(device_config) { - return Some(setting); - } +) -> Option<(T, bool)> { + if let Some(setting) = device_config.and_then(&f) { + Some((setting, false)) + } else if let Some(setting) = f(default_config) { + Some((setting, true)) + } else { + None + } +} + +fn config_set_error( + device: &InputDevice, + setting: &str, + value: T, + err: DeviceConfigError, + is_default: bool, +) { + if !(is_default && err == DeviceConfigError::Unsupported) { + warn!( + ?err, + "Failed to apply {} {:?} for device {:?}.", + setting, + value, + device.name(), + ); } - f(default_config) } pub fn update_device( @@ -130,152 +141,77 @@ pub fn update_device( device.name(), ); } - if let Some(accel) = config!(|x| x.acceleration.as_ref()) { + if let Some((accel, is_default)) = config!(|x| x.acceleration.as_ref()) { if let Some(profile) = accel.profile { if let Err(err) = device.config_accel_set_profile(profile) { - warn!( - ?err, - "Failed to apply acceleration profile {:?} for device {:?}.", - profile, - device.name(), - ); + config_set_error(device, "acceleration profile", profile, err, is_default); } } if let Err(err) = device.config_accel_set_speed(accel.speed) { - warn!( - ?err, - "Failed to apply acceleration speed {:?} for device {:?}.", - accel.speed, - device.name(), - ); + config_set_error(device, "acceleration speed", accel.speed, err, is_default); } } - if let Some(matrix) = config!(|x| x.calibration) { + if let Some((matrix, is_default)) = config!(|x| x.calibration) { if let Err(err) = device.config_calibration_set_matrix(matrix) { - warn!( - ?err, - "Failed to apply calibration matrix {:?} for device {:?}.", - matrix, - device.name(), - ); + config_set_error(device, "calibration matrix", matrix, err, is_default); } } - if let Some(method) = config!(|x| x.click_method) { + if let Some((method, is_default)) = config!(|x| x.click_method) { if let Err(err) = device.config_click_set_method(method) { - warn!( - ?err, - "Failed to apply click method {:?} for device {:?}.", - method, - device.name(), - ); + config_set_error(device, "click method", method, err, is_default); } } - if let Some(dwt) = config!(|x| x.disable_while_typing) { + if let Some((dwt, is_default)) = config!(|x| x.disable_while_typing) { if let Err(err) = device.config_dwt_set_enabled(dwt) { - warn!( - ?err, - "Failed to apply disable-while-typing {:?} for device {:?}.", - dwt, - device.name(), - ); + config_set_error(device, "disable-while-typing", dwt, err, is_default); } } - if let Some(left) = config!(|x| x.left_handed) { + if let Some((left, is_default)) = config!(|x| x.left_handed) { if let Err(err) = device.config_left_handed_set(left) { - warn!( - ?err, - "Failed to apply left-handed {:?} for device {:?}.", - left, - device.name(), - ); + config_set_error(device, "left-handed", left, err, is_default); } } - if let Some(middle) = config!(|x| x.middle_button_emulation) { + if let Some((middle, is_default)) = config!(|x| x.middle_button_emulation) { if let Err(err) = device.config_middle_emulation_set_enabled(middle) { - warn!( - ?err, - "Failed to apply middle-button-emulation {:?} for device {:?}.", - middle, - device.name(), - ); + config_set_error(device, "middle-button-emulation", middle, err, is_default); } } - if let Some(angle) = config!(|x| x.rotation_angle) { + if let Some((angle, is_default)) = config!(|x| x.rotation_angle) { if let Err(err) = device.config_rotation_set_angle(angle) { - warn!( - ?err, - "Failed to apply rotation-angle {:?} for device {:?}", - angle, - device.name(), - ); + config_set_error(device, "rotation-angle", angle, err, is_default); } } - if let Some(scroll) = config!(|x| x.scroll_config.as_ref()) { + if let Some((scroll, is_default)) = config!(|x| x.scroll_config.as_ref()) { if let Some(method) = scroll.method { if let Err(err) = device.config_scroll_set_method(method) { - warn!( - ?err, - "Failed to apply scroll method {:?} for device {:?}.", - method, - device.name(), - ); + config_set_error(device, "scroll method", scroll, err, is_default); } } if let Some(natural) = scroll.natural_scroll { if let Err(err) = device.config_scroll_set_natural_scroll_enabled(natural) { - warn!( - ?err, - "Failed to apply natural scrolling {:?} for device {:?}.", - natural, - device.name(), - ); + config_set_error(device, "natural scrolling", natural, err, is_default); } } if let Some(button) = scroll.scroll_button { if let Err(err) = device.config_scroll_set_button(button) { - warn!( - ?err, - "Failed to apply scroll button {:?} for device {:?}.", - button, - device.name(), - ); + config_set_error(device, "scroll button", button, err, is_default); } } } - if let Some(tap) = config!(|x| x.tap_config.as_ref()) { + if let Some((tap, is_default)) = config!(|x| x.tap_config.as_ref()) { if let Err(err) = device.config_tap_set_enabled(tap.enabled) { - warn!( - ?err, - "Failed to apply tap-to-click {:?} for device {:?}.", - tap.enabled, - device.name(), - ); + config_set_error(device, "tap-to-click", tap.enabled, err, is_default); } if let Some(button_map) = tap.button_map { if let Err(err) = device.config_tap_set_button_map(button_map) { - warn!( - ?err, - "Failed to apply button map {:?} for device {:?}.", - button_map, - device.name(), - ); + config_set_error(device, "button map", button_map, err, is_default); } } if let Err(err) = device.config_tap_set_drag_enabled(tap.drag) { - warn!( - ?err, - "Failed to apply tap-drag {:?} for device {:?}.", - tap.drag, - device.name(), - ); + config_set_error(device, "tap-drag", tap.drag, err, is_default); } if let Err(err) = device.config_tap_set_drag_lock_enabled(tap.drag_lock) { - warn!( - ?err, - "Failed to apply tap-drag-lock {:?} for device {:?}.", - tap.drag_lock, - device.name(), - ); + config_set_error(device, "tap-drag-lock", tap.drag_lock, err, is_default); } } } From 1ea0ffdb91b8d22e2c590b325865d75bda093b14 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 31 Aug 2023 14:03:04 -0700 Subject: [PATCH 7/7] config: Fix default workspace right bindings --- src/config/key_bindings.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/key_bindings.rs b/src/config/key_bindings.rs index b74c240f..d8b8bd06 100644 --- a/src/config/key_bindings.rs +++ b/src/config/key_bindings.rs @@ -195,7 +195,7 @@ pub fn add_default_bindings( { WorkspaceLayout::Horizontal => ( [KeySyms::KEY_Left, KeySyms::KEY_h], - [KeySyms::KEY_Right, KeySyms::KEY_j], + [KeySyms::KEY_Right, KeySyms::KEY_l], [KeySyms::KEY_Up, KeySyms::KEY_k], [KeySyms::KEY_Down, KeySyms::KEY_j], ), @@ -203,7 +203,7 @@ pub fn add_default_bindings( [KeySyms::KEY_Up, KeySyms::KEY_k], [KeySyms::KEY_Down, KeySyms::KEY_j], [KeySyms::KEY_Left, KeySyms::KEY_h], - [KeySyms::KEY_Right, KeySyms::KEY_j], + [KeySyms::KEY_Right, KeySyms::KEY_l], ), };