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