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) => 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    match key {
165        "up" | "down" | "left" | "right" | "pageup" | "pagedown" | "home" | "end" | "delete"
166        | "escape" | "backspace" | "f1" | "f2" | "f3" | "f4" | "f5" | "f6" | "f7" | "f8" | "f9"
167        | "f10" | "f11" | "f12" => false,
168        _ => true,
169    }
170}
171
172impl std::fmt::Display for Keystroke {
173    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174        if self.modifiers.control {
175            f.write_char('^')?;
176        }
177        if self.modifiers.alt {
178            f.write_char('โŒฅ')?;
179        }
180        if self.modifiers.platform {
181            #[cfg(target_os = "macos")]
182            f.write_char('โŒ˜')?;
183
184            #[cfg(target_os = "linux")]
185            f.write_char('โ–')?;
186
187            #[cfg(target_os = "windows")]
188            f.write_char('โŠž')?;
189        }
190        if self.modifiers.shift {
191            f.write_char('โ‡ง')?;
192        }
193        let key = match self.key.as_str() {
194            "backspace" => 'โŒซ',
195            "up" => 'โ†‘',
196            "down" => 'โ†“',
197            "left" => 'โ†',
198            "right" => 'โ†’',
199            "tab" => 'โ‡ฅ',
200            "escape" => 'โŽ‹',
201            "shift" => 'โ‡ง',
202            "control" => 'โŒƒ',
203            "alt" => 'โŒฅ',
204            "platform" => 'โŒ˜',
205            key => {
206                if key.len() == 1 {
207                    key.chars().next().unwrap().to_ascii_uppercase()
208                } else {
209                    return f.write_str(key);
210                }
211            }
212        };
213        f.write_char(key)
214    }
215}
216
217/// The state of the modifier keys at some point in time
218#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
219pub struct Modifiers {
220    /// The control key
221    pub control: bool,
222
223    /// The alt key
224    /// Sometimes also known as the 'meta' key
225    pub alt: bool,
226
227    /// The shift key
228    pub shift: bool,
229
230    /// The command key, on macos
231    /// the windows key, on windows
232    /// the super key, on linux
233    pub platform: bool,
234
235    /// The function key
236    pub function: bool,
237}
238
239impl Modifiers {
240    /// Returns true if any modifier key is pressed
241    pub fn modified(&self) -> bool {
242        self.control || self.alt || self.shift || self.platform || self.function
243    }
244
245    /// Whether the semantically 'secondary' modifier key is pressed
246    /// On macos, this is the command key
247    /// On windows and linux, this is the control key
248    pub fn secondary(&self) -> bool {
249        #[cfg(target_os = "macos")]
250        {
251            return self.platform;
252        }
253
254        #[cfg(not(target_os = "macos"))]
255        {
256            return self.control;
257        }
258    }
259
260    /// How many modifier keys are pressed
261    pub fn number_of_modifiers(&self) -> u8 {
262        self.control as u8
263            + self.alt as u8
264            + self.shift as u8
265            + self.platform as u8
266            + self.function as u8
267    }
268
269    /// helper method for Modifiers with no modifiers
270    pub fn none() -> Modifiers {
271        Default::default()
272    }
273
274    /// helper method for Modifiers with just the command key
275    pub fn command() -> Modifiers {
276        Modifiers {
277            platform: true,
278            ..Default::default()
279        }
280    }
281
282    /// A helper method for Modifiers with just the secondary key pressed
283    pub fn secondary_key() -> Modifiers {
284        #[cfg(target_os = "macos")]
285        {
286            Modifiers {
287                platform: true,
288                ..Default::default()
289            }
290        }
291
292        #[cfg(not(target_os = "macos"))]
293        {
294            Modifiers {
295                control: true,
296                ..Default::default()
297            }
298        }
299    }
300
301    /// helper method for Modifiers with just the windows key
302    pub fn windows() -> Modifiers {
303        Modifiers {
304            platform: true,
305            ..Default::default()
306        }
307    }
308
309    /// helper method for Modifiers with just the super key
310    pub fn super_key() -> Modifiers {
311        Modifiers {
312            platform: true,
313            ..Default::default()
314        }
315    }
316
317    /// helper method for Modifiers with just control
318    pub fn control() -> Modifiers {
319        Modifiers {
320            control: true,
321            ..Default::default()
322        }
323    }
324
325    /// helper method for Modifiers with just shift
326    pub fn shift() -> Modifiers {
327        Modifiers {
328            shift: true,
329            ..Default::default()
330        }
331    }
332
333    /// helper method for Modifiers with command + shift
334    pub fn command_shift() -> Modifiers {
335        Modifiers {
336            shift: true,
337            platform: true,
338            ..Default::default()
339        }
340    }
341
342    /// helper method for Modifiers with command + shift
343    pub fn control_shift() -> Modifiers {
344        Modifiers {
345            shift: true,
346            control: true,
347            ..Default::default()
348        }
349    }
350
351    /// Checks if this Modifiers is a subset of another Modifiers
352    pub fn is_subset_of(&self, other: &Modifiers) -> bool {
353        (other.control || !self.control)
354            && (other.alt || !self.alt)
355            && (other.shift || !self.shift)
356            && (other.platform || !self.platform)
357            && (other.function || !self.function)
358    }
359}