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