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