keystroke.rs

  1use anyhow::anyhow;
  2use serde::Deserialize;
  3use smallvec::SmallVec;
  4use std::fmt::Write;
  5
  6/// A keystroke and associated metadata generated by the platform
  7#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
  8pub struct Keystroke {
  9    /// the state of the modifier keys at the time the keystroke was generated
 10    pub modifiers: Modifiers,
 11
 12    /// key is the character printed on the key that was pressed
 13    /// e.g. for option-s, key is "s"
 14    pub key: String,
 15
 16    /// ime_key is the character inserted by the IME engine when that key was pressed.
 17    /// e.g. for option-s, ime_key is "รŸ"
 18    pub ime_key: Option<String>,
 19}
 20
 21impl Keystroke {
 22    /// When matching a key we cannot know whether the user intended to type
 23    /// the ime_key or the key itself. On some non-US keyboards keys we use in our
 24    /// bindings are behind option (for example `$` is typed `alt-รง` on a Czech keyboard),
 25    /// and on some keyboards the IME handler converts a sequence of keys into a
 26    /// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
 27    ///
 28    /// This method generates a list of potential keystroke candidates that could be matched
 29    /// against when resolving a keybinding.
 30    pub(crate) fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> {
 31        let mut possibilities = SmallVec::new();
 32        match self.ime_key.as_ref() {
 33            Some(ime_key) => {
 34                if ime_key != &self.key {
 35                    possibilities.push(Keystroke {
 36                        modifiers: Modifiers {
 37                            control: self.modifiers.control,
 38                            alt: false,
 39                            shift: false,
 40                            platform: false,
 41                            function: false,
 42                        },
 43                        key: ime_key.to_string(),
 44                        ime_key: None,
 45                    });
 46                }
 47                possibilities.push(Keystroke {
 48                    ime_key: None,
 49                    ..self.clone()
 50                });
 51            }
 52            None => possibilities.push(self.clone()),
 53        }
 54        possibilities
 55    }
 56
 57    /// key syntax is:
 58    /// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
 59    /// ime_key syntax is only used for generating test events,
 60    /// when matching a key with an ime_key set will be matched without it.
 61    pub fn parse(source: &str) -> anyhow::Result<Self> {
 62        let mut control = false;
 63        let mut alt = false;
 64        let mut shift = false;
 65        let mut platform = false;
 66        let mut function = false;
 67        let mut key = None;
 68        let mut ime_key = None;
 69
 70        let mut components = source.split('-').peekable();
 71        while let Some(component) = components.next() {
 72            match component {
 73                "ctrl" => control = true,
 74                "alt" => alt = true,
 75                "shift" => shift = true,
 76                "fn" => function = true,
 77                "cmd" | "super" | "win" => platform = true,
 78                _ => {
 79                    if let Some(next) = components.peek() {
 80                        if next.is_empty() && source.ends_with('-') {
 81                            key = Some(String::from("-"));
 82                            break;
 83                        } else if next.len() > 1 && next.starts_with('>') {
 84                            key = Some(String::from(component));
 85                            ime_key = Some(String::from(&next[1..]));
 86                            components.next();
 87                        } else {
 88                            return Err(anyhow!("Invalid keystroke `{}`", source));
 89                        }
 90                    } else {
 91                        key = Some(String::from(component));
 92                    }
 93                }
 94            }
 95        }
 96
 97        //Allow for the user to specify a keystroke modifier as the key itself
 98        //This sets the `key` to the modifier, and disables the modifier
 99        if key.is_none() {
100            if shift {
101                key = Some("shift".to_string());
102                shift = false;
103            } else if control {
104                key = Some("control".to_string());
105                control = false;
106            } else if alt {
107                key = Some("alt".to_string());
108                alt = false;
109            } else if platform {
110                key = Some("platform".to_string());
111                platform = false;
112            } else if function {
113                key = Some("function".to_string());
114                function = false;
115            }
116        }
117
118        let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
119
120        Ok(Keystroke {
121            modifiers: Modifiers {
122                control,
123                alt,
124                shift,
125                platform,
126                function,
127            },
128            key,
129            ime_key,
130        })
131    }
132
133    /// Returns true if this keystroke left
134    /// the ime system in an incomplete state.
135    pub fn is_ime_in_progress(&self) -> bool {
136        self.ime_key.is_none()
137            && (is_printable_key(&self.key) || self.key.is_empty())
138            && !(self.modifiers.platform
139                || self.modifiers.control
140                || self.modifiers.function
141                || self.modifiers.alt)
142    }
143
144    /// Returns a new keystroke with the ime_key filled.
145    /// This is used for dispatch_keystroke where we want users to
146    /// be able to simulate typing "space", etc.
147    pub fn with_simulated_ime(mut self) -> Self {
148        if self.ime_key.is_none()
149            && !self.modifiers.platform
150            && !self.modifiers.control
151            && !self.modifiers.function
152            && !self.modifiers.alt
153        {
154            self.ime_key = match self.key.as_str() {
155                "space" => Some(" ".into()),
156                "tab" => Some("\t".into()),
157                "enter" => Some("\n".into()),
158                key if !is_printable_key(key) => None,
159                key => {
160                    if self.modifiers.shift {
161                        Some(key.to_uppercase())
162                    } else {
163                        Some(key.into())
164                    }
165                }
166            }
167        }
168        self
169    }
170}
171
172fn is_printable_key(key: &str) -> bool {
173    match key {
174        "up" | "down" | "left" | "right" | "pageup" | "pagedown" | "home" | "end" | "delete"
175        | "escape" | "backspace" | "f1" | "f2" | "f3" | "f4" | "f5" | "f6" | "f7" | "f8" | "f9"
176        | "f10" | "f11" | "f12" => false,
177        _ => true,
178    }
179}
180
181impl std::fmt::Display for Keystroke {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        if self.modifiers.control {
184            f.write_char('^')?;
185        }
186        if self.modifiers.alt {
187            f.write_char('โŒฅ')?;
188        }
189        if self.modifiers.platform {
190            #[cfg(target_os = "macos")]
191            f.write_char('โŒ˜')?;
192
193            #[cfg(target_os = "linux")]
194            f.write_char('โ–')?;
195
196            #[cfg(target_os = "windows")]
197            f.write_char('โŠž')?;
198        }
199        if self.modifiers.shift {
200            f.write_char('โ‡ง')?;
201        }
202        let key = match self.key.as_str() {
203            "backspace" => 'โŒซ',
204            "up" => 'โ†‘',
205            "down" => 'โ†“',
206            "left" => 'โ†',
207            "right" => 'โ†’',
208            "tab" => 'โ‡ฅ',
209            "escape" => 'โŽ‹',
210            "shift" => 'โ‡ง',
211            "control" => 'โŒƒ',
212            "alt" => 'โŒฅ',
213            "platform" => 'โŒ˜',
214            key => {
215                if key.len() == 1 {
216                    key.chars().next().unwrap().to_ascii_uppercase()
217                } else {
218                    return f.write_str(key);
219                }
220            }
221        };
222        f.write_char(key)
223    }
224}
225
226/// The state of the modifier keys at some point in time
227#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
228pub struct Modifiers {
229    /// The control key
230    pub control: bool,
231
232    /// The alt key
233    /// Sometimes also known as the 'meta' key
234    pub alt: bool,
235
236    /// The shift key
237    pub shift: bool,
238
239    /// The command key, on macos
240    /// the windows key, on windows
241    /// the super key, on linux
242    pub platform: bool,
243
244    /// The function key
245    pub function: bool,
246}
247
248impl Modifiers {
249    /// Returns true if any modifier key is pressed
250    pub fn modified(&self) -> bool {
251        self.control || self.alt || self.shift || self.platform || self.function
252    }
253
254    /// Whether the semantically 'secondary' modifier key is pressed
255    /// On macos, this is the command key
256    /// On windows and linux, this is the control key
257    pub fn secondary(&self) -> bool {
258        #[cfg(target_os = "macos")]
259        {
260            return self.platform;
261        }
262
263        #[cfg(not(target_os = "macos"))]
264        {
265            return self.control;
266        }
267    }
268
269    /// How many modifier keys are pressed
270    pub fn number_of_modifiers(&self) -> u8 {
271        self.control as u8
272            + self.alt as u8
273            + self.shift as u8
274            + self.platform as u8
275            + self.function as u8
276    }
277
278    /// helper method for Modifiers with no modifiers
279    pub fn none() -> Modifiers {
280        Default::default()
281    }
282
283    /// helper method for Modifiers with just the command key
284    pub fn command() -> Modifiers {
285        Modifiers {
286            platform: true,
287            ..Default::default()
288        }
289    }
290
291    /// A helper method for Modifiers with just the secondary key pressed
292    pub fn secondary_key() -> Modifiers {
293        #[cfg(target_os = "macos")]
294        {
295            Modifiers {
296                platform: true,
297                ..Default::default()
298            }
299        }
300
301        #[cfg(not(target_os = "macos"))]
302        {
303            Modifiers {
304                control: true,
305                ..Default::default()
306            }
307        }
308    }
309
310    /// helper method for Modifiers with just the windows key
311    pub fn windows() -> Modifiers {
312        Modifiers {
313            platform: true,
314            ..Default::default()
315        }
316    }
317
318    /// helper method for Modifiers with just the super key
319    pub fn super_key() -> Modifiers {
320        Modifiers {
321            platform: true,
322            ..Default::default()
323        }
324    }
325
326    /// helper method for Modifiers with just control
327    pub fn control() -> Modifiers {
328        Modifiers {
329            control: true,
330            ..Default::default()
331        }
332    }
333
334    /// helper method for Modifiers with just shift
335    pub fn shift() -> Modifiers {
336        Modifiers {
337            shift: true,
338            ..Default::default()
339        }
340    }
341
342    /// helper method for 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    /// helper method for 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}