Skip to content

Commit dc1b210

Browse files
committed
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 (pop-os/cosmic-comp#396) Signed-off-by: Ryan Brue <[email protected]>
1 parent 9d9ad8e commit dc1b210

File tree

2 files changed

+291
-0
lines changed

2 files changed

+291
-0
lines changed

config/src/shortcuts/gesture.rs

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
use std::str::FromStr;
3+
4+
use serde::{Deserialize, Serialize};
5+
6+
/// Description of a gesture that can be handled by the compositor
7+
#[serde_with::serde_as]
8+
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Hash)]
9+
#[serde(deny_unknown_fields)]
10+
pub struct Gesture {
11+
/// How many fingers are held down
12+
pub fingers: u32,
13+
pub direction: Direction,
14+
// A custom description for a custom binding
15+
#[serde(default, skip_serializing_if = "Option::is_none")]
16+
pub description: Option<String>,
17+
}
18+
19+
/// Describes a direction, either absolute or relative
20+
#[serde_with::serde_as]
21+
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Hash)]
22+
#[serde(deny_unknown_fields)]
23+
pub enum Direction {
24+
Relative(RelativeDirection),
25+
Absolute(AbsoluteDirection),
26+
}
27+
28+
impl ToString for Direction {
29+
fn to_string(&self) -> String {
30+
match self {
31+
Direction::Absolute(abs) => match abs {
32+
AbsoluteDirection::AbsoluteUp => "AbsoluteUp".to_string(),
33+
AbsoluteDirection::AbsoluteDown => "AbsoluteDown".to_string(),
34+
AbsoluteDirection::AbsoluteLeft => "AbsoluteLeft".to_string(),
35+
AbsoluteDirection::AbsoluteRight => "AbsoluteRight".to_string(),
36+
},
37+
Direction::Relative(rel) => match rel {
38+
RelativeDirection::RelativeForward => "RelativeForward".to_string(),
39+
RelativeDirection::RelativeBackward => "RelativeBackward".to_string(),
40+
RelativeDirection::RelativeLeft => "RelativeLeft".to_string(),
41+
RelativeDirection::RelativeRight => "RelativeRight".to_string(),
42+
},
43+
}
44+
}
45+
}
46+
47+
impl FromStr for Direction {
48+
type Err = String;
49+
50+
fn from_str(s: &str) -> Result<Self, Self::Err> {
51+
match s {
52+
// Absolute directions
53+
"AbsoluteUp" => Ok(Direction::Absolute(AbsoluteDirection::AbsoluteUp)),
54+
"AbsoluteDown" => Ok(Direction::Absolute(AbsoluteDirection::AbsoluteDown)),
55+
"AbsoluteLeft" => Ok(Direction::Absolute(AbsoluteDirection::AbsoluteLeft)),
56+
"AbsoluteRight" => Ok(Direction::Absolute(AbsoluteDirection::AbsoluteRight)),
57+
// Relative directions
58+
"RelativeForward" => Ok(Direction::Relative(RelativeDirection::RelativeForward)),
59+
"RelativeBackward" => Ok(Direction::Relative(RelativeDirection::RelativeBackward)),
60+
"RelativeLeft" => Ok(Direction::Relative(RelativeDirection::RelativeLeft)),
61+
"RelativeRight" => Ok(Direction::Relative(RelativeDirection::RelativeRight)),
62+
_ => Err(format!("Invalid direction string"))
63+
}
64+
}
65+
}
66+
67+
/// Describes a relative direction (typically relative to the workspace direction)
68+
#[serde_with::serde_as]
69+
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Hash)]
70+
#[serde(deny_unknown_fields)]
71+
pub enum RelativeDirection {
72+
RelativeForward,
73+
RelativeBackward,
74+
RelativeLeft,
75+
RelativeRight,
76+
}
77+
78+
/// Describes an absolute direction (i.e. not relative to workspace direction)
79+
#[serde_with::serde_as]
80+
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Hash)]
81+
#[serde(deny_unknown_fields)]
82+
pub enum AbsoluteDirection {
83+
AbsoluteUp,
84+
AbsoluteDown,
85+
AbsoluteLeft,
86+
AbsoluteRight,
87+
}
88+
89+
impl Gesture {
90+
/// Creates a new gesture from a number of fingers and a direction
91+
pub fn new(fingers: impl Into<u32>, direction: impl Into<Direction>) -> Gesture {
92+
Gesture {
93+
fingers: fingers.into(),
94+
direction: direction.into(),
95+
description: None,
96+
}
97+
}
98+
99+
/// Returns true if the direction is absolute
100+
pub fn is_absolute(&self) -> bool {
101+
matches!(self.direction, Direction::Absolute(_))
102+
}
103+
104+
/// Append the binding to an existing string
105+
pub fn to_string_in_place(&self, string: &mut String) {
106+
string.push_str(&format!(
107+
"{} Finger {}",
108+
self.fingers,
109+
self.direction.to_string()
110+
));
111+
}
112+
}
113+
114+
impl ToString for Gesture {
115+
fn to_string(&self) -> String {
116+
let mut string = String::new();
117+
self.to_string_in_place(&mut string);
118+
string
119+
}
120+
}
121+
122+
123+
impl FromStr for Gesture {
124+
type Err = String;
125+
126+
fn from_str(value: &str) -> Result<Self, Self::Err> {
127+
let mut value_iter = value.split("+");
128+
let n = match value_iter.next() {
129+
Some(val) => val,
130+
None => {
131+
return Err(format!("no value for the number of fingers"));
132+
},
133+
};
134+
let fingers = match u32::from_str(n) {
135+
Ok(a) => a,
136+
Err(_) => {
137+
return Err(format!("could not parse number of fingers"));
138+
},
139+
};
140+
141+
let n2 = match value_iter.next() {
142+
Some(val) => val,
143+
None => {
144+
return Err(format!("could not parse direction"));
145+
},
146+
};
147+
148+
let direction = match Direction::from_str(n2) {
149+
Ok(dir) => dir,
150+
Err(e) => {
151+
return Err(e);
152+
},
153+
};
154+
155+
if let Some(n3) = value_iter.next() {
156+
return Err(format!("Extra data {} not expected", n3));
157+
}
158+
159+
return Ok(Self {
160+
fingers,
161+
direction,
162+
description: None,
163+
});
164+
}
165+
}
166+
167+
168+
#[cfg(test)]
169+
mod tests {
170+
use crate::shortcuts::gesture::{AbsoluteDirection, Direction, RelativeDirection};
171+
172+
use super::Gesture;
173+
use std::str::FromStr;
174+
175+
#[test]
176+
fn binding_from_str() {
177+
assert_eq!(
178+
Gesture::from_str("3+RelativeLeft"),
179+
Ok(Gesture::new(
180+
3 as u32,
181+
Direction::Relative(RelativeDirection::RelativeLeft)
182+
))
183+
);
184+
185+
assert_eq!(
186+
Gesture::from_str("5+AbsoluteUp"),
187+
Ok(Gesture::new(
188+
5 as u32,
189+
Direction::Absolute(AbsoluteDirection::AbsoluteUp)
190+
))
191+
);
192+
193+
assert_ne!(
194+
Gesture::from_str("4+AbsoluteLeft+More+Info"),
195+
Ok(Gesture::new(
196+
4 as u32,
197+
Direction::Absolute(AbsoluteDirection::AbsoluteLeft)
198+
))
199+
);
200+
}
201+
}

