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 /// key_char is the character that could have been typed when
16 /// this binding was pressed.
17 /// e.g. for s this is "s", for option-s "ร", and cmd-s None
18 pub key_char: Option<String>,
19}
20
21impl Keystroke {
22 /// When matching a key we cannot know whether the user intended to type
23 /// the key_char 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 assumes that `self` was typed and `target' is in the keymap, and checks
29 /// both possibilities for self against the target.
30 pub(crate) fn should_match(&self, target: &Keystroke) -> bool {
31 if let Some(key_char) = self
32 .key_char
33 .as_ref()
34 .filter(|key_char| key_char != &&self.key)
35 {
36 let ime_modifiers = Modifiers {
37 control: self.modifiers.control,
38 platform: self.modifiers.platform,
39 ..Default::default()
40 };
41
42 if &target.key == key_char && target.modifiers == ime_modifiers {
43 return true;
44 }
45 }
46
47 target.modifiers == self.modifiers && target.key == self.key
48 }
49
50 /// key syntax is:
51 /// [ctrl-][alt-][shift-][cmd-][fn-]key[->key_char]
52 /// key_char syntax is only used for generating test events,
53 /// when matching a key with an key_char set will be matched without it.
54 pub fn parse(source: &str) -> anyhow::Result<Self> {
55 let mut control = false;
56 let mut alt = false;
57 let mut shift = false;
58 let mut platform = false;
59 let mut function = false;
60 let mut key = None;
61 let mut key_char = None;
62
63 let mut components = source.split('-').peekable();
64 while let Some(component) = components.next() {
65 match component {
66 "ctrl" => control = true,
67 "alt" => alt = true,
68 "shift" => shift = true,
69 "fn" => function = true,
70 "cmd" | "super" | "win" => platform = true,
71 _ => {
72 if let Some(next) = components.peek() {
73 if next.is_empty() && source.ends_with('-') {
74 key = Some(String::from("-"));
75 break;
76 } else if next.len() > 1 && next.starts_with('>') {
77 key = Some(String::from(component));
78 key_char = Some(String::from(&next[1..]));
79 components.next();
80 } else {
81 return Err(anyhow!("Invalid keystroke `{}`", source));
82 }
83 } else {
84 key = Some(String::from(component));
85 }
86 }
87 }
88 }
89
90 //Allow for the user to specify a keystroke modifier as the key itself
91 //This sets the `key` to the modifier, and disables the modifier
92 if key.is_none() {
93 if shift {
94 key = Some("shift".to_string());
95 shift = false;
96 } else if control {
97 key = Some("control".to_string());
98 control = false;
99 } else if alt {
100 key = Some("alt".to_string());
101 alt = false;
102 } else if platform {
103 key = Some("platform".to_string());
104 platform = false;
105 } else if function {
106 key = Some("function".to_string());
107 function = false;
108 }
109 }
110
111 let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
112
113 Ok(Keystroke {
114 modifiers: Modifiers {
115 control,
116 alt,
117 shift,
118 platform,
119 function,
120 },
121 key,
122 key_char: key_char,
123 })
124 }
125
126 /// Produces a representation of this key that Parse can understand.
127 pub fn unparse(&self) -> String {
128 let mut str = String::new();
129 if self.modifiers.function {
130 str.push_str("fn-");
131 }
132 if self.modifiers.control {
133 str.push_str("ctrl-");
134 }
135 if self.modifiers.alt {
136 str.push_str("alt-");
137 }
138 if self.modifiers.platform {
139 #[cfg(target_os = "macos")]
140 str.push_str("cmd-");
141
142 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
143 str.push_str("super-");
144
145 #[cfg(target_os = "windows")]
146 str.push_str("win-");
147 }
148 if self.modifiers.shift {
149 str.push_str("shift-");
150 }
151 str.push_str(&self.key);
152 str
153 }
154
155 /// Returns true if this keystroke left
156 /// the ime system in an incomplete state.
157 pub fn is_ime_in_progress(&self) -> bool {
158 self.key_char.is_none()
159 && (is_printable_key(&self.key) || self.key.is_empty())
160 && !(self.modifiers.platform
161 || self.modifiers.control
162 || self.modifiers.function
163 || self.modifiers.alt)
164 }
165
166 /// Returns a new keystroke with the key_char filled.
167 /// This is used for dispatch_keystroke where we want users to
168 /// be able to simulate typing "space", etc.
169 pub fn with_simulated_ime(mut self) -> Self {
170 if self.key_char.is_none()
171 && !self.modifiers.platform
172 && !self.modifiers.control
173 && !self.modifiers.function
174 && !self.modifiers.alt
175 {
176 self.key_char = match self.key.as_str() {
177 "space" => Some(" ".into()),
178 "tab" => Some("\t".into()),
179 "enter" => Some("\n".into()),
180 key if !is_printable_key(key) || key.is_empty() => None,
181 key => {
182 if self.modifiers.shift {
183 Some(key.to_uppercase())
184 } else {
185 Some(key.into())
186 }
187 }
188 }
189 }
190 self
191 }
192}
193
194fn is_printable_key(key: &str) -> bool {
195 !matches!(
196 key,
197 "f1" | "f2"
198 | "f3"
199 | "f4"
200 | "f5"
201 | "f6"
202 | "f7"
203 | "f8"
204 | "f9"
205 | "f10"
206 | "f11"
207 | "f12"
208 | "f13"
209 | "f14"
210 | "f15"
211 | "f16"
212 | "f17"
213 | "f18"
214 | "f19"
215 | "backspace"
216 | "delete"
217 | "left"
218 | "right"
219 | "up"
220 | "down"
221 | "pageup"
222 | "pagedown"
223 | "insert"
224 | "home"
225 | "end"
226 | "escape"
227 )
228}
229
230impl std::fmt::Display for Keystroke {
231 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
232 if self.modifiers.control {
233 f.write_char('^')?;
234 }
235 if self.modifiers.alt {
236 f.write_char('โฅ')?;
237 }
238 if self.modifiers.platform {
239 #[cfg(target_os = "macos")]
240 f.write_char('โ')?;
241
242 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
243 f.write_char('โ')?;
244
245 #[cfg(target_os = "windows")]
246 f.write_char('โ')?;
247 }
248 if self.modifiers.shift {
249 f.write_char('โง')?;
250 }
251 let key = match self.key.as_str() {
252 "backspace" => 'โซ',
253 "up" => 'โ',
254 "down" => 'โ',
255 "left" => 'โ',
256 "right" => 'โ',
257 "tab" => 'โฅ',
258 "escape" => 'โ',
259 "shift" => 'โง',
260 "control" => 'โ',
261 "alt" => 'โฅ',
262 "platform" => 'โ',
263 key => {
264 if key.len() == 1 {
265 key.chars().next().unwrap().to_ascii_uppercase()
266 } else {
267 return f.write_str(key);
268 }
269 }
270 };
271 f.write_char(key)
272 }
273}
274
275/// The state of the modifier keys at some point in time
276#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
277pub struct Modifiers {
278 /// The control key
279 pub control: bool,
280
281 /// The alt key
282 /// Sometimes also known as the 'meta' key
283 pub alt: bool,
284
285 /// The shift key
286 pub shift: bool,
287
288 /// The command key, on macos
289 /// the windows key, on windows
290 /// the super key, on linux
291 pub platform: bool,
292
293 /// The function key
294 pub function: bool,
295}
296
297impl Modifiers {
298 /// Returns whether any modifier key is pressed.
299 pub fn modified(&self) -> bool {
300 self.control || self.alt || self.shift || self.platform || self.function
301 }
302
303 /// Whether the semantically 'secondary' modifier key is pressed.
304 ///
305 /// On macOS, this is the command key.
306 /// On Linux and Windows, this is the control key.
307 pub fn secondary(&self) -> bool {
308 #[cfg(target_os = "macos")]
309 {
310 self.platform
311 }
312
313 #[cfg(not(target_os = "macos"))]
314 {
315 self.control
316 }
317 }
318
319 /// Returns how many modifier keys are pressed.
320 pub fn number_of_modifiers(&self) -> u8 {
321 self.control as u8
322 + self.alt as u8
323 + self.shift as u8
324 + self.platform as u8
325 + self.function as u8
326 }
327
328 /// Returns [`Modifiers`] with no modifiers.
329 pub fn none() -> Modifiers {
330 Default::default()
331 }
332
333 /// Returns [`Modifiers`] with just the command key.
334 pub fn command() -> Modifiers {
335 Modifiers {
336 platform: true,
337 ..Default::default()
338 }
339 }
340
341 /// A Returns [`Modifiers`] with just the secondary key pressed.
342 pub fn secondary_key() -> Modifiers {
343 #[cfg(target_os = "macos")]
344 {
345 Modifiers {
346 platform: true,
347 ..Default::default()
348 }
349 }
350
351 #[cfg(not(target_os = "macos"))]
352 {
353 Modifiers {
354 control: true,
355 ..Default::default()
356 }
357 }
358 }
359
360 /// Returns [`Modifiers`] with just the windows key.
361 pub fn windows() -> Modifiers {
362 Modifiers {
363 platform: true,
364 ..Default::default()
365 }
366 }
367
368 /// Returns [`Modifiers`] with just the super key.
369 pub fn super_key() -> Modifiers {
370 Modifiers {
371 platform: true,
372 ..Default::default()
373 }
374 }
375
376 /// Returns [`Modifiers`] with just control.
377 pub fn control() -> Modifiers {
378 Modifiers {
379 control: true,
380 ..Default::default()
381 }
382 }
383
384 /// Returns [`Modifiers`] with just control.
385 pub fn alt() -> Modifiers {
386 Modifiers {
387 alt: true,
388 ..Default::default()
389 }
390 }
391
392 /// Returns [`Modifiers`] with just shift.
393 pub fn shift() -> Modifiers {
394 Modifiers {
395 shift: true,
396 ..Default::default()
397 }
398 }
399
400 /// Returns [`Modifiers`] with command + shift.
401 pub fn command_shift() -> Modifiers {
402 Modifiers {
403 shift: true,
404 platform: true,
405 ..Default::default()
406 }
407 }
408
409 /// Returns [`Modifiers`] with command + shift.
410 pub fn control_shift() -> Modifiers {
411 Modifiers {
412 shift: true,
413 control: true,
414 ..Default::default()
415 }
416 }
417
418 /// Checks if this [`Modifiers`] is a subset of another [`Modifiers`].
419 pub fn is_subset_of(&self, other: &Modifiers) -> bool {
420 (other.control || !self.control)
421 && (other.alt || !self.alt)
422 && (other.shift || !self.shift)
423 && (other.platform || !self.platform)
424 && (other.function || !self.function)
425 }
426}