Overhaul the Keyboard API

Overhaul the keyboard API in winit to mimic the W3C specification
to achieve better crossplatform parity. The `KeyboardInput` event
is now uses `KeyEvent` which consists of:

  - `physical_key` - a cross platform way to refer to scancodes;
  - `logical_key`  - keysym value, which shows your key respecting the
                     layout;
  - `text`         - the text produced by this keypress;
  - `location`     - the location of the key on the keyboard;
  - `repeat`       - whether the key was produced by the repeat.

And also a `platform_specific` field which encapsulates extra
information on desktop platforms, like key without modifiers
and text with all modifiers.

The `Modifiers` were also slightly reworked as in, the information
whether the left or right modifier is pressed is now also exposed
on platforms where it could be queried reliably. The support was
also added for the web and orbital platforms finishing the API
change.

This change made the `OptionAsAlt` API on macOS redundant thus it
was removed all together.

Co-authored-by: Artúr Kovács <kovacs.artur.barnabas@gmail.com>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
Co-authored-by: daxpedda <daxpedda@gmail.com>
Fixes: #2631.
Fixes: #2055.
Fixes: #2032.
Fixes: #1904.
Fixes: #1810.
Fixes: #1700.
Fixes: #1443.
Fixes: #1343.
Fixes: #1208.
Fixes: #1151.
Fixes: #812.
Fixes: #600.
Fixes: #361.
Fixes: #343.
This commit is contained in:
Markus Røyset 2023-05-28 20:02:59 +02:00 committed by GitHub
parent f3f46cb3f6
commit 918430979f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
81 changed files with 9577 additions and 3419 deletions

View file

