diff --git a/config/src/shortcuts/gesture.rs b/config/src/shortcuts/gesture.rs new file mode 100644 index 0000000..40c2f2e --- /dev/null +++ b/config/src/shortcuts/gesture.rs @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MPL-2.0 +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +/// Description of a gesture that can be handled by the compositor +#[serde_with::serde_as] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Hash)] +#[serde(deny_unknown_fields)] +pub struct Gesture { + /// How many fingers are held down + pub fingers: u32, + pub direction: GestureDirection, + // A custom description for a custom binding + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, +} + +/// Describes a direction, either absolute or relative +#[serde_with::serde_as] +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Hash)] +#[serde(deny_unknown_fields)] +pub enum GestureDirection { + AbsoluteUp, + AbsoluteDown, + AbsoluteLeft, + AbsoluteRight, + RelativeForward, + RelativeBackward, + RelativeLeft, + RelativeRight, +} + +impl ToString for GestureDirection { + fn to_string(&self) -> String { + match self { + GestureDirection::AbsoluteUp => "AbsoluteUp".to_string(), + GestureDirection::AbsoluteDown => "AbsoluteDown".to_string(), + GestureDirection::AbsoluteLeft => "AbsoluteLeft".to_string(), + GestureDirection::AbsoluteRight => "AbsoluteRight".to_string(), + GestureDirection::RelativeForward => "RelativeForward".to_string(), + GestureDirection::RelativeBackward => "RelativeBackward".to_string(), + GestureDirection::RelativeLeft => "RelativeLeft".to_string(), + GestureDirection::RelativeRight => "RelativeRight".to_string(), + } + } +} + +impl FromStr for GestureDirection { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + // Absolute directions + "AbsoluteUp" => Ok(GestureDirection::AbsoluteUp), + "AbsoluteDown" => Ok(GestureDirection::AbsoluteDown), + "AbsoluteLeft" => Ok(GestureDirection::AbsoluteLeft), + "AbsoluteRight" => Ok(GestureDirection::AbsoluteRight), + // Relative directions + "RelativeForward" => Ok(GestureDirection::RelativeForward), + "RelativeBackward" => Ok(GestureDirection::RelativeBackward), + "RelativeLeft" => Ok(GestureDirection::RelativeLeft), + "RelativeRight" => Ok(GestureDirection::RelativeRight), + _ => Err(format!("Invalid direction string")), + } + } +} + +impl Gesture { + /// Creates a new gesture from a number of fingers and a direction + pub fn new(fingers: impl Into, direction: impl Into) -> Gesture { + Gesture { + fingers: fingers.into(), + direction: direction.into(), + description: None, + } + } + + /// Returns true if the direction is absolute + pub fn is_absolute(&self) -> bool { + matches!( + self.direction, + GestureDirection::AbsoluteDown + | GestureDirection::AbsoluteLeft + | GestureDirection::AbsoluteRight + | GestureDirection::AbsoluteUp + ) + } + + /// Append the binding to an existing string + pub fn to_string_in_place(&self, string: &mut String) { + string.push_str(&format!( + "{} Finger {}", + self.fingers, + self.direction.to_string() + )); + } +} + +impl ToString for Gesture { + fn to_string(&self) -> String { + let mut string = String::new(); + self.to_string_in_place(&mut string); + string + } +} + +impl FromStr for Gesture { + type Err = String; + + fn from_str(value: &str) -> Result { + let mut value_iter = value.split("+"); + let n = match value_iter.next() { + Some(val) => val, + None => { + return Err(format!("no value for the number of fingers")); + } + }; + let fingers = match u32::from_str(n) { + Ok(a) => a, + Err(_) => { + return Err(format!("could not parse number of fingers")); + } + }; + + let n2 = match value_iter.next() { + Some(val) => val, + None => { + return Err(format!("could not parse direction")); + } + }; + + let direction = match GestureDirection::from_str(n2) { + Ok(dir) => dir, + Err(e) => { + return Err(e); + } + }; + + if let Some(n3) = value_iter.next() { + return Err(format!("Extra data {} not expected", n3)); + } + + return Ok(Self { + fingers, + direction, + description: None, + }); + } +} + +#[cfg(test)] +mod tests { + use crate::shortcuts::gesture::GestureDirection; + + use super::Gesture; + use std::str::FromStr; + + #[test] + fn binding_from_str() { + assert_eq!( + Gesture::from_str("3+RelativeLeft"), + Ok(Gesture::new( + 3 as u32, + GestureDirection::RelativeLeft + )) + ); + + assert_eq!( + Gesture::from_str("5+AbsoluteUp"), + Ok(Gesture::new( + 5 as u32, + GestureDirection::AbsoluteUp + )) + ); + + assert_ne!( + Gesture::from_str("4+AbsoluteLeft+More+Info"), + Ok(Gesture::new( + 4 as u32, + GestureDirection::AbsoluteLeft + )) + ); + } +} diff --git a/config/src/shortcuts/mod.rs b/config/src/shortcuts/mod.rs index f069855..37258c0 100644 --- a/config/src/shortcuts/mod.rs +++ b/config/src/shortcuts/mod.rs @@ -8,7 +8,10 @@ pub mod modifier; pub use modifier::{Modifier, Modifiers, ModifiersDef}; mod binding; +mod gesture; pub use binding::Binding; +pub use gesture::Gesture; +pub use gesture::GestureDirection; pub mod sym; @@ -49,6 +52,31 @@ pub fn shortcuts(context: &cosmic_config::Config) -> Shortcuts { shortcuts } +/// Get the current system gesture configuration +/// +/// Merges user-defined custom gestures to the system default config +pub fn gestures(context: &cosmic_config::Config) -> Gestures { + // Load gestures defined by the system. + let mut gestures = context + .get::("default_gestures") + .unwrap_or_else(|why| { + tracing::error!("shortcuts defaults config error: {why:?}"); + Gestures::default() + }); + + // Load custom gestures defined by the user. + let custom_gestures = context + .get::("custom_gestures") + .unwrap_or_else(|why| { + tracing::error!("shortcuts custom config error: {why:?}"); + Gestures::default() + }); + + // Combine while overriding system gestures. + gestures.0.extend(custom_gestures.0); + gestures +} + /// Get a map of system actions and their configured commands pub fn system_actions(context: &cosmic_config::Config) -> SystemActions { let mut config = SystemActions::default(); @@ -80,6 +108,8 @@ pub fn system_actions(context: &cosmic_config::Config) -> SystemActions { pub struct Config { pub defaults: Shortcuts, pub custom: Shortcuts, + pub default_gestures: Gestures, + pub custom_gestures: Gestures, pub system_actions: SystemActions, } @@ -97,6 +127,18 @@ impl Config { .shortcut_for_action(action) .or_else(|| self.defaults.shortcut_for_action(action)) } + + pub fn gestures(&self) -> impl Iterator { + self.custom_gestures + .iter() + .chain(self.default_gestures.iter()) + } + + pub fn gesture_for_action(&self, action: &Action) -> Option { + self.custom_gestures + .gesture_for_action(action) + .or_else(|| self.default_gestures.gesture_for_action(action)) + } } /// A map of defined key [Binding]s and their triggerable [Action]s @@ -172,3 +214,49 @@ pub enum State { Pressed, Released, } + +/// A map of defined [Gesture]s and their triggerable [Action]s +#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] +#[serde(transparent)] +pub struct Gestures(pub HashMap); + +impl Gestures { + pub fn insert_default_gesture( + &mut self, + fingers: u32, + direction: gesture::GestureDirection, + action: Action, + ) { + if !self.0.values().any(|a| a == &action) { + let pattern = Gesture { + description: None, + fingers, + direction, + }; + if !self.0.contains_key(&pattern) { + self.0.insert(pattern, action.clone()); + } + } + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.0.iter_mut() + } + + pub fn gesture_for_action(&self, action: &Action) -> Option { + self.gestures(action) + .next() // take the first one + .map(|gesture| gesture.to_string()) + } + + pub fn gestures<'a>(&'a self, action: &'a Action) -> impl Iterator { + self.0 + .iter() + .filter(move |(_, a)| *a == action) + .map(|(b, _)| b) + } +}