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 | "escape"
226 )
227}
228
229impl std::fmt::Display for Keystroke {
230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 if self.modifiers.control {
232 f.write_char('^')?;
233 }
234 if self.modifiers.alt {
235 f.write_char('โฅ')?;
236 }
237 if self.modifiers.platform {
238 #[cfg(target_os = "macos")]
239 f.write_char('โ')?;
240
241 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
242 f.write_char('โ')?;
243
244 #[cfg(target_os = "windows")]
245 f.write_char('โ')?;
246 }
247 if self.modifiers.shift {
248 f.write_char('โง')?;
249 }
250 let key = match self.key.as_str() {
251 "backspace" => 'โซ',
252 "up" => 'โ',
253 "down" => 'โ',
254 "left" => 'โ',
255 "right" => 'โ',
256 "tab" => 'โฅ',
257 "escape" => 'โ',
258 "shift" => 'โง',
259 "control" => 'โ',
260 "alt" => 'โฅ',
261 "platform" => 'โ',
262 key => {
263 if key.len() == 1 {
264 key.chars().next().unwrap().to_ascii_uppercase()
265 } else {
266 return f.write_str(key);
267 }
268 }
269 };
270 f.write_char(key)
271 }
272}
273
274/// The state of the modifier keys at some point in time
275#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
276pub struct Modifiers {
277 /// The control key
278 pub control: bool,
279
280 /// The alt key
281 /// Sometimes also known as the 'meta' key
282 pub alt: bool,
283
284 /// The shift key
285 pub shift: bool,
286
287 /// The command key, on macos
288 /// the windows key, on windows
289 /// the super key, on linux
290 pub platform: bool,
291
292 /// The function key
293 pub function: bool,
294}
295
296impl Modifiers {
297 /// Returns whether any modifier key is pressed.
298 pub fn modified(&self) -> bool {
299 self.control || self.alt || self.shift || self.platform || self.function
300 }
301
302 /// Whether the semantically 'secondary' modifier key is pressed.
303 ///
304 /// On macOS, this is the command key.
305 /// On Linux and Windows, this is the control key.
306 pub fn secondary(&self) -> bool {
307 #[cfg(target_os = "macos")]
308 {
309 self.platform
310 }
311
312 #[cfg(not(target_os = "macos"))]
313 {
314 self.control
315 }
316 }
317
318 /// Returns how many modifier keys are pressed.
319 pub fn number_of_modifiers(&self) -> u8 {
320 self.control as u8
321 + self.alt as u8
322 + self.shift as u8
323 + self.platform as u8
324 + self.function as u8
325 }
326
327 /// Returns [`Modifiers`] with no modifiers.
328 pub fn none() -> Modifiers {
329 Default::default()
330 }
331
332 /// Returns [`Modifiers`] with just the command key.
333 pub fn command() -> Modifiers {
334 Modifiers {
335 platform: true,
336 ..Default::default()
337 }
338 }
339
340 /// A Returns [`Modifiers`] with just the secondary key pressed.
341 pub fn secondary_key() -> Modifiers {
342 #[cfg(target_os = "macos")]
343 {
344 Modifiers {
345 platform: true,
346 ..Default::default()
347 }
348 }
349
350 #[cfg(not(target_os = "macos"))]
351 {
352 Modifiers {
353 control: true,
354 ..Default::default()
355 }
356 }
357 }
358
359 /// Returns [`Modifiers`] with just the windows key.
360 pub fn windows() -> Modifiers {
361 Modifiers {
362 platform: true,
363 ..Default::default()
364 }
365 }
366
367 /// Returns [`Modifiers`] with just the super key.
368 pub fn super_key() -> Modifiers {
369 Modifiers {
370 platform: true,
371 ..Default::default()
372 }
373 }
374
375 /// Returns [`Modifiers`] with just control.
376 pub fn control() -> Modifiers {
377 Modifiers {
378 control: true,
379 ..Default::default()
380 }
381 }
382
383 /// Returns [`Modifiers`] with just control.
384 pub fn alt() -> Modifiers {
385 Modifiers {
386 alt: true,
387 ..Default::default()
388 }
389 }
390
391 /// Returns [`Modifiers`] with just shift.
392 pub fn shift() -> Modifiers {
393 Modifiers {
394 shift: true,
395 ..Default::default()
396 }
397 }
398
399 /// Returns [`Modifiers`] with command + shift.
400 pub fn command_shift() -> Modifiers {
401 Modifiers {
402 shift: true,
403 platform: true,
404 ..Default::default()
405 }
406 }
407
408 /// Returns [`Modifiers`] with command + shift.
409 pub fn control_shift() -> Modifiers {
410 Modifiers {
411 shift: true,
412 control: true,
413 ..Default::default()
414 }
415 }
416
417 /// Checks if this [`Modifiers`] is a subset of another [`Modifiers`].
418 pub fn is_subset_of(&self, other: &Modifiers) -> bool {
419 (other.control || !self.control)
420 && (other.alt || !self.alt)
421 && (other.shift || !self.shift)
422 && (other.platform || !self.platform)
423 && (other.function || !self.function)
424 }
425}