371 lines · 11.0 KB · Rust
RawDownload
use anyhow::Result;use collections::HashMap;use windows::Win32::UI::{    Input::KeyboardAndMouse::{        GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MAPVK_VK_TO_VSC, MapVirtualKeyW, ToUnicode,        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,        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,        VK_OEM_8, VK_OEM_102, VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_SHIFT,    },    WindowsAndMessaging::KL_NAMELENGTH,}; use gpui::{    KeybindingKeystroke, Keystroke, Modifiers, PlatformKeyboardLayout, PlatformKeyboardMapper,}; pub(crate) struct WindowsKeyboardLayout {    id: String,    name: String,} pub(crate) struct WindowsKeyboardMapper {    key_to_vkey: HashMap<String, (u16, bool)>,    vkey_to_key: HashMap<u16, String>,    vkey_to_shifted: HashMap<u16, String>,} impl PlatformKeyboardLayout for WindowsKeyboardLayout {    fn id(&self) -> &str {        &self.id    }     fn name(&self) -> &str {        &self.name    }} impl PlatformKeyboardMapper for WindowsKeyboardMapper {    fn map_key_equivalent(        &self,        mut keystroke: Keystroke,        use_key_equivalents: bool,    ) -> KeybindingKeystroke {        let Some((vkey, shifted_key)) = self.get_vkey_from_key(&keystroke.key, use_key_equivalents)        else {            return KeybindingKeystroke::from_keystroke(keystroke);        };        if shifted_key && keystroke.modifiers.shift {            log::warn!(                "Keystroke '{}' has both shift and a shifted key, this is likely a bug",                keystroke.key            );        }         let shift = shifted_key || keystroke.modifiers.shift;        keystroke.modifiers.shift = false;         let Some(key) = self.vkey_to_key.get(&vkey).cloned() else {            log::error!(                "Failed to map key equivalent '{:?}' to a valid key",                keystroke            );            return KeybindingKeystroke::from_keystroke(keystroke);        };         keystroke.key = if shift {            let Some(shifted_key) = self.vkey_to_shifted.get(&vkey).cloned() else {                log::error!(                    "Failed to map keystroke {:?} with virtual key '{:?}' to a shifted key",                    keystroke,                    vkey                );                return KeybindingKeystroke::from_keystroke(keystroke);            };            shifted_key        } else {            key.clone()        };         let modifiers = Modifiers {            shift,            ..keystroke.modifiers        };         KeybindingKeystroke::new(keystroke, modifiers, key)    }     fn get_key_equivalents(&self) -> Option<&HashMap<char, char>> {        None    }} impl WindowsKeyboardLayout {    pub(crate) fn new() -> Result<Self> {        let mut buffer = [0u16; KL_NAMELENGTH as usize]; // KL_NAMELENGTH includes the null terminator        unsafe { GetKeyboardLayoutNameW(&mut buffer)? };        let id = String::from_utf16_lossy(&buffer[..buffer.len() - 1]); // Remove the null terminator        let entry = windows_registry::LOCAL_MACHINE.open(format!(            "System\\CurrentControlSet\\Control\\Keyboard Layouts\\{id}"        ))?;        let name = entry.get_string("Layout Text")?;        Ok(Self { id, name })    }     pub(crate) fn unknown() -> Self {        Self {            id: "unknown".to_string(),            name: "unknown".to_string(),        }    }} impl WindowsKeyboardMapper {    pub(crate) fn new() -> Self {        let mut key_to_vkey = HashMap::default();        let mut vkey_to_key = HashMap::default();        let mut vkey_to_shifted = HashMap::default();        for vkey in CANDIDATE_VKEYS {            if let Some(key) = get_key_from_vkey(*vkey) {                key_to_vkey.insert(key.clone(), (vkey.0, false));                vkey_to_key.insert(vkey.0, key);            }            let scan_code = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_VSC) };            if scan_code == 0 {                continue;            }            if let Some(shifted_key) = get_shifted_key(*vkey, scan_code) {                key_to_vkey.insert(shifted_key.clone(), (vkey.0, true));                vkey_to_shifted.insert(vkey.0, shifted_key);            }        }        Self {            key_to_vkey,            vkey_to_key,            vkey_to_shifted,        }    }     fn get_vkey_from_key(&self, key: &str, use_key_equivalents: bool) -> Option<(u16, bool)> {        if use_key_equivalents {            get_vkey_from_key_with_us_layout(key)        } else {            self.key_to_vkey.get(key).cloned()        }    }} pub(crate) fn get_keystroke_key(    vkey: VIRTUAL_KEY,    scan_code: u32,    modifiers: &mut Modifiers,) -> Option<String> {    if modifiers.shift && need_to_convert_to_shifted_key(vkey) {        get_shifted_key(vkey, scan_code).inspect(|_| {            modifiers.shift = false;        })    } else {        get_key_from_vkey(vkey)    }} fn get_key_from_vkey(vkey: VIRTUAL_KEY) -> Option<String> {    let key_data = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_CHAR) };    if key_data == 0 {        return None;    }     // The high word contains dead key flag, the low word contains the character    let key = char::from_u32(key_data & 0xFFFF)?;     Some(key.to_ascii_lowercase().to_string())} #[inline]fn need_to_convert_to_shifted_key(vkey: VIRTUAL_KEY) -> bool {    matches!(        vkey,        VK_OEM_3            | VK_OEM_MINUS            | VK_OEM_PLUS            | VK_OEM_4            | VK_OEM_5            | VK_OEM_6            | VK_OEM_1            | VK_OEM_7            | VK_OEM_COMMA            | VK_OEM_PERIOD            | VK_OEM_2            | VK_OEM_102            | VK_OEM_8            | VK_ABNT_C1            | VK_0            | VK_1            | VK_2            | VK_3            | VK_4            | VK_5            | VK_6            | VK_7            | VK_8            | VK_9    )} fn get_shifted_key(vkey: VIRTUAL_KEY, scan_code: u32) -> Option<String> {    generate_key_char(vkey, scan_code, false, true, false)} pub(crate) fn generate_key_char(    vkey: VIRTUAL_KEY,    scan_code: u32,    control: bool,    shift: bool,    alt: bool,) -> Option<String> {    let mut state = [0; 256];    if control {        state[VK_CONTROL.0 as usize] = 0x80;    }    if shift {        state[VK_SHIFT.0 as usize] = 0x80;    }    if alt {        state[VK_MENU.0 as usize] = 0x80;    }     let mut buffer = [0; 8];    let len = unsafe { ToUnicode(vkey.0 as u32, scan_code, Some(&state), &mut buffer, 0x5) };     match len {        len if len > 0 => String::from_utf16(&buffer[..len as usize])            .ok()            .filter(|candidate| {                !candidate.is_empty() && !candidate.chars().next().unwrap().is_control()            }),        len if len < 0 => String::from_utf16(&buffer[..(-len as usize)]).ok(),        _ => None,    }} fn get_vkey_from_key_with_us_layout(key: &str) -> Option<(u16, bool)> {    match key {        // ` => VK_OEM_3        "`" => Some((VK_OEM_3.0, false)),        "~" => Some((VK_OEM_3.0, true)),        "1" => Some((VK_1.0, false)),        "!" => Some((VK_1.0, true)),        "2" => Some((VK_2.0, false)),        "@" => Some((VK_2.0, true)),        "3" => Some((VK_3.0, false)),        "#" => Some((VK_3.0, true)),        "4" => Some((VK_4.0, false)),        "$" => Some((VK_4.0, true)),        "5" => Some((VK_5.0, false)),        "%" => Some((VK_5.0, true)),        "6" => Some((VK_6.0, false)),        "^" => Some((VK_6.0, true)),        "7" => Some((VK_7.0, false)),        "&" => Some((VK_7.0, true)),        "8" => Some((VK_8.0, false)),        "*" => Some((VK_8.0, true)),        "9" => Some((VK_9.0, false)),        "(" => Some((VK_9.0, true)),        "0" => Some((VK_0.0, false)),        ")" => Some((VK_0.0, true)),        "-" => Some((VK_OEM_MINUS.0, false)),        "_" => Some((VK_OEM_MINUS.0, true)),        "=" => Some((VK_OEM_PLUS.0, false)),        "+" => Some((VK_OEM_PLUS.0, true)),        "[" => Some((VK_OEM_4.0, false)),        "{" => Some((VK_OEM_4.0, true)),        "]" => Some((VK_OEM_6.0, false)),        "}" => Some((VK_OEM_6.0, true)),        "\\" => Some((VK_OEM_5.0, false)),        "|" => Some((VK_OEM_5.0, true)),        ";" => Some((VK_OEM_1.0, false)),        ":" => Some((VK_OEM_1.0, true)),        "'" => Some((VK_OEM_7.0, false)),        "\"" => Some((VK_OEM_7.0, true)),        "," => Some((VK_OEM_COMMA.0, false)),        "<" => Some((VK_OEM_COMMA.0, true)),        "." => Some((VK_OEM_PERIOD.0, false)),        ">" => Some((VK_OEM_PERIOD.0, true)),        "/" => Some((VK_OEM_2.0, false)),        "?" => Some((VK_OEM_2.0, true)),        _ => None,    }} const CANDIDATE_VKEYS: &[VIRTUAL_KEY] = &[    VK_OEM_3,    VK_OEM_MINUS,    VK_OEM_PLUS,    VK_OEM_4,    VK_OEM_5,    VK_OEM_6,    VK_OEM_1,    VK_OEM_7,    VK_OEM_COMMA,    VK_OEM_PERIOD,    VK_OEM_2,    VK_OEM_102,    VK_OEM_8,    VK_ABNT_C1,    VK_0,    VK_1,    VK_2,    VK_3,    VK_4,    VK_5,    VK_6,    VK_7,    VK_8,    VK_9,]; #[cfg(test)]mod tests {    use crate::WindowsKeyboardMapper;    use gpui::{Keystroke, Modifiers, PlatformKeyboardMapper};     #[test]    fn test_keyboard_mapper() {        let mapper = WindowsKeyboardMapper::new();         // Normal case        let keystroke = Keystroke {            modifiers: Modifiers::control(),            key: "a".to_string(),            key_char: None,        };        let mapped = mapper.map_key_equivalent(keystroke.clone(), true);        assert_eq!(*mapped.inner(), keystroke);        assert_eq!(mapped.key(), "a");        assert_eq!(*mapped.modifiers(), Modifiers::control());         // Shifted case, ctrl-$        let keystroke = Keystroke {            modifiers: Modifiers::control(),            key: "$".to_string(),            key_char: None,        };        let mapped = mapper.map_key_equivalent(keystroke.clone(), true);        assert_eq!(*mapped.inner(), keystroke);        assert_eq!(mapped.key(), "4");        assert_eq!(*mapped.modifiers(), Modifiers::control_shift());         // Shifted case, but shift is true        let keystroke = Keystroke {            modifiers: Modifiers::control_shift(),            key: "$".to_string(),            key_char: None,        };        let mapped = mapper.map_key_equivalent(keystroke, true);        assert_eq!(mapped.inner().modifiers, Modifiers::control());        assert_eq!(mapped.key(), "4");        assert_eq!(*mapped.modifiers(), Modifiers::control_shift());         // Windows style        let keystroke = Keystroke {            modifiers: Modifiers::control_shift(),            key: "4".to_string(),            key_char: None,        };        let mapped = mapper.map_key_equivalent(keystroke, true);        assert_eq!(mapped.inner().modifiers, Modifiers::control());        assert_eq!(mapped.inner().key, "$");        assert_eq!(mapped.key(), "4");        assert_eq!(*mapped.modifiers(), Modifiers::control_shift());    }}