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