From e5cd473a3a3bc03304dbcf6d529f8c86feff19b5 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 25 Apr 2022 23:00:30 +0200 Subject: [PATCH] config: input device configuration --- src/backend/kms/mod.rs | 6 +- src/config.rs | 476 ------------------------------ src/config/mod.rs | 650 +++++++++++++++++++++++++++++++++++++++++ src/config/types.rs | 251 ++++++++++++++++ 4 files changed, 906 insertions(+), 477 deletions(-) delete mode 100644 src/config.rs create mode 100644 src/config/mod.rs create mode 100644 src/config/types.rs diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index e8f2d478..1ab0de7d 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -16,6 +16,7 @@ use smithay::{ allocator::{gbm::GbmDevice, Format}, drm::{DrmDevice, DrmEvent, DrmEventTime, DrmNode, GbmBufferedSurface, NodeType}, egl::{EGLContext, EGLDevice, EGLDisplay}, + input::InputEvent, libinput::{LibinputInputBackend, LibinputSessionInterface}, renderer::{ multigpu::{egl::EglGlesBackend, GpuManager}, @@ -101,7 +102,10 @@ pub fn init_backend(event_loop: &mut EventLoop, state: &mut State) -> Res let libinput_event_source = event_loop .handle() - .insert_source(libinput_backend, move |event, _, state| { + .insert_source(libinput_backend, move |mut event, _, state| { + if let &mut InputEvent::DeviceAdded { ref mut device } = &mut event { + state.common.config.read_device(device); + } state.process_input_event(event); for output in state.common.shell.outputs() { state.backend.kms().schedule_render(output); diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index e9a07476..00000000 --- a/src/config.rs +++ /dev/null @@ -1,476 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -use crate::{ - shell::{ - Shell, - layout::FocusDirection, - }, - state::BackendData, -}; -use serde::{Deserialize, Serialize}; -pub use smithay::{ - backend::input::KeyState, - utils::{Logical, Physical, Point, Size, Transform}, - wayland::{ - output::{Mode, Output}, - seat::{keysyms as KeySyms, Keysym, ModifiersState as KeyModifiers}, - }, -}; -use std::{cell::RefCell, collections::HashMap, fs::OpenOptions, path::PathBuf}; -use xkbcommon::xkb; - -pub struct Config { - pub static_conf: StaticConfig, - pub dynamic_conf: DynamicConfig, -} - -#[derive(Debug, Deserialize)] -pub struct StaticConfig { - pub key_bindings: HashMap, - pub workspace_mode: crate::shell::Mode, -} - -pub struct DynamicConfig { - outputs: (Option, OutputsConfig), -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct OutputsConfig { - pub config: HashMap, Vec>, -} - -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct OutputInfo { - pub connector: String, - pub make: String, - pub model: String, -} - -impl From for OutputInfo { - fn from(o: Output) -> OutputInfo { - let physical = o.physical_properties(); - OutputInfo { - connector: o.name(), - make: physical.make, - model: physical.model, - } - } -} - -fn default_enabled() -> bool { - true -} - -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] -pub struct OutputConfig { - pub mode: ((i32, i32), Option), - pub vrr: bool, - pub scale: f64, - #[serde(with = "TransformDef")] - pub transform: Transform, - pub position: (i32, i32), - #[serde(default = "default_enabled")] - pub enabled: bool, -} - -impl Default for OutputConfig { - fn default() -> OutputConfig { - OutputConfig { - mode: ((0, 0), None), - vrr: false, - scale: 1.0, - transform: Transform::Normal, - position: (0, 0), - enabled: true, - } - } -} - -impl OutputConfig { - pub fn mode_size(&self) -> Size { - self.mode.0.into() - } - - pub fn mode_refresh(&self) -> u32 { - self.mode.1.unwrap_or(60_000) - } - - pub fn output_mode(&self) -> Mode { - Mode { - size: self.mode_size(), - refresh: self.mode_refresh() as i32, - } - } -} - -#[derive(Serialize, Deserialize)] -#[serde(remote = "Transform")] -enum TransformDef { - Normal, - _90, - _180, - _270, - Flipped, - Flipped90, - Flipped180, - Flipped270, -} - -impl Config { - pub fn load() -> Config { - let xdg = xdg::BaseDirectories::new().ok(); - Config { - static_conf: Self::load_static(xdg.as_ref()), - dynamic_conf: Self::load_dynamic(xdg.as_ref()), - } - } - - fn load_static(xdg: Option<&xdg::BaseDirectories>) -> StaticConfig { - let mut locations = if let Some(base) = xdg { - vec![ - base.get_config_file("cosmic-comp.ron"), - base.get_config_file("cosmic-comp/config.ron"), - ] - } else { - Vec::with_capacity(3) - }; - if cfg!(debug_assertions) { - if let Ok(mut cwd) = std::env::current_dir() { - cwd.push("config.ron"); - locations.push(cwd); - } - } - locations.push(PathBuf::from("/etc/cosmic-comp/config.ron")); - locations.push(PathBuf::from("/etc/cosmic-comp.ron")); - - for path in locations { - slog_scope::debug!("Trying config location: {}", path.display()); - if path.exists() { - slog_scope::info!("Using config at {}", path.display()); - return ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) - .expect("Malformed config file"); - } - } - - StaticConfig { - key_bindings: HashMap::new(), - workspace_mode: crate::shell::Mode::global(), - } - } - - fn load_dynamic(xdg: Option<&xdg::BaseDirectories>) -> DynamicConfig { - let output_path = - xdg.and_then(|base| base.place_state_file("cosmic-comp/outputs.ron").ok()); - let outputs = Self::load_outputs(&output_path); - - DynamicConfig { - outputs: (output_path, outputs), - } - } - - fn load_outputs(path: &Option) -> OutputsConfig { - 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) => { - slog_scope::warn!("Failed to read output_config ({}), resetting..", err); - if let Err(err) = std::fs::remove_file(path) { - slog_scope::error!("Failed to remove output_config {}", err); - } - } - }; - } - } - - OutputsConfig { - config: HashMap::new(), - } - } - - pub fn read_outputs<'a>( - &mut self, - outputs: impl Iterator>, - backend: &mut BackendData, - shell: &mut Shell, - ) { - let outputs = outputs.map(|x| x.borrow().clone()).collect::>(); - let mut infos = outputs - .iter() - .cloned() - .map(Into::::into) - .collect::>(); - infos.sort(); - if let Some(configs) = self.dynamic_conf.outputs().config.get(&infos).cloned() { - let mut reset = false; - let known_good_configs = outputs - .iter() - .map(|output| { - output - .user_data() - .get::>() - .unwrap() - .borrow() - .clone() - }) - .collect::>(); - - for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter()) - { - let output = outputs - .iter() - .find(|o| &o.name() == name) - .unwrap() - .clone(); - *output - .user_data() - .get::>() - .unwrap() - .borrow_mut() = output_config; - if let Err(err) = backend.apply_config_for_output(&output, false, shell) { - slog_scope::warn!( - "Failed to set new config for output {}: {}", - output.name(), - err - ); - reset = true; - break; - } - } - - if reset { - for (output, output_config) in outputs - .clone() - .into_iter() - .zip(known_good_configs.into_iter()) - { - *output - .user_data() - .get::>() - .unwrap() - .borrow_mut() = output_config; - if let Err(err) = backend.apply_config_for_output(&output, false, shell) - { - slog_scope::error!( - "Failed to reset config for output {}: {}", - output.name(), - err - ); - } - } - } - } - } - - pub fn write_outputs<'a>(&mut self, outputs: impl Iterator>) { - let mut infos = outputs - .map(|o| { - let o = o.borrow(); - ( - Into::::into(o.clone()), - o.user_data() - .get::>() - .unwrap() - .borrow() - .clone(), - ) - }) - .collect::>(); - infos.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b)); - let (infos, configs) = infos.into_iter().unzip(); - self - .dynamic_conf - .outputs_mut() - .config - .insert(infos, configs); - } -} - -pub struct PersistenceGuard<'a, T: Serialize>(Option, &'a mut T); - -impl<'a, T: Serialize> std::ops::Deref for PersistenceGuard<'a, T> { - type Target = T; - fn deref(&self) -> &T { - &self.1 - } -} - -impl<'a, T: Serialize> std::ops::DerefMut for PersistenceGuard<'a, T> { - fn deref_mut(&mut self) -> &mut T { - &mut self.1 - } -} - -impl<'a, T: Serialize> Drop for PersistenceGuard<'a, T> { - fn drop(&mut self) { - if let Some(path) = self.0.as_ref() { - let writer = match OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(path) - { - Ok(writer) => writer, - Err(err) => { - slog_scope::warn!("Failed to persist {}: {}", path.display(), err); - return; - } - }; - if let Err(err) = ron::ser::to_writer_pretty(writer, &self.1, Default::default()) { - slog_scope::warn!("Failed to persist {}: {}", path.display(), err); - } - } - } -} - -impl DynamicConfig { - pub fn outputs(&self) -> &OutputsConfig { - &self.outputs.1 - } - - pub fn outputs_mut<'a>(&'a mut self) -> PersistenceGuard<'a, OutputsConfig> { - PersistenceGuard(self.outputs.0.clone(), &mut self.outputs.1) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub enum KeyModifier { - Ctrl, - Alt, - Shift, - Logo, - CapsLock, - NumLock, -} - -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::Logo => self.logo = true, - KeyModifier::CapsLock => self.caps_lock = true, - KeyModifier::NumLock => self.num_lock = 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, - caps_lock: false, - logo: false, - num_lock: false, - }; - modifiers += self; - modifiers - } -} - -#[derive(Deserialize)] -#[serde(transparent)] -struct KeyModifiersDef(Vec); - -impl From for KeyModifiers { - fn from(src: KeyModifiersDef) -> Self { - src.0.into_iter().fold( - KeyModifiers { - ctrl: false, - alt: false, - shift: false, - caps_lock: false, - logo: false, - num_lock: false, - }, - |mut modis, modi| { - modis += modi; - modis - }, - ) - } -} - -#[allow(non_snake_case)] -fn deserialize_KeyModifiers<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - KeyModifiersDef::deserialize(deserializer).map(Into::into) -} - -#[allow(non_snake_case)] -fn deserialize_Keysym<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - use serde::de::{Error, Unexpected}; - - let name = String::deserialize(deserializer)?; - //let name = format!("KEY_{}", code); - match xkb::keysym_from_name(&name, xkb::KEYSYM_NO_FLAGS) { - KeySyms::KEY_NoSymbol => match xkb::keysym_from_name(&name, xkb::KEYSYM_CASE_INSENSITIVE) { - KeySyms::KEY_NoSymbol => Err(::invalid_value( - Unexpected::Str(&name), - &"One of the keysym names of xkbcommon.h without the 'KEY_' prefix", - )), - x => { - slog_scope::warn!( - "Key-Binding '{}' only matched case insensitive for {:?}", - name, - xkb::keysym_get_name(x) - ); - Ok(x) - } - }, - x => Ok(x), - } -} - -/// 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, - } - } -} - -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] -pub enum Action { - Terminate, - Debug, - Close, - Workspace(u8), - MoveToWorkspace(u8), - Focus(FocusDirection), - Orientation(crate::shell::layout::Orientation), - Fullscreen, - Spawn(String), -} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 00000000..a0e1276c --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,650 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{ + shell::{ + Shell, + layout::FocusDirection, + }, + state::BackendData, +}; +use serde::{Deserialize, Serialize}; +pub use smithay::{ + backend::input::KeyState, + reexports::input::{AccelProfile, ClickMethod, ScrollMethod, SendEventsMode, TapButtonMap, Device as InputDevice}, + utils::{Logical, Physical, Point, Size, Transform}, + wayland::{ + output::{Mode, Output}, + seat::{keysyms as KeySyms, Keysym, ModifiersState as KeyModifiers}, + }, +}; +use std::{cell::RefCell, collections::HashMap, fs::OpenOptions, path::PathBuf}; + +mod types; +pub use self::types::*; + +pub struct Config { + pub static_conf: StaticConfig, + pub dynamic_conf: DynamicConfig, +} + +#[derive(Debug, Deserialize)] +pub struct StaticConfig { + pub key_bindings: HashMap, + pub workspace_mode: crate::shell::Mode, +} + +pub struct DynamicConfig { + outputs: (Option, OutputsConfig), + inputs: (Option, InputsConfig), +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct OutputsConfig { + pub config: HashMap, Vec>, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct OutputInfo { + pub connector: String, + pub make: String, + pub model: String, +} + +impl From for OutputInfo { + fn from(o: Output) -> OutputInfo { + let physical = o.physical_properties(); + OutputInfo { + connector: o.name(), + make: physical.make, + model: physical.model, + } + } +} + +fn default_enabled() -> bool { + true +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +pub struct OutputConfig { + pub mode: ((i32, i32), Option), + pub vrr: bool, + pub scale: f64, + #[serde(with = "TransformDef")] + pub transform: Transform, + pub position: (i32, i32), + #[serde(default = "default_enabled")] + pub enabled: bool, +} + +impl Default for OutputConfig { + fn default() -> OutputConfig { + OutputConfig { + mode: ((0, 0), None), + vrr: false, + scale: 1.0, + transform: Transform::Normal, + position: (0, 0), + enabled: true, + } + } +} + +impl OutputConfig { + pub fn mode_size(&self) -> Size { + self.mode.0.into() + } + + pub fn mode_refresh(&self) -> u32 { + self.mode.1.unwrap_or(60_000) + } + + pub fn output_mode(&self) -> Mode { + Mode { + size: self.mode_size(), + refresh: self.mode_refresh() as i32, + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct InputsConfig { + xkb: XkbConfig, + devices: HashMap, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct InputConfig { + state: DeviceState, + #[serde(skip_serializing_if="Option::is_none")] + acceleration: Option, + #[serde(skip_serializing_if="Option::is_none")] + calibration: Option<[f32; 6]>, + #[serde(with = "ClickMethodDef")] + #[serde(skip_serializing_if="Option::is_none")] + click_method: Option, + #[serde(skip_serializing_if="Option::is_none")] + disable_while_typing: Option, + #[serde(skip_serializing_if="Option::is_none")] + left_handed: Option, + #[serde(skip_serializing_if="Option::is_none")] + middle_button_emulation: Option, + #[serde(skip_serializing_if="Option::is_none")] + rotation_angle: Option, + #[serde(skip_serializing_if="Option::is_none")] + scroll_config: Option, + #[serde(skip_serializing_if="Option::is_none")] + 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(); + Config { + static_conf: Self::load_static(xdg.as_ref()), + dynamic_conf: Self::load_dynamic(xdg.as_ref()), + } + } + + fn load_static(xdg: Option<&xdg::BaseDirectories>) -> StaticConfig { + let mut locations = if let Some(base) = xdg { + vec![ + base.get_config_file("cosmic-comp.ron"), + base.get_config_file("cosmic-comp/config.ron"), + ] + } else { + Vec::with_capacity(3) + }; + if cfg!(debug_assertions) { + if let Ok(mut cwd) = std::env::current_dir() { + cwd.push("config.ron"); + locations.push(cwd); + } + } + locations.push(PathBuf::from("/etc/cosmic-comp/config.ron")); + locations.push(PathBuf::from("/etc/cosmic-comp.ron")); + + for path in locations { + slog_scope::debug!("Trying config location: {}", path.display()); + if path.exists() { + slog_scope::info!("Using config at {}", path.display()); + return ron::de::from_reader(OpenOptions::new().read(true).open(path).unwrap()) + .expect("Malformed config file"); + } + } + + StaticConfig { + key_bindings: HashMap::new(), + workspace_mode: crate::shell::Mode::global(), + } + } + + fn load_dynamic(xdg: Option<&xdg::BaseDirectories>) -> DynamicConfig { + let output_path = + 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), + } + } + + fn load_outputs(path: &Option) -> OutputsConfig { + 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) => { + slog_scope::warn!("Failed to read output_config ({}), resetting..", err); + if let Err(err) = std::fs::remove_file(path) { + slog_scope::error!("Failed to remove output_config {}", err); + } + } + }; + } + } + + OutputsConfig { + config: HashMap::new(), + } + } + + 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) => { + slog_scope::warn!("Failed to read input_config ({}), resetting..", err); + if let Err(err) = std::fs::remove_file(path) { + slog_scope::error!("Failed to remove input_config {}", err); + } + } + }; + } + } + + InputsConfig { + xkb: XkbConfig::default(), + devices: HashMap::new(), + } + } + + pub fn read_outputs( + &mut self, + outputs: impl Iterator>, + backend: &mut BackendData, + shell: &mut Shell, + ) { + let outputs = outputs.map(|x| x.borrow().clone()).collect::>(); + let mut infos = outputs + .iter() + .cloned() + .map(Into::::into) + .collect::>(); + infos.sort(); + if let Some(configs) = self.dynamic_conf.outputs().config.get(&infos).cloned() { + let mut reset = false; + let known_good_configs = outputs + .iter() + .map(|output| { + output + .user_data() + .get::>() + .unwrap() + .borrow() + .clone() + }) + .collect::>(); + + for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter()) + { + let output = outputs + .iter() + .find(|o| &o.name() == name) + .unwrap() + .clone(); + *output + .user_data() + .get::>() + .unwrap() + .borrow_mut() = output_config; + if let Err(err) = backend.apply_config_for_output(&output, false, shell) { + slog_scope::warn!( + "Failed to set new config for output {}: {}", + output.name(), + err + ); + reset = true; + break; + } + } + + if reset { + for (output, output_config) in outputs + .clone() + .into_iter() + .zip(known_good_configs.into_iter()) + { + *output + .user_data() + .get::>() + .unwrap() + .borrow_mut() = output_config; + if let Err(err) = backend.apply_config_for_output(&output, false, shell) + { + slog_scope::error!( + "Failed to reset config for output {}: {}", + output.name(), + err + ); + } + } + } + } + } + + pub fn write_outputs(&mut self, outputs: impl Iterator>) { + let mut infos = outputs + .map(|o| { + let o = o.borrow(); + ( + Into::::into(o.clone()), + o.user_data() + .get::>() + .unwrap() + .borrow() + .clone(), + ) + }) + .collect::>(); + infos.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b)); + let (infos, configs) = infos.into_iter().unzip(); + self + .dynamic_conf + .outputs_mut() + .config + .insert(infos, configs); + } + + 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.sysname().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), + } { + slog_scope::warn!("Failed to apply mode {:?} for device {:?}: {:?}", config.state, device.name(), err); + } + if let Some(accel) = config.acceleration.as_ref() { + if let Some(profile) = accel.profile { + if let Err(err) = device.config_accel_set_profile(profile) { + slog_scope::warn!("Failed to apply acceleration profile {:?} for device {:?}: {:?}", profile, device.name(), err); + } + } + if let Err(err) = device.config_accel_set_speed(accel.speed) { + slog_scope::warn!("Failed to apply acceleration speed {:?} for device {:?}: {:?}", accel.speed, device.name(), err); + } + } + if let Some(matrix) = config.calibration { + if let Err(err) = device.config_calibration_set_matrix(matrix) { + slog_scope::warn!("Failed to apply calibration matrix {:?} for device {:?}: {:?}", matrix, device.name(), err); + } + } + if let Some(dwt) = config.disable_while_typing { + if let Err(err) = device.config_dwt_set_enabled(dwt) { + slog_scope::warn!("Failed to apply disable-while-typing {:?} for device {:?}: {:?}", dwt, device.name(), err); + } + } + if let Some(left) = config.left_handed { + if let Err(err) = device.config_left_handed_set(left) { + slog_scope::warn!("Failed to apply left-handed {:?} for device {:?}: {:?}", left, device.name(), err); + } + } + if let Some(middle) = config.middle_button_emulation { + if let Err(err) = device.config_middle_emulation_set_enabled(middle) { + slog_scope::warn!("Failed to apply middle-button-emulation {:?} for device {:?}: {:?}", middle, device.name(), err); + } + } + if let Some(angle) = config.rotation_angle { + if let Err(err) = device.config_rotation_set_angle(angle) { + slog_scope::warn!("Failed to apply rotation-angle {:?} for device {:?}: {:?}", angle, device.name(), err); + } + } + 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) { + slog_scope::warn!("Failed to apply scroll method {:?} for device {:?}: {:?}", method, device.name(), err); + } + } + if let Some(natural) = scroll.natural_scroll { + if let Err(err) = device.config_scroll_set_natural_scroll_enabled(natural) { + slog_scope::warn!("Failed to apply natural scrolling {:?} for device {:?}: {:?}", natural, device.name(), err); + } + } + if let Some(button) = scroll.scroll_button { + if let Err(err) = device.config_scroll_set_button(button) { + slog_scope::warn!("Failed to apply scroll button {:?} for device {:?}: {:?}", button, device.name(), err); + } + } + } + if let Some(tap) = config.tap_config.as_ref() { + if let Err(err) = device.config_tap_set_enabled(tap.enabled) { + slog_scope::warn!("Failed to apply tap-to-click {:?} for device {:?}: {:?}", tap.enabled, device.name(), err); + } + if let Some(button_map) = tap.button_map { + if let Err(err) = device.config_tap_set_button_map(button_map) { + slog_scope::warn!("Failed to apply button map {:?} for device {:?}: {:?}", button_map, device.name(), err); + } + } + if let Err(err) = device.config_tap_set_drag_enabled(tap.drag) { + slog_scope::warn!("Failed to apply tap-drag {:?} for device {:?}: {:?}", tap.drag, device.name(), err); + } + if let Err(err) = device.config_tap_set_drag_lock_enabled(tap.drag_lock) { + slog_scope::warn!("Failed to apply tap-drag-lock {:?} for device {:?}: {:?}", tap.drag_lock, device.name(), err); + } + } + }, + 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 }, + }); + }, + } + } +} + +pub struct PersistenceGuard<'a, T: Serialize>(Option, &'a mut T); + +impl<'a, T: Serialize> std::ops::Deref for PersistenceGuard<'a, T> { + type Target = T; + fn deref(&self) -> &T { + &self.1 + } +} + +impl<'a, T: Serialize> std::ops::DerefMut for PersistenceGuard<'a, T> { + fn deref_mut(&mut self) -> &mut T { + &mut self.1 + } +} + +impl<'a, T: Serialize> Drop for PersistenceGuard<'a, T> { + fn drop(&mut self) { + if let Some(path) = self.0.as_ref() { + let writer = match OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(path) + { + Ok(writer) => writer, + Err(err) => { + slog_scope::warn!("Failed to persist {}: {}", path.display(), err); + return; + } + }; + if let Err(err) = ron::ser::to_writer_pretty(writer, &self.1, Default::default()) { + slog_scope::warn!("Failed to persist {}: {}", path.display(), err); + } + } + } +} + +impl DynamicConfig { + pub fn outputs(&self) -> &OutputsConfig { + &self.outputs.1 + } + + 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 + } + + pub fn inputs_mut<'a>(&'a mut self) -> PersistenceGuard<'a, InputsConfig> { + PersistenceGuard(self.inputs.0.clone(), &mut self.inputs.1) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub enum KeyModifier { + Ctrl, + Alt, + Shift, + Logo, + CapsLock, + NumLock, +} + +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::Logo => self.logo = true, + KeyModifier::CapsLock => self.caps_lock = true, + KeyModifier::NumLock => self.num_lock = 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, + caps_lock: false, + logo: false, + num_lock: 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, + } + } +} + +#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] +pub enum Action { + Terminate, + Debug, + Close, + Workspace(u8), + MoveToWorkspace(u8), + Focus(FocusDirection), + Orientation(crate::shell::layout::Orientation), + Fullscreen, + Spawn(String), +} diff --git a/src/config/types.rs b/src/config/types.rs new file mode 100644 index 00000000..ccb2f36e --- /dev/null +++ b/src/config/types.rs @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-3.0-only +#![allow(non_snake_case)] + +use super::KeyModifier; +use serde::{Deserialize, Serialize}; +pub use smithay::{ + backend::input::KeyState, + reexports::input::{AccelProfile, ClickMethod, ScrollMethod, TapButtonMap}, + utils::{Logical, Physical, Point, Size, Transform}, + wayland::{ + output::{Mode, Output}, + seat::{keysyms as KeySyms, Keysym, ModifiersState as KeyModifiers}, + }, +}; +use xkbcommon::xkb; + +#[derive(Debug, 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, + } + } +} + +pub mod ClickMethodDef { + use smithay::reexports::input::ClickMethod as ClickMethodOrig; + use serde::{Deserialize, Serialize, Deserializer, 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) + } +} + +pub mod AccelProfileDef { + use smithay::reexports::input::AccelProfile as AccelProfileOrig; + use serde::{Deserialize, Serialize, Deserializer, 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) + } +} + +pub mod ScrollMethodDef { + use smithay::reexports::input::ScrollMethod as ScrollMethodOrig; + use serde::{Deserialize, Serialize, Deserializer, 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) + } +} + +#[derive(Serialize, Deserialize)] +#[serde(remote = "Transform")] +pub enum TransformDef { + Normal, + _90, + _180, + _270, + Flipped, + Flipped90, + Flipped180, + Flipped270, +} + +pub mod TapButtonMapDef { + use smithay::reexports::input::TapButtonMap as TapButtonMapOrig; + use serde::{Deserialize, Serialize, Deserializer, 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) + } +} + +#[derive(Deserialize)] +#[serde(transparent)] +pub struct KeyModifiersDef(Vec); + +impl From for KeyModifiers { + fn from(src: KeyModifiersDef) -> Self { + src.0.into_iter().fold( + KeyModifiers { + ctrl: false, + alt: false, + shift: false, + caps_lock: false, + logo: false, + num_lock: false, + }, + |mut modis, modi: KeyModifier| { + modis += modi; + modis + }, + ) + } +} + +#[allow(non_snake_case)] +pub fn deserialize_KeyModifiers<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + KeyModifiersDef::deserialize(deserializer).map(Into::into) +} + +#[allow(non_snake_case)] +pub fn deserialize_Keysym<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + use serde::de::{Error, Unexpected}; + + let name = String::deserialize(deserializer)?; + //let name = format!("KEY_{}", code); + match xkb::keysym_from_name(&name, xkb::KEYSYM_NO_FLAGS) { + KeySyms::KEY_NoSymbol => match xkb::keysym_from_name(&name, xkb::KEYSYM_CASE_INSENSITIVE) { + KeySyms::KEY_NoSymbol => Err(::invalid_value( + Unexpected::Str(&name), + &"One of the keysym names of xkbcommon.h without the 'KEY_' prefix", + )), + x => { + slog_scope::warn!( + "Key-Binding '{}' only matched case insensitive for {:?}", + name, + xkb::keysym_get_name(x) + ); + Ok(x) + } + }, + x => Ok(x), + } +} +