keystroke.rs

  1use anyhow::anyhow;
  2use serde::Deserialize;
  3use smallvec::SmallVec;
  4use std::fmt::Write;
  5
  6/// A keystroke and associated metadata generated by the platform
  7#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
  8pub struct Keystroke {
  9    /// the state of the modifier keys at the time the keystroke was generated
 10    pub modifiers: Modifiers,
 11
 12    /// key is the character printed on the key that was pressed
 13    /// e.g. for option-s, key is "s"
 14    pub key: String,
 15
 16    /// ime_key is the character inserted by the IME engine when that key was pressed.
 17    /// e.g. for option-s, ime_key is "รŸ"
 18    pub ime_key: Option<String>,
 19}
 20
 21impl Keystroke {
 22    /// When matching a key we cannot know whether the user intended to type
 23    /// the ime_key or the key itself. On some non-US keyboards keys we use in our
 24    /// bindings are behind option (for example `$` is typed `alt-รง` on a Czech keyboard),
 25    /// and on some keyboards the IME handler converts a sequence of keys into a
 26    /// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
 27    ///
 28    /// This method generates a list of potential keystroke candidates that could be matched
 29    /// against when resolving a keybinding.
 30    pub(crate) fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> {
 31        let mut possibilities = SmallVec::new();
 32        match self.ime_key.as_ref() {
 33            Some(ime_key) => {
 34                if ime_key != &self.key {
 35                    possibilities.push(Keystroke {
 36                        modifiers: Modifiers {
 37                            control: self.modifiers.control,
 38                            alt: false,
 39                            shift: false,
 40                            platform: false,
 41                            function: false,
 42                        },
 43                        key: ime_key.to_string(),
 44                        ime_key: None,
 45                    });
 46                }
 47                possibilities.push(Keystroke {
 48                    ime_key: None,
 49                    ..self.clone()
 50                });
 51            }
 52            None => possibilities.push(self.clone()),
 53        }
 54        possibilities
 55    }
 56
 57    /// key syntax is:
 58    /// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
 59    /// ime_key syntax is only used for generating test events,
 60    /// when matching a key with an ime_key set will be matched without it.
 61    pub fn parse(source: &str) -> anyhow::Result<Self> {
 62        let mut control = false;
 63        let mut alt = false;
 64        let mut shift = false;
 65        let mut platform = false;
 66        let mut function = false;
 67        let mut key = None;
 68        let mut ime_key = None;
 69
 70        let mut components = source.split('-').peekable();
 71        while let Some(component) = components.next() {
 72            match component {
 73                "ctrl" => control = true,
 74                "alt" => alt = true,
 75                "shift" => shift = true,
 76                "fn" => function = true,
 77                "cmd" | "super" | "win" => platform = true,
 78                _ => {
 79                    if let Some(next) = components.peek() {
 80                        if next.is_empty() && source.ends_with('-') {
 81                            key = Some(String::from("-"));
 82                            break;
 83                        } else if next.len() > 1 && next.starts_with('>') {
 84                            key = Some(String::from(component));
 85                            ime_key = Some(String::from(&next[1..]));
 86                            components.next();
 87                        } else {
 88                            return Err(anyhow!("Invalid keystroke `{}`", source));
 89                        }
 90                    } else {
 91                        key = Some(String::from(component));
 92                    }
 93                }
 94            }
 95        }
 96
 97        let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
 98
 99        Ok(Keystroke {
100            modifiers: Modifiers {
101                control,
102                alt,
103                shift,
104                platform,
105                function,
106            },
107            key,
108            ime_key,
109        })
110    }
111
112    /// Returns true if this keystroke left
113    /// the ime system in an incomplete state.
114    pub fn is_ime_in_progress(&self) -> bool {
115        self.ime_key.is_none()
116            && (is_printable_key(&self.key) || self.key.is_empty())
117            && !(self.modifiers.platform
118                || self.modifiers.control
119                || self.modifiers.function
120                || self.modifiers.alt)
121    }
122
123    /// Returns a new keystroke with the ime_key filled.
124    /// This is used for dispatch_keystroke where we want users to
125    /// be able to simulate typing "space", etc.
126    pub fn with_simulated_ime(mut self) -> Self {
127        if self.ime_key.is_none()
128            && !self.modifiers.platform
129            && !self.modifiers.control
130            && !self.modifiers.function
131            && !self.modifiers.alt
132        {
133            self.ime_key = match self.key.as_str() {
134                "space" => Some(" ".into()),
135                "tab" => Some("\t".into()),
136                "enter" => Some("\n".into()),
137                key if !is_printable_key(key) => None,
138                key => {
139                    if self.modifiers.shift {
140                        Some(key.to_uppercase())
141                    } else {
142                        Some(key.into())
143                    }
144                }
145            }
146        }
147        self
148    }
149}
150
151fn is_printable_key(key: &str) -> bool {
152    match key {
153        "up" | "down" | "left" | "right" | "pageup" | "pagedown" | "home" | "end" | "delete"
154        | "escape" | "backspace" | "f1" | "f2" | "f3" | "f4" | "f5" | "f6" | "f7" | "f8" | "f9"
155        | "f10" | "f11" | "f12" => false,
156        _ => true,
157    }
158}
159
160impl std::fmt::Display for Keystroke {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        if self.modifiers.control {
163            f.write_char('^')?;
164        }
165        if self.modifiers.alt {
166            f.write_char('โŒฅ')?;
167        }
168        if self.modifiers.platform {
169            #[cfg(target_os = "macos")]
170            f.write_char('โŒ˜')?;
171
172            #[cfg(target_os = "linux")]
173            f.write_char('โ–')?;
174
175            #[cfg(target_os = "windows")]
176            f.write_char('โŠž')?;
177        }
178        if self.modifiers.shift {
179            f.write_char('โ‡ง')?;
180        }
181        let key = match self.key.as_str() {
182            "backspace" => 'โŒซ',
183            "up" => 'โ†‘',
184            "down" => 'โ†“',
185            "left" => 'โ†',
186            "right" => 'โ†’',
187            "tab" => 'โ‡ฅ',
188            "escape" => 'โŽ‹',
189            key => {
190                if key.len() == 1 {
191                    key.chars().next().unwrap().to_ascii_uppercase()
192                } else {
193                    return f.write_str(key);
194                }
195            }
196        };
197        f.write_char(key)
198    }
199}
200
201/// The state of the modifier keys at some point in time
202#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
203pub struct Modifiers {
204    /// The control key
205    pub control: bool,
206
207    /// The alt key
208    /// Sometimes also known as the 'meta' key
209    pub alt: bool,
210
211    /// The shift key
212    pub shift: bool,
213
214    /// The command key, on macos
215    /// the windows key, on windows
216    /// the super key, on linux
217    pub platform: bool,
218
219    /// The function key
220    pub function: bool,
221}
222
223impl Modifiers {
224    /// Returns true if any modifier key is pressed
225    pub fn modified(&self) -> bool {
226        self.control || self.alt || self.shift || self.platform || self.function
227    }
228
229    /// Whether the semantically 'secondary' modifier key is pressed
230    /// On macos, this is the command key
231    /// On windows and linux, this is the control key
232    pub fn secondary(&self) -> bool {
233        #[cfg(target_os = "macos")]
234        {
235            return self.platform;
236        }
237
238        #[cfg(not(target_os = "macos"))]
239        {
240            return self.control;
241        }
242    }
243
244    /// helper method for Modifiers with no modifiers
245    pub fn none() -> Modifiers {
246        Default::default()
247    }
248
249    /// helper method for Modifiers with just the command key
250    pub fn command() -> Modifiers {
251        Modifiers {
252            platform: true,
253            ..Default::default()
254        }
255    }
256
257    /// A helper method for Modifiers with just the secondary key pressed
258    pub fn secondary_key() -> Modifiers {
259        #[cfg(target_os = "macos")]
260        {
261            Modifiers {
262                platform: true,
263                ..Default::default()
264            }
265        }
266
267        #[cfg(not(target_os = "macos"))]
268        {
269            Modifiers {
270                control: true,
271                ..Default::default()
272            }
273        }
274    }
275
276    /// helper method for Modifiers with just the windows key
277    pub fn windows() -> Modifiers {
278        Modifiers {
279            platform: true,
280            ..Default::default()
281        }
282    }
283
284    /// helper method for Modifiers with just the super key
285    pub fn super_key() -> Modifiers {
286        Modifiers {
287            platform: true,
288            ..Default::default()
289        }
290    }
291
292    /// helper method for Modifiers with just control
293    pub fn control() -> Modifiers {
294        Modifiers {
295            control: true,
296            ..Default::default()
297        }
298    }
299
300    /// helper method for Modifiers with just shift
301    pub fn shift() -> Modifiers {
302        Modifiers {
303            shift: true,
304            ..Default::default()
305        }
306    }
307
308    /// helper method for Modifiers with command + shift
309    pub fn command_shift() -> Modifiers {
310        Modifiers {
311            shift: true,
312            platform: true,
313            ..Default::default()
314        }
315    }
316
317    /// helper method for Modifiers with command + shift
318    pub fn control_shift() -> Modifiers {
319        Modifiers {
320            shift: true,
321            control: true,
322            ..Default::default()
323        }
324    }
325
326    /// Checks if this Modifiers is a subset of another Modifiers
327    pub fn is_subset_of(&self, other: &Modifiers) -> bool {
328        (other.control || !self.control)
329            && (other.alt || !self.alt)
330            && (other.shift || !self.shift)
331            && (other.platform || !self.platform)
332            && (other.function || !self.function)
333    }
334}