Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Gesture configuration in shortcuts config #63

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion config/src/shortcuts/action.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// SPDX-License-Identifier: MPL-2.0

use std::str::FromStr;

use serde::{Deserialize, Serialize};

/// An operation which may be bound to a keyboard shortcut.
Expand Down Expand Up @@ -180,7 +182,7 @@ pub enum System {
}

/// Defines the direction of an operation
#[derive(Copy, Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[derive(Copy, Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize, Hash)]
pub enum Direction {
Left,
Right,
Expand All @@ -200,6 +202,31 @@ impl std::ops::Not for Direction {
}
}

impl ToString for Direction {
fn to_string(&self) -> String {
match self {
Direction::Left => "Left".to_string(),
Direction::Right => "Right".to_string(),
Direction::Up => "Up".to_string(),
Direction::Down => "Down".to_string(),
}
}
}

impl FromStr for Direction {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Down" => Ok(Self::Down),
"Up" => Ok(Self::Up),
"Left" => Ok(Self::Left),
"Right" => Ok(Self::Right),
_ => return Err(format!("String {} cannot be converted to direction.", s)),
}
}
}

/// Defines the direction to focus towards
#[derive(Copy, Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub enum FocusDirection {
Expand Down
127 changes: 127 additions & 0 deletions config/src/shortcuts/gesture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: MPL-2.0
use std::str::FromStr;

use serde::{Deserialize, Serialize};

use super::action::Direction;

/// 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: Direction,
// A custom description for a custom binding
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}

impl Gesture {
/// Creates a new gesture from a number of fingers and a direction
pub fn new(fingers: impl Into<u32>, direction: impl Into<Direction>) -> Gesture {
Gesture {
fingers: fingers.into(),
direction: direction.into(),
description: None,
}
}

/// 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<Self, Self::Err> {
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 Direction::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::action::Direction;

use super::Gesture;
use std::str::FromStr;

#[test]
fn binding_from_str() {
assert_eq!(
Gesture::from_str("3+Left"),
Ok(Gesture::new(
3 as u32,
Direction::Left
))
);

assert_eq!(
Gesture::from_str("5+Up"),
Ok(Gesture::new(
5 as u32,
Direction::Up
))
);

assert_ne!(
Gesture::from_str("4+Left+More+Info"),
Ok(Gesture::new(
4 as u32,
Direction::Left
))
);
}
}
88 changes: 88 additions & 0 deletions config/src/shortcuts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ pub use action::Action;

pub mod modifier;

use action::Direction;
pub use modifier::{Modifier, Modifiers, ModifiersDef};

mod binding;
mod gesture;
pub use binding::Binding;
pub use gesture::Gesture;

pub mod sym;

Expand Down Expand Up @@ -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::<Gestures>("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::<Gestures>("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();
Expand Down Expand Up @@ -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,
}

Expand All @@ -97,6 +127,18 @@ impl Config {
.shortcut_for_action(action)
.or_else(|| self.defaults.shortcut_for_action(action))
}

pub fn gestures(&self) -> impl Iterator<Item = (&Gesture, &Action)> {
self.custom_gestures
.iter()
.chain(self.default_gestures.iter())
}

pub fn gesture_for_action(&self, action: &Action) -> Option<String> {
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
Expand Down Expand Up @@ -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<Gesture, Action>);

impl Gestures {
pub fn insert_default_gesture(
&mut self,
fingers: u32,
direction: Direction,
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<Item = (&Gesture, &Action)> {
self.0.iter()
}

pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Gesture, &mut Action)> {
self.0.iter_mut()
}

pub fn gesture_for_action(&self, action: &Action) -> Option<String> {
self.gestures(action)
.next() // take the first one
.map(|gesture| gesture.to_string())
}

pub fn gestures<'a>(&'a self, action: &'a Action) -> impl Iterator<Item = &'a Gesture> {
self.0
.iter()
.filter(move |(_, a)| *a == action)
.map(|(b, _)| b)
}
}