keyboard.rs

  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}