keyboard.rs

  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}