@ -1,436 +0,0 @@
use std::{
char,
sync::atomic::{AtomicBool, AtomicIsize, Ordering},
};
use windows_sys::Win32::{
Foundation::{LPARAM, WPARAM},
UI::{
Input::KeyboardAndMouse::{
GetKeyState, GetKeyboardLayout, GetKeyboardState, MapVirtualKeyA, ToUnicodeEx,
MAPVK_VK_TO_CHAR, MAPVK_VSC_TO_VK_EX, VIRTUAL_KEY, VK_0, VK_1, VK_2, VK_3, VK_4, VK_5,
VK_6, VK_7, VK_8, VK_9, VK_A, VK_ADD, VK_APPS, VK_B, VK_BACK, VK_BROWSER_BACK,
VK_BROWSER_FAVORITES, VK_BROWSER_FORWARD, VK_BROWSER_HOME, VK_BROWSER_REFRESH,
VK_BROWSER_SEARCH, VK_BROWSER_STOP, VK_C, VK_CAPITAL, VK_CONTROL, VK_CONVERT, VK_D,
VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_E, VK_END, VK_ESCAPE, VK_F, VK_F1,
VK_F10, VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18, VK_F19, VK_F2,
VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8,
VK_F9, VK_G, VK_H, VK_HOME, VK_I, VK_INSERT, VK_J, VK_K, VK_KANA, VK_KANJI, VK_L,
VK_LAUNCH_MAIL, VK_LAUNCH_MEDIA_SELECT, VK_LCONTROL, VK_LEFT, VK_LMENU, VK_LSHIFT,
VK_LWIN, VK_M, VK_MEDIA_NEXT_TRACK, VK_MEDIA_PLAY_PAUSE, VK_MEDIA_PREV_TRACK,
VK_MEDIA_STOP, VK_MENU, VK_MULTIPLY, VK_N, VK_NEXT, VK_NONCONVERT, VK_NUMLOCK,
VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6,
VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_O, VK_OEM_1, VK_OEM_102, VK_OEM_2, VK_OEM_3,
VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD,
VK_OEM_PLUS, VK_P, VK_PAUSE, VK_PRIOR, VK_Q, VK_R, VK_RCONTROL, VK_RETURN, VK_RIGHT,
VK_RMENU, VK_RSHIFT, VK_RWIN, VK_S, VK_SCROLL, VK_SHIFT, VK_SLEEP, VK_SNAPSHOT,
VK_SPACE, VK_SUBTRACT, VK_T, VK_TAB, VK_U, VK_UP, VK_V, VK_VOLUME_DOWN, VK_VOLUME_MUTE,
VK_VOLUME_UP, VK_W, VK_X, VK_Y, VK_Z,
},
TextServices::HKL,
},
};
use crate::event::{ModifiersState, ScanCode, VirtualKeyCode};
use super::util::has_flag;
fn key_pressed(vkey: VIRTUAL_KEY) -> bool {
unsafe { has_flag(GetKeyState(vkey as i32), 1 << 15) }
}
pub fn get_key_mods() -> ModifiersState {
let filter_out_altgr = layout_uses_altgr() && key_pressed(VK_RMENU);
let mut mods = ModifiersState::empty();
mods.set(ModifiersState::SHIFT, key_pressed(VK_SHIFT));
mods.set(
ModifiersState::CTRL,
key_pressed(VK_CONTROL) && !filter_out_altgr,
);
mods.set(
ModifiersState::ALT,
key_pressed(VK_MENU) && !filter_out_altgr,
);
mods.set(
ModifiersState::LOGO,
key_pressed(VK_LWIN) || key_pressed(VK_RWIN),
);
mods
}
bitflags! {
#[derive(Default)]
pub struct ModifiersStateSide: u32 {
const LSHIFT = 0b010;
const RSHIFT = 0b001;
const LCTRL = 0b010 << 3;
const RCTRL = 0b001 << 3;
const LALT = 0b010 << 6;
const RALT = 0b001 << 6;
const LLOGO = 0b010 << 9;
const RLOGO = 0b001 << 9;
}
}
impl ModifiersStateSide {
pub fn filter_out_altgr(&self) -> ModifiersStateSide {
match layout_uses_altgr() && self.contains(Self::RALT) {
false => *self,
true => *self & !(Self::LCTRL | Self::RCTRL | Self::LALT | Self::RALT),
}
}
}
impl From<ModifiersStateSide> for ModifiersState {
fn from(side: ModifiersStateSide) -> Self {
let mut state = ModifiersState::default();
state.set(
Self::SHIFT,
side.intersects(ModifiersStateSide::LSHIFT | ModifiersStateSide::RSHIFT),
);
state.set(
Self::CTRL,
side.intersects(ModifiersStateSide::LCTRL | ModifiersStateSide::RCTRL),
);
state.set(
Self::ALT,
side.intersects(ModifiersStateSide::LALT | ModifiersStateSide::RALT),
);
state.set(
Self::LOGO,
side.intersects(ModifiersStateSide::LLOGO | ModifiersStateSide::RLOGO),
);
state
}
}
pub fn get_pressed_keys() -> impl Iterator<Item = VIRTUAL_KEY> {
let mut keyboard_state = vec![0u8; 256];
unsafe { GetKeyboardState(keyboard_state.as_mut_ptr()) };
keyboard_state
.into_iter()
.enumerate()
.filter(|(_, p)| (*p & (1 << 7)) != 0) // whether or not a key is pressed is communicated via the high-order bit
.map(|(i, _)| i as u16)
}
unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option<char> {
let mut unicode_bytes = [0u16; 5];
let len = ToUnicodeEx(
v_key,
0,
keyboard_state.as_ptr(),
unicode_bytes.as_mut_ptr(),
unicode_bytes.len() as _,
0,
hkl,
);
if len >= 1 {
char::decode_utf16(unicode_bytes.iter().cloned())
.next()
.and_then(|c| c.ok())
} else {
None
}
}
/// Figures out if the keyboard layout has an AltGr key instead of an Alt key.
///
/// Unfortunately, the Windows API doesn't give a way for us to conveniently figure that out. So,
/// we use a technique blatantly stolen from [the Firefox source code][source]: iterate over every
/// possible virtual key and compare the `char` output when AltGr is pressed vs when it isn't. If
/// pressing AltGr outputs characters that are different from the standard characters, the layout
/// uses AltGr. Otherwise, it doesn't.
///
/// [source]: https://github.com/mozilla/gecko-dev/blob/265e6721798a455604328ed5262f430cfcc37c2f/widget/windows/KeyboardLayout.cpp#L4356-L4416
fn layout_uses_altgr() -> bool {
unsafe {
static ACTIVE_LAYOUT: AtomicIsize = AtomicIsize::new(0);
static USES_ALTGR: AtomicBool = AtomicBool::new(false);
let hkl = GetKeyboardLayout(0);
let old_hkl = ACTIVE_LAYOUT.swap(hkl, Ordering::SeqCst);
if hkl == old_hkl {
return USES_ALTGR.load(Ordering::SeqCst);
}
let mut keyboard_state_altgr = [0u8; 256];
// AltGr is an alias for Ctrl+Alt for... some reason. Whatever it is, those are the keypresses
// we have to emulate to do an AltGr test.
keyboard_state_altgr[VK_MENU as usize] = 0x80;
keyboard_state_altgr[VK_CONTROL as usize] = 0x80;
let keyboard_state_empty = [0u8; 256];
for v_key in 0..=255 {
let key_noaltgr = get_char(&keyboard_state_empty, v_key, hkl);
let key_altgr = get_char(&keyboard_state_altgr, v_key, hkl);
if let (Some(noaltgr), Some(altgr)) = (key_noaltgr, key_altgr) {
if noaltgr != altgr {
USES_ALTGR.store(true, Ordering::SeqCst);
return true;
}
}
}
USES_ALTGR.store(false, Ordering::SeqCst);
false
}
}
pub fn vkey_to_winit_vkey(vkey: VIRTUAL_KEY) -> Option<VirtualKeyCode> {
// VK_* codes are documented here https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
match vkey {
//VK_LBUTTON => Some(VirtualKeyCode::Lbutton),
//VK_RBUTTON => Some(VirtualKeyCode::Rbutton),
//VK_CANCEL => Some(VirtualKeyCode::Cancel),
//VK_MBUTTON => Some(VirtualKeyCode::Mbutton),
//VK_XBUTTON1 => Some(VirtualKeyCode::Xbutton1),
//VK_XBUTTON2 => Some(VirtualKeyCode::Xbutton2),
VK_BACK => Some(VirtualKeyCode::Back),
VK_TAB => Some(VirtualKeyCode::Tab),
//VK_CLEAR => Some(VirtualKeyCode::Clear),
VK_RETURN => Some(VirtualKeyCode::Return),
VK_LSHIFT => Some(VirtualKeyCode::LShift),
VK_RSHIFT => Some(VirtualKeyCode::RShift),
VK_LCONTROL => Some(VirtualKeyCode::LControl),
VK_RCONTROL => Some(VirtualKeyCode::RControl),
VK_LMENU => Some(VirtualKeyCode::LAlt),
VK_RMENU => Some(VirtualKeyCode::RAlt),
VK_PAUSE => Some(VirtualKeyCode::Pause),
VK_CAPITAL => Some(VirtualKeyCode::Capital),
VK_KANA => Some(VirtualKeyCode::Kana),
//VK_HANGUEL => Some(VirtualKeyCode::Hanguel),
//VK_HANGUL => Some(VirtualKeyCode::Hangul),
//VK_JUNJA => Some(VirtualKeyCode::Junja),
//VK_FINAL => Some(VirtualKeyCode::Final),
//VK_HANJA => Some(VirtualKeyCode::Hanja),
VK_KANJI => Some(VirtualKeyCode::Kanji),
VK_ESCAPE => Some(VirtualKeyCode::Escape),
VK_CONVERT => Some(VirtualKeyCode::Convert),
VK_NONCONVERT => Some(VirtualKeyCode::NoConvert),
//VK_ACCEPT => Some(VirtualKeyCode::Accept),
//VK_MODECHANGE => Some(VirtualKeyCode::Modechange),
VK_SPACE => Some(VirtualKeyCode::Space),
VK_PRIOR => Some(VirtualKeyCode::PageUp),
VK_NEXT => Some(VirtualKeyCode::PageDown),
VK_END => Some(VirtualKeyCode::End),
VK_HOME => Some(VirtualKeyCode::Home),
VK_LEFT => Some(VirtualKeyCode::Left),
VK_UP => Some(VirtualKeyCode::Up),
VK_RIGHT => Some(VirtualKeyCode::Right),
VK_DOWN => Some(VirtualKeyCode::Down),
//VK_SELECT => Some(VirtualKeyCode::Select),
//VK_PRINT => Some(VirtualKeyCode::Print),
//VK_EXECUTE => Some(VirtualKeyCode::Execute),
VK_SNAPSHOT => Some(VirtualKeyCode::Snapshot),
VK_INSERT => Some(VirtualKeyCode::Insert),
VK_DELETE => Some(VirtualKeyCode::Delete),
//VK_HELP => Some(VirtualKeyCode::Help),
VK_0 => Some(VirtualKeyCode::Key0),
VK_1 => Some(VirtualKeyCode::Key1),
VK_2 => Some(VirtualKeyCode::Key2),
VK_3 => Some(VirtualKeyCode::Key3),
VK_4 => Some(VirtualKeyCode::Key4),
VK_5 => Some(VirtualKeyCode::Key5),
VK_6 => Some(VirtualKeyCode::Key6),
VK_7 => Some(VirtualKeyCode::Key7),
VK_8 => Some(VirtualKeyCode::Key8),
VK_9 => Some(VirtualKeyCode::Key9),
VK_A => Some(VirtualKeyCode::A),
VK_B => Some(VirtualKeyCode::B),
VK_C => Some(VirtualKeyCode::C),
VK_D => Some(VirtualKeyCode::D),
VK_E => Some(VirtualKeyCode::E),
VK_F => Some(VirtualKeyCode::F),
VK_G => Some(VirtualKeyCode::G),
VK_H => Some(VirtualKeyCode::H),
VK_I => Some(VirtualKeyCode::I),
VK_J => Some(VirtualKeyCode::J),
VK_K => Some(VirtualKeyCode::K),
VK_L => Some(VirtualKeyCode::L),
VK_M => Some(VirtualKeyCode::M),
VK_N => Some(VirtualKeyCode::N),
VK_O => Some(VirtualKeyCode::O),
VK_P => Some(VirtualKeyCode::P),
VK_Q => Some(VirtualKeyCode::Q),
VK_R => Some(VirtualKeyCode::R),
VK_S => Some(VirtualKeyCode::S),
VK_T => Some(VirtualKeyCode::T),
VK_U => Some(VirtualKeyCode::U),
VK_V => Some(VirtualKeyCode::V),
VK_W => Some(VirtualKeyCode::W),
VK_X => Some(VirtualKeyCode::X),
VK_Y => Some(VirtualKeyCode::Y),
VK_Z => Some(VirtualKeyCode::Z),
VK_LWIN => Some(VirtualKeyCode::LWin),
VK_RWIN => Some(VirtualKeyCode::RWin),
VK_APPS => Some(VirtualKeyCode::Apps),
VK_SLEEP => Some(VirtualKeyCode::Sleep),
VK_NUMPAD0 => Some(VirtualKeyCode::Numpad0),
VK_NUMPAD1 => Some(VirtualKeyCode::Numpad1),
VK_NUMPAD2 => Some(VirtualKeyCode::Numpad2),
VK_NUMPAD3 => Some(VirtualKeyCode::Numpad3),
VK_NUMPAD4 => Some(VirtualKeyCode::Numpad4),
VK_NUMPAD5 => Some(VirtualKeyCode::Numpad5),
VK_NUMPAD6 => Some(VirtualKeyCode::Numpad6),
VK_NUMPAD7 => Some(VirtualKeyCode::Numpad7),
VK_NUMPAD8 => Some(VirtualKeyCode::Numpad8),
VK_NUMPAD9 => Some(VirtualKeyCode::Numpad9),
VK_MULTIPLY => Some(VirtualKeyCode::NumpadMultiply),
VK_ADD => Some(VirtualKeyCode::NumpadAdd),
//VK_SEPARATOR => Some(VirtualKeyCode::Separator),
VK_SUBTRACT => Some(VirtualKeyCode::NumpadSubtract),
VK_DECIMAL => Some(VirtualKeyCode::NumpadDecimal),
VK_DIVIDE => Some(VirtualKeyCode::NumpadDivide),
VK_F1 => Some(VirtualKeyCode::F1),
VK_F2 => Some(VirtualKeyCode::F2),
VK_F3 => Some(VirtualKeyCode::F3),
VK_F4 => Some(VirtualKeyCode::F4),
VK_F5 => Some(VirtualKeyCode::F5),
VK_F6 => Some(VirtualKeyCode::F6),
VK_F7 => Some(VirtualKeyCode::F7),
VK_F8 => Some(VirtualKeyCode::F8),
VK_F9 => Some(VirtualKeyCode::F9),
VK_F10 => Some(VirtualKeyCode::F10),
VK_F11 => Some(VirtualKeyCode::F11),
VK_F12 => Some(VirtualKeyCode::F12),
VK_F13 => Some(VirtualKeyCode::F13),
VK_F14 => Some(VirtualKeyCode::F14),
VK_F15 => Some(VirtualKeyCode::F15),
VK_F16 => Some(VirtualKeyCode::F16),
VK_F17 => Some(VirtualKeyCode::F17),
VK_F18 => Some(VirtualKeyCode::F18),
VK_F19 => Some(VirtualKeyCode::F19),
VK_F20 => Some(VirtualKeyCode::F20),
VK_F21 => Some(VirtualKeyCode::F21),
VK_F22 => Some(VirtualKeyCode::F22),
VK_F23 => Some(VirtualKeyCode::F23),
VK_F24 => Some(VirtualKeyCode::F24),
VK_NUMLOCK => Some(VirtualKeyCode::Numlock),
VK_SCROLL => Some(VirtualKeyCode::Scroll),
VK_BROWSER_BACK => Some(VirtualKeyCode::NavigateBackward),
VK_BROWSER_FORWARD => Some(VirtualKeyCode::NavigateForward),
VK_BROWSER_REFRESH => Some(VirtualKeyCode::WebRefresh),
VK_BROWSER_STOP => Some(VirtualKeyCode::WebStop),
VK_BROWSER_SEARCH => Some(VirtualKeyCode::WebSearch),
VK_BROWSER_FAVORITES => Some(VirtualKeyCode::WebFavorites),
VK_BROWSER_HOME => Some(VirtualKeyCode::WebHome),
VK_VOLUME_MUTE => Some(VirtualKeyCode::Mute),
VK_VOLUME_DOWN => Some(VirtualKeyCode::VolumeDown),
VK_VOLUME_UP => Some(VirtualKeyCode::VolumeUp),
VK_MEDIA_NEXT_TRACK => Some(VirtualKeyCode::NextTrack),
VK_MEDIA_PREV_TRACK => Some(VirtualKeyCode::PrevTrack),
VK_MEDIA_STOP => Some(VirtualKeyCode::MediaStop),
VK_MEDIA_PLAY_PAUSE => Some(VirtualKeyCode::PlayPause),
VK_LAUNCH_MAIL => Some(VirtualKeyCode::Mail),
VK_LAUNCH_MEDIA_SELECT => Some(VirtualKeyCode::MediaSelect),
/*VK_LAUNCH_APP1 => Some(VirtualKeyCode::Launch_app1),
VK_LAUNCH_APP2 => Some(VirtualKeyCode::Launch_app2),*/
VK_OEM_PLUS => Some(VirtualKeyCode::Equals),
VK_OEM_COMMA => Some(VirtualKeyCode::Comma),
VK_OEM_MINUS => Some(VirtualKeyCode::Minus),
VK_OEM_PERIOD => Some(VirtualKeyCode::Period),
VK_OEM_1 => map_text_keys(vkey),
VK_OEM_2 => map_text_keys(vkey),
VK_OEM_3 => map_text_keys(vkey),
VK_OEM_4 => map_text_keys(vkey),
VK_OEM_5 => map_text_keys(vkey),
VK_OEM_6 => map_text_keys(vkey),
VK_OEM_7 => map_text_keys(vkey),
/* VK_OEM_8 => Some(VirtualKeyCode::Oem_8), */
VK_OEM_102 => Some(VirtualKeyCode::OEM102),
/*VK_PROCESSKEY => Some(VirtualKeyCode::Processkey),
VK_PACKET => Some(VirtualKeyCode::Packet),
VK_ATTN => Some(VirtualKeyCode::Attn),
VK_CRSEL => Some(VirtualKeyCode::Crsel),
VK_EXSEL => Some(VirtualKeyCode::Exsel),
VK_EREOF => Some(VirtualKeyCode::Ereof),
VK_PLAY => Some(VirtualKeyCode::Play),
VK_ZOOM => Some(VirtualKeyCode::Zoom),
VK_NONAME => Some(VirtualKeyCode::Noname),
VK_PA1 => Some(VirtualKeyCode::Pa1),
VK_OEM_CLEAR => Some(VirtualKeyCode::Oem_clear),*/
_ => None,
}
}
pub fn handle_extended_keys(
vkey: VIRTUAL_KEY,
mut scancode: u32,
extended: bool,
) -> Option<(VIRTUAL_KEY, u32)> {
// Welcome to hell https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/
scancode |= if extended { 0xE000 } else { 0x0000 };
let vkey = match vkey {
VK_SHIFT => (unsafe { MapVirtualKeyA(scancode, MAPVK_VSC_TO_VK_EX) } as u16),
VK_CONTROL => {
if extended {
VK_RCONTROL
} else {
VK_LCONTROL
}
}
VK_MENU => {
if extended {
VK_RMENU
} else {
VK_LMENU
}
}
_ => {
match scancode {
// When VK_PAUSE is pressed it emits a LeftControl + NumLock scancode event sequence, but reports VK_PAUSE
// as the virtual key on both events, or VK_PAUSE on the first event or 0xFF when using raw input.
// Don't emit anything for the LeftControl event in the pair...
0xE01D if vkey == VK_PAUSE => return None,
// ...and emit the Pause event for the second event in the pair.
0x45 if vkey == VK_PAUSE || vkey == 0xFF => {
scancode = 0xE059;
VK_PAUSE
}
// VK_PAUSE has an incorrect vkey value when used with modifiers. VK_PAUSE also reports a different
// scancode when used with modifiers than when used without
0xE046 => {
scancode = 0xE059;
VK_PAUSE
}
// VK_SCROLL has an incorrect vkey value when used with modifiers.
0x46 => VK_SCROLL,
_ => vkey,
}
}
};
Some((vkey, scancode))
}
pub fn process_key_params(
wparam: WPARAM,
lparam: LPARAM,
) -> Option<(ScanCode, Option<VirtualKeyCode>)> {
let scancode = ((lparam >> 16) & 0xff) as u32;
let extended = (lparam & 0x01000000) != 0;
handle_extended_keys(wparam as u16, scancode, extended)
.map(|(vkey, scancode)| (scancode, vkey_to_winit_vkey(vkey)))
}
// This is needed as windows doesn't properly distinguish
// some virtual key codes for different keyboard layouts
fn map_text_keys(win_virtual_key: VIRTUAL_KEY) -> Option<VirtualKeyCode> {
let char_key = unsafe { MapVirtualKeyA(win_virtual_key as u32, MAPVK_VK_TO_CHAR) } & 0x7FFF;
match char::from_u32(char_key) {
Some(';') => Some(VirtualKeyCode::Semicolon),
Some('/') => Some(VirtualKeyCode::Slash),
Some('`') => Some(VirtualKeyCode::Grave),
Some('[') => Some(VirtualKeyCode::LBracket),
Some(']') => Some(VirtualKeyCode::RBracket),
Some('\'') => Some(VirtualKeyCode::Apostrophe),
Some('\\') => Some(VirtualKeyCode::Backslash),
_ => None,
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,997 @@
use std::{
collections::{hash_map::Entry, HashMap, HashSet},
ffi::OsString,
os::windows::ffi::OsStringExt,
sync::Mutex,
};
use once_cell::sync::Lazy;
use smol_str::SmolStr;
use windows_sys::Win32::{
System::SystemServices::{LANG_JAPANESE, LANG_KOREAN},
UI::{
Input::KeyboardAndMouse::{
GetKeyState, GetKeyboardLayout, MapVirtualKeyExW, ToUnicodeEx, MAPVK_VK_TO_VSC_EX,
VIRTUAL_KEY, VK_ACCEPT, VK_ADD, VK_APPS, VK_ATTN, VK_BACK, VK_BROWSER_BACK,
VK_BROWSER_FAVORITES, VK_BROWSER_FORWARD, VK_BROWSER_HOME, VK_BROWSER_REFRESH,
VK_BROWSER_SEARCH, VK_BROWSER_STOP, VK_CANCEL, VK_CAPITAL, VK_CLEAR, VK_CONTROL,
VK_CONVERT, VK_CRSEL, VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_EREOF,
VK_ESCAPE, VK_EXECUTE, VK_EXSEL, VK_F1, VK_F10, VK_F11, VK_F12, VK_F13, VK_F14, VK_F15,
VK_F16, VK_F17, VK_F18, VK_F19, VK_F2, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, VK_F3,
VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_FINAL, VK_GAMEPAD_A, VK_GAMEPAD_B,
VK_GAMEPAD_DPAD_DOWN, VK_GAMEPAD_DPAD_LEFT, VK_GAMEPAD_DPAD_RIGHT, VK_GAMEPAD_DPAD_UP,
VK_GAMEPAD_LEFT_SHOULDER, VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON,
VK_GAMEPAD_LEFT_THUMBSTICK_DOWN, VK_GAMEPAD_LEFT_THUMBSTICK_LEFT,
VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT, VK_GAMEPAD_LEFT_THUMBSTICK_UP,
VK_GAMEPAD_LEFT_TRIGGER, VK_GAMEPAD_MENU, VK_GAMEPAD_RIGHT_SHOULDER,
VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON, VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN,
VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT, VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT,
VK_GAMEPAD_RIGHT_THUMBSTICK_UP, VK_GAMEPAD_RIGHT_TRIGGER, VK_GAMEPAD_VIEW,
VK_GAMEPAD_X, VK_GAMEPAD_Y, VK_HANGUL, VK_HANJA, VK_HELP, VK_HOME, VK_ICO_00,
VK_ICO_CLEAR, VK_ICO_HELP, VK_INSERT, VK_JUNJA, VK_KANA, VK_KANJI, VK_LAUNCH_APP1,
VK_LAUNCH_APP2, VK_LAUNCH_MAIL, VK_LAUNCH_MEDIA_SELECT, VK_LBUTTON, VK_LCONTROL,
VK_LEFT, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_MBUTTON, VK_MEDIA_NEXT_TRACK,
VK_MEDIA_PLAY_PAUSE, VK_MEDIA_PREV_TRACK, VK_MEDIA_STOP, VK_MENU, VK_MODECHANGE,
VK_MULTIPLY, VK_NAVIGATION_ACCEPT, VK_NAVIGATION_CANCEL, VK_NAVIGATION_DOWN,
VK_NAVIGATION_LEFT, VK_NAVIGATION_MENU, VK_NAVIGATION_RIGHT, VK_NAVIGATION_UP,
VK_NAVIGATION_VIEW, VK_NEXT, VK_NONAME, VK_NONCONVERT, VK_NUMLOCK, VK_NUMPAD0,
VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7,
VK_NUMPAD8, VK_NUMPAD9, VK_OEM_1, VK_OEM_102, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5,
VK_OEM_6, VK_OEM_7, VK_OEM_8, VK_OEM_ATTN, VK_OEM_AUTO, VK_OEM_AX, VK_OEM_BACKTAB,
VK_OEM_CLEAR, VK_OEM_COMMA, VK_OEM_COPY, VK_OEM_CUSEL, VK_OEM_ENLW, VK_OEM_FINISH,
VK_OEM_FJ_LOYA, VK_OEM_FJ_MASSHOU, VK_OEM_FJ_ROYA, VK_OEM_FJ_TOUROKU, VK_OEM_JUMP,
VK_OEM_MINUS, VK_OEM_NEC_EQUAL, VK_OEM_PA1, VK_OEM_PA2, VK_OEM_PA3, VK_OEM_PERIOD,
VK_OEM_PLUS, VK_OEM_RESET, VK_OEM_WSCTRL, VK_PA1, VK_PACKET, VK_PAUSE, VK_PLAY,
VK_PRINT, VK_PRIOR, VK_PROCESSKEY, VK_RBUTTON, VK_RCONTROL, VK_RETURN, VK_RIGHT,
VK_RMENU, VK_RSHIFT, VK_RWIN, VK_SCROLL, VK_SELECT, VK_SEPARATOR, VK_SHIFT, VK_SLEEP,
VK_SNAPSHOT, VK_SPACE, VK_SUBTRACT, VK_TAB, VK_UP, VK_VOLUME_DOWN, VK_VOLUME_MUTE,
VK_VOLUME_UP, VK_XBUTTON1, VK_XBUTTON2, VK_ZOOM,
},
TextServices::HKL,
},
};
use crate::{
keyboard::{Key, KeyCode, ModifiersState, NativeKey},
platform::scancode::KeyCodeExtScancode,
platform_impl::{loword, primarylangid},
};
pub(crate) static LAYOUT_CACHE: Lazy<Mutex<LayoutCache>> =
Lazy::new(|| Mutex::new(LayoutCache::default()));
fn key_pressed(vkey: VIRTUAL_KEY) -> bool {
unsafe { (GetKeyState(vkey as i32) & (1 << 15)) == (1 << 15) }
}
const NUMPAD_VKEYS: [VIRTUAL_KEY; 16] = [
VK_NUMPAD0,
VK_NUMPAD1,
VK_NUMPAD2,
VK_NUMPAD3,
VK_NUMPAD4,
VK_NUMPAD5,
VK_NUMPAD6,
VK_NUMPAD7,
VK_NUMPAD8,
VK_NUMPAD9,
VK_MULTIPLY,
VK_ADD,
VK_SEPARATOR,
VK_SUBTRACT,
VK_DECIMAL,
VK_DIVIDE,
];
static NUMPAD_KEYCODES: Lazy<HashSet<KeyCode>> = Lazy::new(|| {
let mut keycodes = HashSet::new();
keycodes.insert(KeyCode::Numpad0);
keycodes.insert(KeyCode::Numpad1);
keycodes.insert(KeyCode::Numpad2);
keycodes.insert(KeyCode::Numpad3);
keycodes.insert(KeyCode::Numpad4);
keycodes.insert(KeyCode::Numpad5);
keycodes.insert(KeyCode::Numpad6);
keycodes.insert(KeyCode::Numpad7);
keycodes.insert(KeyCode::Numpad8);
keycodes.insert(KeyCode::Numpad9);
keycodes.insert(KeyCode::NumpadMultiply);
keycodes.insert(KeyCode::NumpadAdd);
keycodes.insert(KeyCode::NumpadComma);
keycodes.insert(KeyCode::NumpadSubtract);
keycodes.insert(KeyCode::NumpadDecimal);
keycodes.insert(KeyCode::NumpadDivide);
keycodes
});
bitflags! {
pub struct WindowsModifiers : u8 {
const SHIFT = 1 << 0;
const CONTROL = 1 << 1;
const ALT = 1 << 2;
const CAPS_LOCK = 1 << 3;
const FLAGS_END = 1 << 4;
}
}
impl WindowsModifiers {
pub fn active_modifiers(key_state: &[u8; 256]) -> WindowsModifiers {
let shift = key_state[VK_SHIFT as usize] & 0x80 != 0;
let lshift = key_state[VK_LSHIFT as usize] & 0x80 != 0;
let rshift = key_state[VK_RSHIFT as usize] & 0x80 != 0;
let control = key_state[VK_CONTROL as usize] & 0x80 != 0;
let lcontrol = key_state[VK_LCONTROL as usize] & 0x80 != 0;
let rcontrol = key_state[VK_RCONTROL as usize] & 0x80 != 0;
let alt = key_state[VK_MENU as usize] & 0x80 != 0;
let lalt = key_state[VK_LMENU as usize] & 0x80 != 0;
let ralt = key_state[VK_RMENU as usize] & 0x80 != 0;
let caps = key_state[VK_CAPITAL as usize] & 0x01 != 0;
let mut result = WindowsModifiers::empty();
if shift || lshift || rshift {
result.insert(WindowsModifiers::SHIFT);
}
if control || lcontrol || rcontrol {
result.insert(WindowsModifiers::CONTROL);
}
if alt || lalt || ralt {
result.insert(WindowsModifiers::ALT);
}
if caps {
result.insert(WindowsModifiers::CAPS_LOCK);
}
result
}
pub fn apply_to_kbd_state(self, key_state: &mut [u8; 256]) {
if self.intersects(Self::SHIFT) {
key_state[VK_SHIFT as usize] |= 0x80;
} else {
key_state[VK_SHIFT as usize] &= !0x80;
key_state[VK_LSHIFT as usize] &= !0x80;
key_state[VK_RSHIFT as usize] &= !0x80;
}
if self.intersects(Self::CONTROL) {
key_state[VK_CONTROL as usize] |= 0x80;
} else {
key_state[VK_CONTROL as usize] &= !0x80;
key_state[VK_LCONTROL as usize] &= !0x80;
key_state[VK_RCONTROL as usize] &= !0x80;
}
if self.intersects(Self::ALT) {
key_state[VK_MENU as usize] |= 0x80;
} else {
key_state[VK_MENU as usize] &= !0x80;
key_state[VK_LMENU as usize] &= !0x80;
key_state[VK_RMENU as usize] &= !0x80;
}
if self.intersects(Self::CAPS_LOCK) {
key_state[VK_CAPITAL as usize] |= 0x01;
} else {
key_state[VK_CAPITAL as usize] &= !0x01;
}
}
/// Removes the control modifier if the alt modifier is not present.
/// This is useful because on Windows: (Control + Alt) == AltGr
/// but we don't want to interfere with the AltGr state.
pub fn remove_only_ctrl(mut self) -> WindowsModifiers {
if !self.contains(WindowsModifiers::ALT) {
self.remove(WindowsModifiers::CONTROL);
}
self
}
}
pub(crate) struct Layout {
pub hkl: u64,
/// Maps numpad keys from Windows virtual key to a `Key`.
///
/// This is useful because some numpad keys generate different charcaters based on the locale.
/// For example `VK_DECIMAL` is sometimes "." and sometimes ",". Note: numpad-specific virtual
/// keys are only produced by Windows when the NumLock is active.
///
/// Making this field separate from the `keys` field saves having to add NumLock as a modifier
/// to `WindowsModifiers`, which would double the number of items in keys.
pub numlock_on_keys: HashMap<VIRTUAL_KEY, Key>,
/// Like `numlock_on_keys` but this will map to the key that would be produced if numlock was
/// off. The keys of this map are identical to the keys of `numlock_on_keys`.
pub numlock_off_keys: HashMap<VIRTUAL_KEY, Key>,
/// Maps a modifier state to group of key strings
/// We're not using `ModifiersState` here because that object cannot express caps lock,
/// but we need to handle caps lock too.
///
/// This map shouldn't need to exist.
/// However currently this seems to be the only good way
/// of getting the label for the pressed key. Note that calling `ToUnicode`
/// just when the key is pressed/released would be enough if `ToUnicode` wouldn't
/// change the keyboard state (it clears the dead key). There is a flag to prevent
/// changing the state, but that flag requires Windows 10, version 1607 or newer)
pub keys: HashMap<WindowsModifiers, HashMap<KeyCode, Key>>,
pub has_alt_graph: bool,
}
impl Layout {
pub fn get_key(
&self,
mods: WindowsModifiers,
num_lock_on: bool,
vkey: VIRTUAL_KEY,
keycode: &KeyCode,
) -> Key {
let native_code = NativeKey::Windows(vkey);
let unknown_alt = vkey == VK_MENU;
if !unknown_alt {
// Here we try using the virtual key directly but if the virtual key doesn't distinguish
// between left and right alt, we can't report AltGr. Therefore, we only do this if the
// key is not the "unknown alt" key.
//
// The reason for using the virtual key directly is that `MapVirtualKeyExW` (used when
// building the keys map) sometimes maps virtual keys to odd scancodes that don't match
// the scancode coming from the KEYDOWN message for the same key. For example: `VK_LEFT`
// is mapped to `0x004B`, but the scancode for the left arrow is `0xE04B`.
let key_from_vkey =
vkey_to_non_char_key(vkey, native_code.clone(), self.hkl, self.has_alt_graph);
if !matches!(key_from_vkey, Key::Unidentified(_)) {
return key_from_vkey;
}
}
if num_lock_on {
if let Some(key) = self.numlock_on_keys.get(&vkey) {
return key.clone();
}
} else if let Some(key) = self.numlock_off_keys.get(&vkey) {
return key.clone();
}
if let Some(keys) = self.keys.get(&mods) {
if let Some(key) = keys.get(keycode) {
return key.clone();
}
}
Key::Unidentified(native_code)
}
}
#[derive(Default)]
pub(crate) struct LayoutCache {
/// Maps locale identifiers (HKL) to layouts
pub layouts: HashMap<u64, Layout>,
}
impl LayoutCache {
/// Checks whether the current layout is already known and
/// prepares the layout if it isn't known.
/// The current layout is then returned.
pub fn get_current_layout(&mut self) -> (u64, &Layout) {
let locale_id = unsafe { GetKeyboardLayout(0) } as u64;
match self.layouts.entry(locale_id) {
Entry::Occupied(entry) => (locale_id, entry.into_mut()),
Entry::Vacant(entry) => {
let layout = Self::prepare_layout(locale_id);
(locale_id, entry.insert(layout))
}
}
}
pub fn get_agnostic_mods(&mut self) -> ModifiersState {
let (_, layout) = self.get_current_layout();
let filter_out_altgr = layout.has_alt_graph && key_pressed(VK_RMENU);
let mut mods = ModifiersState::empty();
mods.set(ModifiersState::SHIFT, key_pressed(VK_SHIFT));
mods.set(
ModifiersState::CONTROL,
key_pressed(VK_CONTROL) && !filter_out_altgr,
);
mods.set(
ModifiersState::ALT,
key_pressed(VK_MENU) && !filter_out_altgr,
);
mods.set(
ModifiersState::SUPER,
key_pressed(VK_LWIN) || key_pressed(VK_RWIN),
);
mods
}
fn prepare_layout(locale_id: u64) -> Layout {
let mut layout = Layout {
hkl: locale_id,
numlock_on_keys: Default::default(),
numlock_off_keys: Default::default(),
keys: Default::default(),
has_alt_graph: false,
};
// We initialize the keyboard state with all zeros to
// simulate a scenario when no modifier is active.
let mut key_state = [0u8; 256];
// `MapVirtualKeyExW` maps (non-numpad-specific) virtual keys to scancodes as if numlock
// was off. We rely on this behavior to find all virtual keys which are not numpad-specific
// but map to the numpad.
//
// src_vkey: VK ==> scancode: u16 (on the numpad)
//
// Then we convert the source virtual key into a `Key` and the scancode into a virtual key
// to get the reverse mapping.
//
// src_vkey: VK ==> scancode: u16 (on the numpad)
// || ||
// \/ \/
// map_value: Key <- map_vkey: VK
layout.numlock_off_keys.reserve(NUMPAD_KEYCODES.len());
for vk in 0..256 {
let scancode = unsafe { MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, locale_id as HKL) };
if scancode == 0 {
continue;
}
let keycode = KeyCode::from_scancode(scancode);
if !is_numpad_specific(vk as VIRTUAL_KEY) && NUMPAD_KEYCODES.contains(&keycode) {
let native_code = NativeKey::Windows(vk as VIRTUAL_KEY);
let map_vkey = keycode_to_vkey(keycode, locale_id);
if map_vkey == 0 {
continue;
}
let map_value =
vkey_to_non_char_key(vk as VIRTUAL_KEY, native_code, locale_id, false);
if matches!(map_value, Key::Unidentified(_)) {
continue;
}
layout.numlock_off_keys.insert(map_vkey, map_value);
}
}
layout.numlock_on_keys.reserve(NUMPAD_VKEYS.len());
for vk in NUMPAD_VKEYS.iter() {
let vk = (*vk) as u32;
let scancode = unsafe { MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, locale_id as HKL) };
let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id);
if let ToUnicodeResult::Str(s) = unicode {
layout
.numlock_on_keys
.insert(vk as VIRTUAL_KEY, Key::Character(SmolStr::new(s)));
}
}
// Iterate through every combination of modifiers
let mods_end = WindowsModifiers::FLAGS_END.bits;
for mod_state in 0..mods_end {
let mut keys_for_this_mod = HashMap::with_capacity(256);
let mod_state = unsafe { WindowsModifiers::from_bits_unchecked(mod_state) };
mod_state.apply_to_kbd_state(&mut key_state);
// Virtual key values are in the domain [0, 255].
// This is reinforced by the fact that the keyboard state array has 256
// elements. This array is allowed to be indexed by virtual key values
// giving the key state for the virtual key used for indexing.
for vk in 0..256 {
let scancode =
unsafe { MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, locale_id as HKL) };
if scancode == 0 {
continue;
}
let native_code = NativeKey::Windows(vk as VIRTUAL_KEY);
let key_code = KeyCode::from_scancode(scancode);
// Let's try to get the key from just the scancode and vk
// We don't necessarily know yet if AltGraph is present on this layout so we'll
// assume it isn't. Then we'll do a second pass where we set the "AltRight" keys to
// "AltGr" in case we find out that there's an AltGraph.
let preliminary_key =
vkey_to_non_char_key(vk as VIRTUAL_KEY, native_code, locale_id, false);
match preliminary_key {
Key::Unidentified(_) => (),
_ => {
keys_for_this_mod.insert(key_code, preliminary_key);
continue;
}
}
let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id);
let key = match unicode {
ToUnicodeResult::Str(str) => Key::Character(SmolStr::new(str)),
ToUnicodeResult::Dead(dead_char) => {
//println!("{:?} - {:?} produced dead {:?}", key_code, mod_state, dead_char);
Key::Dead(dead_char)
}
ToUnicodeResult::None => {
let has_alt = mod_state.contains(WindowsModifiers::ALT);
let has_ctrl = mod_state.contains(WindowsModifiers::CONTROL);
// HACK: `ToUnicodeEx` seems to fail getting the string for the numpad
// divide key, so we handle that explicitly here
if !has_alt && !has_ctrl && key_code == KeyCode::NumpadDivide {
Key::Character(SmolStr::new("/"))
} else {
// Just use the unidentified key, we got earlier
preliminary_key
}
}
};
// Check for alt graph.
// The logic is that if a key pressed with no modifier produces
// a different `Character` from when it's pressed with CTRL+ALT then the layout
// has AltGr.
let ctrl_alt: WindowsModifiers = WindowsModifiers::CONTROL | WindowsModifiers::ALT;
let is_in_ctrl_alt = mod_state == ctrl_alt;
if !layout.has_alt_graph && is_in_ctrl_alt {
// Unwrapping here because if we are in the ctrl+alt modifier state
// then the alt modifier state must have come before.
let simple_keys = layout.keys.get(&WindowsModifiers::empty()).unwrap();
if let Some(Key::Character(key_no_altgr)) = simple_keys.get(&key_code) {
if let Key::Character(key) = &key {
layout.has_alt_graph = key != key_no_altgr;
}
}
}
keys_for_this_mod.insert(key_code, key);
}
layout.keys.insert(mod_state, keys_for_this_mod);
}
// Second pass: replace right alt keys with AltGr if the layout has alt graph
if layout.has_alt_graph {
for mod_state in 0..mods_end {
let mod_state = unsafe { WindowsModifiers::from_bits_unchecked(mod_state) };
if let Some(keys) = layout.keys.get_mut(&mod_state) {
if let Some(key) = keys.get_mut(&KeyCode::AltRight) {
*key = Key::AltGraph;
}
}
}
}
layout
}
fn to_unicode_string(
key_state: &[u8; 256],
vkey: u32,
scancode: u32,
locale_id: u64,
) -> ToUnicodeResult {
unsafe {
let mut label_wide = [0u16; 8];
let mut wide_len = ToUnicodeEx(
vkey,
scancode,
(&key_state[0]) as *const _,
(&mut label_wide[0]) as *mut _,
label_wide.len() as i32,
0,
locale_id as HKL,
);
if wide_len < 0 {
// If it's dead, we run `ToUnicode` again to consume the dead-key
wide_len = ToUnicodeEx(
vkey,
scancode,
(&key_state[0]) as *const _,
(&mut label_wide[0]) as *mut _,
label_wide.len() as i32,
0,
locale_id as HKL,
);
if wide_len > 0 {
let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]);
if let Ok(label_str) = os_string.into_string() {
if let Some(ch) = label_str.chars().next() {
return ToUnicodeResult::Dead(Some(ch));
}
}
}
return ToUnicodeResult::Dead(None);
}
if wide_len > 0 {
let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]);
if let Ok(label_str) = os_string.into_string() {
return ToUnicodeResult::Str(label_str);
}
}
}
ToUnicodeResult::None
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
enum ToUnicodeResult {
Str(String),
Dead(Option<char>),
None,
}
fn is_numpad_specific(vk: VIRTUAL_KEY) -> bool {
matches!(
vk,
VK_NUMPAD0
| VK_NUMPAD1
| VK_NUMPAD2
| VK_NUMPAD3
| VK_NUMPAD4
| VK_NUMPAD5
| VK_NUMPAD6
| VK_NUMPAD7
| VK_NUMPAD8
| VK_NUMPAD9
| VK_ADD
| VK_SUBTRACT
| VK_DIVIDE
| VK_DECIMAL
| VK_SEPARATOR
)
}
fn keycode_to_vkey(keycode: KeyCode, hkl: u64) -> VIRTUAL_KEY {
let primary_lang_id = primarylangid(loword(hkl as u32));
let is_korean = primary_lang_id as u32 == LANG_KOREAN;
let is_japanese = primary_lang_id as u32 == LANG_JAPANESE;
match keycode {
KeyCode::Backquote => 0,
KeyCode::Backslash => 0,
KeyCode::BracketLeft => 0,
KeyCode::BracketRight => 0,
KeyCode::Comma => 0,
KeyCode::Digit0 => 0,
KeyCode::Digit1 => 0,
KeyCode::Digit2 => 0,
KeyCode::Digit3 => 0,
KeyCode::Digit4 => 0,
KeyCode::Digit5 => 0,
KeyCode::Digit6 => 0,
KeyCode::Digit7 => 0,
KeyCode::Digit8 => 0,
KeyCode::Digit9 => 0,
KeyCode::Equal => 0,
KeyCode::IntlBackslash => 0,
KeyCode::IntlRo => 0,
KeyCode::IntlYen => 0,
KeyCode::KeyA => 0,
KeyCode::KeyB => 0,
KeyCode::KeyC => 0,
KeyCode::KeyD => 0,
KeyCode::KeyE => 0,
KeyCode::KeyF => 0,
KeyCode::KeyG => 0,
KeyCode::KeyH => 0,
KeyCode::KeyI => 0,
KeyCode::KeyJ => 0,
KeyCode::KeyK => 0,
KeyCode::KeyL => 0,
KeyCode::KeyM => 0,
KeyCode::KeyN => 0,
KeyCode::KeyO => 0,
KeyCode::KeyP => 0,
KeyCode::KeyQ => 0,
KeyCode::KeyR => 0,
KeyCode::KeyS => 0,
KeyCode::KeyT => 0,
KeyCode::KeyU => 0,
KeyCode::KeyV => 0,
KeyCode::KeyW => 0,
KeyCode::KeyX => 0,
KeyCode::KeyY => 0,
KeyCode::KeyZ => 0,
KeyCode::Minus => 0,
KeyCode::Period => 0,
KeyCode::Quote => 0,
KeyCode::Semicolon => 0,
KeyCode::Slash => 0,
KeyCode::AltLeft => VK_LMENU,
KeyCode::AltRight => VK_RMENU,
KeyCode::Backspace => VK_BACK,
KeyCode::CapsLock => VK_CAPITAL,
KeyCode::ContextMenu => VK_APPS,
KeyCode::ControlLeft => VK_LCONTROL,
KeyCode::ControlRight => VK_RCONTROL,
KeyCode::Enter => VK_RETURN,
KeyCode::SuperLeft => VK_LWIN,
KeyCode::SuperRight => VK_RWIN,
KeyCode::ShiftLeft => VK_RSHIFT,
KeyCode::ShiftRight => VK_LSHIFT,
KeyCode::Space => VK_SPACE,
KeyCode::Tab => VK_TAB,
KeyCode::Convert => VK_CONVERT,
KeyCode::KanaMode => VK_KANA,
KeyCode::Lang1 if is_korean => VK_HANGUL,
KeyCode::Lang1 if is_japanese => VK_KANA,
KeyCode::Lang2 if is_korean => VK_HANJA,
KeyCode::Lang2 if is_japanese => 0,
KeyCode::Lang3 if is_japanese => VK_OEM_FINISH,
KeyCode::Lang4 if is_japanese => 0,
KeyCode::Lang5 if is_japanese => 0,
KeyCode::NonConvert => VK_NONCONVERT,
KeyCode::Delete => VK_DELETE,
KeyCode::End => VK_END,
KeyCode::Help => VK_HELP,
KeyCode::Home => VK_HOME,
KeyCode::Insert => VK_INSERT,
KeyCode::PageDown => VK_NEXT,
KeyCode::PageUp => VK_PRIOR,
KeyCode::ArrowDown => VK_DOWN,
KeyCode::ArrowLeft => VK_LEFT,
KeyCode::ArrowRight => VK_RIGHT,
KeyCode::ArrowUp => VK_UP,
KeyCode::NumLock => VK_NUMLOCK,
KeyCode::Numpad0 => VK_NUMPAD0,
KeyCode::Numpad1 => VK_NUMPAD1,
KeyCode::Numpad2 => VK_NUMPAD2,
KeyCode::Numpad3 => VK_NUMPAD3,
KeyCode::Numpad4 => VK_NUMPAD4,
KeyCode::Numpad5 => VK_NUMPAD5,
KeyCode::Numpad6 => VK_NUMPAD6,
KeyCode::Numpad7 => VK_NUMPAD7,
KeyCode::Numpad8 => VK_NUMPAD8,
KeyCode::Numpad9 => VK_NUMPAD9,
KeyCode::NumpadAdd => VK_ADD,
KeyCode::NumpadBackspace => VK_BACK,
KeyCode::NumpadClear => VK_CLEAR,
KeyCode::NumpadClearEntry => 0,
KeyCode::NumpadComma => VK_SEPARATOR,
KeyCode::NumpadDecimal => VK_DECIMAL,
KeyCode::NumpadDivide => VK_DIVIDE,
KeyCode::NumpadEnter => VK_RETURN,
KeyCode::NumpadEqual => 0,
KeyCode::NumpadHash => 0,
KeyCode::NumpadMemoryAdd => 0,
KeyCode::NumpadMemoryClear => 0,
KeyCode::NumpadMemoryRecall => 0,
KeyCode::NumpadMemoryStore => 0,
KeyCode::NumpadMemorySubtract => 0,
KeyCode::NumpadMultiply => VK_MULTIPLY,
KeyCode::NumpadParenLeft => 0,
KeyCode::NumpadParenRight => 0,
KeyCode::NumpadStar => 0,
KeyCode::NumpadSubtract => VK_SUBTRACT,
KeyCode::Escape => VK_ESCAPE,
KeyCode::Fn => 0,
KeyCode::FnLock => 0,
KeyCode::PrintScreen => VK_SNAPSHOT,
KeyCode::ScrollLock => VK_SCROLL,
KeyCode::Pause => VK_PAUSE,
KeyCode::BrowserBack => VK_BROWSER_BACK,
KeyCode::BrowserFavorites => VK_BROWSER_FAVORITES,
KeyCode::BrowserForward => VK_BROWSER_FORWARD,
KeyCode::BrowserHome => VK_BROWSER_HOME,
KeyCode::BrowserRefresh => VK_BROWSER_REFRESH,
KeyCode::BrowserSearch => VK_BROWSER_SEARCH,
KeyCode::BrowserStop => VK_BROWSER_STOP,
KeyCode::Eject => 0,
KeyCode::LaunchApp1 => VK_LAUNCH_APP1,
KeyCode::LaunchApp2 => VK_LAUNCH_APP2,
KeyCode::LaunchMail => VK_LAUNCH_MAIL,
KeyCode::MediaPlayPause => VK_MEDIA_PLAY_PAUSE,
KeyCode::MediaSelect => VK_LAUNCH_MEDIA_SELECT,
KeyCode::MediaStop => VK_MEDIA_STOP,
KeyCode::MediaTrackNext => VK_MEDIA_NEXT_TRACK,
KeyCode::MediaTrackPrevious => VK_MEDIA_PREV_TRACK,
KeyCode::Power => 0,
KeyCode::Sleep => 0,
KeyCode::AudioVolumeDown => VK_VOLUME_DOWN,
KeyCode::AudioVolumeMute => VK_VOLUME_MUTE,
KeyCode::AudioVolumeUp => VK_VOLUME_UP,
KeyCode::WakeUp => 0,
KeyCode::Hyper => 0,
KeyCode::Turbo => 0,
KeyCode::Abort => 0,
KeyCode::Resume => 0,
KeyCode::Suspend => 0,
KeyCode::Again => 0,
KeyCode::Copy => 0,
KeyCode::Cut => 0,
KeyCode::Find => 0,
KeyCode::Open => 0,
KeyCode::Paste => 0,
KeyCode::Props => 0,
KeyCode::Select => VK_SELECT,
KeyCode::Undo => 0,
KeyCode::Hiragana => 0,
KeyCode::Katakana => 0,
KeyCode::F1 => VK_F1,
KeyCode::F2 => VK_F2,
KeyCode::F3 => VK_F3,
KeyCode::F4 => VK_F4,
KeyCode::F5 => VK_F5,
KeyCode::F6 => VK_F6,
KeyCode::F7 => VK_F7,
KeyCode::F8 => VK_F8,
KeyCode::F9 => VK_F9,
KeyCode::F10 => VK_F10,
KeyCode::F11 => VK_F11,
KeyCode::F12 => VK_F12,
KeyCode::F13 => VK_F13,
KeyCode::F14 => VK_F14,
KeyCode::F15 => VK_F15,
KeyCode::F16 => VK_F16,
KeyCode::F17 => VK_F17,
KeyCode::F18 => VK_F18,
KeyCode::F19 => VK_F19,
KeyCode::F20 => VK_F20,
KeyCode::F21 => VK_F21,
KeyCode::F22 => VK_F22,
KeyCode::F23 => VK_F23,
KeyCode::F24 => VK_F24,
KeyCode::F25 => 0,
KeyCode::F26 => 0,
KeyCode::F27 => 0,
KeyCode::F28 => 0,
KeyCode::F29 => 0,
KeyCode::F30 => 0,
KeyCode::F31 => 0,
KeyCode::F32 => 0,
KeyCode::F33 => 0,
KeyCode::F34 => 0,
KeyCode::F35 => 0,
KeyCode::Unidentified(_) => 0,
_ => 0,
}
}
/// This converts virtual keys to `Key`s. Only virtual keys which can be unambiguously converted to
/// a `Key`, with only the information passed in as arguments, are converted.
///
/// In other words: this function does not need to "prepare" the current layout in order to do
/// the conversion, but as such it cannot convert certain keys, like language-specific character keys.
///
/// The result includes all non-character keys defined within `Key` plus characters from numpad keys.
/// For example, backspace and tab are included.
fn vkey_to_non_char_key(
vkey: VIRTUAL_KEY,
native_code: NativeKey,
hkl: u64,
has_alt_graph: bool,
) -> Key {
// List of the Web key names and their corresponding platform-native key names:
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
let primary_lang_id = primarylangid(loword(hkl as u32));
let is_korean = primary_lang_id as u32 == LANG_KOREAN;
let is_japanese = primary_lang_id as u32 == LANG_JAPANESE;
match vkey {
VK_LBUTTON => Key::Unidentified(NativeKey::Unidentified), // Mouse
VK_RBUTTON => Key::Unidentified(NativeKey::Unidentified), // Mouse
// I don't think this can be represented with a Key
VK_CANCEL => Key::Unidentified(native_code),
VK_MBUTTON => Key::Unidentified(NativeKey::Unidentified), // Mouse
VK_XBUTTON1 => Key::Unidentified(NativeKey::Unidentified), // Mouse
VK_XBUTTON2 => Key::Unidentified(NativeKey::Unidentified), // Mouse
VK_BACK => Key::Backspace,
VK_TAB => Key::Tab,
VK_CLEAR => Key::Clear,
VK_RETURN => Key::Enter,
VK_SHIFT => Key::Shift,
VK_CONTROL => Key::Control,
VK_MENU => Key::Alt,
VK_PAUSE => Key::Pause,
VK_CAPITAL => Key::CapsLock,
//VK_HANGEUL => Key::HangulMode, // Deprecated in favour of VK_HANGUL
// VK_HANGUL and VK_KANA are defined as the same constant, therefore
// we use appropriate conditions to differentate between them
VK_HANGUL if is_korean => Key::HangulMode,
VK_KANA if is_japanese => Key::KanaMode,
VK_JUNJA => Key::JunjaMode,
VK_FINAL => Key::FinalMode,
// VK_HANJA and VK_KANJI are defined as the same constant, therefore
// we use appropriate conditions to differentate between them
VK_HANJA if is_korean => Key::HanjaMode,
VK_KANJI if is_japanese => Key::KanjiMode,
VK_ESCAPE => Key::Escape,
VK_CONVERT => Key::Convert,
VK_NONCONVERT => Key::NonConvert,
VK_ACCEPT => Key::Accept,
VK_MODECHANGE => Key::ModeChange,
VK_SPACE => Key::Space,
VK_PRIOR => Key::PageUp,
VK_NEXT => Key::PageDown,
VK_END => Key::End,
VK_HOME => Key::Home,
VK_LEFT => Key::ArrowLeft,
VK_UP => Key::ArrowUp,
VK_RIGHT => Key::ArrowRight,
VK_DOWN => Key::ArrowDown,
VK_SELECT => Key::Select,
VK_PRINT => Key::Print,
VK_EXECUTE => Key::Execute,
VK_SNAPSHOT => Key::PrintScreen,
VK_INSERT => Key::Insert,
VK_DELETE => Key::Delete,
VK_HELP => Key::Help,
VK_LWIN => Key::Super,
VK_RWIN => Key::Super,
VK_APPS => Key::ContextMenu,
VK_SLEEP => Key::Standby,
// Numpad keys produce characters
VK_NUMPAD0 => Key::Unidentified(native_code),
VK_NUMPAD1 => Key::Unidentified(native_code),
VK_NUMPAD2 => Key::Unidentified(native_code),
VK_NUMPAD3 => Key::Unidentified(native_code),
VK_NUMPAD4 => Key::Unidentified(native_code),
VK_NUMPAD5 => Key::Unidentified(native_code),
VK_NUMPAD6 => Key::Unidentified(native_code),
VK_NUMPAD7 => Key::Unidentified(native_code),
VK_NUMPAD8 => Key::Unidentified(native_code),
VK_NUMPAD9 => Key::Unidentified(native_code),
VK_MULTIPLY => Key::Unidentified(native_code),
VK_ADD => Key::Unidentified(native_code),
VK_SEPARATOR => Key::Unidentified(native_code),
VK_SUBTRACT => Key::Unidentified(native_code),
VK_DECIMAL => Key::Unidentified(native_code),
VK_DIVIDE => Key::Unidentified(native_code),
VK_F1 => Key::F1,
VK_F2 => Key::F2,
VK_F3 => Key::F3,
VK_F4 => Key::F4,
VK_F5 => Key::F5,
VK_F6 => Key::F6,
VK_F7 => Key::F7,
VK_F8 => Key::F8,
VK_F9 => Key::F9,
VK_F10 => Key::F10,
VK_F11 => Key::F11,
VK_F12 => Key::F12,
VK_F13 => Key::F13,
VK_F14 => Key::F14,
VK_F15 => Key::F15,
VK_F16 => Key::F16,
VK_F17 => Key::F17,
VK_F18 => Key::F18,
VK_F19 => Key::F19,
VK_F20 => Key::F20,
VK_F21 => Key::F21,
VK_F22 => Key::F22,
VK_F23 => Key::F23,
VK_F24 => Key::F24,
VK_NAVIGATION_VIEW => Key::Unidentified(native_code),
VK_NAVIGATION_MENU => Key::Unidentified(native_code),
VK_NAVIGATION_UP => Key::Unidentified(native_code),
VK_NAVIGATION_DOWN => Key::Unidentified(native_code),
VK_NAVIGATION_LEFT => Key::Unidentified(native_code),
VK_NAVIGATION_RIGHT => Key::Unidentified(native_code),
VK_NAVIGATION_ACCEPT => Key::Unidentified(native_code),
VK_NAVIGATION_CANCEL => Key::Unidentified(native_code),
VK_NUMLOCK => Key::NumLock,
VK_SCROLL => Key::ScrollLock,
VK_OEM_NEC_EQUAL => Key::Unidentified(native_code),
//VK_OEM_FJ_JISHO => Key::Unidentified(native_code), // Conflicts with `VK_OEM_NEC_EQUAL`
VK_OEM_FJ_MASSHOU => Key::Unidentified(native_code),
VK_OEM_FJ_TOUROKU => Key::Unidentified(native_code),
VK_OEM_FJ_LOYA => Key::Unidentified(native_code),
VK_OEM_FJ_ROYA => Key::Unidentified(native_code),
VK_LSHIFT => Key::Shift,
VK_RSHIFT => Key::Shift,
VK_LCONTROL => Key::Control,
VK_RCONTROL => Key::Control,
VK_LMENU => Key::Alt,
VK_RMENU => {
if has_alt_graph {
Key::AltGraph
} else {
Key::Alt
}
}
VK_BROWSER_BACK => Key::BrowserBack,
VK_BROWSER_FORWARD => Key::BrowserForward,
VK_BROWSER_REFRESH => Key::BrowserRefresh,
VK_BROWSER_STOP => Key::BrowserStop,
VK_BROWSER_SEARCH => Key::BrowserSearch,
VK_BROWSER_FAVORITES => Key::BrowserFavorites,
VK_BROWSER_HOME => Key::BrowserHome,
VK_VOLUME_MUTE => Key::AudioVolumeMute,
VK_VOLUME_DOWN => Key::AudioVolumeDown,
VK_VOLUME_UP => Key::AudioVolumeUp,
VK_MEDIA_NEXT_TRACK => Key::MediaTrackNext,
VK_MEDIA_PREV_TRACK => Key::MediaTrackPrevious,
VK_MEDIA_STOP => Key::MediaStop,
VK_MEDIA_PLAY_PAUSE => Key::MediaPlayPause,
VK_LAUNCH_MAIL => Key::LaunchMail,
VK_LAUNCH_MEDIA_SELECT => Key::LaunchMediaPlayer,
VK_LAUNCH_APP1 => Key::LaunchApplication1,
VK_LAUNCH_APP2 => Key::LaunchApplication2,
// This function only converts "non-printable"
VK_OEM_1 => Key::Unidentified(native_code),
VK_OEM_PLUS => Key::Unidentified(native_code),
VK_OEM_COMMA => Key::Unidentified(native_code),
VK_OEM_MINUS => Key::Unidentified(native_code),
VK_OEM_PERIOD => Key::Unidentified(native_code),
VK_OEM_2 => Key::Unidentified(native_code),
VK_OEM_3 => Key::Unidentified(native_code),
VK_GAMEPAD_A => Key::Unidentified(native_code),
VK_GAMEPAD_B => Key::Unidentified(native_code),
VK_GAMEPAD_X => Key::Unidentified(native_code),
VK_GAMEPAD_Y => Key::Unidentified(native_code),
VK_GAMEPAD_RIGHT_SHOULDER => Key::Unidentified(native_code),
VK_GAMEPAD_LEFT_SHOULDER => Key::Unidentified(native_code),
VK_GAMEPAD_LEFT_TRIGGER => Key::Unidentified(native_code),
VK_GAMEPAD_RIGHT_TRIGGER => Key::Unidentified(native_code),
VK_GAMEPAD_DPAD_UP => Key::Unidentified(native_code),
VK_GAMEPAD_DPAD_DOWN => Key::Unidentified(native_code),
VK_GAMEPAD_DPAD_LEFT => Key::Unidentified(native_code),
VK_GAMEPAD_DPAD_RIGHT => Key::Unidentified(native_code),
VK_GAMEPAD_MENU => Key::Unidentified(native_code),
VK_GAMEPAD_VIEW => Key::Unidentified(native_code),
VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON => Key::Unidentified(native_code),
VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON => Key::Unidentified(native_code),
VK_GAMEPAD_LEFT_THUMBSTICK_UP => Key::Unidentified(native_code),
VK_GAMEPAD_LEFT_THUMBSTICK_DOWN => Key::Unidentified(native_code),
VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT => Key::Unidentified(native_code),
VK_GAMEPAD_LEFT_THUMBSTICK_LEFT => Key::Unidentified(native_code),
VK_GAMEPAD_RIGHT_THUMBSTICK_UP => Key::Unidentified(native_code),
VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN => Key::Unidentified(native_code),
VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT => Key::Unidentified(native_code),
VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT => Key::Unidentified(native_code),
// This function only converts "non-printable"
VK_OEM_4 => Key::Unidentified(native_code),
VK_OEM_5 => Key::Unidentified(native_code),
VK_OEM_6 => Key::Unidentified(native_code),
VK_OEM_7 => Key::Unidentified(native_code),
VK_OEM_8 => Key::Unidentified(native_code),
VK_OEM_AX => Key::Unidentified(native_code),
VK_OEM_102 => Key::Unidentified(native_code),
VK_ICO_HELP => Key::Unidentified(native_code),
VK_ICO_00 => Key::Unidentified(native_code),
VK_PROCESSKEY => Key::Process,
VK_ICO_CLEAR => Key::Unidentified(native_code),
VK_PACKET => Key::Unidentified(native_code),
VK_OEM_RESET => Key::Unidentified(native_code),
VK_OEM_JUMP => Key::Unidentified(native_code),
VK_OEM_PA1 => Key::Unidentified(native_code),
VK_OEM_PA2 => Key::Unidentified(native_code),
VK_OEM_PA3 => Key::Unidentified(native_code),
VK_OEM_WSCTRL => Key::Unidentified(native_code),
VK_OEM_CUSEL => Key::Unidentified(native_code),
VK_OEM_ATTN => Key::Attn,
VK_OEM_FINISH => {
if is_japanese {
Key::Katakana
} else {
// This matches IE and Firefox behaviour according to
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
// At the time of writing, there is no `Key::Finish` variant as
// Finish is not mentionned at https://w3c.github.io/uievents-key/
// Also see: https://github.com/pyfisch/keyboard-types/issues/9
Key::Unidentified(native_code)
}
}
VK_OEM_COPY => Key::Copy,
VK_OEM_AUTO => Key::Hankaku,
VK_OEM_ENLW => Key::Zenkaku,
VK_OEM_BACKTAB => Key::Romaji,
VK_ATTN => Key::KanaMode,
VK_CRSEL => Key::CrSel,
VK_EXSEL => Key::ExSel,
VK_EREOF => Key::EraseEof,
VK_PLAY => Key::Play,
VK_ZOOM => Key::ZoomToggle,
VK_NONAME => Key::Unidentified(native_code),
VK_PA1 => Key::Unidentified(native_code),
VK_OEM_CLEAR => Key::Clear,
_ => Key::Unidentified(native_code),
}
}

