keystroke.rs

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