1use anyhow::Result;
2use windows::Win32::UI::{
3 Input::KeyboardAndMouse::{
4 GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MapVirtualKeyW, ToUnicode, VIRTUAL_KEY, VK_0,
5 VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9, VK_ABNT_C1, VK_CONTROL, VK_MENU,
6 VK_OEM_1, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_8, VK_OEM_102,
7 VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_SHIFT,
8 },
9 WindowsAndMessaging::KL_NAMELENGTH,
10};
11use windows_core::HSTRING;
12
13use crate::{Modifiers, PlatformKeyboardLayout};
14
15pub(crate) struct WindowsKeyboardLayout {
16 id: String,
17 name: String,
18}
19
20impl PlatformKeyboardLayout for WindowsKeyboardLayout {
21 fn id(&self) -> &str {
22 &self.id
23 }
24
25 fn name(&self) -> &str {
26 &self.name
27 }
28}
29
30impl WindowsKeyboardLayout {
31 pub(crate) fn new() -> Result<Self> {
32 let mut buffer = [0u16; KL_NAMELENGTH as usize];
33 unsafe { GetKeyboardLayoutNameW(&mut buffer)? };
34 let id = HSTRING::from_wide(&buffer).to_string();
35 let entry = windows_registry::LOCAL_MACHINE.open(format!(
36 "System\\CurrentControlSet\\Control\\Keyboard Layouts\\{}",
37 id
38 ))?;
39 let name = entry.get_hstring("Layout Text")?.to_string();
40 Ok(Self { id, name })
41 }
42
43 pub(crate) fn unknown() -> Self {
44 Self {
45 id: "unknown".to_string(),
46 name: "unknown".to_string(),
47 }
48 }
49}
50
51pub(crate) fn get_keystroke_key(
52 vkey: VIRTUAL_KEY,
53 scan_code: u32,
54 modifiers: &mut Modifiers,
55) -> Option<String> {
56 if modifiers.shift && need_to_convert_to_shifted_key(vkey) {
57 get_shifted_key(vkey, scan_code).inspect(|_| {
58 modifiers.shift = false;
59 })
60 } else {
61 get_key_from_vkey(vkey)
62 }
63}
64
65fn get_key_from_vkey(vkey: VIRTUAL_KEY) -> Option<String> {
66 let key_data = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_CHAR) };
67 if key_data == 0 {
68 return None;
69 }
70
71 // The high word contains dead key flag, the low word contains the character
72 let key = char::from_u32(key_data & 0xFFFF)?;
73
74 Some(key.to_ascii_lowercase().to_string())
75}
76
77#[inline]
78fn need_to_convert_to_shifted_key(vkey: VIRTUAL_KEY) -> bool {
79 matches!(
80 vkey,
81 VK_OEM_3
82 | VK_OEM_MINUS
83 | VK_OEM_PLUS
84 | VK_OEM_4
85 | VK_OEM_5
86 | VK_OEM_6
87 | VK_OEM_1
88 | VK_OEM_7
89 | VK_OEM_COMMA
90 | VK_OEM_PERIOD
91 | VK_OEM_2
92 | VK_OEM_102
93 | VK_OEM_8
94 | VK_ABNT_C1
95 | VK_0
96 | VK_1
97 | VK_2
98 | VK_3
99 | VK_4
100 | VK_5
101 | VK_6
102 | VK_7
103 | VK_8
104 | VK_9
105 )
106}
107
108fn get_shifted_key(vkey: VIRTUAL_KEY, scan_code: u32) -> Option<String> {
109 generate_key_char(vkey, scan_code, false, true, false)
110}
111
112pub(crate) fn generate_key_char(
113 vkey: VIRTUAL_KEY,
114 scan_code: u32,
115 control: bool,
116 shift: bool,
117 alt: bool,
118) -> Option<String> {
119 let mut state = [0; 256];
120 if control {
121 state[VK_CONTROL.0 as usize] = 0x80;
122 }
123 if shift {
124 state[VK_SHIFT.0 as usize] = 0x80;
125 }
126 if alt {
127 state[VK_MENU.0 as usize] = 0x80;
128 }
129
130 let mut buffer = [0; 8];
131 let len = unsafe { ToUnicode(vkey.0 as u32, scan_code, Some(&state), &mut buffer, 1 << 2) };
132
133 match len {
134 len if len > 0 => String::from_utf16(&buffer[..len as usize])
135 .ok()
136 .filter(|candidate| {
137 !candidate.is_empty() && !candidate.chars().next().unwrap().is_control()
138 }),
139 len if len < 0 => String::from_utf16(&buffer[..(-len as usize)]).ok(),
140 _ => None,
141 }
142}