1use anyhow::Result;
2use collections::HashMap;
3use windows::Win32::UI::{
4 Input::KeyboardAndMouse::{
5 GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MAPVK_VK_TO_VSC, MapVirtualKeyW, ToUnicode,
6 VIRTUAL_KEY, VK_0, VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9, VK_ABNT_C1,
7 VK_CONTROL, VK_MENU, VK_OEM_1, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7,
8 VK_OEM_8, VK_OEM_102, VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_SHIFT,
9 },
10 WindowsAndMessaging::KL_NAMELENGTH,
11};
12use windows_core::HSTRING;
13
14use crate::{
15 KeybindingKeystroke, Keystroke, Modifiers, PlatformKeyboardLayout, PlatformKeyboardMapper,
16};
17
18pub(crate) struct WindowsKeyboardLayout {
19 id: String,
20 name: String,
21}
22
23pub(crate) struct WindowsKeyboardMapper {
24 key_to_vkey: HashMap<String, (u16, bool)>,
25 vkey_to_key: HashMap<u16, String>,
26 vkey_to_shifted: HashMap<u16, String>,
27}
28
29impl PlatformKeyboardLayout for WindowsKeyboardLayout {
30 fn id(&self) -> &str {
31 &self.id
32 }
33
34 fn name(&self) -> &str {
35 &self.name
36 }
37}
38
39impl PlatformKeyboardMapper for WindowsKeyboardMapper {
40 fn map_key_equivalent(
41 &self,
42 mut keystroke: Keystroke,
43 use_key_equivalents: bool,
44 ) -> KeybindingKeystroke {
45 let Some((vkey, shifted_key)) = self.get_vkey_from_key(&keystroke.key, use_key_equivalents)
46 else {
47 return KeybindingKeystroke::from_keystroke(keystroke);
48 };
49 if shifted_key && keystroke.modifiers.shift {
50 log::warn!(
51 "Keystroke '{}' has both shift and a shifted key, this is likely a bug",
52 keystroke.key
53 );
54 }
55
56 let shift = shifted_key || keystroke.modifiers.shift;
57 keystroke.modifiers.shift = false;
58
59 let Some(key) = self.vkey_to_key.get(&vkey).cloned() else {
60 log::error!(
61 "Failed to map key equivalent '{:?}' to a valid key",
62 keystroke
63 );
64 return KeybindingKeystroke::from_keystroke(keystroke);
65 };
66
67 keystroke.key = if shift {
68 let Some(shifted_key) = self.vkey_to_shifted.get(&vkey).cloned() else {
69 log::error!(
70 "Failed to map keystroke {:?} with virtual key '{:?}' to a shifted key",
71 keystroke,
72 vkey
73 );
74 return KeybindingKeystroke::from_keystroke(keystroke);
75 };
76 shifted_key
77 } else {
78 key.clone()
79 };
80
81 let modifiers = Modifiers {
82 shift,
83 ..keystroke.modifiers
84 };
85
86 KeybindingKeystroke {
87 inner: keystroke,
88 display_modifiers: modifiers,
89 display_key: key,
90 }
91 }
92
93 fn get_key_equivalents(&self) -> Option<&HashMap<char, char>> {
94 None
95 }
96}
97
98impl WindowsKeyboardLayout {
99 pub(crate) fn new() -> Result<Self> {
100 let mut buffer = [0u16; KL_NAMELENGTH as usize];
101 unsafe { GetKeyboardLayoutNameW(&mut buffer)? };
102 let id = HSTRING::from_wide(&buffer).to_string();
103 let entry = windows_registry::LOCAL_MACHINE.open(format!(
104 "System\\CurrentControlSet\\Control\\Keyboard Layouts\\{}",
105 id
106 ))?;
107 let name = entry.get_hstring("Layout Text")?.to_string();
108 Ok(Self { id, name })
109 }
110
111 pub(crate) fn unknown() -> Self {
112 Self {
113 id: "unknown".to_string(),
114 name: "unknown".to_string(),
115 }
116 }
117}
118
119impl WindowsKeyboardMapper {
120 pub(crate) fn new() -> Self {
121 let mut key_to_vkey = HashMap::default();
122 let mut vkey_to_key = HashMap::default();
123 let mut vkey_to_shifted = HashMap::default();
124 for vkey in CANDIDATE_VKEYS {
125 if let Some(key) = get_key_from_vkey(*vkey) {
126 key_to_vkey.insert(key.clone(), (vkey.0, false));
127 vkey_to_key.insert(vkey.0, key);
128 }
129 let scan_code = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_VSC) };
130 if scan_code == 0 {
131 continue;
132 }
133 if let Some(shifted_key) = get_shifted_key(*vkey, scan_code) {
134 key_to_vkey.insert(shifted_key.clone(), (vkey.0, true));
135 vkey_to_shifted.insert(vkey.0, shifted_key);
136 }
137 }
138 Self {
139 key_to_vkey,
140 vkey_to_key,
141 vkey_to_shifted,
142 }
143 }
144
145 fn get_vkey_from_key(&self, key: &str, use_key_equivalents: bool) -> Option<(u16, bool)> {
146 if use_key_equivalents {
147 get_vkey_from_key_with_us_layout(key)
148 } else {
149 self.key_to_vkey.get(key).cloned()
150 }
151 }
152}
153
154pub(crate) fn get_keystroke_key(
155 vkey: VIRTUAL_KEY,
156 scan_code: u32,
157 modifiers: &mut Modifiers,
158) -> Option<String> {
159 if modifiers.shift && need_to_convert_to_shifted_key(vkey) {
160 get_shifted_key(vkey, scan_code).inspect(|_| {
161 modifiers.shift = false;
162 })
163 } else {
164 get_key_from_vkey(vkey)
165 }
166}
167
168fn get_key_from_vkey(vkey: VIRTUAL_KEY) -> Option<String> {
169 let key_data = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_CHAR) };
170 if key_data == 0 {
171 return None;
172 }
173
174 // The high word contains dead key flag, the low word contains the character
175 let key = char::from_u32(key_data & 0xFFFF)?;
176
177 Some(key.to_ascii_lowercase().to_string())
178}
179
180#[inline]
181fn need_to_convert_to_shifted_key(vkey: VIRTUAL_KEY) -> bool {
182 matches!(
183 vkey,
184 VK_OEM_3
185 | VK_OEM_MINUS
186 | VK_OEM_PLUS
187 | VK_OEM_4
188 | VK_OEM_5
189 | VK_OEM_6
190 | VK_OEM_1
191 | VK_OEM_7
192 | VK_OEM_COMMA
193 | VK_OEM_PERIOD
194 | VK_OEM_2
195 | VK_OEM_102
196 | VK_OEM_8
197 | VK_ABNT_C1
198 | VK_0
199 | VK_1
200 | VK_2
201 | VK_3
202 | VK_4
203 | VK_5
204 | VK_6
205 | VK_7
206 | VK_8
207 | VK_9
208 )
209}
210
211fn get_shifted_key(vkey: VIRTUAL_KEY, scan_code: u32) -> Option<String> {
212 generate_key_char(vkey, scan_code, false, true, false)
213}
214
215pub(crate) fn generate_key_char(
216 vkey: VIRTUAL_KEY,
217 scan_code: u32,
218 control: bool,
219 shift: bool,
220 alt: bool,
221) -> Option<String> {
222 let mut state = [0; 256];
223 if control {
224 state[VK_CONTROL.0 as usize] = 0x80;
225 }
226 if shift {
227 state[VK_SHIFT.0 as usize] = 0x80;
228 }
229 if alt {
230 state[VK_MENU.0 as usize] = 0x80;
231 }
232
233 let mut buffer = [0; 8];
234 let len = unsafe { ToUnicode(vkey.0 as u32, scan_code, Some(&state), &mut buffer, 1 << 2) };
235
236 match len {
237 len if len > 0 => String::from_utf16(&buffer[..len as usize])
238 .ok()
239 .filter(|candidate| {
240 !candidate.is_empty() && !candidate.chars().next().unwrap().is_control()
241 }),
242 len if len < 0 => String::from_utf16(&buffer[..(-len as usize)]).ok(),
243 _ => None,
244 }
245}
246
247fn get_vkey_from_key_with_us_layout(key: &str) -> Option<(u16, bool)> {
248 match key {
249 // ` => VK_OEM_3
250 "`" => Some((VK_OEM_3.0, false)),
251 "~" => Some((VK_OEM_3.0, true)),
252 "1" => Some((VK_1.0, false)),
253 "!" => Some((VK_1.0, true)),
254 "2" => Some((VK_2.0, false)),
255 "@" => Some((VK_2.0, true)),
256 "3" => Some((VK_3.0, false)),
257 "#" => Some((VK_3.0, true)),
258 "4" => Some((VK_4.0, false)),
259 "$" => Some((VK_4.0, true)),
260 "5" => Some((VK_5.0, false)),
261 "%" => Some((VK_5.0, true)),
262 "6" => Some((VK_6.0, false)),
263 "^" => Some((VK_6.0, true)),
264 "7" => Some((VK_7.0, false)),
265 "&" => Some((VK_7.0, true)),
266 "8" => Some((VK_8.0, false)),
267 "*" => Some((VK_8.0, true)),
268 "9" => Some((VK_9.0, false)),
269 "(" => Some((VK_9.0, true)),
270 "0" => Some((VK_0.0, false)),
271 ")" => Some((VK_0.0, true)),
272 "-" => Some((VK_OEM_MINUS.0, false)),
273 "_" => Some((VK_OEM_MINUS.0, true)),
274 "=" => Some((VK_OEM_PLUS.0, false)),
275 "+" => Some((VK_OEM_PLUS.0, true)),
276 "[" => Some((VK_OEM_4.0, false)),
277 "{" => Some((VK_OEM_4.0, true)),
278 "]" => Some((VK_OEM_6.0, false)),
279 "}" => Some((VK_OEM_6.0, true)),
280 "\\" => Some((VK_OEM_5.0, false)),
281 "|" => Some((VK_OEM_5.0, true)),
282 ";" => Some((VK_OEM_1.0, false)),
283 ":" => Some((VK_OEM_1.0, true)),
284 "'" => Some((VK_OEM_7.0, false)),
285 "\"" => Some((VK_OEM_7.0, true)),
286 "," => Some((VK_OEM_COMMA.0, false)),
287 "<" => Some((VK_OEM_COMMA.0, true)),
288 "." => Some((VK_OEM_PERIOD.0, false)),
289 ">" => Some((VK_OEM_PERIOD.0, true)),
290 "/" => Some((VK_OEM_2.0, false)),
291 "?" => Some((VK_OEM_2.0, true)),
292 _ => None,
293 }
294}
295
296const CANDIDATE_VKEYS: &[VIRTUAL_KEY] = &[
297 VK_OEM_3,
298 VK_OEM_MINUS,
299 VK_OEM_PLUS,
300 VK_OEM_4,
301 VK_OEM_5,
302 VK_OEM_6,
303 VK_OEM_1,
304 VK_OEM_7,
305 VK_OEM_COMMA,
306 VK_OEM_PERIOD,
307 VK_OEM_2,
308 VK_OEM_102,
309 VK_OEM_8,
310 VK_ABNT_C1,
311 VK_0,
312 VK_1,
313 VK_2,
314 VK_3,
315 VK_4,
316 VK_5,
317 VK_6,
318 VK_7,
319 VK_8,
320 VK_9,
321];
322
323#[cfg(test)]
324mod tests {
325 use crate::{Keystroke, Modifiers, PlatformKeyboardMapper, WindowsKeyboardMapper};
326
327 #[test]
328 fn test_keyboard_mapper() {
329 let mapper = WindowsKeyboardMapper::new();
330
331 // Normal case
332 let keystroke = Keystroke {
333 modifiers: Modifiers::control(),
334 key: "a".to_string(),
335 key_char: None,
336 };
337 let mapped = mapper.map_key_equivalent(keystroke.clone(), true);
338 assert_eq!(mapped.inner, keystroke);
339 assert_eq!(mapped.display_key, "a");
340 assert_eq!(mapped.display_modifiers, Modifiers::control());
341
342 // Shifted case, ctrl-$
343 let keystroke = Keystroke {
344 modifiers: Modifiers::control(),
345 key: "$".to_string(),
346 key_char: None,
347 };
348 let mapped = mapper.map_key_equivalent(keystroke.clone(), true);
349 assert_eq!(mapped.inner, keystroke);
350 assert_eq!(mapped.display_key, "4");
351 assert_eq!(mapped.display_modifiers, Modifiers::control_shift());
352
353 // Shifted case, but shift is true
354 let keystroke = Keystroke {
355 modifiers: Modifiers::control_shift(),
356 key: "$".to_string(),
357 key_char: None,
358 };
359 let mapped = mapper.map_key_equivalent(keystroke, true);
360 assert_eq!(mapped.inner.modifiers, Modifiers::control());
361 assert_eq!(mapped.display_key, "4");
362 assert_eq!(mapped.display_modifiers, Modifiers::control_shift());
363
364 // Windows style
365 let keystroke = Keystroke {
366 modifiers: Modifiers::control_shift(),
367 key: "4".to_string(),
368 key_char: None,
369 };
370 let mapped = mapper.map_key_equivalent(keystroke, true);
371 assert_eq!(mapped.inner.modifiers, Modifiers::control());
372 assert_eq!(mapped.inner.key, "$");
373 assert_eq!(mapped.display_key, "4");
374 assert_eq!(mapped.display_modifiers, Modifiers::control_shift());
375 }
376}