From 70de36c323f619f655f36114809d0b77788de913 Mon Sep 17 00:00:00 2001 From: Ryan Brue Date: Wed, 18 Dec 2024 00:40:20 -0600 Subject: [PATCH] feat: Gesture configuration in shortcuts config This commit adds a Gesture abstraction, which can define gestures by the number of fingers used, and the direction the gesture is performed in. Oftentimes, we want the gesture to be either absolute (i.e. ignoring context such as workspace direction), or relative (if workspaces are horizontal, forward would be right, backward would be left. if workspaces are vertical, forward would be up, backward would be down). The abstraction is modeled after the Binding type, and the resulting Gestures type is similar to the Shortcuts type. This is the first step in implementing configurable touchpad gestures (https://github.com/pop-os/cosmic-comp/pull/396) Signed-off-by: Ryan Brue --- config/src/shortcuts/gesture.rs | 185 ++++++++++++++++++++++++++++++++ config/src/shortcuts/mod.rs | 88 +++++++++++++++ 2 files changed, 273 insertions(+) create mode 100644 config/src/shortcuts/gesture.rs 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) + } +}