306 lines
10 KiB
Rust
306 lines
10 KiB
Rust
//! This is a WINDOWS specific implementation for input related action.
|
|
|
|
use std::io;
|
|
use std::io::ErrorKind;
|
|
use std::sync::Mutex;
|
|
use std::time::Duration;
|
|
|
|
use crossterm_winapi::{
|
|
ConsoleMode, ControlKeyState, EventFlags, Handle, KeyEventRecord, MouseEvent, ScreenBuffer,
|
|
Semaphore,
|
|
};
|
|
use winapi::shared::winerror::WAIT_TIMEOUT;
|
|
use winapi::um::{
|
|
synchapi::WaitForMultipleObjects,
|
|
winbase::{INFINITE, WAIT_ABANDONED_0, WAIT_FAILED, WAIT_OBJECT_0},
|
|
};
|
|
use winapi::um::{
|
|
wincon::{
|
|
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,
|
|
},
|
|
};
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use crate::{
|
|
event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton},
|
|
Result,
|
|
};
|
|
|
|
const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008;
|
|
|
|
lazy_static! {
|
|
static ref ORIGINAL_CONSOLE_MODE: Mutex<Option<u32>> = Mutex::new(None);
|
|
}
|
|
|
|
/// Initializes the default console color. It will will be skipped if it has already been initialized.
|
|
fn init_original_console_mode(original_mode: u32) {
|
|
let mut lock = ORIGINAL_CONSOLE_MODE.lock().unwrap();
|
|
|
|
if lock.is_none() {
|
|
*lock = Some(original_mode);
|
|
}
|
|
}
|
|
|
|
/// Returns the original console color, make sure to call `init_console_color` before calling this function. Otherwise this function will panic.
|
|
fn original_console_mode() -> u32 {
|
|
// safe unwrap, initial console color was set with `init_console_color` in `WinApiColor::new()`
|
|
ORIGINAL_CONSOLE_MODE
|
|
.lock()
|
|
.unwrap()
|
|
.expect("Original console mode not set")
|
|
}
|
|
|
|
pub(crate) fn enable_mouse_capture() -> Result<()> {
|
|
let mode = ConsoleMode::from(Handle::current_in_handle()?);
|
|
init_original_console_mode(mode.mode()?);
|
|
mode.set_mode(ENABLE_MOUSE_MODE)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn disable_mouse_capture() -> Result<()> {
|
|
let mode = ConsoleMode::from(Handle::current_in_handle()?);
|
|
mode.set_mode(original_console_mode())?;
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn handle_mouse_event(mouse_event: MouseEvent) -> Result<Option<Event>> {
|
|
if let Ok(Some(event)) = parse_mouse_event_record(&mouse_event) {
|
|
return Ok(Some(Event::Mouse(event)));
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
pub(crate) fn handle_key_event(key_event: KeyEventRecord) -> Result<Option<Event>> {
|
|
if key_event.key_down {
|
|
if let Some(event) = parse_key_event_record(&key_event) {
|
|
return Ok(Some(Event::Key(event)));
|
|
}
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
|
|
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);
|
|
|
|
let mut modifier = KeyModifiers::empty();
|
|
|
|
if shift {
|
|
modifier |= KeyModifiers::SHIFT;
|
|
}
|
|
if control {
|
|
modifier |= KeyModifiers::CONTROL;
|
|
}
|
|
if alt {
|
|
modifier |= KeyModifiers::ALT;
|
|
}
|
|
|
|
modifier
|
|
}
|
|
}
|
|
|
|
fn parse_key_event_record(key_event: &KeyEventRecord) -> Option<KeyEvent> {
|
|
let modifiers = KeyModifiers::from(key_event.control_key_state);
|
|
|
|
let key_code = key_event.virtual_key_code as i32;
|
|
|
|
let parse_result = match 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_F1..=VK_F24 => Some(KeyCode::F((key_event.virtual_key_code - 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),
|
|
_ => {
|
|
// Modifier Keys (Ctrl, Alt, Shift) Support
|
|
let character_raw = { (unsafe { *key_event.u_char.UnicodeChar() } as u16) };
|
|
|
|
if character_raw < 255 {
|
|
let mut character = character_raw as u8 as char;
|
|
|
|
if modifiers.contains(KeyModifiers::ALT) {
|
|
// If the ALT key is held down, pressing the A key produces ALT+A, which the system does not treat as a character at all, but rather as a system command.
|
|
// The pressed command is stored in `virtual_key_code`.
|
|
let command = key_event.virtual_key_code as u8 as char;
|
|
|
|
if command.is_alphabetic() {
|
|
character = command;
|
|
} else {
|
|
return None;
|
|
}
|
|
} else if modifiers.contains(KeyModifiers::CONTROL) {
|
|
// we need to do some parsing
|
|
character = match character_raw as u8 {
|
|
c @ b'\x01'..=b'\x1A' => (c as u8 - 0x1 + b'a') as char,
|
|
c @ b'\x1C'..=b'\x1F' => (c as u8 - 0x1C + b'4') as char,
|
|
_ => return None,
|
|
}
|
|
}
|
|
|
|
if modifiers.contains(KeyModifiers::SHIFT) && character == '\t' {
|
|
Some(KeyCode::BackTab)
|
|
} else if character == '\t' {
|
|
Some(KeyCode::Tab)
|
|
} else {
|
|
Some(KeyCode::Char(character))
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
};
|
|
|
|
if let Some(key_code) = parse_result {
|
|
return Some(KeyEvent::new(key_code, modifiers));
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
// The 'y' position of a mouse event or resize event is not relative to the window but absolute to screen buffer.
|
|
// This means that when the mouse cursor is at the top left it will be x: 0, y: 2295 (e.g. y = number of cells conting from the absolute buffer height) instead of relative x: 0, y: 0 to the window.
|
|
pub fn parse_relative_y(y: i16) -> Result<i16> {
|
|
let window_size = ScreenBuffer::current()?.info()?.terminal_window();
|
|
Ok(y - window_size.top)
|
|
}
|
|
|
|
fn parse_mouse_event_record(event: &MouseEvent) -> Result<Option<crate::event::MouseEvent>> {
|
|
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;
|
|
|
|
let button_state = event.button_state;
|
|
let button = if button_state.right_button() {
|
|
MouseButton::Right
|
|
} else if button_state.middle_button() {
|
|
MouseButton::Middle
|
|
} else {
|
|
MouseButton::Left
|
|
};
|
|
|
|
Ok(match event.event_flags {
|
|
EventFlags::PressOrRelease => {
|
|
if button_state.release_button() {
|
|
// in order to read the up button type, we have to check the last down input record.
|
|
Some(crate::event::MouseEvent::Up(
|
|
MouseButton::Left,
|
|
xpos,
|
|
ypos,
|
|
modifiers,
|
|
))
|
|
} else {
|
|
Some(crate::event::MouseEvent::Down(
|
|
button, xpos, ypos, modifiers,
|
|
))
|
|
}
|
|
}
|
|
EventFlags::MouseMoved => {
|
|
// Click + Move
|
|
// Only register when mouse is not released
|
|
// because unix systems share this behaviour.
|
|
if !button_state.release_button() {
|
|
Some(crate::event::MouseEvent::Drag(
|
|
button, xpos, ypos, modifiers,
|
|
))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
EventFlags::MouseWheeled => {
|
|
// Vertical scroll
|
|
// from https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str
|
|
// if `button_state` is negative then the wheel was rotated backward, toward the user.
|
|
if button_state.scroll_down() {
|
|
Some(crate::event::MouseEvent::ScrollDown(xpos, ypos, modifiers))
|
|
} else if button_state.scroll_up() {
|
|
Some(crate::event::MouseEvent::ScrollUp(xpos, ypos, modifiers))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
EventFlags::DoubleClick => None, // double click not supported by unix terminals
|
|
EventFlags::MouseHwheeled => None, // horizontal scroll not supported by unix terminals
|
|
})
|
|
}
|
|
|
|
pub(crate) struct WinApiPoll {
|
|
semaphore: Option<Semaphore>,
|
|
}
|
|
|
|
impl WinApiPoll {
|
|
pub(crate) fn new() -> Result<WinApiPoll> {
|
|
Ok(WinApiPoll { semaphore: None })
|
|
}
|
|
}
|
|
|
|
impl WinApiPoll {
|
|
pub fn poll(&mut self, timeout: Option<Duration>) -> Result<Option<bool>> {
|
|
let dw_millis = if let Some(duration) = timeout {
|
|
duration.as_millis() as u32
|
|
} else {
|
|
INFINITE
|
|
};
|
|
|
|
let semaphore = Semaphore::new()?;
|
|
let console_handle = Handle::current_in_handle()?;
|
|
let handles = &[*console_handle, semaphore.handle()];
|
|
|
|
self.semaphore = Some(semaphore);
|
|
|
|
let output =
|
|
unsafe { WaitForMultipleObjects(handles.len() as u32, handles.as_ptr(), 0, dw_millis) };
|
|
|
|
let result = match output {
|
|
output if output == WAIT_OBJECT_0 => {
|
|
// input handle triggered
|
|
Ok(Some(true))
|
|
}
|
|
output if output == WAIT_OBJECT_0 + 1 => {
|
|
// semaphore handle triggered
|
|
Ok(None)
|
|
}
|
|
WAIT_TIMEOUT | WAIT_ABANDONED_0 => {
|
|
// timeout elapsed
|
|
Ok(None)
|
|
}
|
|
WAIT_FAILED => return Err(io::Error::last_os_error().into()),
|
|
_ => Err(io::Error::new(
|
|
ErrorKind::Other,
|
|
"WaitForMultipleObjects returned unexpected result.",
|
|
)
|
|
.into()),
|
|
};
|
|
|
|
self.semaphore = None;
|
|
|
|
result
|
|
}
|
|
|
|
pub fn cancel(&self) -> Result<()> {
|
|
if let Some(semaphore) = &self.semaphore {
|
|
semaphore.release()?
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|