diff --git a/src/event/source/windows.rs b/src/event/source/windows.rs index 935988395..f53a39250 100644 --- a/src/event/source/windows.rs +++ b/src/event/source/windows.rs @@ -16,6 +16,7 @@ use super::super::{ pub(crate) struct WindowsEventSource { console: Console, poll: WinApiPoll, + surrogate_buffer: Option, } impl WindowsEventSource { @@ -28,6 +29,8 @@ impl WindowsEventSource { poll: WinApiPoll::new(), #[cfg(feature = "event-stream")] poll: WinApiPoll::new()?, + + surrogate_buffer: None, }) } } @@ -41,7 +44,9 @@ impl EventSource for WindowsEventSource { let number = self.console.number_of_console_input_events()?; if event_ready && number != 0 { 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::WindowBufferSizeEvent(record) => { Some(Event::Resize(record.size.x as u16, record.size.y as u16)) diff --git a/src/event/sys/windows/parse.rs b/src/event/sys/windows/parse.rs index 93000b86d..b5c8cf7bd 100644 --- a/src/event/sys/windows/parse.rs +++ b/src/event/sys/windows/parse.rs @@ -1,11 +1,14 @@ use crossterm_winapi::{ControlKeyState, EventFlags, KeyEventRecord, MouseEvent, ScreenBuffer}; use winapi::um::{ 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::{ - VK_BACK, 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_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT, VK_UP, + GetForegroundWindow, GetKeyboardLayout, GetWindowThreadProcessId, ToUnicodeEx, VK_BACK, + 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 { None } -pub(crate) fn handle_key_event(key_event: KeyEventRecord) -> Option { - if key_event.key_down { - if let Some(event) = parse_key_event_record(&key_event) { - return Some(Event::Key(event)); +enum WindowsKeyEvent { + KeyEvent(KeyEvent), + Surrogate(u16), +} + +pub(crate) fn handle_key_event( + key_event: KeyEventRecord, + surrogate_buffer: &mut Option, +) -> Option { + 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)) } } +} - None +fn handle_surrogate(surrogate_buffer: &mut Option, new_surrogate: u16) -> Option { + 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 + } + } } -impl From for KeyModifiers { - fn from(state: ControlKeyState) -> Self { +impl From<&ControlKeyState> for KeyModifiers { + fn from(state: &ControlKeyState) -> Self { let shift = state.has_state(SHIFT_PRESSED); let alt = state.has_state(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED); let control = state.has_state(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED); @@ -54,12 +89,147 @@ impl From for KeyModifiers { } } -fn parse_key_event_record(key_event: &KeyEventRecord) -> Option { - let modifiers = KeyModifiers::from(key_event.control_key_state); +enum CharCase { + LowerCase, + UpperCase, +} + +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, + } +} + +// 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 { + 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 { + 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)); + } + } + } - let key_code = key_event.virtual_key_code as i32; + // 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; + } - let parse_result = match key_code { + if !key_event.key_down { + return None; + } + + let parse_result = match virtual_key_code { VK_SHIFT | VK_CONTROL | VK_MENU => None, VK_BACK => Some(KeyCode::Backspace), VK_ESCAPE => Some(KeyCode::Esc), @@ -75,55 +245,36 @@ fn parse_key_event_record(key_event: &KeyEventRecord) -> Option { VK_END => Some(KeyCode::End), VK_DELETE => Some(KeyCode::Delete), 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 character_raw = key_event.u_char; - - if character_raw < 255 { - // Invalid character - if character_raw == 0 { - return None; + let utf16 = key_event.u_char; + match utf16 { + 0x00..=0x1f => { + // Some key combinations generate either no u_char value or generate control + // codes. To deliver back a KeyCode::Char(...) event we want to know which + // character the key normally maps to on the user's keyboard layout. + // 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) } - - let mut character = character_raw as u8 as char; - - 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 { - return None; - } + surrogate @ 0xD800..=0xDFFF => { + return Some(WindowsKeyEvent::Surrogate(surrogate)); } - - if modifiers.contains(KeyModifiers::SHIFT) && character == '\t' { - Some(KeyCode::BackTab) - } else if character == '\t' { - Some(KeyCode::Tab) - } else { - Some(KeyCode::Char(character)) + 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(); + Some(KeyCode::Char(ch)) } - } else { - std::char::from_u32(character_raw as u32).map(KeyCode::Char) } } }; 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 @@ -137,7 +288,7 @@ pub fn parse_relative_y(y: i16) -> Result { } fn parse_mouse_event_record(event: &MouseEvent) -> Result> { - 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 ypos = parse_relative_y(event.mouse_position.y)? as u16;