ZIm/crates/gpui/src/platform/windows/keyboard.rs

142 lines
3.7 KiB
Rust

use anyhow::Result;
use windows::Win32::UI::{
Input::KeyboardAndMouse::{
GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, 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 windows_core::HSTRING;
use crate::{Modifiers, PlatformKeyboardLayout};
pub(crate) struct WindowsKeyboardLayout {
id: String,
name: String,
}
impl PlatformKeyboardLayout for WindowsKeyboardLayout {
fn id(&self) -> &str {
&self.id
}
fn name(&self) -> &str {
&self.name
}
}
impl WindowsKeyboardLayout {
pub(crate) fn new() -> Result<Self> {
let mut buffer = [0u16; KL_NAMELENGTH as usize];
unsafe { GetKeyboardLayoutNameW(&mut buffer)? };
let id = HSTRING::from_wide(&buffer).to_string();
let entry = windows_registry::LOCAL_MACHINE.open(format!(
"System\\CurrentControlSet\\Control\\Keyboard Layouts\\{}",
id
))?;
let name = entry.get_hstring("Layout Text")?.to_string();
Ok(Self { id, name })
}
pub(crate) fn unknown() -> Self {
Self {
id: "unknown".to_string(),
name: "unknown".to_string(),
}
}
}
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, 1 << 2) };
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,
}
}