View file

@ -0,0 +1,67 @@
use std::sync::{
atomic::{AtomicBool, Ordering::Relaxed},
Mutex,
};
use winapi::{
shared::{
minwindef::{LPARAM, WPARAM},
windef::HWND,
},
um::winuser,
};
use crate::platform_impl::platform::{event_loop::ProcResult, keyboard::next_kbd_msg};
pub struct MinimalIme {
// True if we're currently receiving messages belonging to a finished IME session.
getting_ime_text: AtomicBool,
utf16parts: Mutex<Vec<u16>>,
}
impl Default for MinimalIme {
fn default() -> Self {
MinimalIme {
getting_ime_text: AtomicBool::new(false),
utf16parts: Mutex::new(Vec::with_capacity(16)),
}
}
}
impl MinimalIme {
pub(crate) fn process_message(
&self,
hwnd: HWND,
msg_kind: u32,
wparam: WPARAM,
_lparam: LPARAM,
result: &mut ProcResult,
) -> Option<String> {
match msg_kind {
winuser::WM_IME_ENDCOMPOSITION => {
self.getting_ime_text.store(true, Relaxed);
}
winuser::WM_CHAR | winuser::WM_SYSCHAR => {
if self.getting_ime_text.load(Relaxed) {
*result = ProcResult::Value(0);
self.utf16parts.lock().unwrap().push(wparam as u16);
// It's important that we push the new character and release the lock
// before getting the next message
let next_msg = next_kbd_msg(hwnd);
let more_char_coming = next_msg
.map(|m| matches!(m.message, winuser::WM_CHAR | winuser::WM_SYSCHAR))
.unwrap_or(false);
if !more_char_coming {
let mut utf16parts = self.utf16parts.lock().unwrap();
let result = String::from_utf16(&utf16parts).ok();
utf16parts.clear();
self.getting_ime_text.store(false, Relaxed);
return result;
}
}
}
_ => (),
}
None
}
}

