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