Skip to main content

kiosk_core/
keyboard.rs

1// This code is adapted from Helix via scooter: https://github.com/helix-editor/helix/blob/d79cce4e/helix-view/src/keyboard.rs
2use anyhow::anyhow;
3use bitflags::bitflags;
4use serde::Serialize;
5
6bitflags! {
7    /// Represents key modifiers (shift, control, alt).
8    #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
9    pub struct KeyModifiers: u8 {
10        const SHIFT = 0b0000_0001;
11        const CONTROL = 0b0000_0010;
12        const ALT = 0b0000_0100;
13        const SUPER = 0b0000_1000;
14        const HYPER = 0b0001_0000;
15        const META = 0b0010_0000;
16        const NONE = 0b0000_0000;
17    }
18}
19
20impl From<KeyModifiers> for crossterm::event::KeyModifiers {
21    fn from(key_modifiers: KeyModifiers) -> Self {
22        use crossterm::event::KeyModifiers as CKeyModifiers;
23
24        let mut result = CKeyModifiers::NONE;
25
26        if key_modifiers.contains(KeyModifiers::SHIFT) {
27            result.insert(CKeyModifiers::SHIFT);
28        }
29        if key_modifiers.contains(KeyModifiers::CONTROL) {
30            result.insert(CKeyModifiers::CONTROL);
31        }
32        if key_modifiers.contains(KeyModifiers::ALT) {
33            result.insert(CKeyModifiers::ALT);
34        }
35        if key_modifiers.contains(KeyModifiers::SUPER) {
36            result.insert(CKeyModifiers::SUPER);
37        }
38
39        result
40    }
41}
42
43impl From<crossterm::event::KeyModifiers> for KeyModifiers {
44    fn from(val: crossterm::event::KeyModifiers) -> Self {
45        use crossterm::event::KeyModifiers as CKeyModifiers;
46
47        let mut result = KeyModifiers::NONE;
48
49        if val.contains(CKeyModifiers::SHIFT) {
50            result.insert(KeyModifiers::SHIFT);
51        }
52        if val.contains(CKeyModifiers::CONTROL) {
53            result.insert(KeyModifiers::CONTROL);
54        }
55        if val.contains(CKeyModifiers::ALT) {
56            result.insert(KeyModifiers::ALT);
57        }
58        if val.contains(CKeyModifiers::SUPER) {
59            result.insert(KeyModifiers::SUPER);
60        }
61
62        result
63    }
64}
65
66/// Represents a media key (as part of [`KeyCode::Media`]).
67#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
68pub enum MediaKeyCode {
69    /// Play media key.
70    Play,
71    /// Pause media key.
72    Pause,
73    /// Play/Pause media key.
74    PlayPause,
75    /// Reverse media key.
76    Reverse,
77    /// Stop media key.
78    Stop,
79    /// Fast-forward media key.
80    FastForward,
81    /// Rewind media key.
82    Rewind,
83    /// Next-track media key.
84    TrackNext,
85    /// Previous-track media key.
86    TrackPrevious,
87    /// Record media key.
88    Record,
89    /// Lower-volume media key.
90    LowerVolume,
91    /// Raise-volume media key.
92    RaiseVolume,
93    /// Mute media key.
94    MuteVolume,
95}
96
97impl From<MediaKeyCode> for crossterm::event::MediaKeyCode {
98    fn from(media_key_code: MediaKeyCode) -> Self {
99        use crossterm::event::MediaKeyCode as CMediaKeyCode;
100
101        match media_key_code {
102            MediaKeyCode::Play => CMediaKeyCode::Play,
103            MediaKeyCode::Pause => CMediaKeyCode::Pause,
104            MediaKeyCode::PlayPause => CMediaKeyCode::PlayPause,
105            MediaKeyCode::Reverse => CMediaKeyCode::Reverse,
106            MediaKeyCode::Stop => CMediaKeyCode::Stop,
107            MediaKeyCode::FastForward => CMediaKeyCode::FastForward,
108            MediaKeyCode::Rewind => CMediaKeyCode::Rewind,
109            MediaKeyCode::TrackNext => CMediaKeyCode::TrackNext,
110            MediaKeyCode::TrackPrevious => CMediaKeyCode::TrackPrevious,
111            MediaKeyCode::Record => CMediaKeyCode::Record,
112            MediaKeyCode::LowerVolume => CMediaKeyCode::LowerVolume,
113            MediaKeyCode::RaiseVolume => CMediaKeyCode::RaiseVolume,
114            MediaKeyCode::MuteVolume => CMediaKeyCode::MuteVolume,
115        }
116    }
117}
118
119impl From<crossterm::event::MediaKeyCode> for MediaKeyCode {
120    fn from(val: crossterm::event::MediaKeyCode) -> Self {
121        use crossterm::event::MediaKeyCode as CMediaKeyCode;
122
123        match val {
124            CMediaKeyCode::Play => MediaKeyCode::Play,
125            CMediaKeyCode::Pause => MediaKeyCode::Pause,
126            CMediaKeyCode::PlayPause => MediaKeyCode::PlayPause,
127            CMediaKeyCode::Reverse => MediaKeyCode::Reverse,
128            CMediaKeyCode::Stop => MediaKeyCode::Stop,
129            CMediaKeyCode::FastForward => MediaKeyCode::FastForward,
130            CMediaKeyCode::Rewind => MediaKeyCode::Rewind,
131            CMediaKeyCode::TrackNext => MediaKeyCode::TrackNext,
132            CMediaKeyCode::TrackPrevious => MediaKeyCode::TrackPrevious,
133            CMediaKeyCode::Record => MediaKeyCode::Record,
134            CMediaKeyCode::LowerVolume => MediaKeyCode::LowerVolume,
135            CMediaKeyCode::RaiseVolume => MediaKeyCode::RaiseVolume,
136            CMediaKeyCode::MuteVolume => MediaKeyCode::MuteVolume,
137        }
138    }
139}
140
141/// Represents a media key (as part of [`KeyCode::Modifier`]).
142#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
143pub enum ModifierKeyCode {
144    /// Left Shift key.
145    LeftShift,
146    /// Left Control key.
147    LeftControl,
148    /// Left Alt key.
149    LeftAlt,
150    /// Left Super key.
151    LeftSuper,
152    /// Left Hyper key.
153    LeftHyper,
154    /// Left Meta key.
155    LeftMeta,
156    /// Right Shift key.
157    RightShift,
158    /// Right Control key.
159    RightControl,
160    /// Right Alt key.
161    RightAlt,
162    /// Right Super key.
163    RightSuper,
164    /// Right Hyper key.
165    RightHyper,
166    /// Right Meta key.
167    RightMeta,
168    /// Iso Level3 Shift key.
169    IsoLevel3Shift,
170    /// Iso Level5 Shift key.
171    IsoLevel5Shift,
172}
173
174impl From<ModifierKeyCode> for crossterm::event::ModifierKeyCode {
175    fn from(modifier_key_code: ModifierKeyCode) -> Self {
176        use crossterm::event::ModifierKeyCode as CModifierKeyCode;
177
178        match modifier_key_code {
179            ModifierKeyCode::LeftShift => CModifierKeyCode::LeftShift,
180            ModifierKeyCode::LeftControl => CModifierKeyCode::LeftControl,
181            ModifierKeyCode::LeftAlt => CModifierKeyCode::LeftAlt,
182            ModifierKeyCode::LeftSuper => CModifierKeyCode::LeftSuper,
183            ModifierKeyCode::LeftHyper => CModifierKeyCode::LeftHyper,
184            ModifierKeyCode::LeftMeta => CModifierKeyCode::LeftMeta,
185            ModifierKeyCode::RightShift => CModifierKeyCode::RightShift,
186            ModifierKeyCode::RightControl => CModifierKeyCode::RightControl,
187            ModifierKeyCode::RightAlt => CModifierKeyCode::RightAlt,
188            ModifierKeyCode::RightSuper => CModifierKeyCode::RightSuper,
189            ModifierKeyCode::RightHyper => CModifierKeyCode::RightHyper,
190            ModifierKeyCode::RightMeta => CModifierKeyCode::RightMeta,
191            ModifierKeyCode::IsoLevel3Shift => CModifierKeyCode::IsoLevel3Shift,
192            ModifierKeyCode::IsoLevel5Shift => CModifierKeyCode::IsoLevel5Shift,
193        }
194    }
195}
196
197impl From<crossterm::event::ModifierKeyCode> for ModifierKeyCode {
198    fn from(val: crossterm::event::ModifierKeyCode) -> Self {
199        use crossterm::event::ModifierKeyCode as CModifierKeyCode;
200
201        match val {
202            CModifierKeyCode::LeftShift => ModifierKeyCode::LeftShift,
203            CModifierKeyCode::LeftControl => ModifierKeyCode::LeftControl,
204            CModifierKeyCode::LeftAlt => ModifierKeyCode::LeftAlt,
205            CModifierKeyCode::LeftSuper => ModifierKeyCode::LeftSuper,
206            CModifierKeyCode::LeftHyper => ModifierKeyCode::LeftHyper,
207            CModifierKeyCode::LeftMeta => ModifierKeyCode::LeftMeta,
208            CModifierKeyCode::RightShift => ModifierKeyCode::RightShift,
209            CModifierKeyCode::RightControl => ModifierKeyCode::RightControl,
210            CModifierKeyCode::RightAlt => ModifierKeyCode::RightAlt,
211            CModifierKeyCode::RightSuper => ModifierKeyCode::RightSuper,
212            CModifierKeyCode::RightHyper => ModifierKeyCode::RightHyper,
213            CModifierKeyCode::RightMeta => ModifierKeyCode::RightMeta,
214            CModifierKeyCode::IsoLevel3Shift => ModifierKeyCode::IsoLevel3Shift,
215            CModifierKeyCode::IsoLevel5Shift => ModifierKeyCode::IsoLevel5Shift,
216        }
217    }
218}
219
220/// Represents a key.
221#[allow(clippy::doc_markdown)]
222#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
223pub enum KeyCode {
224    /// Backspace key.
225    Backspace,
226    /// Enter key.
227    Enter,
228    /// Left arrow key.
229    Left,
230    /// Right arrow key.
231    Right,
232    /// Up arrow key.
233    Up,
234    /// Down arrow key.
235    Down,
236    /// Home key.
237    Home,
238    /// End key.
239    End,
240    /// Page up key.
241    PageUp,
242    /// Page down key.
243    PageDown,
244    /// Tab key.
245    Tab,
246    /// Delete key.
247    Delete,
248    /// Insert key.
249    Insert,
250    /// F key.
251    ///
252    /// `KeyCode::F(1)` represents F1 key, etc.
253    F(u8),
254    /// A character.
255    ///
256    /// `KeyCode::Char('c')` represents `c` character, etc.
257    Char(char),
258    /// Null.
259    Null,
260    /// Escape key.
261    Esc,
262    /// CapsLock key.
263    CapsLock,
264    /// ScrollLock key.
265    ScrollLock,
266    /// NumLock key.
267    NumLock,
268    /// PrintScreen key.
269    PrintScreen,
270    /// Pause key.
271    Pause,
272    /// Menu key.
273    Menu,
274    /// KeypadBegin key.
275    KeypadBegin,
276    /// A media key.
277    Media(MediaKeyCode),
278    /// A modifier key.
279    Modifier(ModifierKeyCode),
280}
281
282impl From<KeyCode> for crossterm::event::KeyCode {
283    fn from(key_code: KeyCode) -> Self {
284        use crossterm::event::KeyCode as CKeyCode;
285
286        match key_code {
287            KeyCode::Backspace => CKeyCode::Backspace,
288            KeyCode::Enter => CKeyCode::Enter,
289            KeyCode::Left => CKeyCode::Left,
290            KeyCode::Right => CKeyCode::Right,
291            KeyCode::Up => CKeyCode::Up,
292            KeyCode::Down => CKeyCode::Down,
293            KeyCode::Home => CKeyCode::Home,
294            KeyCode::End => CKeyCode::End,
295            KeyCode::PageUp => CKeyCode::PageUp,
296            KeyCode::PageDown => CKeyCode::PageDown,
297            KeyCode::Tab => CKeyCode::Tab,
298            KeyCode::Delete => CKeyCode::Delete,
299            KeyCode::Insert => CKeyCode::Insert,
300            KeyCode::F(f_number) => CKeyCode::F(f_number),
301            KeyCode::Char(character) => CKeyCode::Char(character),
302            KeyCode::Null => CKeyCode::Null,
303            KeyCode::Esc => CKeyCode::Esc,
304            KeyCode::CapsLock => CKeyCode::CapsLock,
305            KeyCode::ScrollLock => CKeyCode::ScrollLock,
306            KeyCode::NumLock => CKeyCode::NumLock,
307            KeyCode::PrintScreen => CKeyCode::PrintScreen,
308            KeyCode::Pause => CKeyCode::Pause,
309            KeyCode::Menu => CKeyCode::Menu,
310            KeyCode::KeypadBegin => CKeyCode::KeypadBegin,
311            KeyCode::Media(media_key_code) => CKeyCode::Media(media_key_code.into()),
312            KeyCode::Modifier(modifier_key_code) => CKeyCode::Modifier(modifier_key_code.into()),
313        }
314    }
315}
316
317impl From<crossterm::event::KeyCode> for KeyCode {
318    fn from(val: crossterm::event::KeyCode) -> Self {
319        use crossterm::event::KeyCode as CKeyCode;
320
321        match val {
322            CKeyCode::Backspace => KeyCode::Backspace,
323            CKeyCode::Enter => KeyCode::Enter,
324            CKeyCode::Left => KeyCode::Left,
325            CKeyCode::Right => KeyCode::Right,
326            CKeyCode::Up => KeyCode::Up,
327            CKeyCode::Down => KeyCode::Down,
328            CKeyCode::Home => KeyCode::Home,
329            CKeyCode::End => KeyCode::End,
330            CKeyCode::PageUp => KeyCode::PageUp,
331            CKeyCode::PageDown => KeyCode::PageDown,
332            CKeyCode::Tab => KeyCode::Tab,
333            CKeyCode::BackTab => unreachable!("BackTab should have been handled on KeyEvent level"),
334            CKeyCode::Delete => KeyCode::Delete,
335            CKeyCode::Insert => KeyCode::Insert,
336            CKeyCode::F(f_number) => KeyCode::F(f_number),
337            CKeyCode::Char(character) => KeyCode::Char(character),
338            CKeyCode::Null => KeyCode::Null,
339            CKeyCode::Esc => KeyCode::Esc,
340            CKeyCode::CapsLock => KeyCode::CapsLock,
341            CKeyCode::ScrollLock => KeyCode::ScrollLock,
342            CKeyCode::NumLock => KeyCode::NumLock,
343            CKeyCode::PrintScreen => KeyCode::PrintScreen,
344            CKeyCode::Pause => KeyCode::Pause,
345            CKeyCode::Menu => KeyCode::Menu,
346            CKeyCode::KeypadBegin => KeyCode::KeypadBegin,
347            CKeyCode::Media(media_key_code) => KeyCode::Media(media_key_code.into()),
348            CKeyCode::Modifier(modifier_key_code) => KeyCode::Modifier(modifier_key_code.into()),
349        }
350    }
351}
352
353pub(crate) mod keys {
354    pub(crate) const BACKSPACE: &str = "backspace";
355    pub(crate) const ENTER: &str = "ret";
356    pub(crate) const ENTER2: &str = "enter";
357    pub(crate) const LEFT: &str = "left";
358    pub(crate) const RIGHT: &str = "right";
359    pub(crate) const UP: &str = "up";
360    pub(crate) const DOWN: &str = "down";
361    pub(crate) const HOME: &str = "home";
362    pub(crate) const END: &str = "end";
363    pub(crate) const PAGEUP: &str = "pageup";
364    pub(crate) const PAGEDOWN: &str = "pagedown";
365    pub(crate) const TAB: &str = "tab";
366    pub(crate) const DELETE: &str = "del";
367    pub(crate) const INSERT: &str = "ins";
368    pub(crate) const NULL: &str = "null";
369    pub(crate) const ESC: &str = "esc";
370    pub(crate) const SPACE: &str = "space";
371    pub(crate) const MINUS: &str = "minus"; // DOCS: ignore
372    pub(crate) const LESS_THAN: &str = "lt"; // DOCS: ignore
373    pub(crate) const GREATER_THAN: &str = "gt"; // DOCS: ignore
374    pub(crate) const CAPS_LOCK: &str = "capslock"; // DOCS: ignore
375    pub(crate) const SCROLL_LOCK: &str = "scrolllock"; // DOCS: ignore
376    pub(crate) const NUM_LOCK: &str = "numlock"; // DOCS: ignore
377    pub(crate) const PRINT_SCREEN: &str = "printscreen"; // DOCS: ignore
378    pub(crate) const PAUSE: &str = "pause"; // DOCS: ignore
379    pub(crate) const MENU: &str = "menu"; // DOCS: ignore
380    pub(crate) const KEYPAD_BEGIN: &str = "keypadbegin"; // DOCS: ignore
381    pub(crate) const PLAY: &str = "play"; // DOCS: ignore
382    pub(crate) const PAUSE_MEDIA: &str = "pausemedia"; // DOCS: ignore
383    pub(crate) const PLAY_PAUSE: &str = "playpause"; // DOCS: ignore
384    pub(crate) const REVERSE: &str = "reverse"; // DOCS: ignore
385    pub(crate) const STOP: &str = "stop"; // DOCS: ignore
386    pub(crate) const FAST_FORWARD: &str = "fastforward"; // DOCS: ignore
387    pub(crate) const REWIND: &str = "rewind"; // DOCS: ignore
388    pub(crate) const TRACK_NEXT: &str = "tracknext"; // DOCS: ignore
389    pub(crate) const TRACK_PREVIOUS: &str = "trackprevious"; // DOCS: ignore
390    pub(crate) const RECORD: &str = "record"; // DOCS: ignore
391    pub(crate) const LOWER_VOLUME: &str = "lowervolume"; // DOCS: ignore
392    pub(crate) const RAISE_VOLUME: &str = "raisevolume"; // DOCS: ignore
393    pub(crate) const MUTE_VOLUME: &str = "mutevolume"; // DOCS: ignore
394    pub(crate) const LEFT_SHIFT: &str = "leftshift"; // DOCS: ignore
395    pub(crate) const LEFT_CONTROL: &str = "leftcontrol"; // DOCS: ignore
396    pub(crate) const LEFT_ALT: &str = "leftalt"; // DOCS: ignore
397    pub(crate) const LEFT_SUPER: &str = "leftsuper"; // DOCS: ignore
398    pub(crate) const LEFT_HYPER: &str = "lefthyper"; // DOCS: ignore
399    pub(crate) const LEFT_META: &str = "leftmeta"; // DOCS: ignore
400    pub(crate) const RIGHT_SHIFT: &str = "rightshift"; // DOCS: ignore
401    pub(crate) const RIGHT_CONTROL: &str = "rightcontrol"; // DOCS: ignore
402    pub(crate) const RIGHT_ALT: &str = "rightalt"; // DOCS: ignore
403    pub(crate) const RIGHT_SUPER: &str = "rightsuper"; // DOCS: ignore
404    pub(crate) const RIGHT_HYPER: &str = "righthyper"; // DOCS: ignore
405    pub(crate) const RIGHT_META: &str = "rightmeta"; // DOCS: ignore
406    pub(crate) const ISO_LEVEL_3_SHIFT: &str = "isolevel3shift"; // DOCS: ignore
407    pub(crate) const ISO_LEVEL_5_SHIFT: &str = "isolevel5shift"; // DOCS: ignore
408}
409
410/// Represents a key event.
411// We use a newtype here because we want to customize Deserialize and Display.
412#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
413pub struct KeyEvent {
414    pub code: KeyCode,
415    pub modifiers: KeyModifiers,
416}
417
418impl KeyEvent {
419    pub fn new(code: KeyCode, modifiers: KeyModifiers) -> Self {
420        Self { code, modifiers }
421    }
422
423    /// Canonicalize the key event by removing the SHIFT modifier from character keys.
424    /// This is necessary because terminals send uppercase characters with the SHIFT modifier,
425    /// so e.g. a press of `G` arrives as `S-G` from crossterm. However, we just want `G`. This
426    /// matches the `FromStr for KeyEvent` implementation used when parsing config.
427    pub fn canonicalize(&mut self) {
428        if let KeyCode::Char(_) = self.code {
429            self.modifiers.remove(KeyModifiers::SHIFT);
430        }
431    }
432}
433
434impl Serialize for KeyEvent {
435    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
436    where
437        S: serde::Serializer,
438    {
439        serializer.serialize_str(&self.to_string())
440    }
441}
442
443const MODIFIERS: [(KeyModifiers, &str); 3] = [
444    (KeyModifiers::SHIFT, "S-"),
445    (KeyModifiers::CONTROL, "C-"),
446    (KeyModifiers::ALT, "A-"),
447];
448
449impl std::fmt::Display for KeyEvent {
450    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451        let mut result = String::new();
452
453        for (modifier, str) in MODIFIERS {
454            if self.modifiers.contains(modifier) {
455                result.push_str(str);
456            }
457        }
458
459        match self.code {
460            KeyCode::Backspace => result.push_str(keys::BACKSPACE),
461            KeyCode::Enter => result.push_str(keys::ENTER2),
462            KeyCode::Left => result.push_str(keys::LEFT),
463            KeyCode::Right => result.push_str(keys::RIGHT),
464            KeyCode::Up => result.push_str(keys::UP),
465            KeyCode::Down => result.push_str(keys::DOWN),
466            KeyCode::Home => result.push_str(keys::HOME),
467            KeyCode::End => result.push_str(keys::END),
468            KeyCode::PageUp => result.push_str(keys::PAGEUP),
469            KeyCode::PageDown => result.push_str(keys::PAGEDOWN),
470            KeyCode::Tab => result.push_str(keys::TAB),
471            KeyCode::Delete => result.push_str(keys::DELETE),
472            KeyCode::Insert => result.push_str(keys::INSERT),
473            KeyCode::Null => result.push_str(keys::NULL),
474            KeyCode::Esc => result.push_str(keys::ESC),
475            KeyCode::CapsLock => result.push_str(keys::CAPS_LOCK),
476            KeyCode::ScrollLock => result.push_str(keys::SCROLL_LOCK),
477            KeyCode::NumLock => result.push_str(keys::NUM_LOCK),
478            KeyCode::PrintScreen => result.push_str(keys::PRINT_SCREEN),
479            KeyCode::Pause => result.push_str(keys::PAUSE),
480            KeyCode::Menu => result.push_str(keys::MENU),
481            KeyCode::KeypadBegin => result.push_str(keys::KEYPAD_BEGIN),
482            KeyCode::Media(media) => match media {
483                MediaKeyCode::Play => result.push_str(keys::PLAY),
484                MediaKeyCode::Pause => result.push_str(keys::PAUSE_MEDIA),
485                MediaKeyCode::PlayPause => result.push_str(keys::PLAY_PAUSE),
486                MediaKeyCode::Reverse => result.push_str(keys::REVERSE),
487                MediaKeyCode::Stop => result.push_str(keys::STOP),
488                MediaKeyCode::FastForward => result.push_str(keys::FAST_FORWARD),
489                MediaKeyCode::Rewind => result.push_str(keys::REWIND),
490                MediaKeyCode::TrackNext => result.push_str(keys::TRACK_NEXT),
491                MediaKeyCode::TrackPrevious => result.push_str(keys::TRACK_PREVIOUS),
492                MediaKeyCode::Record => result.push_str(keys::RECORD),
493                MediaKeyCode::LowerVolume => result.push_str(keys::LOWER_VOLUME),
494                MediaKeyCode::RaiseVolume => result.push_str(keys::RAISE_VOLUME),
495                MediaKeyCode::MuteVolume => result.push_str(keys::MUTE_VOLUME),
496            },
497            KeyCode::Modifier(modifier) => match modifier {
498                ModifierKeyCode::LeftShift => result.push_str(keys::LEFT_SHIFT),
499                ModifierKeyCode::LeftControl => result.push_str(keys::LEFT_CONTROL),
500                ModifierKeyCode::LeftAlt => result.push_str(keys::LEFT_ALT),
501                ModifierKeyCode::LeftSuper => result.push_str(keys::LEFT_SUPER),
502                ModifierKeyCode::LeftHyper => result.push_str(keys::LEFT_HYPER),
503                ModifierKeyCode::LeftMeta => result.push_str(keys::LEFT_META),
504                ModifierKeyCode::RightShift => result.push_str(keys::RIGHT_SHIFT),
505                ModifierKeyCode::RightControl => result.push_str(keys::RIGHT_CONTROL),
506                ModifierKeyCode::RightAlt => result.push_str(keys::RIGHT_ALT),
507                ModifierKeyCode::RightSuper => result.push_str(keys::RIGHT_SUPER),
508                ModifierKeyCode::RightHyper => result.push_str(keys::RIGHT_HYPER),
509                ModifierKeyCode::RightMeta => result.push_str(keys::RIGHT_META),
510                ModifierKeyCode::IsoLevel3Shift => result.push_str(keys::ISO_LEVEL_3_SHIFT),
511                ModifierKeyCode::IsoLevel5Shift => result.push_str(keys::ISO_LEVEL_5_SHIFT),
512            },
513            KeyCode::Char(' ') => result.push_str(keys::SPACE),
514            KeyCode::Char('<') => result.push_str(keys::LESS_THAN),
515            KeyCode::Char('>') => result.push_str(keys::GREATER_THAN),
516            KeyCode::Char('-') => result.push_str(keys::MINUS),
517            KeyCode::Char(c) => result.push(c),
518            KeyCode::F(n) => {
519                use std::fmt::Write;
520                write!(&mut result, "F{n}").unwrap();
521            }
522        }
523
524        write!(f, "{result}")
525    }
526}
527
528impl std::str::FromStr for KeyEvent {
529    type Err = anyhow::Error;
530
531    #[allow(clippy::too_many_lines)]
532    fn from_str(s: &str) -> Result<Self, Self::Err> {
533        let mut tokens: Vec<_> = s.split('-').collect();
534        let mut code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? {
535            keys::BACKSPACE => KeyCode::Backspace,
536            keys::ENTER | keys::ENTER2 => KeyCode::Enter,
537            keys::LEFT => KeyCode::Left,
538            keys::RIGHT => KeyCode::Right,
539            keys::UP => KeyCode::Up,
540            keys::DOWN => KeyCode::Down,
541            keys::HOME => KeyCode::Home,
542            keys::END => KeyCode::End,
543            keys::PAGEUP => KeyCode::PageUp,
544            keys::PAGEDOWN => KeyCode::PageDown,
545            keys::TAB => KeyCode::Tab,
546            keys::DELETE => KeyCode::Delete,
547            keys::INSERT => KeyCode::Insert,
548            keys::NULL => KeyCode::Null,
549            keys::ESC => KeyCode::Esc,
550            keys::SPACE => KeyCode::Char(' '),
551            keys::MINUS => KeyCode::Char('-'),
552            keys::LESS_THAN => KeyCode::Char('<'),
553            keys::GREATER_THAN => KeyCode::Char('>'),
554            keys::CAPS_LOCK => KeyCode::CapsLock,
555            keys::SCROLL_LOCK => KeyCode::ScrollLock,
556            keys::NUM_LOCK => KeyCode::NumLock,
557            keys::PRINT_SCREEN => KeyCode::PrintScreen,
558            keys::PAUSE => KeyCode::Pause,
559            keys::MENU => KeyCode::Menu,
560            keys::KEYPAD_BEGIN => KeyCode::KeypadBegin,
561            keys::PLAY => KeyCode::Media(MediaKeyCode::Play),
562            keys::PAUSE_MEDIA => KeyCode::Media(MediaKeyCode::Pause),
563            keys::PLAY_PAUSE => KeyCode::Media(MediaKeyCode::PlayPause),
564            keys::STOP => KeyCode::Media(MediaKeyCode::Stop),
565            keys::REVERSE => KeyCode::Media(MediaKeyCode::Reverse),
566            keys::FAST_FORWARD => KeyCode::Media(MediaKeyCode::FastForward),
567            keys::REWIND => KeyCode::Media(MediaKeyCode::Rewind),
568            keys::TRACK_NEXT => KeyCode::Media(MediaKeyCode::TrackNext),
569            keys::TRACK_PREVIOUS => KeyCode::Media(MediaKeyCode::TrackPrevious),
570            keys::RECORD => KeyCode::Media(MediaKeyCode::Record),
571            keys::LOWER_VOLUME => KeyCode::Media(MediaKeyCode::LowerVolume),
572            keys::RAISE_VOLUME => KeyCode::Media(MediaKeyCode::RaiseVolume),
573            keys::MUTE_VOLUME => KeyCode::Media(MediaKeyCode::MuteVolume),
574            keys::LEFT_SHIFT => KeyCode::Modifier(ModifierKeyCode::LeftShift),
575            keys::LEFT_CONTROL => KeyCode::Modifier(ModifierKeyCode::LeftControl),
576            keys::LEFT_ALT => KeyCode::Modifier(ModifierKeyCode::LeftAlt),
577            keys::LEFT_SUPER => KeyCode::Modifier(ModifierKeyCode::LeftSuper),
578            keys::LEFT_HYPER => KeyCode::Modifier(ModifierKeyCode::LeftHyper),
579            keys::LEFT_META => KeyCode::Modifier(ModifierKeyCode::LeftMeta),
580            keys::RIGHT_SHIFT => KeyCode::Modifier(ModifierKeyCode::RightShift),
581            keys::RIGHT_CONTROL => KeyCode::Modifier(ModifierKeyCode::RightControl),
582            keys::RIGHT_ALT => KeyCode::Modifier(ModifierKeyCode::RightAlt),
583            keys::RIGHT_SUPER => KeyCode::Modifier(ModifierKeyCode::RightSuper),
584            keys::RIGHT_HYPER => KeyCode::Modifier(ModifierKeyCode::RightHyper),
585            keys::RIGHT_META => KeyCode::Modifier(ModifierKeyCode::RightMeta),
586            keys::ISO_LEVEL_3_SHIFT => KeyCode::Modifier(ModifierKeyCode::IsoLevel3Shift),
587            keys::ISO_LEVEL_5_SHIFT => KeyCode::Modifier(ModifierKeyCode::IsoLevel5Shift),
588            single if single.chars().count() == 1 => KeyCode::Char(single.chars().next().unwrap()),
589            function if function.len() > 1 && function.starts_with('F') => {
590                let function: String = function.chars().skip(1).collect();
591                let function = str::parse::<u8>(&function)?;
592                (function > 0 && function < 25)
593                    .then_some(KeyCode::F(function))
594                    .ok_or_else(|| anyhow!("Invalid function key '{function}'"))?
595            }
596            // Checking that the last token is empty ensures that this branch is only taken if
597            // `-` is used as a code. For example this branch will not be taken for `S-` (which is
598            // missing a code).
599            _ if s.ends_with('-') && tokens.last().is_some_and(|t| t.is_empty()) => {
600                if s == "-" {
601                    return Ok(KeyEvent {
602                        code: KeyCode::Char('-'),
603                        modifiers: KeyModifiers::empty(),
604                    });
605                }
606                let suggestion = format!("{}-{}", s.trim_end_matches('-'), keys::MINUS);
607                return Err(anyhow!(
608                    "Key '-' cannot be used with modifiers, use '{suggestion}' instead",
609                ));
610            }
611            invalid => return Err(anyhow!("Invalid key code '{invalid}'")),
612        };
613
614        let mut modifiers = KeyModifiers::empty();
615        for token in tokens {
616            let flag = match token {
617                "S" => KeyModifiers::SHIFT,
618                "A" | "M" => KeyModifiers::ALT,
619                "C" => KeyModifiers::CONTROL,
620                "Meta" | "Cmd" | "Win" => KeyModifiers::SUPER,
621                _ => return Err(anyhow!("Invalid key modifier '{token}-'")),
622            };
623
624            if modifiers.contains(flag) {
625                return Err(anyhow!("Repeated key modifier '{token}-'"));
626            }
627            modifiers.insert(flag);
628        }
629
630        // Normalize character keys so that characters like C-S-r and C-R
631        // are represented by equal KeyEvents.
632        match code {
633            KeyCode::Char(ch)
634                if ch.is_ascii_lowercase() && modifiers.contains(KeyModifiers::SHIFT) =>
635            {
636                code = KeyCode::Char(ch.to_ascii_uppercase());
637                modifiers.remove(KeyModifiers::SHIFT);
638            }
639            _ => (),
640        }
641
642        Ok(KeyEvent { code, modifiers })
643    }
644}
645
646impl From<crossterm::event::KeyEvent> for KeyEvent {
647    fn from(
648        crossterm::event::KeyEvent {
649            code, modifiers, ..
650        }: crossterm::event::KeyEvent,
651    ) -> Self {
652        if code == crossterm::event::KeyCode::BackTab {
653            // special case for BackTab -> Shift-Tab
654            let mut modifiers: KeyModifiers = modifiers.into();
655            modifiers.insert(KeyModifiers::SHIFT);
656            Self {
657                code: KeyCode::Tab,
658                modifiers,
659            }
660        } else {
661            Self {
662                code: code.into(),
663                modifiers: modifiers.into(),
664            }
665        }
666    }
667}
668
669impl From<KeyEvent> for crossterm::event::KeyEvent {
670    fn from(KeyEvent { code, modifiers }: KeyEvent) -> Self {
671        if code == KeyCode::Tab && modifiers.contains(KeyModifiers::SHIFT) {
672            // special case for Shift-Tab -> BackTab
673            let mut modifiers = modifiers;
674            modifiers.remove(KeyModifiers::SHIFT);
675            crossterm::event::KeyEvent {
676                code: crossterm::event::KeyCode::BackTab,
677                modifiers: modifiers.into(),
678                kind: crossterm::event::KeyEventKind::Press,
679                state: crossterm::event::KeyEventState::NONE,
680            }
681        } else {
682            crossterm::event::KeyEvent {
683                code: code.into(),
684                modifiers: modifiers.into(),
685                kind: crossterm::event::KeyEventKind::Press,
686                state: crossterm::event::KeyEventState::NONE,
687            }
688        }
689    }
690}