keystroke.rs

  1use anyhow::anyhow;
  2use serde::Deserialize;
  3use smallvec::SmallVec;
  4use std::fmt::Write;
  5
  6#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
  7pub struct Keystroke {
  8    pub modifiers: Modifiers,
  9    /// key is the character printed on the key that was pressed
 10    /// e.g. for option-s, key is "s"
 11    pub key: String,
 12    /// ime_key is the character inserted by the IME engine when that key was pressed.
 13    /// e.g. for option-s, ime_key is "รŸ"
 14    pub ime_key: Option<String>,
 15}
 16
 17impl Keystroke {
 18    // When matching a key we cannot know whether the user intended to type
 19    // the ime_key or the key. On some non-US keyboards keys we use in our
 20    // bindings are behind option (for example `$` is typed `alt-รง` on a Czech keyboard),
 21    // and on some keyboards the IME handler converts a sequence of keys into a
 22    // specific character (for example `"` is typed as `" space` on a brazillian keyboard).
 23    pub fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> {
 24        let mut possibilities = SmallVec::new();
 25        match self.ime_key.as_ref() {
 26            None => possibilities.push(self.clone()),
 27            Some(ime_key) => {
 28                possibilities.push(Keystroke {
 29                    modifiers: Modifiers {
 30                        control: self.modifiers.control,
 31                        alt: false,
 32                        shift: false,
 33                        command: false,
 34                        function: false,
 35                    },
 36                    key: ime_key.to_string(),
 37                    ime_key: None,
 38                });
 39                possibilities.push(Keystroke {
 40                    ime_key: None,
 41                    ..self.clone()
 42                });
 43            }
 44        }
 45        possibilities
 46    }
 47
 48    /// key syntax is:
 49    /// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
 50    /// ime_key is only used for generating test events,
 51    /// when matching a key with an ime_key set will be matched without it.
 52    pub fn parse(source: &str) -> anyhow::Result<Self> {
 53        let mut control = false;
 54        let mut alt = false;
 55        let mut shift = false;
 56        let mut command = false;
 57        let mut function = false;
 58        let mut key = None;
 59        let mut ime_key = None;
 60
 61        let mut components = source.split('-').peekable();
 62        while let Some(component) = components.next() {
 63            match component {
 64                "ctrl" => control = true,
 65                "alt" => alt = true,
 66                "shift" => shift = true,
 67                "cmd" => command = true,
 68                "fn" => function = true,
 69                _ => {
 70                    if let Some(next) = components.peek() {
 71                        if next.is_empty() && source.ends_with('-') {
 72                            key = Some(String::from("-"));
 73                            break;
 74                        } else if next.len() > 1 && next.starts_with('>') {
 75                            key = Some(String::from(component));
 76                            ime_key = Some(String::from(&next[1..]));
 77                            components.next();
 78                        } else {
 79                            return Err(anyhow!("Invalid keystroke `{}`", source));
 80                        }
 81                    } else {
 82                        key = Some(String::from(component));
 83                    }
 84                }
 85            }
 86        }
 87
 88        let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
 89
 90        Ok(Keystroke {
 91            modifiers: Modifiers {
 92                control,
 93                alt,
 94                shift,
 95                command,
 96                function,
 97            },
 98            key,
 99            ime_key,
100        })
101    }
102}
103
104impl std::fmt::Display for Keystroke {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        if self.modifiers.control {
107            f.write_char('^')?;
108        }
109        if self.modifiers.alt {
110            f.write_char('โŒฅ')?;
111        }
112        if self.modifiers.command {
113            f.write_char('โŒ˜')?;
114        }
115        if self.modifiers.shift {
116            f.write_char('โ‡ง')?;
117        }
118        let key = match self.key.as_str() {
119            "backspace" => 'โŒซ',
120            "up" => 'โ†‘',
121            "down" => 'โ†“',
122            "left" => 'โ†',
123            "right" => 'โ†’',
124            "tab" => 'โ‡ฅ',
125            "escape" => 'โŽ‹',
126            key => {
127                if key.len() == 1 {
128                    key.chars().next().unwrap().to_ascii_uppercase()
129                } else {
130                    return f.write_str(key);
131                }
132            }
133        };
134        f.write_char(key)
135    }
136}
137
138#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
139pub struct Modifiers {
140    pub control: bool,
141    pub alt: bool,
142    pub shift: bool,
143    pub command: bool,
144    pub function: bool,
145}
146
147impl Modifiers {
148    pub fn modified(&self) -> bool {
149        self.control || self.alt || self.shift || self.command || self.function
150    }
151}