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 pub(crate) fn uses_altgr(&self) -> bool {
115 // Check if this is a known AltGr layout by examining the layout ID
116 // The layout ID is a hex string like "00000409" (US) or "00000407" (German)
117 // Extract the language ID (last 4 bytes)
118 let id_bytes = self.id.as_bytes();
119 if id_bytes.len() >= 4 {
120 let lang_id = &id_bytes[id_bytes.len() - 4..];
121 // List of keyboard layouts that use AltGr (non-exhaustive)
122 matches!(
123 lang_id,
124 b"0407" | // German
125 b"040C" | // French
126 b"040A" | // Spanish
127 b"0415" | // Polish
128 b"0413" | // Dutch
129 b"0816" | // Portuguese
130 b"041D" | // Swedish
131 b"0414" | // Norwegian
132 b"040B" | // Finnish
133 b"041F" | // Turkish
134 b"0419" | // Russian
135 b"0405" | // Czech
136 b"040E" | // Hungarian
137 b"0424" | // Slovenian
138 b"041B" | // Slovak
139 b"0418" // Romanian
140 )
141 } else {
142 false
143 }
144 }
145}
146
147impl WindowsKeyboardMapper {
148 pub(crate) fn new() -> Self {
149 let mut key_to_vkey = HashMap::default();
150 let mut vkey_to_key = HashMap::default();
151 let mut vkey_to_shifted = HashMap::default();
152 for vkey in CANDIDATE_VKEYS {
153 if let Some(key) = get_key_from_vkey(*vkey) {
154 key_to_vkey.insert(key.clone(), (vkey.0, false));
155 vkey_to_key.insert(vkey.0, key);
156 }
157 let scan_code = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_VSC) };
158 if scan_code == 0 {
159 continue;
160 }
161 if let Some(shifted_key) = get_shifted_key(*vkey, scan_code) {
162 key_to_vkey.insert(shifted_key.clone(), (vkey.0, true));
163 vkey_to_shifted.insert(vkey.0, shifted_key);
164 }
165 }
166 Self {
167 key_to_vkey,
168 vkey_to_key,
169 vkey_to_shifted,
170 }
171 }
172
173 fn get_vkey_from_key(&self, key: &str, use_key_equivalents: bool) -> Option<(u16, bool)> {
174 if use_key_equivalents {
175 get_vkey_from_key_with_us_layout(key)
176 } else {
177 self.key_to_vkey.get(key).cloned()
178 }
179 }
180}
181
182pub(crate) fn get_keystroke_key(
183 vkey: VIRTUAL_KEY,
184 scan_code: u32,
185 modifiers: &mut Modifiers,
186) -> Option<String> {
187 if modifiers.shift && need_to_convert_to_shifted_key(vkey) {
188 get_shifted_key(vkey, scan_code).inspect(|_| {
189 modifiers.shift = false;
190 })
191 } else {
192 get_key_from_vkey(vkey)
193 }
194}
195
196fn get_key_from_vkey(vkey: VIRTUAL_KEY) -> Option<String> {
197 let key_data = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_CHAR) };
198 if key_data == 0 {
199 return None;
200 }
201
202 // The high word contains dead key flag, the low word contains the character
203 let key = char::from_u32(key_data & 0xFFFF)?;
204
205 Some(key.to_ascii_lowercase().to_string())
206}
207
208#[inline]
209fn need_to_convert_to_shifted_key(vkey: VIRTUAL_KEY) -> bool {
210 matches!(
211 vkey,
212 VK_OEM_3
213 | VK_OEM_MINUS
214 | VK_OEM_PLUS
215 | VK_OEM_4
216 | VK_OEM_5
217 | VK_OEM_6
218 | VK_OEM_1
219 | VK_OEM_7
220 | VK_OEM_COMMA
221 | VK_OEM_PERIOD
222 | VK_OEM_2
223 | VK_OEM_102
224 | VK_OEM_8
225 | VK_ABNT_C1
226 | VK_0
227 | VK_1
228 | VK_2
229 | VK_3
230 | VK_4
231 | VK_5
232 | VK_6
233 | VK_7
234 | VK_8
235 | VK_9
236 )
237}
238
239fn get_shifted_key(vkey: VIRTUAL_KEY, scan_code: u32) -> Option<String> {
240 generate_key_char(vkey, scan_code, false, true, false)
241}
242
243pub(crate) fn generate_key_char(
244 vkey: VIRTUAL_KEY,
245 scan_code: u32,
246 control: bool,
247 shift: bool,
248 alt: bool,
249) -> Option<String> {
250 let mut state = [0; 256];
251 if control {
252 state[VK_CONTROL.0 as usize] = 0x80;
253 }
254 if shift {
255 state[VK_SHIFT.0 as usize] = 0x80;
256 }
257 if alt {
258 state[VK_MENU.0 as usize] = 0x80;
259 }
260
261 let mut buffer = [0; 8];
262 let len = unsafe { ToUnicode(vkey.0 as u32, scan_code, Some(&state), &mut buffer, 1 << 2) };
263
264 match len {
265 len if len > 0 => String::from_utf16(&buffer[..len as usize])
266 .ok()
267 .filter(|candidate| {
268 !candidate.is_empty() && !candidate.chars().next().unwrap().is_control()
269 }),
270 len if len < 0 => String::from_utf16(&buffer[..(-len as usize)]).ok(),
271 _ => None,
272 }
273}
274
275fn get_vkey_from_key_with_us_layout(key: &str) -> Option<(u16, bool)> {
276 match key {
277 // ` => VK_OEM_3
278 "`" => Some((VK_OEM_3.0, false)),
279 "~" => Some((VK_OEM_3.0, true)),
280 "1" => Some((VK_1.0, false)),
281 "!" => Some((VK_1.0, true)),
282 "2" => Some((VK_2.0, false)),
283 "@" => Some((VK_2.0, true)),
284 "3" => Some((VK_3.0, false)),
285 "#" => Some((VK_3.0, true)),
286 "4" => Some((VK_4.0, false)),
287 "$" => Some((VK_4.0, true)),
288 "5" => Some((VK_5.0, false)),
289 "%" => Some((VK_5.0, true)),
290 "6" => Some((VK_6.0, false)),
291 "^" => Some((VK_6.0, true)),
292 "7" => Some((VK_7.0, false)),
293 "&" => Some((VK_7.0, true)),
294 "8" => Some((VK_8.0, false)),
295 "*" => Some((VK_8.0, true)),
296 "9" => Some((VK_9.0, false)),
297 "(" => Some((VK_9.0, true)),
298 "0" => Some((VK_0.0, false)),
299 ")" => Some((VK_0.0, true)),
300 "-" => Some((VK_OEM_MINUS.0, false)),
301 "_" => Some((VK_OEM_MINUS.0, true)),
302 "=" => Some((VK_OEM_PLUS.0, false)),
303 "+" => Some((VK_OEM_PLUS.0, true)),
304 "[" => Some((VK_OEM_4.0, false)),
305 "{" => Some((VK_OEM_4.0, true)),
306 "]" => Some((VK_OEM_6.0, false)),
307 "}" => Some((VK_OEM_6.0, true)),
308 "\\" => Some((VK_OEM_5.0, false)),
309 "|" => Some((VK_OEM_5.0, true)),
310 ";" => Some((VK_OEM_1.0, false)),
311 ":" => Some((VK_OEM_1.0, true)),
312 "'" => Some((VK_OEM_7.0, false)),
313 "\"" => Some((VK_OEM_7.0, true)),
314 "," => Some((VK_OEM_COMMA.0, false)),
315 "<" => Some((VK_OEM_COMMA.0, true)),
316 "." => Some((VK_OEM_PERIOD.0, false)),
317 ">" => Some((VK_OEM_PERIOD.0, true)),
318 "/" => Some((VK_OEM_2.0, false)),
319 "?" => Some((VK_OEM_2.0, true)),
320 _ => None,
321 }
322}
323
324const CANDIDATE_VKEYS: &[VIRTUAL_KEY] = &[
325 VK_OEM_3,
326 VK_OEM_MINUS,
327 VK_OEM_PLUS,
328 VK_OEM_4,
329 VK_OEM_5,
330 VK_OEM_6,
331 VK_OEM_1,
332 VK_OEM_7,
333 VK_OEM_COMMA,
334 VK_OEM_PERIOD,
335 VK_OEM_2,
336 VK_OEM_102,
337 VK_OEM_8,
338 VK_ABNT_C1,
339 VK_0,
340 VK_1,
341 VK_2,
342 VK_3,
343 VK_4,
344 VK_5,
345 VK_6,
346 VK_7,
347 VK_8,
348 VK_9,
349];
350
351#[cfg(test)]
352mod tests {
353 use crate::{Keystroke, Modifiers, PlatformKeyboardMapper, WindowsKeyboardMapper};
354
355 #[test]
356 fn test_keyboard_mapper() {
357 let mapper = WindowsKeyboardMapper::new();
358
359 // Normal case
360 let keystroke = Keystroke {
361 modifiers: Modifiers::control(),
362 key: "a".to_string(),
363 key_char: None,
364 };
365 let mapped = mapper.map_key_equivalent(keystroke.clone(), true);
366 assert_eq!(*mapped.inner(), keystroke);
367 assert_eq!(mapped.key(), "a");
368 assert_eq!(*mapped.modifiers(), Modifiers::control());
369
370 // Shifted case, ctrl-$
371 let keystroke = Keystroke {
372 modifiers: Modifiers::control(),
373 key: "$".to_string(),
374 key_char: None,
375 };
376 let mapped = mapper.map_key_equivalent(keystroke.clone(), true);
377 assert_eq!(*mapped.inner(), keystroke);
378 assert_eq!(mapped.key(), "4");
379 assert_eq!(*mapped.modifiers(), Modifiers::control_shift());
380
381 // Shifted case, but shift is true
382 let keystroke = Keystroke {
383 modifiers: Modifiers::control_shift(),
384 key: "$".to_string(),
385 key_char: None,
386 };
387 let mapped = mapper.map_key_equivalent(keystroke, true);
388 assert_eq!(mapped.inner().modifiers, Modifiers::control());
389 assert_eq!(mapped.key(), "4");
390 assert_eq!(*mapped.modifiers(), Modifiers::control_shift());
391
392 // Windows style
393 let keystroke = Keystroke {
394 modifiers: Modifiers::control_shift(),
395 key: "4".to_string(),
396 key_char: None,
397 };
398 let mapped = mapper.map_key_equivalent(keystroke, true);
399 assert_eq!(mapped.inner().modifiers, Modifiers::control());
400 assert_eq!(mapped.inner().key, "$");
401 assert_eq!(mapped.key(), "4");
402 assert_eq!(*mapped.modifiers(), Modifiers::control_shift());
403 }
404}