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}