config/src/shortcuts/mod.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ pub mod modifier;
88
pub use modifier::{Modifier, Modifiers, ModifiersDef};
99

1010
mod binding;
11+
mod gesture;
1112
pub use binding::Binding;
13+
pub use gesture::Gesture;
14+
pub use gesture::Direction;
15+
pub use gesture::AbsoluteDirection;
16+
pub use gesture::RelativeDirection;
1217

1318
pub mod sym;
1419

@@ -49,6 +54,31 @@ pub fn shortcuts(context: &cosmic_config::Config) -> Shortcuts {
4954
shortcuts
5055
}
5156

57+
/// Get the current system gesture configuration
58+
///
59+
/// Merges user-defined custom gestures to the system default config
60+
pub fn gestures(context: &cosmic_config::Config) -> Gestures {
61+
// Load gestures defined by the system.
62+
let mut gestures = context
63+
.get::<Gestures>("default_gestures")
64+
.unwrap_or_else(|why| {
65+
tracing::error!("shortcuts defaults config error: {why:?}");
66+
Gestures::default()
67+
});
68+
69+
// Load custom gestures defined by the user.
70+
let custom_gestures = context
71+
.get::<Gestures>("custom_gestures")
72+
.unwrap_or_else(|why| {
73+
tracing::error!("shortcuts custom config error: {why:?}");
74+
Gestures::default()
75+
});
76+
77+
// Combine while overriding system gestures.
78+
gestures.0.extend(custom_gestures.0);
79+
gestures
80+
}
81+
5282
/// Get a map of system actions and their configured commands
5383
pub fn system_actions(context: &cosmic_config::Config) -> SystemActions {
5484
let mut config = SystemActions::default();
@@ -80,6 +110,8 @@ pub fn system_actions(context: &cosmic_config::Config) -> SystemActions {
80110
pub struct Config {
81111
pub defaults: Shortcuts,
82112
pub custom: Shortcuts,
113+
pub default_gestures: Gestures,
114+
pub custom_gestures: Gestures,
83115
pub system_actions: SystemActions,
84116
}
85117

@@ -97,6 +129,18 @@ impl Config {
97129
.shortcut_for_action(action)
98130
.or_else(|| self.defaults.shortcut_for_action(action))
99131
}
132+
133+
pub fn gestures(&self) -> impl Iterator<Item = (&Gesture, &Action)> {
134+
self.custom_gestures
135+
.iter()
136+
.chain(self.default_gestures.iter())
137+
}
138+
139+
pub fn gesture_for_action(&self, action: &Action) -> Option<String> {
140+
self.custom_gestures
141+
.gesture_for_action(action)
142+
.or_else(|| self.default_gestures.gesture_for_action(action))
143+
}
100144
}
101145

102146
/// A map of defined key [Binding]s and their triggerable [Action]s
@@ -172,3 +216,49 @@ pub enum State {
172216
Pressed,
173217
Released,
174218
}
219+
220+
/// A map of defined [Gesture]s and their triggerable [Action]s
221+
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
222+
#[serde(transparent)]
223+
pub struct Gestures(pub HashMap<Gesture, Action>);
224+
225+
impl Gestures {
226+
pub fn insert_default_gesture(
227+
&mut self,
228+
fingers: u32,
229+
direction: gesture::Direction,
230+
action: Action,
231+
) {
232+
if !self.0.values().any(|a| a == &action) {
233+
let pattern = Gesture {
234+
description: None,
235+
fingers,
236+
direction,
237+
};
238+
if !self.0.contains_key(&pattern) {
239+
self.0.insert(pattern, action.clone());
240+
}
241+
}
242+
}
243+
244+
pub fn iter(&self) -> impl Iterator<Item = (&Gesture, &Action)> {
245+
self.0.iter()
246+
}
247+
248+
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Gesture, &mut Action)> {
249+
self.0.iter_mut()
250+
}
251+
252+
pub fn gesture_for_action(&self, action: &Action) -> Option<String> {
253+
self.gestures(action)
254+
.next() // take the first one
255+
.map(|gesture| gesture.to_string())
256+
}
257+
258+
pub fn gestures<'a>(&'a self, action: &'a Action) -> impl Iterator<Item = &'a Gesture> {
259+
self.0
260+
.iter()
261+
.filter(move |(_, a)| *a == action)
262+
.map(|(b, _)| b)
263+
}
264+
}

0 commit comments

Comments
 (0)