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