1use super::parser::{KeyCombo, Modifiers, ParsedKey};
8use winit::event::{KeyEvent, Modifiers as WinitModifiers};
9use winit::keyboard::{Key, KeyCode, NamedKey, PhysicalKey};
10
11#[derive(Debug)]
13pub struct KeybindingMatcher {
14 modifiers: Modifiers,
16 key: Option<MatchKey>,
18 physical_key: Option<KeyCode>,
20}
21
22#[derive(Debug)]
24enum MatchKey {
25 Character(char),
26 Named(NamedKey),
27}
28
29impl KeybindingMatcher {
30 pub fn from_event(event: &KeyEvent, modifiers: &WinitModifiers) -> Self {
32 let mods = Modifiers {
33 ctrl: modifiers.state().control_key(),
34 alt: modifiers.state().alt_key(),
35 shift: modifiers.state().shift_key(),
36 super_key: modifiers.state().super_key(),
37 cmd_or_ctrl: false, };
39
40 let key = match &event.logical_key {
41 Key::Character(c) => {
42 c.chars()
44 .next()
45 .map(|ch| MatchKey::Character(ch.to_ascii_uppercase()))
46 }
47 Key::Named(named) => Some(MatchKey::Named(*named)),
48 _ => None,
49 };
50
51 let physical_key = match event.physical_key {
53 PhysicalKey::Code(code) => Some(code),
54 PhysicalKey::Unidentified(_) => None,
55 };
56
57 Self {
58 modifiers: mods,
59 key,
60 physical_key,
61 }
62 }
63
64 pub fn from_event_with_remapping(
69 event: &KeyEvent,
70 modifiers: &WinitModifiers,
71 remapping: &par_term_config::ModifierRemapping,
72 ) -> Self {
73 use par_term_config::ModifierTarget;
74
75 let mut ctrl = modifiers.state().control_key();
77 let mut alt = modifiers.state().alt_key();
78 let mut shift = modifiers.state().shift_key();
79 let mut super_key = modifiers.state().super_key();
80
81 let apply_remap = |target: ModifierTarget,
86 ctrl: &mut bool,
87 alt: &mut bool,
88 shift: &mut bool,
89 super_key: &mut bool,
90 is_pressed: bool| {
91 if !is_pressed {
92 return;
93 }
94 match target {
95 ModifierTarget::None => {} ModifierTarget::Ctrl => *ctrl = true,
97 ModifierTarget::Alt => *alt = true,
98 ModifierTarget::Shift => *shift = true,
99 ModifierTarget::Super => *super_key = true,
100 }
101 };
102
103 let has_remapping = remapping.left_ctrl != ModifierTarget::None
105 || remapping.right_ctrl != ModifierTarget::None
106 || remapping.left_alt != ModifierTarget::None
107 || remapping.right_alt != ModifierTarget::None
108 || remapping.left_super != ModifierTarget::None
109 || remapping.right_super != ModifierTarget::None;
110
111 if has_remapping {
112 if let PhysicalKey::Code(code) = event.physical_key {
114 let orig_ctrl = ctrl;
116 let orig_alt = alt;
117 let _orig_shift = shift; let orig_super = super_key;
119
120 if remapping.left_ctrl != ModifierTarget::None
122 || remapping.right_ctrl != ModifierTarget::None
123 {
124 ctrl = false;
125 }
126 if remapping.left_alt != ModifierTarget::None
127 || remapping.right_alt != ModifierTarget::None
128 {
129 alt = false;
130 }
131 if remapping.left_super != ModifierTarget::None
132 || remapping.right_super != ModifierTarget::None
133 {
134 super_key = false;
135 }
136
137 if orig_ctrl {
143 if remapping.left_ctrl != ModifierTarget::None {
144 apply_remap(
145 remapping.left_ctrl,
146 &mut ctrl,
147 &mut alt,
148 &mut shift,
149 &mut super_key,
150 true,
151 );
152 } else if remapping.right_ctrl != ModifierTarget::None {
153 apply_remap(
154 remapping.right_ctrl,
155 &mut ctrl,
156 &mut alt,
157 &mut shift,
158 &mut super_key,
159 true,
160 );
161 } else {
162 ctrl = true; }
164 }
165
166 if orig_alt {
168 if remapping.left_alt != ModifierTarget::None {
169 apply_remap(
170 remapping.left_alt,
171 &mut ctrl,
172 &mut alt,
173 &mut shift,
174 &mut super_key,
175 true,
176 );
177 } else if remapping.right_alt != ModifierTarget::None {
178 apply_remap(
179 remapping.right_alt,
180 &mut ctrl,
181 &mut alt,
182 &mut shift,
183 &mut super_key,
184 true,
185 );
186 } else {
187 alt = true; }
189 }
190
191 if orig_super {
193 if remapping.left_super != ModifierTarget::None {
194 apply_remap(
195 remapping.left_super,
196 &mut ctrl,
197 &mut alt,
198 &mut shift,
199 &mut super_key,
200 true,
201 );
202 } else if remapping.right_super != ModifierTarget::None {
203 apply_remap(
204 remapping.right_super,
205 &mut ctrl,
206 &mut alt,
207 &mut shift,
208 &mut super_key,
209 true,
210 );
211 } else {
212 super_key = true; }
214 }
215
216 match code {
218 KeyCode::ControlLeft if remapping.left_ctrl != ModifierTarget::None => {
219 }
221 KeyCode::ControlRight if remapping.right_ctrl != ModifierTarget::None => {}
222 KeyCode::AltLeft if remapping.left_alt != ModifierTarget::None => {}
223 KeyCode::AltRight if remapping.right_alt != ModifierTarget::None => {}
224 KeyCode::SuperLeft if remapping.left_super != ModifierTarget::None => {}
225 KeyCode::SuperRight if remapping.right_super != ModifierTarget::None => {}
226 _ => {}
227 }
228 }
229 }
230
231 let mods = Modifiers {
232 ctrl,
233 alt,
234 shift,
235 super_key,
236 cmd_or_ctrl: false,
237 };
238
239 let key = match &event.logical_key {
240 Key::Character(c) => c
241 .chars()
242 .next()
243 .map(|ch| MatchKey::Character(ch.to_ascii_uppercase())),
244 Key::Named(named) => Some(MatchKey::Named(*named)),
245 _ => None,
246 };
247
248 let physical_key = match event.physical_key {
249 PhysicalKey::Code(code) => Some(code),
250 PhysicalKey::Unidentified(_) => None,
251 };
252
253 Self {
254 modifiers: mods,
255 key,
256 physical_key,
257 }
258 }
259
260 pub fn matches(&self, combo: &KeyCombo) -> bool {
262 self.matches_with_physical_preference(combo, false)
263 }
264
265 pub fn matches_with_physical_preference(
270 &self,
271 combo: &KeyCombo,
272 use_physical_keys: bool,
273 ) -> bool {
274 let key_matches = match (&combo.key, use_physical_keys) {
276 (ParsedKey::Physical(combo_code), _) => self.physical_key.as_ref() == Some(combo_code),
278 (ParsedKey::Character(combo_char), true) => {
280 if let Some(physical) = self.physical_key {
282 physical_key_matches_char(physical, *combo_char)
283 } else if let Some(MatchKey::Character(event_char)) = &self.key {
284 event_char.eq_ignore_ascii_case(combo_char)
286 } else {
287 false
288 }
289 }
290 (ParsedKey::Character(combo_char), false) => {
292 if let Some(MatchKey::Character(event_char)) = &self.key {
293 event_char.eq_ignore_ascii_case(combo_char)
294 } else {
295 false
296 }
297 }
298 (ParsedKey::Named(combo_named), _) => {
300 if let Some(MatchKey::Named(event_named)) = &self.key {
301 event_named == combo_named
302 } else {
303 false
304 }
305 }
306 };
307
308 if !key_matches {
309 return false;
310 }
311
312 self.modifiers_match(&combo.modifiers)
314 }
315
316 fn modifiers_match(&self, combo_mods: &Modifiers) -> bool {
318 let (expected_ctrl, expected_super) = if combo_mods.cmd_or_ctrl {
320 #[cfg(target_os = "macos")]
321 {
322 (combo_mods.ctrl, true) }
324 #[cfg(not(target_os = "macos"))]
325 {
326 (true, combo_mods.super_key) }
328 } else {
329 (combo_mods.ctrl, combo_mods.super_key)
330 };
331
332 self.modifiers.ctrl == expected_ctrl
334 && self.modifiers.alt == combo_mods.alt
335 && self.modifiers.shift == combo_mods.shift
336 && self.modifiers.super_key == expected_super
337 }
338}
339
340fn physical_key_matches_char(code: KeyCode, ch: char) -> bool {
345 let expected_char = match code {
346 KeyCode::KeyA => 'A',
347 KeyCode::KeyB => 'B',
348 KeyCode::KeyC => 'C',
349 KeyCode::KeyD => 'D',
350 KeyCode::KeyE => 'E',
351 KeyCode::KeyF => 'F',
352 KeyCode::KeyG => 'G',
353 KeyCode::KeyH => 'H',
354 KeyCode::KeyI => 'I',
355 KeyCode::KeyJ => 'J',
356 KeyCode::KeyK => 'K',
357 KeyCode::KeyL => 'L',
358 KeyCode::KeyM => 'M',
359 KeyCode::KeyN => 'N',
360 KeyCode::KeyO => 'O',
361 KeyCode::KeyP => 'P',
362 KeyCode::KeyQ => 'Q',
363 KeyCode::KeyR => 'R',
364 KeyCode::KeyS => 'S',
365 KeyCode::KeyT => 'T',
366 KeyCode::KeyU => 'U',
367 KeyCode::KeyV => 'V',
368 KeyCode::KeyW => 'W',
369 KeyCode::KeyX => 'X',
370 KeyCode::KeyY => 'Y',
371 KeyCode::KeyZ => 'Z',
372 KeyCode::Digit0 => '0',
373 KeyCode::Digit1 => '1',
374 KeyCode::Digit2 => '2',
375 KeyCode::Digit3 => '3',
376 KeyCode::Digit4 => '4',
377 KeyCode::Digit5 => '5',
378 KeyCode::Digit6 => '6',
379 KeyCode::Digit7 => '7',
380 KeyCode::Digit8 => '8',
381 KeyCode::Digit9 => '9',
382 KeyCode::Minus => '-',
383 KeyCode::Equal => '=',
384 KeyCode::BracketLeft => '[',
385 KeyCode::BracketRight => ']',
386 KeyCode::Backslash => '\\',
387 KeyCode::Semicolon => ';',
388 KeyCode::Quote => '\'',
389 KeyCode::Backquote => '`',
390 KeyCode::Comma => ',',
391 KeyCode::Period => '.',
392 KeyCode::Slash => '/',
393 _ => return false,
394 };
395 expected_char.eq_ignore_ascii_case(&ch)
396}
397
398#[cfg(test)]
406mod tests {
407 use super::*;
408 use crate::parser::parse_key_combo;
409
410 #[test]
412 fn test_cmd_or_ctrl_modifiers() {
413 let matcher_ctrl_shift = KeybindingMatcher {
415 modifiers: Modifiers {
416 ctrl: true,
417 alt: false,
418 shift: true,
419 super_key: false,
420 cmd_or_ctrl: false,
421 },
422 key: Some(MatchKey::Character('B')),
423 physical_key: Some(KeyCode::KeyB),
424 };
425
426 let matcher_super_shift = KeybindingMatcher {
427 modifiers: Modifiers {
428 ctrl: false,
429 alt: false,
430 shift: true,
431 super_key: true,
432 cmd_or_ctrl: false,
433 },
434 key: Some(MatchKey::Character('B')),
435 physical_key: Some(KeyCode::KeyB),
436 };
437
438 let combo = parse_key_combo("CmdOrCtrl+Shift+B").unwrap();
439
440 #[cfg(target_os = "macos")]
442 {
443 assert!(matcher_super_shift.matches(&combo));
444 assert!(!matcher_ctrl_shift.matches(&combo));
445 }
446
447 #[cfg(not(target_os = "macos"))]
449 {
450 assert!(matcher_ctrl_shift.matches(&combo));
451 assert!(!matcher_super_shift.matches(&combo));
452 }
453 }
454
455 #[test]
457 fn test_character_matching() {
458 let combo = parse_key_combo("Ctrl+A").unwrap();
459
460 let matcher_lower = KeybindingMatcher {
462 modifiers: Modifiers {
463 ctrl: true,
464 alt: false,
465 shift: false,
466 super_key: false,
467 cmd_or_ctrl: false,
468 },
469 key: Some(MatchKey::Character('a')),
470 physical_key: Some(KeyCode::KeyA),
471 };
472 assert!(matcher_lower.matches(&combo));
473
474 let matcher_upper = KeybindingMatcher {
476 modifiers: Modifiers {
477 ctrl: true,
478 alt: false,
479 shift: false,
480 super_key: false,
481 cmd_or_ctrl: false,
482 },
483 key: Some(MatchKey::Character('A')),
484 physical_key: Some(KeyCode::KeyA),
485 };
486 assert!(matcher_upper.matches(&combo));
487
488 let matcher_wrong = KeybindingMatcher {
490 modifiers: Modifiers {
491 ctrl: true,
492 alt: false,
493 shift: false,
494 super_key: false,
495 cmd_or_ctrl: false,
496 },
497 key: Some(MatchKey::Character('B')),
498 physical_key: Some(KeyCode::KeyB),
499 };
500 assert!(!matcher_wrong.matches(&combo));
501 }
502
503 #[test]
505 fn test_named_key_matching() {
506 let combo = parse_key_combo("F5").unwrap();
507
508 let matcher = KeybindingMatcher {
509 modifiers: Modifiers::default(),
510 key: Some(MatchKey::Named(NamedKey::F5)),
511 physical_key: Some(KeyCode::F5),
512 };
513 assert!(matcher.matches(&combo));
514
515 let matcher_wrong = KeybindingMatcher {
516 modifiers: Modifiers::default(),
517 key: Some(MatchKey::Named(NamedKey::F6)),
518 physical_key: Some(KeyCode::F6),
519 };
520 assert!(!matcher_wrong.matches(&combo));
521 }
522
523 #[test]
525 fn test_modifier_mismatch() {
526 let combo = parse_key_combo("Ctrl+Shift+B").unwrap();
527
528 let matcher = KeybindingMatcher {
530 modifiers: Modifiers {
531 ctrl: true,
532 alt: false,
533 shift: false,
534 super_key: false,
535 cmd_or_ctrl: false,
536 },
537 key: Some(MatchKey::Character('B')),
538 physical_key: Some(KeyCode::KeyB),
539 };
540 assert!(!matcher.matches(&combo));
541
542 let matcher = KeybindingMatcher {
544 modifiers: Modifiers {
545 ctrl: true,
546 alt: true,
547 shift: true,
548 super_key: false,
549 cmd_or_ctrl: false,
550 },
551 key: Some(MatchKey::Character('B')),
552 physical_key: Some(KeyCode::KeyB),
553 };
554 assert!(!matcher.matches(&combo));
555 }
556
557 #[test]
559 fn test_physical_key_matching() {
560 let combo = parse_key_combo("Ctrl+[KeyZ]").unwrap();
562
563 let matcher = KeybindingMatcher {
565 modifiers: Modifiers {
566 ctrl: true,
567 alt: false,
568 shift: false,
569 super_key: false,
570 cmd_or_ctrl: false,
571 },
572 key: Some(MatchKey::Character('W')), physical_key: Some(KeyCode::KeyZ),
574 };
575 assert!(matcher.matches(&combo));
576
577 let matcher_wrong = KeybindingMatcher {
579 modifiers: Modifiers {
580 ctrl: true,
581 alt: false,
582 shift: false,
583 super_key: false,
584 cmd_or_ctrl: false,
585 },
586 key: Some(MatchKey::Character('Z')),
587 physical_key: Some(KeyCode::KeyW),
588 };
589 assert!(!matcher_wrong.matches(&combo));
590 }
591
592 #[test]
594 fn test_physical_key_preference() {
595 let combo = parse_key_combo("Ctrl+Z").unwrap();
597
598 let matcher_azerty = KeybindingMatcher {
601 modifiers: Modifiers {
602 ctrl: true,
603 alt: false,
604 shift: false,
605 super_key: false,
606 cmd_or_ctrl: false,
607 },
608 key: Some(MatchKey::Character('W')), physical_key: Some(KeyCode::KeyZ), };
611
612 assert!(!matcher_azerty.matches_with_physical_preference(&combo, false));
614
615 assert!(matcher_azerty.matches_with_physical_preference(&combo, true));
617 }
618}