fix: preserve native Windows key events for ConPTY input

This commit is contained in:
kuddev committed 2026-08-08 14:03:05 +08:00
1 parent ffb2eebaf6
commit 8fa86ead34
7 files changed
+425 -103

No files matched your search

+3 -101
View File
@@ -4,8 +4,6 @@ use winit::event::{ElementState, KeyEvent};
#[cfg(target_os = "macos")]
use winit::keyboard::ModifiersKeyState;
use winit::keyboard::{Key, KeyLocation, ModifiersState, NamedKey};
#[cfg(target_os = "windows")]
use winit::platform::scancode::PhysicalKeyExtScancode;
#[cfg(target_os = "macos")]
use winit::platform::macos::OptionAsAlt;
@@ -16,14 +14,9 @@ use winit::platform::modifier_supplement::KeyEventExtModifierSupplement;
use crate::config::{Action, BindingKey, BindingMode, KeyBinding};
use crate::display::window::ImeInhibitor;
use crate::event::TYPING_SEARCH_DELAY;
use crate::input::{ActionContext, Execute, Processor};
use crate::input::{ActionContext, Execute, Processor, terminal_input};
use crate::scheduler::{TimerId, Topic};
#[cfg(target_os = "windows")]
use windows_sys::Win32::UI::Input::KeyboardAndMouse::{
MapVirtualKeyW, MAPVK_VSC_TO_VK_EX,
};
impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
/// Process key input.
pub fn key_input(&mut self, key: KeyEvent) {
@@ -995,7 +988,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
mods: ModifiersState,
) -> bool {
if mode.contains(TermMode::WIN32_INPUT_MODE)
&& build_win32_input_sequence(key, mods).is_some()
&& terminal_input::build_win32_input_sequence(key).is_some()
{
return true;
}
@@ -1159,7 +1152,7 @@ impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
#[inline(never)]
fn build_sequence(key: KeyEvent, mods: ModifiersState, mode: TermMode) -> Vec<u8> {
if mode.contains(TermMode::WIN32_INPUT_MODE) {
if let Some(sequence) = build_win32_input_sequence(&key, mods) {
if let Some(sequence) = terminal_input::build_win32_input_sequence(&key) {
return sequence;
}
}
@@ -1231,74 +1224,6 @@ fn build_sequence(key: KeyEvent, mods: ModifiersState, mode: TermMode) -> Vec<u8
payload.into_bytes()
}
/// Build a ConPTY Win32 input record for functional keys and modified chords.
/// Printable text deliberately returns `None`, so IME and layout-generated
/// characters continue through the normal UTF-8 path used by every shell.
#[cfg(target_os = "windows")]
fn build_win32_input_sequence(key: &KeyEvent, mods: ModifiersState) -> Option<Vec<u8>> {
if win32_input_uses_text_path(&key.logical_key) {
return None;
}
let (virtual_key, scan_code, enhanced) = win32_key_codes(key.physical_key)?;
const SHIFT_PRESSED: u32 = 0x10;
const RIGHT_ALT_PRESSED: u32 = 0x01;
const LEFT_ALT_PRESSED: u32 = 0x02;
const RIGHT_CTRL_PRESSED: u32 = 0x04;
const LEFT_CTRL_PRESSED: u32 = 0x08;
const ENHANCED_KEY: u32 = 0x100;
let current_is_shift = matches!(key.logical_key, Key::Named(NamedKey::Shift));
let current_is_alt = matches!(key.logical_key, Key::Named(NamedKey::Alt));
let current_is_ctrl = matches!(key.logical_key, Key::Named(NamedKey::Control));
let pressed = key.state == ElementState::Pressed;
let mut control_key_state = 0;
if (mods.shift_key() || (current_is_shift && pressed)) && !(current_is_shift && !pressed) {
control_key_state |= SHIFT_PRESSED;
}
if (mods.alt_key() || (current_is_alt && pressed)) && !(current_is_alt && !pressed) {
control_key_state |=
if current_is_alt && enhanced { RIGHT_ALT_PRESSED } else { LEFT_ALT_PRESSED };
}
if (mods.control_key() || (current_is_ctrl && pressed)) && !(current_is_ctrl && !pressed) {
control_key_state |=
if current_is_ctrl && enhanced { RIGHT_CTRL_PRESSED } else { LEFT_CTRL_PRESSED };
}
if enhanced {
control_key_state |= ENHANCED_KEY;
}
let down = u8::from(pressed);
Some(
format!(
"\x1b[{virtual_key};{scan_code};0;{down};{control_key_state};1_"
)
.into_bytes(),
)
}
#[cfg(target_os = "windows")]
fn win32_input_uses_text_path(key: &Key) -> bool {
matches!(key, Key::Character(_) | Key::Named(NamedKey::Space))
}
#[cfg(target_os = "windows")]
fn win32_key_codes(key: winit::keyboard::PhysicalKey) -> Option<(u32, u32, bool)> {
let extended_scan_code = key.to_scancode()?;
let virtual_key = unsafe { MapVirtualKeyW(extended_scan_code, MAPVK_VSC_TO_VK_EX) };
(virtual_key != 0).then_some((
virtual_key,
extended_scan_code & 0xff,
extended_scan_code > 0xff,
))
}
#[cfg(not(target_os = "windows"))]
fn build_win32_input_sequence(_: &KeyEvent, _: ModifiersState) -> Option<Vec<u8>> {
None
}
/// Helper to build escape sequence payloads from [`KeyEvent`].
pub struct SequenceBuilder {
mode: TermMode,
@@ -1654,26 +1579,3 @@ fn is_control_character(text: &str) -> bool {
let codepoint = text.bytes().next().unwrap();
text.len() == 1 && (codepoint < 0x20 || (0x7f..=0x9f).contains(&codepoint))
}
#[cfg(all(test, target_os = "windows"))]
mod win32_input_tests {
use super::*;
use winit::keyboard::{KeyCode, PhysicalKey};
#[test]
fn printable_symbols_remain_on_the_utf8_text_path() {
for text in [",", "。", "/", "【", "】"] {
assert!(win32_input_uses_text_path(&Key::Character(text.into())));
}
}
#[test]
fn enter_uses_the_native_windows_key_identity() {
let (virtual_key, scan_code, enhanced) =
win32_key_codes(PhysicalKey::Code(KeyCode::Enter)).unwrap();
assert_eq!(virtual_key, 0x0d);
assert_eq!(scan_code, 0x1c);
assert!(!enhanced);
}
}
+1
View File
@@ -33,6 +33,7 @@ mod action;
mod chrome;
pub mod keyboard;
mod mouse;
mod terminal_input;
mod touch;
#[cfg(test)]
+146
View File
@@ -0,0 +1,146 @@
//! Platform input encoders kept outside the general keyboard dispatcher.
//!
//! The dispatcher decides whether an event belongs to the terminal or to the
//! Nebula chrome. This module owns the wire format for platform terminal modes;
//! keeping that contract isolated makes adding a Linux or macOS raw backend a
//! local change instead of another branch in `keyboard.rs`.
#[cfg(target_os = "windows")]
use std::fmt::Write;
#[cfg(target_os = "windows")]
use winit::event::ElementState;
use winit::event::KeyEvent;
#[cfg(target_os = "windows")]
use winit::keyboard::{Key, NamedKey};
#[cfg(target_os = "windows")]
use winit::platform::modifier_supplement::KeyEventExtModifierSupplement;
/// Build one or more ConPTY Win32 input-mode records for a native key event.
///
/// Dead keys intentionally return `None`; their eventual composition is
/// delivered through Winit's OS-resolved text/IME path. Every other key keeps
/// its native Windows identity. Printable keys additionally carry the UTF-16
/// code units emitted by WM_CHAR, matching KEY_EVENT_RECORD semantics without
/// reconstructing VKEYs from text.
#[inline]
pub(crate) fn build_win32_input_sequence(key: &KeyEvent) -> Option<Vec<u8>> {
#[cfg(target_os = "windows")]
{
if uses_composition_path(&key.logical_key) {
return None;
}
let raw = winit::platform::windows::KeyEventExtWindows::raw_key_event(key);
let mut sequence = String::with_capacity(48);
match key.logical_key.as_ref() {
Key::Character(_) | Key::Named(NamedKey::Space) => {
let mut wrote_text = false;
if let Some(text) = key.text_with_all_modifiers().filter(|text| !text.is_empty()) {
for unicode_char in text.encode_utf16() {
append_raw_key_event(&mut sequence, raw, key.state, unicode_char);
wrote_text = true;
}
}
if !wrote_text {
append_raw_key_event(&mut sequence, raw, key.state, raw.unicode_char);
}
},
_ => append_raw_key_event(&mut sequence, raw, key.state, 0),
}
return Some(sequence.into_bytes());
}
#[cfg(not(target_os = "windows"))]
{
let _ = key;
None
}
}
#[cfg(target_os = "windows")]
#[inline]
fn uses_composition_path(key: &Key) -> bool {
matches!(key, Key::Dead(_))
}
#[cfg(target_os = "windows")]
#[inline]
fn append_raw_key_event(
sequence: &mut String,
raw: winit::platform::windows::RawKeyEventInfo,
state: ElementState,
unicode_char: u16,
) {
// ConPTY Win32 input mode 的字段顺序是 CSI Vk;Sc;Uc;Kd;Cs;Rc_。
let key_down = u8::from(state == ElementState::Pressed);
let repeat_count = raw.repeat_count.max(1);
let _ = write!(
sequence,
"\x1b[{};{};{};{};{};{}_",
raw.virtual_key, raw.scan_code, unicode_char, key_down, raw.control_key_state, repeat_count
);
}
#[cfg(all(test, target_os = "windows"))]
mod tests {
use super::*;
use winit::platform::windows::RawKeyEventInfo;
#[test]
fn only_dead_keys_stay_on_the_composition_path() {
assert!(!uses_composition_path(&Key::Character("/".into())));
assert!(!uses_composition_path(&Key::Character("【】".into())));
assert!(uses_composition_path(&Key::Dead(Some('\''))));
assert!(!uses_composition_path(&Key::Named(NamedKey::Space)));
}
#[test]
fn functional_key_uses_the_captured_native_identity() {
let raw = RawKeyEventInfo {
virtual_key: 0x0d,
scan_code: 0x1c,
repeat_count: 1,
is_extended: false,
unicode_char: 0,
control_key_state: 0x10,
};
let mut sequence = String::new();
append_raw_key_event(&mut sequence, raw, ElementState::Pressed, 0);
assert_eq!(sequence.as_bytes(), b"\x1b[13;28;0;1;16;1_");
}
#[test]
fn printable_key_keeps_native_identity_and_unicode_char() {
let raw = RawKeyEventInfo {
virtual_key: 0xbf,
scan_code: 0x35,
repeat_count: 1,
is_extended: false,
unicode_char: b'/' as u16,
control_key_state: 0,
};
let mut sequence = String::new();
append_raw_key_event(&mut sequence, raw, ElementState::Pressed, raw.unicode_char);
assert_eq!(sequence.as_bytes(), b"\x1b[191;53;47;1;0;1_");
}
#[test]
fn repeat_count_zero_is_normalized_to_protocol_default() {
let raw = RawKeyEventInfo {
virtual_key: 0x25,
scan_code: 0x4b,
repeat_count: 0,
is_extended: true,
unicode_char: 0,
control_key_state: 0x100,
};
let mut sequence = String::new();
append_raw_key_event(&mut sequence, raw, ElementState::Released, 0);
assert_eq!(sequence.as_bytes(), b"\x1b[37;75;0;0;256;1_");
}
}
+11
View File
@@ -5,8 +5,19 @@ Windows backend backport from upstream commit `488c036a05d418e13bbdcdc349e2db2f6
- Windows 10 builds below 22000 keep the legacy mixed-DPI reposition workaround.
- Windows 11 builds use the `WM_DPICHANGED` suggested rectangle directly.
- Windows key events retain the raw `WM_KEYDOWN/UP` virtual key, scan code,
repeat count, extended flag, layout-resolved UTF-16 character, and
`KEY_EVENT_RECORD` control-key state. The
public `winit::platform::windows::KeyEventExtWindows` trait exposes that
snapshot to the terminal input adapter without a second Win32 message hook
or a `MapVirtualKeyW` reverse lookup.
The upstream commit is on a newer winit API generation whose `EventLoop` is no
longer generic. Keeping this 0.30.13 overlay preserves Nebula's existing
`EventLoop<Event>` and `rwh_06` integration. Remove the overlay once an
API-compatible crates.io release contains the same fix.
When updating this overlay, preserve the Windows `KeyEventExtra::raw` field and
the `PartialKeyEventInfo`/`KeyLParam` propagation. The terminal's Win32 input
mode depends on those fields for layout-independent functional keys and
modifier chords; printable and IME text remains on Winit's normal text path.
+19 -1
View File
@@ -7,11 +7,13 @@ use std::ffi::c_void;
use std::path::Path;
use crate::dpi::PhysicalSize;
use crate::event::DeviceId;
use crate::event::{DeviceId, KeyEvent};
use crate::event_loop::EventLoopBuilder;
use crate::monitor::MonitorHandle;
use crate::window::{BadIcon, Icon, Window, WindowAttributes};
pub use crate::platform_impl::RawKeyEventInfo;
/// Window Handle type used by Win32 API
pub type HWND = isize;
/// Menu Handle type used by Win32 API
@@ -19,6 +21,22 @@ pub type HMENU = isize;
/// Monitor Handle type used by Win32 API
pub type HMONITOR = isize;
/// Provides the unmodified Win32 fields needed by ConPTY's Win32 input mode.
///
/// Winit already receives these values from WM_KEYDOWN/WM_KEYUP. Exposing the
/// captured snapshot avoids reconstructing a virtual key from a Unicode value,
/// which is incorrect for layout-dependent keys, AltGr, dead keys, and IMEs.
pub trait KeyEventExtWindows {
fn raw_key_event(&self) -> RawKeyEventInfo;
}
impl KeyEventExtWindows for KeyEvent {
#[inline]
fn raw_key_event(&self) -> RawKeyEventInfo {
self.platform_specific.raw
}
}
/// Describes a system-drawn backdrop material of a window.
///
/// For a detailed explanation, see [`DWM_SYSTEMBACKDROP_TYPE docs`].
@@ -33,7 +33,7 @@ use crate::platform_impl::platform::event_loop::ProcResult;
use crate::platform_impl::platform::keyboard_layout::{
Layout, LayoutCache, WindowsModifiers, LAYOUT_CACHE,
};
use crate::platform_impl::platform::{loword, primarylangid, KeyEventExtra};
use crate::platform_impl::platform::{loword, primarylangid, KeyEventExtra, RawKeyEventInfo};
pub type ExScancode = u16;
@@ -444,6 +444,20 @@ impl KeyEventBuilder {
key_without_modifiers,
key_state,
is_repeat: false,
raw: RawKeyEventInfo {
virtual_key: vk as u16,
scan_code: (scancode & 0xff) as u8,
repeat_count: 1,
is_extended: (scancode & 0xff00) == 0xe000,
unicode_char: win32_unicode_char(&logical_key),
control_key_state: synthetic_control_key_state(
vk,
caps_lock_on,
num_lock_on,
(scancode & 0xff00) == 0xe000,
key_state,
),
},
physical_key,
location: get_location(scancode, locale_id),
utf16parts: Vec::with_capacity(8),
@@ -484,6 +498,8 @@ struct PartialKeyEventInfo {
key_without_modifiers: Key,
raw: RawKeyEventInfo,
/// The UTF-16 code units of the text that was produced by the keypress event.
/// This take all modifiers into account. Including CTRL
utf16parts: Vec<u16>,
@@ -536,6 +552,8 @@ impl PartialKeyEventInfo {
let preliminary_logical_key =
layout.get_key(mods_without_ctrl, num_lock_on, vkey, &physical_key);
let unicode_char =
win32_unicode_char(&layout.get_key(mods, num_lock_on, vkey, &physical_key));
let key_is_char = matches!(preliminary_logical_key, Key::Character(_));
let is_pressed = state == ElementState::Pressed;
@@ -577,6 +595,19 @@ impl PartialKeyEventInfo {
logical_key,
key_without_modifiers,
is_repeat: lparam_struct.is_repeat,
raw: RawKeyEventInfo {
virtual_key: vkey as u16,
scan_code: lparam_struct.scancode,
repeat_count: lparam_struct.repeat_count,
is_extended: lparam_struct.extended,
unicode_char,
control_key_state: control_key_state(
&kbd_state,
vkey as u16,
lparam_struct.extended,
state,
),
},
physical_key,
location,
utf16parts: Vec::with_capacity(8),
@@ -623,6 +654,17 @@ impl PartialKeyEventInfo {
PartialLogicalKey::This(v) => v,
};
let mut raw = self.raw;
if matches!(logical_key, Key::Character(_) | Key::Named(NamedKey::Space)) {
if let Some(text) = char_with_all_modifiers.as_deref() {
if let Some(unicode_char) = single_utf16_code_unit(text) {
// WM_CHAR is the authoritative layout/dead-key result. The cached
// layout value above is primarily needed for key-up events.
raw.unicode_char = unicode_char;
}
}
}
KeyEvent {
physical_key: self.physical_key,
logical_key,
@@ -633,6 +675,7 @@ impl PartialKeyEventInfo {
platform_specific: KeyEventExtra {
text_with_all_modifiers: char_with_all_modifiers,
key_without_modifiers: self.key_without_modifiers,
raw,
},
}
}
@@ -643,6 +686,10 @@ struct KeyLParam {
pub scancode: u8,
pub extended: bool,
/// The low word of lParam is the repeat count. Preserve it instead of
/// converting every repeated key into a synthetic single press.
pub repeat_count: u16,
/// This is `previous_state XOR transition_state`. See the lParam for WM_KEYDOWN and WM_KEYUP
/// for further details.
pub is_repeat: bool,
@@ -654,10 +701,148 @@ fn destructure_key_lparam(lparam: LPARAM) -> KeyLParam {
KeyLParam {
scancode: ((lparam >> 16) & 0xff) as u8,
extended: ((lparam >> 24) & 0x01) != 0,
repeat_count: (lparam & 0xffff) as u16,
is_repeat: (previous_state ^ transition_state) != 0,
}
}
#[inline]
fn single_utf16_code_unit(text: &str) -> Option<u16> {
let mut units = text.encode_utf16();
let first = units.next()?;
units.next().is_none().then_some(first)
}
#[inline]
fn win32_unicode_char(key: &Key) -> u16 {
match key {
Key::Character(text) => single_utf16_code_unit(text).unwrap_or(0),
Key::Named(NamedKey::Space) => b' ' as u16,
_ => 0,
}
}
/// 将布局解析已经读取的键盘状态转换为 Win32 KEY_EVENT_RECORD 字段。
/// 状态在 Winit 内只读取一次,避免应用热路径再次调用 GetKeyboardState。
fn control_key_state(
kbd_state: &[u8; 256],
virtual_key: u16,
extended: bool,
key_state: ElementState,
) -> u32 {
const RIGHT_ALT_PRESSED: u32 = 0x01;
const LEFT_ALT_PRESSED: u32 = 0x02;
const RIGHT_CTRL_PRESSED: u32 = 0x04;
const LEFT_CTRL_PRESSED: u32 = 0x08;
const SHIFT_PRESSED: u32 = 0x10;
const NUMLOCK_ON: u32 = 0x20;
const SCROLLLOCK_ON: u32 = 0x40;
const CAPSLOCK_ON: u32 = 0x80;
const ENHANCED_KEY: u32 = 0x100;
let is_down = |vk: VIRTUAL_KEY| kbd_state[vk as usize] & 0x80 != 0;
let mut state = 0;
if is_down(VK_SHIFT) || is_down(VK_LSHIFT) || is_down(VK_RSHIFT) {
state |= SHIFT_PRESSED;
}
if is_down(VK_LMENU) {
state |= LEFT_ALT_PRESSED;
}
if is_down(VK_RMENU) {
state |= RIGHT_ALT_PRESSED;
}
if state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) == 0 && is_down(VK_MENU) {
state |= if virtual_key as VIRTUAL_KEY == VK_MENU && extended {
RIGHT_ALT_PRESSED
} else {
LEFT_ALT_PRESSED
};
}
if is_down(VK_LCONTROL) {
state |= LEFT_CTRL_PRESSED;
}
if is_down(VK_RCONTROL) {
state |= RIGHT_CTRL_PRESSED;
}
if state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) == 0 && is_down(VK_CONTROL) {
state |= if virtual_key as VIRTUAL_KEY == VK_CONTROL && extended {
RIGHT_CTRL_PRESSED
} else {
LEFT_CTRL_PRESSED
};
}
if kbd_state[VK_NUMLOCK as usize] & 1 != 0 {
state |= NUMLOCK_ON;
}
if kbd_state[VK_SCROLL as usize] & 1 != 0 {
state |= SCROLLLOCK_ON;
}
if kbd_state[VK_CAPITAL as usize] & 1 != 0 {
state |= CAPSLOCK_ON;
}
if extended {
state |= ENHANCED_KEY;
}
// 某些布局在 GetKeyboardState 中只报告通用修饰键,但 WM_KEY 消息仍带有
// 左右位置;仅对 keydown 补入当前消息的身份,避免 keyup 把按下位卡住。
if key_state == ElementState::Pressed {
match virtual_key as VIRTUAL_KEY {
VK_LMENU => state |= LEFT_ALT_PRESSED,
VK_RMENU => state |= RIGHT_ALT_PRESSED,
VK_LCONTROL => state |= LEFT_CTRL_PRESSED,
VK_RCONTROL => state |= RIGHT_CTRL_PRESSED,
VK_LSHIFT | VK_RSHIFT => state |= SHIFT_PRESSED,
VK_MENU if extended => state |= RIGHT_ALT_PRESSED,
VK_MENU => state |= LEFT_ALT_PRESSED,
VK_CONTROL if extended => state |= RIGHT_CTRL_PRESSED,
VK_CONTROL => state |= LEFT_CTRL_PRESSED,
VK_SHIFT => state |= SHIFT_PRESSED,
_ => {},
}
}
state
}
fn synthetic_control_key_state(
virtual_key: VIRTUAL_KEY,
caps_lock_on: bool,
num_lock_on: bool,
extended: bool,
key_state: ElementState,
) -> u32 {
const RIGHT_ALT_PRESSED: u32 = 0x01;
const LEFT_ALT_PRESSED: u32 = 0x02;
const RIGHT_CTRL_PRESSED: u32 = 0x04;
const LEFT_CTRL_PRESSED: u32 = 0x08;
const SHIFT_PRESSED: u32 = 0x10;
const NUMLOCK_ON: u32 = 0x20;
const CAPSLOCK_ON: u32 = 0x80;
const ENHANCED_KEY: u32 = 0x100;
let mut state = 0;
if caps_lock_on {
state |= CAPSLOCK_ON;
}
if num_lock_on {
state |= NUMLOCK_ON;
}
if extended {
state |= ENHANCED_KEY;
}
if key_state == ElementState::Pressed {
match virtual_key {
VK_LSHIFT | VK_RSHIFT => state |= SHIFT_PRESSED,
VK_LMENU => state |= LEFT_ALT_PRESSED,
VK_RMENU => state |= RIGHT_ALT_PRESSED,
VK_LCONTROL => state |= LEFT_CTRL_PRESSED,
VK_RCONTROL => state |= RIGHT_CTRL_PRESSED,
_ => {},
}
}
state
}
#[inline]
fn new_ex_scancode(scancode: u8, extended: bool) -> ExScancode {
(scancode as u16) | (if extended { 0xe000 } else { 0 })
@@ -1241,3 +1426,44 @@ pub(crate) fn scancode_to_physicalkey(scancode: u32) -> PhysicalKey {
_ => return PhysicalKey::Unidentified(NativeKeyCode::Windows(scancode as u16)),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn key_lparam_keeps_repeat_and_extended_bits() {
let lparam = (3 | (0x1c << 16) | (1 << 24)) as LPARAM;
let parsed = destructure_key_lparam(lparam);
assert_eq!(parsed.repeat_count, 3);
assert_eq!(parsed.scancode, 0x1c);
assert!(parsed.extended);
assert!(!parsed.is_repeat);
}
#[test]
fn modifier_release_does_not_retain_the_pressed_bit() {
let mut keyboard = [0; 256];
keyboard[VK_LSHIFT as usize] = 0x80;
assert_ne!(
control_key_state(&keyboard, VK_LSHIFT as u16, false, ElementState::Pressed) & 0x10,
0
);
keyboard[VK_LSHIFT as usize] = 0;
assert_eq!(
control_key_state(&keyboard, VK_LSHIFT as u16, false, ElementState::Released) & 0x10,
0
);
}
#[test]
fn unicode_char_requires_exactly_one_utf16_code_unit() {
assert_eq!(single_utf16_code_unit("/"), Some(47));
assert_eq!(single_utf16_code_unit(","), Some(0xff0c));
assert_eq!(single_utf16_code_unit("【】"), None);
assert_eq!(single_utf16_code_unit("😀"), None);
}
}
@@ -91,10 +91,28 @@ fn wrap_device_id(id: u32) -> RootDeviceId {
pub type OsError = std::io::Error;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct RawKeyEventInfo {
/// The virtual-key code copied directly from the WM_KEY* message.
pub virtual_key: u16,
/// The low byte of the scan-code field in the WM_KEY* lParam.
pub scan_code: u8,
/// The repeat count copied from the low word of WM_KEY* lParam.
pub repeat_count: u16,
/// Whether the WM_KEY* lParam carried the extended-key flag.
pub is_extended: bool,
/// The UTF-16 code unit produced by the active Windows keyboard layout.
/// A value of zero means that the key has no single-code-unit text value.
pub unicode_char: u16,
/// The KEY_EVENT_RECORD-compatible control-key state captured by Winit.
pub control_key_state: u32,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEventExtra {
pub text_with_all_modifiers: Option<SmolStr>,
pub key_without_modifiers: Key,
pub raw: RawKeyEventInfo,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]