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 whether 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    ///
247    /// On macOS, this is the command key.
248    /// On Linux and Windows, this is the control key.
249    pub fn secondary(&self) -> bool {
250        #[cfg(target_os = "macos")]
251        {
252            return self.platform;
253        }
254
255        #[cfg(not(target_os = "macos"))]
256        {
257            return self.control;
258        }
259    }
260
261    /// Returns how many modifier keys are pressed.
262    pub fn number_of_modifiers(&self) -> u8 {
263        self.control as u8
264            + self.alt as u8
265            + self.shift as u8
266            + self.platform as u8
267            + self.function as u8
268    }
269
270    /// Returns [`Modifiers`] with no modifiers.
271    pub fn none() -> Modifiers {
272        Default::default()
273    }
274
275    /// Returns [`Modifiers`] with just the command key.
276    pub fn command() -> Modifiers {
277        Modifiers {
278            platform: true,
279            ..Default::default()
280        }
281    }
282
283    /// A Returns [`Modifiers`] with just the secondary key pressed.
284    pub fn secondary_key() -> Modifiers {
285        #[cfg(target_os = "macos")]
286        {
287            Modifiers {
288                platform: true,
289                ..Default::default()
290            }
291        }
292
293        #[cfg(not(target_os = "macos"))]
294        {
295            Modifiers {
296                control: true,
297                ..Default::default()
298            }
299        }
300    }
301
302    /// Returns [`Modifiers`] with just the windows key.
303    pub fn windows() -> Modifiers {
304        Modifiers {
305            platform: true,
306            ..Default::default()
307        }
308    }
309
310    /// Returns [`Modifiers`] with just the super key.
311    pub fn super_key() -> Modifiers {
312        Modifiers {
313            platform: true,
314            ..Default::default()
315        }
316    }
317
318    /// Returns [`Modifiers`] with just control.
319    pub fn control() -> Modifiers {
320        Modifiers {
321            control: true,
322            ..Default::default()
323        }
324    }
325
326    /// Returns [`Modifiers`] with just control.
327    pub fn alt() -> Modifiers {
328        Modifiers {
329            alt: true,
330            ..Default::default()
331        }
332    }
333
334    /// Returns [`Modifiers`] with just shift.
335    pub fn shift() -> Modifiers {
336        Modifiers {
337            shift: true,
338            ..Default::default()
339        }
340    }
341
342    /// Returns [`Modifiers`] with command + shift.
343    pub fn command_shift() -> Modifiers {
344        Modifiers {
345            shift: true,
346            platform: true,
347            ..Default::default()
348        }
349    }
350
351    /// Returns [`Modifiers`] with command + shift.
352    pub fn control_shift() -> Modifiers {
353        Modifiers {
354            shift: true,
355            control: true,
356            ..Default::default()
357        }
358    }
359
360    /// Checks if this [`Modifiers`] is a subset of another [`Modifiers`].
361    pub fn is_subset_of(&self, other: &Modifiers) -> bool {
362        (other.control || !self.control)
363            && (other.alt || !self.alt)
364            && (other.shift || !self.shift)
365            && (other.platform || !self.platform)
366            && (other.function || !self.function)
367    }
368}