use windows::Win32::{
Foundation::HANDLE,
System::Console::{
GetConsoleMode, GetStdHandle, SetConsoleMode, CAPSLOCK_ON, CONSOLE_MODE,
ENABLE_EXTENDED_FLAGS, ENABLE_MOUSE_INPUT, ENABLE_PROCESSED_OUTPUT,
ENABLE_QUICK_EDIT_MODE, ENABLE_VIRTUAL_TERMINAL_PROCESSING,
ENABLE_WINDOW_INPUT, FROM_LEFT_1ST_BUTTON_PRESSED,
FROM_LEFT_2ND_BUTTON_PRESSED, INPUT_RECORD, KEY_EVENT, KEY_EVENT_RECORD,
MOUSE_EVENT, MOUSE_EVENT_RECORD, MOUSE_HWHEELED, MOUSE_MOVED,
MOUSE_WHEELED, RIGHTMOST_BUTTON_PRESSED, SHIFT_PRESSED, STD_INPUT_HANDLE,
STD_OUTPUT_HANDLE, WINDOW_BUFFER_SIZE_EVENT, WINDOW_BUFFER_SIZE_RECORD,
},
UI::{
Input::KeyboardAndMouse::{GetKeyboardLayout, ToUnicodeEx, VK_MENU},
WindowsAndMessaging::{GetForegroundWindow, GetWindowThreadProcessId},
},
};
use crate::{
error::ResultLogger,
key::{Key, KeyCode, KeyEventKind, KeyMods},
mouse::{MouseButton, MouseEvent, MouseEventKind},
term::{input_parser::InputParser, internal::InternalTermEvent},
};
pub struct WinVt {
h_in: HANDLE,
h_out: HANDLE,
orig_stdin_mode: CONSOLE_MODE,
orig_stdout_mode: CONSOLE_MODE,
}
impl WinVt {
pub fn enable() -> anyhow::Result<Self> {
unsafe {
let h_in = GetStdHandle(STD_INPUT_HANDLE)?;
let h_out = GetStdHandle(STD_OUTPUT_HANDLE)?;
let mut orig_stdin_mode = std::mem::zeroed();
GetConsoleMode(h_in, &mut orig_stdin_mode)?;
SetConsoleMode(
h_in,
(ENABLE_MOUSE_INPUT | ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT)
& !ENABLE_QUICK_EDIT_MODE,
)?;
let mut orig_stdout_mode = std::mem::zeroed();
GetConsoleMode(h_out, &mut orig_stdout_mode)?;
SetConsoleMode(
h_out,
orig_stdout_mode
| ENABLE_PROCESSED_OUTPUT
| ENABLE_VIRTUAL_TERMINAL_PROCESSING,
)?;
Ok(Self {
h_in,
h_out,
orig_stdin_mode,
orig_stdout_mode,
})
}
}
pub fn disable(&mut self) {
unsafe {
SetConsoleMode(self.h_in as _, self.orig_stdin_mode).log_ignore();
SetConsoleMode(self.h_out as _, self.orig_stdout_mode).log_ignore();
}
}
}
fn decode_key_record<F: FnMut(InternalTermEvent)>(
record: &KEY_EVENT_RECORD,
f: &mut F,
) {
use windows::Win32::UI::Input::KeyboardAndMouse::*;
let uchar = unsafe { record.uChar.UnicodeChar };
let modifiers = modifiers_from_ctrl_key_state(record.dwControlKeyState);
let virtual_key_code = record.wVirtualKeyCode;
if is_alt_code(record) {
match uchar {
surrogate @ 0xD800..=0xDFFF => {
log::error!("Unhandled surrogate key record.");
return;
}
unicode_scalar_value => {
let ch = std::char::from_u32(unicode_scalar_value as u32).unwrap();
let key_code = KeyCode::Char(ch);
let kind = if record.bKeyDown.as_bool() {
KeyEventKind::Press
} else {
KeyEventKind::Release
};
let key_event = Key::new_with_kind(key_code, modifiers, kind);
f(InternalTermEvent::Key(key_event));
return;
}
}
}
let is_numpad_numeric_key =
(VK_NUMPAD0.0..=VK_NUMPAD9.0).contains(&virtual_key_code);
let is_only_alt_modifier = modifiers.contains(KeyMods::ALT)
&& !modifiers.contains(KeyMods::SHIFT | KeyMods::CONTROL);
if is_only_alt_modifier && is_numpad_numeric_key {
return;
}
let parse_result = match VIRTUAL_KEY(virtual_key_code) {
VK_SHIFT | VK_CONTROL | VK_MENU => None,
VK_BACK => Some(KeyCode::Backspace),
VK_ESCAPE => Some(KeyCode::Esc),
VK_RETURN => Some(KeyCode::Enter),
vk if (VK_F1.0..=VK_F24.0).contains(&vk.0) => {
Some(KeyCode::F((record.wVirtualKeyCode - 111) as u8))
}
VK_LEFT => Some(KeyCode::Left),
VK_UP => Some(KeyCode::Up),
VK_RIGHT => Some(KeyCode::Right),
VK_DOWN => Some(KeyCode::Down),
VK_PRIOR => Some(KeyCode::PageUp),
VK_NEXT => Some(KeyCode::PageDown),
VK_HOME => Some(KeyCode::Home),
VK_END => Some(KeyCode::End),
VK_DELETE => Some(KeyCode::Delete),
VK_INSERT => Some(KeyCode::Insert),
VK_TAB => Some(KeyCode::Tab),
_ => {
match uchar {
0x00..=0x1f => {
get_char_for_key(record).map(KeyCode::Char)
}
surrogate @ 0xD800..=0xDFFF => {
log::error!("Unhandled surrogate key record.");
return;
}
unicode_scalar_value => {
let ch = std::char::from_u32(unicode_scalar_value as u32).unwrap();
Some(KeyCode::Char(ch))
}
}
}
};
if let Some(key_code) = parse_result {
let kind = if record.bKeyDown.as_bool() {
KeyEventKind::Press
} else {
KeyEventKind::Release
};
let key_event = Key::new_with_kind(key_code, modifiers, kind);
f(InternalTermEvent::Key(key_event));
}
}
fn is_alt_code(record: &KEY_EVENT_RECORD) -> bool {
record.wVirtualKeyCode == VK_MENU.0
&& !record.bKeyDown.as_bool()
&& unsafe { record.uChar.UnicodeChar } != 0
}
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();
let ch_lower = iter.next().unwrap();
if iter.next().is_none() {
ch_lower
} else {
ch
}
}
CharCase::UpperCase if ch.is_lowercase() => {
let mut iter = ch.to_uppercase();
let ch_upper = iter.next().unwrap();
if iter.next().is_none() {
ch_upper
} else {
ch
}
}
_ => ch,
}
}
fn get_char_for_key(record: &KEY_EVENT_RECORD) -> Option<char> {
let virtual_key_code = record.wVirtualKeyCode as u32;
let virtual_scan_code = record.wVirtualScanCode as u32;
let key_state = [0u8; 256];
let mut utf16_buf = [0u16, 16];
let dont_change_kernel_keyboard_state = 0x4;
let active_keyboard_layout = unsafe {
let foreground_window = GetForegroundWindow();
let foreground_thread = GetWindowThreadProcessId(foreground_window, None);
GetKeyboardLayout(foreground_thread)
};
let ret = unsafe {
ToUnicodeEx(
virtual_key_code,
virtual_scan_code,
&key_state,
&mut utf16_buf,
dont_change_kernel_keyboard_state,
Some(active_keyboard_layout),
)
};
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().is_some() {
return None;
}
let is_shift_pressed = record.dwControlKeyState & SHIFT_PRESSED != 0;
let is_capslock_on = record.dwControlKeyState & CAPSLOCK_ON != 0;
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)
}
struct Buttons(u32);
impl Buttons {
pub fn none(&self) -> bool {
self.0 == 0
}
pub fn left(&self) -> bool {
self.0 & FROM_LEFT_1ST_BUTTON_PRESSED != 0
}
pub fn right(&self) -> bool {
self.0 & RIGHTMOST_BUTTON_PRESSED != 0
}
pub fn middle(&self) -> bool {
self.0 & FROM_LEFT_2ND_BUTTON_PRESSED != 0
}
}
fn decode_mouse_record<F: FnMut(InternalTermEvent)>(
input_parser: &mut InputParser,
event: &MOUSE_EVENT_RECORD,
f: &mut F,
) {
let prev = Buttons(input_parser.windows_mouse_buttons);
input_parser.windows_mouse_buttons = event.dwButtonState;
let btns = Buttons(event.dwButtonState);
let kind = if event.dwEventFlags & MOUSE_MOVED != 0 {
if btns.none() {
MouseEventKind::Moved
} else if btns.left() {
MouseEventKind::Drag(MouseButton::Left)
} else if btns.right() {
MouseEventKind::Drag(MouseButton::Right)
} else if btns.middle() {
MouseEventKind::Drag(MouseButton::Middle)
} else {
return;
}
} else if event.dwEventFlags & MOUSE_WHEELED != 0 {
if (btns.0 as i32) < 0 {
MouseEventKind::ScrollDown
} else {
MouseEventKind::ScrollUp
}
} else if event.dwEventFlags & MOUSE_HWHEELED != 0 {
if (btns.0 as i32) < 0 {
MouseEventKind::ScrollLeft
} else {
MouseEventKind::ScrollRight
}
} else {
if btns.left() && !prev.left() {
MouseEventKind::Down(MouseButton::Left)
} else if !btns.left() && prev.left() {
MouseEventKind::Up(MouseButton::Left)
} else if btns.right() && !prev.right() {
MouseEventKind::Down(MouseButton::Right)
} else if !btns.right() && prev.right() {
MouseEventKind::Up(MouseButton::Right)
} else if btns.middle() && !prev.middle() {
MouseEventKind::Down(MouseButton::Middle)
} else if !btns.middle() && prev.middle() {
MouseEventKind::Up(MouseButton::Middle)
} else {
return;
}
};
let mods = modifiers_from_ctrl_key_state(event.dwControlKeyState);
f(InternalTermEvent::Mouse(MouseEvent {
kind,
x: event.dwMousePosition.X as i32,
y: event.dwMousePosition.Y as i32,
mods,
}))
}
fn decode_resize_record<F: FnMut(InternalTermEvent)>(
event: &WINDOW_BUFFER_SIZE_RECORD,
f: &mut F,
) {
f(InternalTermEvent::Resize(
event.dwSize.X as u16,
event.dwSize.Y as u16,
));
}
pub fn decode_input_records<F: FnMut(InternalTermEvent)>(
input_parser: &mut InputParser,
records: &[INPUT_RECORD],
f: &mut F,
) {
for record in records {
match record.EventType as u32 {
KEY_EVENT => {
let record = unsafe { &record.Event.KeyEvent };
let uchar = unsafe { record.uChar.UnicodeChar };
if !record.bKeyDown.as_bool() && !is_alt_code(record) {
} else if (1..=0x7f).contains(&uchar)
&& record.dwControlKeyState == 0
&& record.bKeyDown.as_bool()
{
let ch = uchar as u8 as char;
let mut buf = [0u8; 4];
let bytes = ch.encode_utf8(&mut buf).as_bytes();
for _ in 0..record.wRepeatCount {
input_parser.parse_input(bytes, true, true, &mut *f);
}
} else {
decode_key_record(record, f);
}
}
MOUSE_EVENT => decode_mouse_record(
input_parser,
unsafe { &record.Event.MouseEvent },
f,
),
WINDOW_BUFFER_SIZE_EVENT => {
decode_resize_record(unsafe { &record.Event.WindowBufferSizeEvent }, f)
}
_ => {}
}
}
input_parser.parse_input(b"", true, false, f);
}
fn modifiers_from_ctrl_key_state(state: u32) -> KeyMods {
use windows::Win32::System::Console::*;
let mut mods = KeyMods::NONE;
if (state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) != 0 {
mods |= KeyMods::ALT;
}
if (state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) != 0 {
mods |= KeyMods::CONTROL;
}
if (state & SHIFT_PRESSED) != 0 {
mods |= KeyMods::SHIFT;
}
mods
}