View file

@ -1,5 +1,6 @@
#![cfg(windows_platform)]
use smol_str::SmolStr;
use windows_sys::Win32::{
Foundation::{HANDLE, HWND},
UI::WindowsAndMessaging::{HMENU, WINDOW_LONG_PTR_INDEX},
@ -19,6 +20,7 @@ pub(self) use crate::platform_impl::Fullscreen;
use crate::event::DeviceId as RootDeviceId;
use crate::icon::Icon;
use crate::keyboard::Key;
#[derive(Clone)]
pub struct PlatformSpecificWindowBuilderAttributes {
@ -82,6 +84,12 @@ fn wrap_device_id(id: u32) -> RootDeviceId {
pub type OsError = std::io::Error;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {
pub text_with_all_modifers: Option<SmolStr>,
pub key_without_modifiers: Key,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(HWND);
unsafe impl Send for WindowId {}
@ -127,7 +135,12 @@ const fn get_y_lparam(x: u32) -> i16 {
}
#[inline(always)]
const fn loword(x: u32) -> u16 {
pub(crate) const fn primarylangid(lgid: u16) -> u16 {
lgid & 0x3FF
}
#[inline(always)]
pub(crate) const fn loword(x: u32) -> u16 {
(x & 0xFFFF) as u16
}
@ -162,10 +175,11 @@ mod dark_mode;
mod definitions;
mod dpi;
mod drop_handler;
mod event;
mod event_loop;
mod icon;
mod ime;
mod keyboard;
mod keyboard_layout;
mod monitor;
mod raw_input;
mod window;

View file

@ -6,7 +6,9 @@ use raw_window_handle::{
use std::{
cell::Cell,
ffi::c_void,
io, mem, panic, ptr,
io,
mem::{self, MaybeUninit},
panic, ptr,
sync::{mpsc::channel, Arc, Mutex, MutexGuard},
};
@ -32,9 +34,9 @@ use windows_sys::Win32::{
UI::{
Input::{
KeyboardAndMouse::{
EnableWindow, GetActiveWindow, MapVirtualKeyW, ReleaseCapture, SendInput, INPUT,
INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP,
MAPVK_VK_TO_VSC, VK_LMENU, VK_MENU,
EnableWindow, GetActiveWindow, MapVirtualKeyW, ReleaseCapture, SendInput,
ToUnicode, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_EXTENDEDKEY,
KEYEVENTF_KEYUP, MAPVK_VK_TO_VSC, VIRTUAL_KEY, VK_LMENU, VK_MENU, VK_SPACE,
},
Touch::{RegisterTouchWindow, TWF_WANTPALM},
},
@ -65,6 +67,7 @@ use crate::{
event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID},
icon::{self, IconType},
ime::ImeContext,
keyboard::KeyEventBuilder,
monitor::{self, MonitorHandle},
util,
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
@ -833,6 +836,26 @@ impl Window {
)
};
}
#[inline]
pub fn reset_dead_keys(&self) {
// `ToUnicode` consumes the dead-key by default, so we are constructing a fake (but valid)
// key input which we can call `ToUnicode` with.
unsafe {
let vk = VK_SPACE as VIRTUAL_KEY;
let scancode = MapVirtualKeyW(vk as u32, MAPVK_VK_TO_VSC);
let kbd_state = [0; 256];
let mut char_buff = [MaybeUninit::uninit(); 8];
ToUnicode(
vk as u32,
scancode,
kbd_state.as_ptr(),
char_buff[0].as_mut_ptr(),
char_buff.len() as i32,
0,
);
}
}
}
impl Drop for Window {
@ -950,6 +973,7 @@ impl<'a, T: 'static> InitData<'a, T> {
event_loop::WindowData {
window_state: win.window_state.clone(),
event_loop_runner: self.event_loop.runner_shared.clone(),
key_event_builder: KeyEventBuilder::default(),
_file_drop_handler: file_drop_handler,
userdata_removed: Cell::new(false),
recurse_depth: Cell::new(0),

View file

@ -1,7 +1,7 @@
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Size},
event::ModifiersState,
icon::Icon,
keyboard::ModifiersState,
platform_impl::platform::{event_loop, util, Fullscreen},
window::{CursorIcon, Theme, WindowAttributes},
};
@ -42,7 +42,7 @@ pub(crate) struct WindowState {
pub fullscreen: Option<Fullscreen>,
pub current_theme: Theme,
pub preferred_theme: Option<Theme>,
pub high_surrogate: Option<u16>,
pub window_flags: WindowFlags,
pub ime_state: ImeState,
@ -157,7 +157,6 @@ impl WindowState {
fullscreen: None,
current_theme,
preferred_theme,
high_surrogate: None,
window_flags: WindowFlags::empty(),
ime_state: ImeState::Disabled,