Windows input improvements (#651)
This commit is contained in:
parent
5b19aa6d6a
commit
0b4a06a97f
@ -16,6 +16,7 @@ use super::super::{
|
|||||||
pub(crate) struct WindowsEventSource {
|
pub(crate) struct WindowsEventSource {
|
||||||
console: Console,
|
console: Console,
|
||||||
poll: WinApiPoll,
|
poll: WinApiPoll,
|
||||||
|
surrogate_buffer: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowsEventSource {
|
impl WindowsEventSource {
|
||||||
@ -28,6 +29,8 @@ impl WindowsEventSource {
|
|||||||
poll: WinApiPoll::new(),
|
poll: WinApiPoll::new(),
|
||||||
#[cfg(feature = "event-stream")]
|
#[cfg(feature = "event-stream")]
|
||||||
poll: WinApiPoll::new()?,
|
poll: WinApiPoll::new()?,
|
||||||
|
|
||||||
|
surrogate_buffer: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,7 +44,9 @@ impl EventSource for WindowsEventSource {
|
|||||||
let number = self.console.number_of_console_input_events()?;
|
let number = self.console.number_of_console_input_events()?;
|
||||||
if event_ready && number != 0 {
|
if event_ready && number != 0 {
|
||||||
let event = match self.console.read_single_input_event()? {
|
let event = match self.console.read_single_input_event()? {
|
||||||
InputRecord::KeyEvent(record) => handle_key_event(record),
|
InputRecord::KeyEvent(record) => {
|
||||||
|
handle_key_event(record, &mut self.surrogate_buffer)
|
||||||
|
}
|
||||||
InputRecord::MouseEvent(record) => handle_mouse_event(record),
|
InputRecord::MouseEvent(record) => handle_mouse_event(record),
|
||||||
InputRecord::WindowBufferSizeEvent(record) => {
|
InputRecord::WindowBufferSizeEvent(record) => {
|
||||||
Some(Event::Resize(record.size.x as u16, record.size.y as u16))
|
Some(Event::Resize(record.size.x as u16, record.size.y as u16))
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
use crossterm_winapi::{ControlKeyState, EventFlags, KeyEventRecord, MouseEvent, ScreenBuffer};
|
use crossterm_winapi::{ControlKeyState, EventFlags, KeyEventRecord, MouseEvent, ScreenBuffer};
|
||||||
use winapi::um::{
|
use winapi::um::{
|
||||||
wincon::{
|
wincon::{
|
||||||
LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED, SHIFT_PRESSED,
|
CAPSLOCK_ON, LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED,
|
||||||
|
SHIFT_PRESSED,
|
||||||
},
|
},
|
||||||
winuser::{
|
winuser::{
|
||||||
VK_BACK, VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_F24, VK_HOME,
|
GetForegroundWindow, GetKeyboardLayout, GetWindowThreadProcessId, ToUnicodeEx, VK_BACK,
|
||||||
VK_INSERT, VK_LEFT, VK_MENU, VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT, VK_UP,
|
VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_F24, VK_HOME, VK_INSERT,
|
||||||
|
VK_LEFT, VK_MENU, VK_NEXT, VK_NUMPAD0, VK_NUMPAD9, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT,
|
||||||
|
VK_TAB, VK_UP,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -22,18 +25,50 @@ pub(crate) fn handle_mouse_event(mouse_event: MouseEvent) -> Option<Event> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_key_event(key_event: KeyEventRecord) -> Option<Event> {
|
enum WindowsKeyEvent {
|
||||||
if key_event.key_down {
|
KeyEvent(KeyEvent),
|
||||||
if let Some(event) = parse_key_event_record(&key_event) {
|
Surrogate(u16),
|
||||||
return Some(Event::Key(event));
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_key_event(
|
||||||
|
key_event: KeyEventRecord,
|
||||||
|
surrogate_buffer: &mut Option<u16>,
|
||||||
|
) -> Option<Event> {
|
||||||
|
let windows_key_event = parse_key_event_record(&key_event)?;
|
||||||
|
match windows_key_event {
|
||||||
|
WindowsKeyEvent::KeyEvent(key_event) => {
|
||||||
|
// Discard any buffered surrogate value if another valid key event comes before the
|
||||||
|
// next surrogate value.
|
||||||
|
*surrogate_buffer = None;
|
||||||
|
Some(Event::Key(key_event))
|
||||||
|
}
|
||||||
|
WindowsKeyEvent::Surrogate(new_surrogate) => {
|
||||||
|
let ch = handle_surrogate(surrogate_buffer, new_surrogate)?;
|
||||||
|
let modifiers = KeyModifiers::from(&key_event.control_key_state);
|
||||||
|
let key_event = KeyEvent::new(KeyCode::Char(ch), modifiers);
|
||||||
|
Some(Event::Key(key_event))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_surrogate(surrogate_buffer: &mut Option<u16>, new_surrogate: u16) -> Option<char> {
|
||||||
|
match *surrogate_buffer {
|
||||||
|
Some(buffered_surrogate) => {
|
||||||
|
*surrogate_buffer = None;
|
||||||
|
std::char::decode_utf16([buffered_surrogate, new_surrogate])
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
*surrogate_buffer = Some(new_surrogate);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<ControlKeyState> for KeyModifiers {
|
impl From<&ControlKeyState> for KeyModifiers {
|
||||||
fn from(state: ControlKeyState) -> Self {
|
fn from(state: &ControlKeyState) -> Self {
|
||||||
let shift = state.has_state(SHIFT_PRESSED);
|
let shift = state.has_state(SHIFT_PRESSED);
|
||||||
let alt = state.has_state(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED);
|
let alt = state.has_state(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED);
|
||||||
let control = state.has_state(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED);
|
let control = state.has_state(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED);
|
||||||
@ -54,12 +89,147 @@ impl From<ControlKeyState> for KeyModifiers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_key_event_record(key_event: &KeyEventRecord) -> Option<KeyEvent> {
|
enum CharCase {
|
||||||
let modifiers = KeyModifiers::from(key_event.control_key_state);
|
LowerCase,
|
||||||
|
UpperCase,
|
||||||
|
}
|
||||||
|
|
||||||
let key_code = key_event.virtual_key_code as i32;
|
fn try_ensure_char_case(ch: char, desired_case: CharCase) -> char {
|
||||||
|
match desired_case {
|
||||||
|
CharCase::LowerCase if ch.is_uppercase() => {
|
||||||
|
let mut iter = ch.to_lowercase();
|
||||||
|
// Unwrap is safe; iterator yields one or more chars.
|
||||||
|
let ch_lower = iter.next().unwrap();
|
||||||
|
if iter.next() == None {
|
||||||
|
ch_lower
|
||||||
|
} else {
|
||||||
|
ch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CharCase::UpperCase if ch.is_lowercase() => {
|
||||||
|
let mut iter = ch.to_uppercase();
|
||||||
|
// Unwrap is safe; iterator yields one or more chars.
|
||||||
|
let ch_upper = iter.next().unwrap();
|
||||||
|
if iter.next() == None {
|
||||||
|
ch_upper
|
||||||
|
} else {
|
||||||
|
ch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let parse_result = match key_code {
|
// Attempts to return the character for a key event accounting for the user's keyboard layout.
|
||||||
|
// The returned character (if any) is capitalized (if applicable) based on shift and capslock state.
|
||||||
|
// Returns None if the key doesn't map to a character or if it is a dead key.
|
||||||
|
// We use the *currently* active keyboard layout (if it can be determined). This layout may not
|
||||||
|
// correspond to the keyboard layout that was active when the user typed their input, since console
|
||||||
|
// applications get their input asynchronously from the terminal. By the time a console application
|
||||||
|
// can process a key input, the user may have changed the active layout. In this case, the character
|
||||||
|
// returned might not correspond to what the user expects, but there is no way for a console
|
||||||
|
// application to know what the keyboard layout actually was for a key event, so this is our best
|
||||||
|
// effort. If a console application processes input in a timely fashion, then it is unlikely that a
|
||||||
|
// user has time to change their keyboard layout before a key event is processed.
|
||||||
|
fn get_char_for_key(key_event: &KeyEventRecord) -> Option<char> {
|
||||||
|
let virtual_key_code = key_event.virtual_key_code as u32;
|
||||||
|
let virtual_scan_code = key_event.virtual_scan_code as u32;
|
||||||
|
let key_state = [0u8; 256];
|
||||||
|
let mut utf16_buf = [0u16, 16];
|
||||||
|
let dont_change_kernel_keyboard_state = 0x4;
|
||||||
|
|
||||||
|
// Best-effort attempt at determining the currently active keyboard layout.
|
||||||
|
// At the time of writing, this works for a console application running in Windows Terminal, but
|
||||||
|
// doesn't work under a Conhost terminal. For Conhost, the window handle returned by
|
||||||
|
// GetForegroundWindow() does not appear to actually be the foreground window which has the
|
||||||
|
// keyboard layout associated with it (or perhaps it is, but also has special protection that
|
||||||
|
// doesn't allow us to query it).
|
||||||
|
// When this determination fails, the returned keyboard layout handle will be null, which is an
|
||||||
|
// acceptable input for ToUnicodeEx, as that argument is optional. In this case ToUnicodeEx
|
||||||
|
// appears to use the keyboard layout associated with the current thread, which will be the
|
||||||
|
// layout that was inherited when the console application started (or possibly when the current
|
||||||
|
// thread was spawned). This is then unfortunately not updated when the user changes their
|
||||||
|
// keyboard layout in the terminal, but it's what we get.
|
||||||
|
let active_keyboard_layout = unsafe {
|
||||||
|
let foreground_window = GetForegroundWindow();
|
||||||
|
let foreground_thread = GetWindowThreadProcessId(foreground_window, std::ptr::null_mut());
|
||||||
|
GetKeyboardLayout(foreground_thread)
|
||||||
|
};
|
||||||
|
|
||||||
|
let ret = unsafe {
|
||||||
|
ToUnicodeEx(
|
||||||
|
virtual_key_code,
|
||||||
|
virtual_scan_code,
|
||||||
|
key_state.as_ptr(),
|
||||||
|
utf16_buf.as_mut_ptr(),
|
||||||
|
utf16_buf.len() as i32,
|
||||||
|
dont_change_kernel_keyboard_state,
|
||||||
|
active_keyboard_layout,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// -1 indicates a dead key.
|
||||||
|
// 0 indicates no character for this key.
|
||||||
|
if ret < 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ch_iter = std::char::decode_utf16(utf16_buf.into_iter().take(ret as usize));
|
||||||
|
let mut ch = ch_iter.next()?.ok()?;
|
||||||
|
if ch_iter.next() != None {
|
||||||
|
// Key doesn't map to a single char.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_shift_pressed = key_event.control_key_state.has_state(SHIFT_PRESSED);
|
||||||
|
let is_capslock_on = key_event.control_key_state.has_state(CAPSLOCK_ON);
|
||||||
|
let desired_case = if is_shift_pressed ^ is_capslock_on {
|
||||||
|
CharCase::UpperCase
|
||||||
|
} else {
|
||||||
|
CharCase::LowerCase
|
||||||
|
};
|
||||||
|
ch = try_ensure_char_case(ch, desired_case);
|
||||||
|
Some(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_key_event_record(key_event: &KeyEventRecord) -> Option<WindowsKeyEvent> {
|
||||||
|
let modifiers = KeyModifiers::from(&key_event.control_key_state);
|
||||||
|
let virtual_key_code = key_event.virtual_key_code as i32;
|
||||||
|
|
||||||
|
// We normally ignore all key release events, but we will make an exception for an Alt key
|
||||||
|
// release if it carries a u_char value, as this indicates an Alt code.
|
||||||
|
let is_alt_code = virtual_key_code == VK_MENU && !key_event.key_down && key_event.u_char != 0;
|
||||||
|
if is_alt_code {
|
||||||
|
let utf16 = key_event.u_char;
|
||||||
|
match utf16 {
|
||||||
|
surrogate @ 0xD800..=0xDFFF => {
|
||||||
|
return Some(WindowsKeyEvent::Surrogate(surrogate));
|
||||||
|
}
|
||||||
|
unicode_scalar_value => {
|
||||||
|
// Unwrap is safe: We tested for surrogate values above and those are the only
|
||||||
|
// u16 values that are invalid when directly interpreted as unicode scalar
|
||||||
|
// values.
|
||||||
|
let ch = std::char::from_u32(unicode_scalar_value as u32).unwrap();
|
||||||
|
let key_code = KeyCode::Char(ch);
|
||||||
|
let key_event = KeyEvent::new(key_code, modifiers);
|
||||||
|
return Some(WindowsKeyEvent::KeyEvent(key_event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't generate events for numpad key presses when they're producing Alt codes.
|
||||||
|
let is_numpad_numeric_key = (VK_NUMPAD0..=VK_NUMPAD9).contains(&virtual_key_code);
|
||||||
|
let is_only_alt_modifier = modifiers.contains(KeyModifiers::ALT)
|
||||||
|
&& !modifiers.contains(KeyModifiers::SHIFT | KeyModifiers::CONTROL);
|
||||||
|
if is_only_alt_modifier && is_numpad_numeric_key {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !key_event.key_down {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parse_result = match virtual_key_code {
|
||||||
VK_SHIFT | VK_CONTROL | VK_MENU => None,
|
VK_SHIFT | VK_CONTROL | VK_MENU => None,
|
||||||
VK_BACK => Some(KeyCode::Backspace),
|
VK_BACK => Some(KeyCode::Backspace),
|
||||||
VK_ESCAPE => Some(KeyCode::Esc),
|
VK_ESCAPE => Some(KeyCode::Esc),
|
||||||
@ -75,55 +245,36 @@ fn parse_key_event_record(key_event: &KeyEventRecord) -> Option<KeyEvent> {
|
|||||||
VK_END => Some(KeyCode::End),
|
VK_END => Some(KeyCode::End),
|
||||||
VK_DELETE => Some(KeyCode::Delete),
|
VK_DELETE => Some(KeyCode::Delete),
|
||||||
VK_INSERT => Some(KeyCode::Insert),
|
VK_INSERT => Some(KeyCode::Insert),
|
||||||
|
VK_TAB if modifiers.contains(KeyModifiers::SHIFT) => Some(KeyCode::BackTab),
|
||||||
|
VK_TAB => Some(KeyCode::Tab),
|
||||||
_ => {
|
_ => {
|
||||||
// Modifier Keys (Ctrl, Alt, Shift) Support
|
let utf16 = key_event.u_char;
|
||||||
let character_raw = key_event.u_char;
|
match utf16 {
|
||||||
|
0x00..=0x1f => {
|
||||||
if character_raw < 255 {
|
// Some key combinations generate either no u_char value or generate control
|
||||||
// Invalid character
|
// codes. To deliver back a KeyCode::Char(...) event we want to know which
|
||||||
if character_raw == 0 {
|
// character the key normally maps to on the user's keyboard layout.
|
||||||
return None;
|
// The keys that intentionally generate control codes (ESC, ENTER, TAB, etc.)
|
||||||
|
// are handled by their virtual key codes above.
|
||||||
|
get_char_for_key(key_event).map(KeyCode::Char)
|
||||||
}
|
}
|
||||||
|
surrogate @ 0xD800..=0xDFFF => {
|
||||||
let mut character = character_raw as u8 as char;
|
return Some(WindowsKeyEvent::Surrogate(surrogate));
|
||||||
|
|
||||||
if modifiers.contains(KeyModifiers::CONTROL)
|
|
||||||
&& !modifiers.contains(KeyModifiers::ALT)
|
|
||||||
{
|
|
||||||
// we need to do some parsing
|
|
||||||
// Control character will take the ASCII code produced by the key and bitwise AND
|
|
||||||
// it with 31, forcing bits 6 and bits 7 to zero.
|
|
||||||
// So we can make a bitwise OR back to see what's the raw control character.
|
|
||||||
let c = character_raw as u8;
|
|
||||||
if c <= b'\x1F' {
|
|
||||||
character = (c | b'\x40') as char;
|
|
||||||
// if we press something like ctrl-g, we will get `character` with value `G`.
|
|
||||||
// in this case, convert the `character` to lowercase `g`.
|
|
||||||
if character.is_ascii_uppercase()
|
|
||||||
&& !modifiers.contains(KeyModifiers::SHIFT)
|
|
||||||
{
|
|
||||||
character.make_ascii_lowercase();
|
|
||||||
}
|
}
|
||||||
} else {
|
unicode_scalar_value => {
|
||||||
return None;
|
// Unwrap is safe: We tested for surrogate values above and those are the only
|
||||||
|
// u16 values that are invalid when directly interpreted as unicode scalar
|
||||||
|
// values.
|
||||||
|
let ch = std::char::from_u32(unicode_scalar_value as u32).unwrap();
|
||||||
|
Some(KeyCode::Char(ch))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if modifiers.contains(KeyModifiers::SHIFT) && character == '\t' {
|
|
||||||
Some(KeyCode::BackTab)
|
|
||||||
} else if character == '\t' {
|
|
||||||
Some(KeyCode::Tab)
|
|
||||||
} else {
|
|
||||||
Some(KeyCode::Char(character))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
std::char::from_u32(character_raw as u32).map(KeyCode::Char)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(key_code) = parse_result {
|
if let Some(key_code) = parse_result {
|
||||||
return Some(KeyEvent::new(key_code, modifiers));
|
let key_event = KeyEvent::new(key_code, modifiers);
|
||||||
|
return Some(WindowsKeyEvent::KeyEvent(key_event));
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
@ -137,7 +288,7 @@ pub fn parse_relative_y(y: i16) -> Result<i16> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_mouse_event_record(event: &MouseEvent) -> Result<Option<crate::event::MouseEvent>> {
|
fn parse_mouse_event_record(event: &MouseEvent) -> Result<Option<crate::event::MouseEvent>> {
|
||||||
let modifiers = KeyModifiers::from(event.control_key_state);
|
let modifiers = KeyModifiers::from(&event.control_key_state);
|
||||||
|
|
||||||
let xpos = event.mouse_position.x as u16;
|
let xpos = event.mouse_position.x as u16;
|
||||||
let ypos = parse_relative_y(event.mouse_position.y)? as u16;
|
let ypos = parse_relative_y(event.mouse_position.y)? as u16;
|
||||||
|
Loading…
Reference in New Issue
Block a user