1pub use keyboard_types::{Code, Modifiers};
27use std::error::Error as StdError;
28use std::{borrow::Borrow, hash::Hash, str::FromStr};
29
30pub const CMD_OR_CTRL: Modifiers = Modifiers::CONTROL;
31
32#[derive(Debug)]
34pub enum AcceleratorParseError {
35 UnsupportedKey(String),
38 EmptyToken(String),
40 InvalidFormat(String),
43}
44
45impl std::fmt::Display for AcceleratorParseError {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 AcceleratorParseError::UnsupportedKey(key) => write!(
49 f,
50 "Couldn't recognize \"{}\" as a valid key for accelerator, if you feel like it should be, please report this to https://github.com/win-rs/muda-win",
51 key
52 ),
53 AcceleratorParseError::EmptyToken(token) => {
54 write!(f, "Found empty token while parsing accelerator: {}", token)
55 }
56 AcceleratorParseError::InvalidFormat(format) => write!(
57 f,
58 "Invalid accelerator format: \"{}\", an accelerator should have the modifiers first and only one main key, for example: \"Shift + Alt + K\"",
59 format
60 ),
61 }
62 }
63}
64
65impl StdError for AcceleratorParseError {}
66
67#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72pub struct Accelerator {
73 pub(crate) mods: Modifiers,
74 pub(crate) key: Code,
75 id: u32,
76}
77
78impl Accelerator {
79 pub fn new(mods: Option<Modifiers>, key: Code) -> Self {
82 let mut mods = mods.unwrap_or_else(Modifiers::empty);
83 if mods.contains(Modifiers::META) {
84 mods.remove(Modifiers::META);
85 mods.insert(Modifiers::SUPER);
86 }
87
88 let id = Self::generate_hash(mods, key);
89
90 Self { mods, key, id }
91 }
92
93 fn generate_hash(mods: Modifiers, key: Code) -> u32 {
94 let mut accelerator_str = String::new();
95 if mods.contains(Modifiers::SHIFT) {
96 accelerator_str.push_str("shift+")
97 }
98 if mods.contains(Modifiers::CONTROL) {
99 accelerator_str.push_str("control+")
100 }
101 if mods.contains(Modifiers::ALT) {
102 accelerator_str.push_str("alt+")
103 }
104 if mods.contains(Modifiers::SUPER) {
105 accelerator_str.push_str("super+")
106 }
107 accelerator_str.push_str(&key.to_string());
108
109 let mut hasher = std::collections::hash_map::DefaultHasher::new();
110 accelerator_str.hash(&mut hasher);
111 std::hash::Hasher::finish(&hasher) as u32
112 }
113
114 pub fn id(&self) -> u32 {
117 self.id
118 }
119
120 pub fn modifiers(&self) -> Modifiers {
122 self.mods
123 }
124
125 pub fn key(&self) -> Code {
127 self.key
128 }
129
130 pub fn matches(&self, modifiers: impl Borrow<Modifiers>, key: impl Borrow<Code>) -> bool {
132 let base_mods = Modifiers::SHIFT | Modifiers::CONTROL | Modifiers::ALT | Modifiers::SUPER;
134 let modifiers = modifiers.borrow();
135 let key = key.borrow();
136 self.mods == *modifiers & base_mods && self.key == *key
137 }
138}
139
140impl FromStr for Accelerator {
141 type Err = AcceleratorParseError;
142 fn from_str(accelerator_string: &str) -> Result<Self, Self::Err> {
143 parse_accelerator(accelerator_string)
144 }
145}
146
147impl TryFrom<&str> for Accelerator {
148 type Error = AcceleratorParseError;
149
150 fn try_from(value: &str) -> Result<Self, Self::Error> {
151 parse_accelerator(value)
152 }
153}
154
155impl TryFrom<String> for Accelerator {
156 type Error = AcceleratorParseError;
157
158 fn try_from(value: String) -> Result<Self, Self::Error> {
159 parse_accelerator(&value)
160 }
161}
162
163fn parse_accelerator(accelerator: &str) -> Result<Accelerator, AcceleratorParseError> {
164 let tokens = accelerator.split('+').collect::<Vec<&str>>();
165
166 let mut mods = Modifiers::empty();
167 let mut key = None;
168
169 match tokens.len() {
170 1 => {
172 key = Some(parse_key(tokens[0])?);
173 }
174
175 _ => {
177 for raw in tokens {
178 let token = raw.trim();
179
180 if token.is_empty() {
181 return Err(AcceleratorParseError::EmptyToken(accelerator.to_string()));
182 }
183
184 if key.is_some() {
185 return Err(AcceleratorParseError::InvalidFormat(
192 accelerator.to_string(),
193 ));
194 }
195
196 match token.to_uppercase().as_str() {
197 "OPTION" | "ALT" => {
198 mods |= Modifiers::ALT;
199 }
200 "CONTROL" | "CTRL" => {
201 mods |= Modifiers::CONTROL;
202 }
203 "COMMAND" | "CMD" | "SUPER" => {
204 mods |= Modifiers::META;
205 }
206 "SHIFT" => {
207 mods |= Modifiers::SHIFT;
208 }
209 "COMMANDORCONTROL" | "COMMANDORCTRL" | "CMDORCTRL" | "CMDORCONTROL" => {
210 mods |= Modifiers::CONTROL;
211 }
212 _ => {
213 key = Some(parse_key(token)?);
214 }
215 }
216 }
217 }
218 }
219
220 let key = key.ok_or_else(|| AcceleratorParseError::InvalidFormat(accelerator.to_string()))?;
221 Ok(Accelerator::new(Some(mods), key))
222}
223
224fn parse_key(key: &str) -> Result<Code, AcceleratorParseError> {
225 use Code::*;
226 match key.to_uppercase().as_str() {
227 "BACKQUOTE" | "`" => Ok(Backquote),
228 "BACKSLASH" | "\\" => Ok(Backslash),
229 "BRACKETLEFT" | "[" => Ok(BracketLeft),
230 "BRACKETRIGHT" | "]" => Ok(BracketRight),
231 "COMMA" | "," => Ok(Comma),
232 "DIGIT0" | "0" => Ok(Digit0),
233 "DIGIT1" | "1" => Ok(Digit1),
234 "DIGIT2" | "2" => Ok(Digit2),
235 "DIGIT3" | "3" => Ok(Digit3),
236 "DIGIT4" | "4" => Ok(Digit4),
237 "DIGIT5" | "5" => Ok(Digit5),
238 "DIGIT6" | "6" => Ok(Digit6),
239 "DIGIT7" | "7" => Ok(Digit7),
240 "DIGIT8" | "8" => Ok(Digit8),
241 "DIGIT9" | "9" => Ok(Digit9),
242 "EQUAL" | "=" => Ok(Equal),
243 "KEYA" | "A" => Ok(KeyA),
244 "KEYB" | "B" => Ok(KeyB),
245 "KEYC" | "C" => Ok(KeyC),
246 "KEYD" | "D" => Ok(KeyD),
247 "KEYE" | "E" => Ok(KeyE),
248 "KEYF" | "F" => Ok(KeyF),
249 "KEYG" | "G" => Ok(KeyG),
250 "KEYH" | "H" => Ok(KeyH),
251 "KEYI" | "I" => Ok(KeyI),
252 "KEYJ" | "J" => Ok(KeyJ),
253 "KEYK" | "K" => Ok(KeyK),
254 "KEYL" | "L" => Ok(KeyL),
255 "KEYM" | "M" => Ok(KeyM),
256 "KEYN" | "N" => Ok(KeyN),
257 "KEYO" | "O" => Ok(KeyO),
258 "KEYP" | "P" => Ok(KeyP),
259 "KEYQ" | "Q" => Ok(KeyQ),
260 "KEYR" | "R" => Ok(KeyR),
261 "KEYS" | "S" => Ok(KeyS),
262 "KEYT" | "T" => Ok(KeyT),
263 "KEYU" | "U" => Ok(KeyU),
264 "KEYV" | "V" => Ok(KeyV),
265 "KEYW" | "W" => Ok(KeyW),
266 "KEYX" | "X" => Ok(KeyX),
267 "KEYY" | "Y" => Ok(KeyY),
268 "KEYZ" | "Z" => Ok(KeyZ),
269 "MINUS" | "-" => Ok(Minus),
270 "PERIOD" | "." => Ok(Period),
271 "QUOTE" | "'" => Ok(Quote),
272 "SEMICOLON" | ";" => Ok(Semicolon),
273 "SLASH" | "/" => Ok(Slash),
274 "BACKSPACE" => Ok(Backspace),
275 "CAPSLOCK" => Ok(CapsLock),
276 "ENTER" => Ok(Enter),
277 "SPACE" => Ok(Space),
278 "TAB" => Ok(Tab),
279 "DELETE" => Ok(Delete),
280 "END" => Ok(End),
281 "HOME" => Ok(Home),
282 "INSERT" => Ok(Insert),
283 "PAGEDOWN" => Ok(PageDown),
284 "PAGEUP" => Ok(PageUp),
285 "PRINTSCREEN" => Ok(PrintScreen),
286 "SCROLLLOCK" => Ok(ScrollLock),
287 "ARROWDOWN" | "DOWN" => Ok(ArrowDown),
288 "ARROWLEFT" | "LEFT" => Ok(ArrowLeft),
289 "ARROWRIGHT" | "RIGHT" => Ok(ArrowRight),
290 "ARROWUP" | "UP" => Ok(ArrowUp),
291 "NUMLOCK" => Ok(NumLock),
292 "NUMPAD0" | "NUM0" => Ok(Numpad0),
293 "NUMPAD1" | "NUM1" => Ok(Numpad1),
294 "NUMPAD2" | "NUM2" => Ok(Numpad2),
295 "NUMPAD3" | "NUM3" => Ok(Numpad3),
296 "NUMPAD4" | "NUM4" => Ok(Numpad4),
297 "NUMPAD5" | "NUM5" => Ok(Numpad5),
298 "NUMPAD6" | "NUM6" => Ok(Numpad6),
299 "NUMPAD7" | "NUM7" => Ok(Numpad7),
300 "NUMPAD8" | "NUM8" => Ok(Numpad8),
301 "NUMPAD9" | "NUM9" => Ok(Numpad9),
302 "NUMPADADD" | "NUMADD" | "NUMPADPLUS" | "NUMPLUS" => Ok(NumpadAdd),
303 "NUMPADDECIMAL" | "NUMDECIMAL" => Ok(NumpadDecimal),
304 "NUMPADDIVIDE" | "NUMDIVIDE" => Ok(NumpadDivide),
305 "NUMPADENTER" | "NUMENTER" => Ok(NumpadEnter),
306 "NUMPADEQUAL" | "NUMEQUAL" => Ok(NumpadEqual),
307 "NUMPADMULTIPLY" | "NUMMULTIPLY" => Ok(NumpadMultiply),
308 "NUMPADSUBTRACT" | "NUMSUBTRACT" => Ok(NumpadSubtract),
309 "ESCAPE" | "ESC" => Ok(Escape),
310 "F1" => Ok(F1),
311 "F2" => Ok(F2),
312 "F3" => Ok(F3),
313 "F4" => Ok(F4),
314 "F5" => Ok(F5),
315 "F6" => Ok(F6),
316 "F7" => Ok(F7),
317 "F8" => Ok(F8),
318 "F9" => Ok(F9),
319 "F10" => Ok(F10),
320 "F11" => Ok(F11),
321 "F12" => Ok(F12),
322 "AUDIOVOLUMEDOWN" | "VOLUMEDOWN" => Ok(AudioVolumeDown),
323 "AUDIOVOLUMEUP" | "VOLUMEUP" => Ok(AudioVolumeUp),
324 "AUDIOVOLUMEMUTE" | "VOLUMEMUTE" => Ok(AudioVolumeMute),
325 "F13" => Ok(F13),
326 "F14" => Ok(F14),
327 "F15" => Ok(F15),
328 "F16" => Ok(F16),
329 "F17" => Ok(F17),
330 "F18" => Ok(F18),
331 "F19" => Ok(F19),
332 "F20" => Ok(F20),
333 "F21" => Ok(F21),
334 "F22" => Ok(F22),
335 "F23" => Ok(F23),
336 "F24" => Ok(F24),
337
338 _ => Err(AcceleratorParseError::UnsupportedKey(key.to_string())),
339 }
340}
341
342#[test]
343fn test_parse_accelerator() {
344 macro_rules! assert_parse_accelerator {
345 ($key:literal, $lrh:expr) => {
346 let r = parse_accelerator($key).unwrap();
347 let l = $lrh;
348 assert_eq!(r.mods, l.mods);
349 assert_eq!(r.key, l.key);
350 };
351 }
352
353 assert_parse_accelerator!(
354 "KeyX",
355 Accelerator {
356 mods: Modifiers::empty(),
357 key: Code::KeyX,
358 id: 0,
359 }
360 );
361
362 assert_parse_accelerator!(
363 "CTRL+KeyX",
364 Accelerator {
365 mods: Modifiers::CONTROL,
366 key: Code::KeyX,
367 id: 0,
368 }
369 );
370
371 assert_parse_accelerator!(
372 "SHIFT+KeyC",
373 Accelerator {
374 mods: Modifiers::SHIFT,
375 key: Code::KeyC,
376 id: 0,
377 }
378 );
379
380 assert_parse_accelerator!(
381 "SHIFT+KeyC",
382 Accelerator {
383 mods: Modifiers::SHIFT,
384 key: Code::KeyC,
385 id: 0,
386 }
387 );
388
389 assert_parse_accelerator!(
390 "super+ctrl+SHIFT+alt+ArrowUp",
391 Accelerator {
392 mods: Modifiers::SUPER | Modifiers::CONTROL | Modifiers::SHIFT | Modifiers::ALT,
393 key: Code::ArrowUp,
394 id: 0,
395 }
396 );
397 assert_parse_accelerator!(
398 "Digit5",
399 Accelerator {
400 mods: Modifiers::empty(),
401 key: Code::Digit5,
402 id: 0,
403 }
404 );
405 assert_parse_accelerator!(
406 "KeyG",
407 Accelerator {
408 mods: Modifiers::empty(),
409 key: Code::KeyG,
410 id: 0,
411 }
412 );
413
414 assert_parse_accelerator!(
415 "SHiFT+F12",
416 Accelerator {
417 mods: Modifiers::SHIFT,
418 key: Code::F12,
419 id: 0,
420 }
421 );
422
423 assert_parse_accelerator!(
424 "CmdOrCtrl+Space",
425 Accelerator {
426 #[cfg(target_os = "macos")]
427 mods: Modifiers::SUPER,
428 #[cfg(not(target_os = "macos"))]
429 mods: Modifiers::CONTROL,
430 key: Code::Space,
431 id: 0,
432 }
433 );
434}
435
436#[test]
437fn test_equality() {
438 let h1 = parse_accelerator("Shift+KeyR").unwrap();
439 let h2 = parse_accelerator("Shift+KeyR").unwrap();
440 let h3 = Accelerator::new(Some(Modifiers::SHIFT), Code::KeyR);
441 let h4 = parse_accelerator("Alt+KeyR").unwrap();
442 let h5 = parse_accelerator("Alt+KeyR").unwrap();
443 let h6 = parse_accelerator("KeyR").unwrap();
444
445 assert!(h1 == h2 && h2 == h3 && h3 != h4 && h4 == h5 && h5 != h6);
446 assert!(
447 h1.id() == h2.id()
448 && h2.id() == h3.id()
449 && h3.id() != h4.id()
450 && h4.id() == h5.id()
451 && h5.id() != h6.id()
452 );
453}