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            | "back"
227            | "forward"
228            | "escape"
229    )
230}
231
232impl std::fmt::Display for Keystroke {
233    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234        if self.modifiers.control {
235            f.write_char('^')?;
236        }
237        if self.modifiers.alt {
238            f.write_char('โŒฅ')?;
239        }
240        if self.modifiers.platform {
241            #[cfg(target_os = "macos")]
242            f.write_char('โŒ˜')?;
243
244            #[cfg(any(target_os = "linux", target_os = "freebsd"))]
245            f.write_char('โ–')?;
246
247            #[cfg(target_os = "windows")]
248            f.write_char('โŠž')?;
249        }
250        if self.modifiers.shift {
251            f.write_char('โ‡ง')?;
252        }
253        let key = match self.key.as_str() {
254            "backspace" => 'โŒซ',
255            "up" => 'โ†‘',
256            "down" => 'โ†“',
257            "left" => 'โ†',
258            "right" => 'โ†’',
259            "tab" => 'โ‡ฅ',
260            "escape" => 'โŽ‹',
261            "shift" => 'โ‡ง',
262            "control" => 'โŒƒ',
263            "alt" => 'โŒฅ',
264            "platform" => 'โŒ˜',
265            key => {
266                if key.len() == 1 {
267                    key.chars().next().unwrap().to_ascii_uppercase()
268                } else {
269                    return f.write_str(key);
270                }
271            }
272        };
273        f.write_char(key)
274    }
275}
276
277/// The state of the modifier keys at some point in time
278#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
279pub struct Modifiers {
280    /// The control key
281    pub control: bool,
282
283    /// The alt key
284    /// Sometimes also known as the 'meta' key
285    pub alt: bool,
286
287    /// The shift key
288    pub shift: bool,
289
290    /// The command key, on macos
291    /// the windows key, on windows
292    /// the super key, on linux
293    pub platform: bool,
294
295    /// The function key
296    pub function: bool,
297}
298
299impl Modifiers {
300    /// Returns whether any modifier key is pressed.
301    pub fn modified(&self) -> bool {
302        self.control || self.alt || self.shift || self.platform || self.function
303    }
304
305    /// Whether the semantically 'secondary' modifier key is pressed.
306    ///
307    /// On macOS, this is the command key.
308    /// On Linux and Windows, this is the control key.
309    pub fn secondary(&self) -> bool {
310        #[cfg(target_os = "macos")]
311        {
312            self.platform
313        }
314
315        #[cfg(not(target_os = "macos"))]
316        {
317            self.control
318        }
319    }
320
321    /// Returns how many modifier keys are pressed.
322    pub fn number_of_modifiers(&self) -> u8 {
323        self.control as u8
324            + self.alt as u8
325            + self.shift as u8
326            + self.platform as u8
327            + self.function as u8
328    }
329
330    /// Returns [`Modifiers`] with no modifiers.
331    pub fn none() -> Modifiers {
332        Default::default()
333    }
334
335    /// Returns [`Modifiers`] with just the command key.
336    pub fn command() -> Modifiers {
337        Modifiers {
338            platform: true,
339            ..Default::default()
340        }
341    }
342
343    /// A Returns [`Modifiers`] with just the secondary key pressed.
344    pub fn secondary_key() -> Modifiers {
345        #[cfg(target_os = "macos")]
346        {
347            Modifiers {
348                platform: true,
349                ..Default::default()
350            }
351        }
352
353        #[cfg(not(target_os = "macos"))]
354        {
355            Modifiers {
356                control: true,
357                ..Default::default()
358            }
359        }
360    }
361
362    /// Returns [`Modifiers`] with just the windows key.
363    pub fn windows() -> Modifiers {
364        Modifiers {
365            platform: true,
366            ..Default::default()
367        }
368    }
369
370    /// Returns [`Modifiers`] with just the super key.
371    pub fn super_key() -> Modifiers {
372        Modifiers {
373            platform: true,
374            ..Default::default()
375        }
376    }
377
378    /// Returns [`Modifiers`] with just control.
379    pub fn control() -> Modifiers {
380        Modifiers {
381            control: true,
382            ..Default::default()
383        }
384    }
385
386    /// Returns [`Modifiers`] with just control.
387    pub fn alt() -> Modifiers {
388        Modifiers {
389            alt: true,
390            ..Default::default()
391        }
392    }
393
394    /// Returns [`Modifiers`] with just shift.
395    pub fn shift() -> Modifiers {
396        Modifiers {
397            shift: true,
398            ..Default::default()
399        }
400    }
401
402    /// Returns [`Modifiers`] with command + shift.
403    pub fn command_shift() -> Modifiers {
404        Modifiers {
405            shift: true,
406            platform: true,
407            ..Default::default()
408        }
409    }
410
411    /// Returns [`Modifiers`] with command + shift.
412    pub fn control_shift() -> Modifiers {
413        Modifiers {
414            shift: true,
415            control: true,
416            ..Default::default()
417        }
418    }
419
420    /// Checks if this [`Modifiers`] is a subset of another [`Modifiers`].
421    pub fn is_subset_of(&self, other: &Modifiers) -> bool {
422        (other.control || !self.control)
423            && (other.alt || !self.alt)
424            && (other.shift || !self.shift)
425            && (other.platform || !self.platform)
426            && (other.function || !self.function)
427    }
428}