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