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