1pub use keycodes::Key;
43use parse_display::{Display, FromStr, ParseError};
44use std::collections::HashSet;
45use std::fmt::{self, Debug, Display, Formatter};
46use std::path::PathBuf;
47use std::str::FromStr;
48
49mod keycodes;
50
51#[cfg(feature = "listener")]
52mod listener;
53
54#[cfg(feature = "listener")]
55pub use listener::ShortcutListener;
56
57#[derive(Debug, Clone)]
59pub struct DeviceOpenError {
60 pub device: PathBuf,
61}
62
63impl Display for DeviceOpenError {
64 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
65 write!(f, "Failed to open device {:?}", self.device)
66 }
67}
68
69impl std::error::Error for DeviceOpenError {}
70
71#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Display, FromStr)]
73#[repr(u8)]
74pub enum Modifier {
75 Alt,
76 LeftAlt,
77 RightAlt,
78 Ctrl,
79 LeftCtrl,
80 RightCtrl,
81 Shift,
82 LeftShift,
83 RightShift,
84 Meta,
85 LeftMeta,
86 RightMeta,
87}
88
89const ALL_MODIFIERS: &[Modifier] = &[
90 Modifier::Alt,
91 Modifier::LeftAlt,
92 Modifier::RightAlt,
93 Modifier::Ctrl,
94 Modifier::LeftCtrl,
95 Modifier::RightCtrl,
96 Modifier::Shift,
97 Modifier::LeftShift,
98 Modifier::RightShift,
99 Modifier::Meta,
100 Modifier::LeftMeta,
101 Modifier::RightMeta,
102];
103
104const COMBINED_MODIFIERS: &[Modifier] = &[
105 Modifier::Alt,
106 Modifier::Ctrl,
107 Modifier::Shift,
108 Modifier::Meta,
109];
110
111impl Modifier {
112 pub fn mask(&self) -> u8 {
113 match self {
114 Modifier::Alt => 0b00000011,
115 Modifier::LeftAlt => 0b00000001,
116 Modifier::RightAlt => 0b00000010,
117 Modifier::Ctrl => 0b00001100,
118 Modifier::LeftCtrl => 0b00000100,
119 Modifier::RightCtrl => 0b00001000,
120 Modifier::Meta => 0b00110000,
121 Modifier::LeftMeta => 0b00010000,
122 Modifier::RightMeta => 0b00100000,
123 Modifier::Shift => 0b11000000,
124 Modifier::LeftShift => 0b01000000,
125 Modifier::RightShift => 0b10000000,
126 }
127 }
128
129 pub fn mask_from_key(key: Key) -> u8 {
130 match key {
131 Key::KeyLeftAlt => 0b00000001,
132 Key::KeyRightAlt => 0b00000010,
133 Key::KeyLeftCtrl => 0b00000100,
134 Key::KeyRightCtrl => 0b00001000,
135 Key::KeyLeftMeta => 0b00010000,
136 Key::KeyRightMeta => 0b00100000,
137 Key::KeyLeftShift => 0b01000000,
138 Key::KeyRightShift => 0b10000000,
139 _ => 0,
140 }
141 }
142}
143
144#[derive(Clone, Debug, Hash, PartialEq, Eq, Copy, Default)]
146pub struct ModifierList(u8);
147
148impl ModifierList {
149 pub fn new(modifiers: &[Modifier]) -> Self {
150 ModifierList(
151 modifiers
152 .iter()
153 .fold(0, |mask, modifier| mask | modifier.mask()),
154 )
155 }
156
157 pub fn mask(&self) -> u8 {
158 self.0
159 }
160
161 pub fn modifiers(&self) -> impl Iterator<Item = Modifier> {
162 let mask = self.mask();
163 ALL_MODIFIERS.iter().copied().filter(move |modifier| {
164 for combined in COMBINED_MODIFIERS {
165 if combined != modifier
167 && combined.mask() & modifier.mask() == modifier.mask()
168 && combined.mask() & mask == combined.mask()
169 {
170 return false;
171 }
172 }
173 modifier.mask() & mask == modifier.mask()
174 })
175 }
176
177 pub fn len(&self) -> u32 {
178 self.modifiers().count() as u32
179 }
180
181 pub fn is_empty(&self) -> bool {
182 self.mask() == 0
183 }
184}
185
186impl Display for ModifierList {
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 for modifier in self.modifiers() {
189 write!(f, "<{}>", modifier)?;
190 }
191 Ok(())
192 }
193}
194
195impl FromStr for ModifierList {
196 type Err = ParseError;
197
198 fn from_str(s: &str) -> Result<Self, Self::Err> {
199 let modifiers = s
200 .split('>')
201 .filter(|part| !part.is_empty())
202 .map(|part| {
203 if !part.starts_with('<') {
204 Err(ParseError::with_message("Invalid modifier"))
205 } else {
206 Ok(part[1..].parse::<Modifier>()?)
207 }
208 })
209 .collect::<Result<Vec<Modifier>, ParseError>>()?;
210 Ok(ModifierList::new(&modifiers))
211 }
212}
213
214#[derive(Clone, Debug, Hash, PartialEq, Eq)]
238pub struct Shortcut {
239 pub modifiers: ModifierList,
240 pub key: Key,
241}
242
243impl FromStr for Shortcut {
244 type Err = ParseError;
245
246 fn from_str(s: &str) -> Result<Self, Self::Err> {
247 if let Some((modifiers, key)) = s.split_once('-') {
248 Ok(Shortcut {
249 modifiers: modifiers.parse()?,
250 key: key.parse()?,
251 })
252 } else {
253 Ok(Shortcut {
254 modifiers: ModifierList::default(),
255 key: s.parse()?,
256 })
257 }
258 }
259}
260
261impl Display for Shortcut {
262 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
263 if self.modifiers.is_empty() {
264 write!(f, "{}", self.key)
265 } else {
266 write!(f, "{}-{}", self.modifiers, self.key)
267 }
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use crate::{Key, Modifier, ModifierList, Shortcut};
274 use test_case::test_case;
275
276 #[test_case("KeyP", Shortcut::new(& [], Key::KeyP))]
277 #[test_case("<Ctrl>-KeyP", Shortcut::new(& [Modifier::Ctrl], Key::KeyP))]
278 #[test_case("<LeftAlt><LeftCtrl>-KeyLeft", Shortcut::new(& [Modifier::LeftCtrl, Modifier::LeftAlt], Key::KeyLeft))]
279 fn shortcut_parse_display_test(s: &str, shortcut: Shortcut) {
280 assert_eq!(s, format!("{}", shortcut));
281
282 assert_eq!(shortcut, s.parse().unwrap());
283 }
284
285 #[test_case(& [Modifier::Ctrl])]
286 #[test_case(& [Modifier::LeftAlt, Modifier::LeftCtrl])]
287 #[test_case(& [Modifier::Shift, Modifier::Meta])]
288 fn test_modifier_list(modifiers: &[Modifier]) {
289 assert_eq!(
290 modifiers.to_vec(),
291 ModifierList::new(modifiers).modifiers().collect::<Vec<_>>()
292 )
293 }
294}
295
296impl Shortcut {
297 pub fn new(modifiers: &[Modifier], key: Key) -> Self {
298 Shortcut {
299 modifiers: ModifierList::new(modifiers),
300 key,
301 }
302 }
303
304 pub fn identifier(&self) -> String {
305 self.to_string().replace(['<', '>'], "").replace('-', "_")
306 }
307}
308
309impl Shortcut {
310 pub fn is_triggered(&self, active_keys: &HashSet<Key>) -> bool {
311 let desired_mask = self.modifiers.mask();
312 let pressed_mask = active_keys
313 .iter()
314 .fold(0, |mask, key| mask | Modifier::mask_from_key(*key));
315
316 let desired_presses = desired_mask & pressed_mask;
317 let modifiers_match = (desired_presses == pressed_mask)
318 && (desired_presses.count_ones() == self.modifiers.len());
319
320 modifiers_match && active_keys.contains(&self.key)
321 }
322}
323
324#[cfg(test)]
325mod triggered_tests {
326 use crate::{Key, Shortcut};
327 use test_case::test_case;
328
329 #[test_case("<Ctrl>-KeyP", & [] => false)]
330 #[test_case("<Ctrl>-KeyP", & [Key::KeyLeftCtrl, Key::KeyP] => true)]
331 #[test_case("<Ctrl>-KeyP", & [Key::KeyRightCtrl, Key::KeyP] => true)]
332 #[test_case("<LeftCtrl>-KeyP", & [Key::KeyLeftCtrl, Key::KeyP] => true)]
333 #[test_case("<LeftCtrl>-KeyP", & [Key::KeyRightCtrl, Key::KeyP] => false)]
334 #[test_case("<Ctrl>-KeyP", & [Key::KeyLeftCtrl, Key::KeyLeftAlt, Key::KeyP] => false)]
335 #[test_case("<LeftCtrl><LeftAlt>-KeyLeft", & [] => false)]
336 #[test_case("<LeftCtrl><LeftAlt>-KeyLeft", & [Key::KeyLeft] => false)]
337 #[test_case("<LeftCtrl><LeftAlt>-KeyLeft", & [Key::KeyLeftCtrl, Key::KeyLeft] => false)]
338 #[test_case("<LeftCtrl><LeftAlt>-KeyLeft", & [Key::KeyLeftCtrl, Key::KeyLeftAlt] => false)]
339 #[test_case("<LeftCtrl><LeftAlt>-KeyLeft", & [Key::KeyLeftCtrl, Key::KeyLeftAlt, Key::KeyRight] => false)]
340 #[test_case("<LeftCtrl><LeftAlt>-KeyLeft", & [Key::KeyLeftCtrl, Key::KeyLeftAlt, Key::KeyLeft] => true)]
341 #[test_case("<LeftCtrl><LeftAlt>-KeyLeft", & [Key::KeyLeftCtrl, Key::KeyRightAlt, Key::KeyLeft] => false)]
342 #[test_case("<Ctrl><Alt>-KeyLeft", & [Key::KeyLeftCtrl, Key::KeyRightAlt, Key::KeyLeft] => true)]
343 fn shortcut_triggered(s: &str, keys: &[Key]) -> bool {
344 let shortcut: Shortcut = s.parse().unwrap();
345 shortcut.is_triggered(&keys.iter().copied().collect())
346 }
347}
348
349#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
351pub enum ShortcutState {
352 Pressed,
353 Released,
354}
355
356impl ShortcutState {
357 pub fn as_str(&self) -> &'static str {
358 match self {
359 ShortcutState::Pressed => "pressed",
360 ShortcutState::Released => "released",
361 }
362 }
363}
364
365impl Display for ShortcutState {
366 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
367 write!(f, "{}", self.as_str())
368 }
369}
370
371#[derive(Debug, Clone)]
373pub struct ShortcutEvent {
374 pub shortcut: Shortcut,
375 pub state: ShortcutState,
376}