prototype Windows input, needs testing

This commit is contained in:
2025-03-30 17:20:44 -04:00
parent cb336e8bb9
commit 1be8ae3112
6 changed files with 252 additions and 61 deletions

View File

@@ -80,16 +80,6 @@ pub fn move_cursor_to_position(column: u16, line: u16) -> String {
) )
} }
/// Sends input when terminal is in focus
pub const FOCUS_REPORTING_ENABLE: &str = "\x1b[?1004h";
/// Stops sending input when terminal is in focus
pub const FOCUS_REPORTING_DISABLE: &str = "\x1b[?1004l";
/// Makes all pasted text treated differently
pub const BRACKETED_PASTE_ENABLE: &str = "\x1b[?2004h";
/// Disables bracketed paste
pub const BRACKETED_PASTE_DISABLE: &str = "\x1b[?2004l";
/// Saves the current cursor position /// Saves the current cursor position
pub const CURSOR_POSITION_SAVE: &str = "\x1b7"; pub const CURSOR_POSITION_SAVE: &str = "\x1b7";
/// Restores the saved cursor position /// Restores the saved cursor position

View File

@@ -18,7 +18,9 @@ pub use windows_input::*;
pub enum Event { pub enum Event {
Key(KeyEvent), Key(KeyEvent),
Mouse(MouseEvent) Mouse(MouseEvent),
FocusGained,
FocusLost,
} }
pub enum KeyEvent { pub enum KeyEvent {

View File

@@ -1,4 +1,4 @@
use std::ffi::{c_int, c_uint, c_ulong, c_ushort}; use std::ffi::{c_int, c_short, c_uint, c_ulong, c_ushort};
use std::io; use std::io;
unsafe extern "C" { unsafe extern "C" {
@@ -8,8 +8,9 @@ unsafe extern "C" {
fn tcsetattr(fd: c_int, optional_actions: c_int, termios: *mut Termios) -> c_int; fn tcsetattr(fd: c_int, optional_actions: c_int, termios: *mut Termios) -> c_int;
} }
const STDIN_FILENO: c_int = 0x0; pub(crate) const STDIN_FILENO: c_int = 0x0;
const STDOUT_FILENO: c_int = 0x1; pub(crate) const STDOUT_FILENO: c_int = 0x1;
pub(crate) const POLLIN: c_short = 0x001;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
const TIOCGWINSZ: c_ulong = 0x5413; const TIOCGWINSZ: c_ulong = 0x5413;
@@ -21,6 +22,7 @@ const TIOCGWINSZ: c_ulong = 0x4008_7468;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
const NCCS: usize = 0x14; const NCCS: usize = 0x14;
//
#[repr(C)] #[repr(C)]
#[derive(Default, Debug, Clone, Copy)] #[derive(Default, Debug, Clone, Copy)]
struct Winsize { struct Winsize {

View File

@@ -1,14 +1,80 @@
use crate::input::{Event, KeyEvent, MouseEvent, MouseButton}; use crate::input::{Event, KeyEvent, MouseButton, MouseEvent};
use crate::os::{POLLIN, STDIN_FILENO};
use std::ffi::{c_int, c_short, c_ulong, c_void};
use std::io; use std::io;
use std::time::Duration;
pub fn try_parse_event<I>(item: u8, iter: &mut I) -> io::Result<Event> unsafe extern "C" {
fn poll(fds: *mut PollFD, nfds: c_ulong, timeout: c_int) -> c_int;
fn read(fd: c_int, buf: *mut c_void, count: c_ulong) -> c_short;
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct PollFD {
fd: c_int,
events: c_short,
revents: c_short,
}
struct ReadIterator {
fd: c_int,
buf: u8,
}
impl ReadIterator {
fn new(fd: c_int) -> Self {
Self { fd, buf: 0 }
}
}
impl Iterator for ReadIterator {
type Item = io::Result<u8>;
fn next(&mut self) -> Option<Self::Item> {
let bytes_read = unsafe { read(self.fd, &raw mut self.buf as *mut c_void, 1) };
if bytes_read > 0 {
Some(Ok(self.buf))
} else if bytes_read == 0 {
None
} else {
Some(Err(io::Error::last_os_error()))
}
}
}
pub fn poll_input(timeout: Duration) -> Option<io::Result<Event>> {
let mut fds = [PollFD {
fd: STDIN_FILENO,
events: POLLIN,
revents: 0,
}];
let result = unsafe {
poll(
fds.as_mut_ptr(),
fds.len() as c_ulong,
timeout.as_millis() as c_int,
)
};
let mut read_iter = ReadIterator::new(STDIN_FILENO);
if result > 0 {
let item = read_iter.next()?.ok()?;
Some(try_parse_event(item, &mut read_iter))
} else if result == 0 {
None
} else {
Some(Err(io::Error::last_os_error()))
}
}
fn try_parse_event<I>(item: u8, iter: &mut I) -> io::Result<Event>
where where
I: Iterator<Item = io::Result<u8>>, I: Iterator<Item = io::Result<u8>>,
{ {
match item { match item {
b'\x1b' => { b'\x1b' => try_parse_ansi_sequence(iter),
try_parse_ansi_sequence(iter)
}
b'\n' | b'\r' => Ok(Event::Key(KeyEvent::Char('\n'))), b'\n' | b'\r' => Ok(Event::Key(KeyEvent::Char('\n'))),
b'\t' => Ok(Event::Key(KeyEvent::Tab)), b'\t' => Ok(Event::Key(KeyEvent::Tab)),
b'\x7f' => Ok(Event::Key(KeyEvent::Backspace)), b'\x7f' => Ok(Event::Key(KeyEvent::Backspace)),
@@ -25,10 +91,10 @@ where
{ {
let error = || io::Error::new(io::ErrorKind::InvalidData, "Input char is not valid UTF-8"); let error = || io::Error::new(io::ErrorKind::InvalidData, "Input char is not valid UTF-8");
let mut bytes = vec![c]; let mut bytes = vec![c];
for _ in 1..=4 { for _ in 1..=4 {
if let Ok(string) = std::str::from_utf8(&bytes) { if let Ok(string) = std::str::from_utf8(&bytes) {
return Ok(string.chars().next().unwrap()) return Ok(string.chars().next().unwrap());
} }
bytes.push(iter.next().ok_or_else(error)??); bytes.push(iter.next().ok_or_else(error)??);
} }
@@ -41,15 +107,11 @@ where
{ {
let error = io::Error::new(io::ErrorKind::Other, "Could not parse event"); let error = io::Error::new(io::ErrorKind::Other, "Could not parse event");
match iter.next() { match iter.next() {
Some(Ok(b'O')) => { Some(Ok(b'O')) => match iter.next() {
match iter.next() { Some(Ok(val @ b'P'..=b's')) => Ok(Event::Key(KeyEvent::F(1 + val - b'P'))),
Some(Ok(val @ b'P'..=b's')) => Ok(Event::Key(KeyEvent::F(1 + val - b'P'))), _ => Err(error),
_ => Err(error) },
} Some(Ok(b'[')) => try_parse_csi_sequence(iter).ok_or(error),
}
Some(Ok(b'[')) => {
try_parse_csi_sequence(iter).ok_or(error)
}
_ => Err(error), _ => Err(error),
} }
} }
@@ -59,13 +121,10 @@ where
I: Iterator<Item = io::Result<u8>>, I: Iterator<Item = io::Result<u8>>,
{ {
match iter.next() { match iter.next() {
Some(Ok(b'[')) => { Some(Ok(b'[')) => match iter.next() {
match iter.next() { Some(Ok(val @ b'A'..=b'E')) => Some(Event::Key(KeyEvent::F(1 + val - b'A'))),
Some(Ok(val @ b'A'..=b'E')) => Some(Event::Key(KeyEvent::F(1 + val - b'A'))), _ => None,
_ => None },
}
}
Some(Ok(b'D')) => Some(Event::Key(KeyEvent::Left)), Some(Ok(b'D')) => Some(Event::Key(KeyEvent::Left)),
Some(Ok(b'C')) => Some(Event::Key(KeyEvent::Right)), Some(Ok(b'C')) => Some(Event::Key(KeyEvent::Right)),
Some(Ok(b'A')) => Some(Event::Key(KeyEvent::Up)), Some(Ok(b'A')) => Some(Event::Key(KeyEvent::Up)),
@@ -73,17 +132,10 @@ where
Some(Ok(b'H')) => Some(Event::Key(KeyEvent::Home)), Some(Ok(b'H')) => Some(Event::Key(KeyEvent::Home)),
Some(Ok(b'F')) => Some(Event::Key(KeyEvent::End)), Some(Ok(b'F')) => Some(Event::Key(KeyEvent::End)),
Some(Ok(b'Z')) => Some(Event::Key(KeyEvent::BackTab)), Some(Ok(b'Z')) => Some(Event::Key(KeyEvent::BackTab)),
Some(Ok(b'M')) => { Some(Ok(b'M')) => try_parse_x10_mouse(iter),
try_parse_x10_mouse(iter) Some(Ok(b'<')) => try_parse_xterm_mouse(iter),
} Some(Ok(c @ b'0'..=b'9')) => try_parse_rxvt_mouse(c, iter),
Some(Ok(b'<')) => { _ => None,
try_parse_xterm_mouse(iter)
}
Some(Ok(c @ b'0'..=b'9')) => {
try_parse_rxvt_mouse(c, iter)
}
_ => None
} }
} }
@@ -92,35 +144,50 @@ where
I: Iterator<Item = io::Result<u8>>, I: Iterator<Item = io::Result<u8>>,
{ {
let cb = iter.next()?.ok()? - 32; let cb = iter.next()?.ok()? - 32;
let cx = u16::from(iter.next()?.ok()?.saturating_sub(33)); let cx = u16::from(iter.next()?.ok()?.saturating_sub(33));
let cy = u16::from(iter.next()?.ok()?.saturating_sub(33)); let cy = u16::from(iter.next()?.ok()?.saturating_sub(33));
match cb & 0x11 { match cb & 0x11 {
0 => { 0 => {
if cb & 0x40 != 0 { if cb & 0x40 != 0 {
Some(Event::Mouse(MouseEvent::Press(MouseButton::WheelUp, cx, cy))) Some(Event::Mouse(MouseEvent::Press(
MouseButton::WheelUp,
cx,
cy,
)))
} else { } else {
Some(Event::Mouse(MouseEvent::Press(MouseButton::Left, cx, cy))) Some(Event::Mouse(MouseEvent::Press(MouseButton::Left, cx, cy)))
} }
} }
1 => { 1 => {
if cb & 0x40 != 0 { if cb & 0x40 != 0 {
Some(Event::Mouse(MouseEvent::Press(MouseButton::WheelDown, cx, cy))) Some(Event::Mouse(MouseEvent::Press(
MouseButton::WheelDown,
cx,
cy,
)))
} else { } else {
Some(Event::Mouse(MouseEvent::Press(MouseButton::Middle, cx, cy))) Some(Event::Mouse(MouseEvent::Press(MouseButton::Middle, cx, cy)))
} }
} }
2 => { 2 => {
if cb & 0x40 != 0 { if cb & 0x40 != 0 {
Some(Event::Mouse(MouseEvent::Press(MouseButton::WheelLeft, cx, cy))) Some(Event::Mouse(MouseEvent::Press(
MouseButton::WheelLeft,
cx,
cy,
)))
} else { } else {
Some(Event::Mouse(MouseEvent::Press(MouseButton::Right, cx, cy))) Some(Event::Mouse(MouseEvent::Press(MouseButton::Right, cx, cy)))
} }
} }
3 => { 3 => {
if cb & 0x40 != 0 { if cb & 0x40 != 0 {
Some(Event::Mouse(MouseEvent::Press(MouseButton::WheelRight, cx, cy))) Some(Event::Mouse(MouseEvent::Press(
MouseButton::WheelRight,
cx,
cy,
)))
} else { } else {
Some(Event::Mouse(MouseEvent::Release(cx, cy))) Some(Event::Mouse(MouseEvent::Release(cx, cy)))
} }
@@ -135,7 +202,7 @@ where
{ {
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut character = iter.next()?.ok()?; let mut character = iter.next()?.ok()?;
while !matches!(character, b'm' | b'M' ) { while !matches!(character, b'm' | b'M') {
buf.push(character); buf.push(character);
character = iter.next()?.ok()?; character = iter.next()?.ok()?;
} }
@@ -164,7 +231,7 @@ where
b'm' => MouseEvent::Release(cx, cy), b'm' => MouseEvent::Release(cx, cy),
_ => return None, _ => return None,
} }
}, }
32 | 3 => MouseEvent::Hold(cx, cy), 32 | 3 => MouseEvent::Hold(cx, cy),
_ => return None, _ => return None,
}; };

View File

@@ -10,8 +10,8 @@ unsafe extern "system" {
) -> u32; ) -> u32;
} }
const STD_INPUT_HANDLE: u32 = 0xFFFF_FFF6; pub(crate) const STD_INPUT_HANDLE: u32 = 0xFFFF_FFF6;
const STD_OUTPUT_HANDLE: u32 = 0xFFFF_FFF5; pub(crate) const STD_OUTPUT_HANDLE: u32 = 0xFFFF_FFF5;
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 4; const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 4;
const ENABLE_ECHO_INPUT: u32 = 4; const ENABLE_ECHO_INPUT: u32 = 4;
const ENABLE_LINE_INPUT: u32 = 2; const ENABLE_LINE_INPUT: u32 = 2;
@@ -103,7 +103,7 @@ pub fn get_terminal_size() -> io::Result<(u16, u16)> {
Err(io::Error::last_os_error()) Err(io::Error::last_os_error())
} }
fn get_std_handle(handle: u32) -> io::Result<usize> { pub(crate) fn get_std_handle(handle: u32) -> io::Result<usize> {
let handle = unsafe { GetStdHandle(handle) }; let handle = unsafe { GetStdHandle(handle) };
if handle == INVALID_HANDLE_VALUE { if handle == INVALID_HANDLE_VALUE {
Err(io::Error::last_os_error()) Err(io::Error::last_os_error())
@@ -112,7 +112,7 @@ fn get_std_handle(handle: u32) -> io::Result<usize> {
} }
} }
fn set_console_mode(handle: usize, mode: &mut u32) -> io::Result<()> { pub(crate) fn set_console_mode(handle: usize, mode: &mut u32) -> io::Result<()> {
if unsafe { SetConsoleMode(handle, mode) == 0 } { if unsafe { SetConsoleMode(handle, mode) == 0 } {
Err(io::Error::last_os_error()) Err(io::Error::last_os_error())
} else { } else {
@@ -120,7 +120,7 @@ fn set_console_mode(handle: usize, mode: &mut u32) -> io::Result<()> {
} }
} }
fn get_console_mode(handle: usize, mode: &mut u32) -> io::Result<()> { pub(crate) fn get_console_mode(handle: usize, mode: &mut u32) -> io::Result<()> {
if unsafe { GetConsoleMode(handle, mode) == 0 } { if unsafe { GetConsoleMode(handle, mode) == 0 } {
Err(io::Error::last_os_error()) Err(io::Error::last_os_error())
} else { } else {

View File

@@ -0,0 +1,130 @@
use crate::input::{Event, KeyEvent};
use crate::os::{STD_INPUT_HANDLE, get_std_handle};
use std::{io, mem, time::Duration};
#[repr(C)]
#[derive(Copy, Clone)]
struct InputRecord {
event_type: u16,
event: EventRecord,
}
#[repr(C)]
#[derive(Copy, Clone)]
union EventRecord {
key_event: KeyEventRecord,
mouse_event: MouseEventRecord,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct KeyEventRecord {
key_down: i32,
virtual_key_code: u16,
u_char: CharUnion,
control_key_state: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
union CharUnion {
unicode_char: u16,
ascii_char: u8,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct MouseEventRecord {
mouse_position: Coord,
button_state: u32,
control_key_state: u32,
event_flags: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct Coord {
x: i16,
y: i16,
}
unsafe extern "system" {
fn ReadConsoleInputW(
h_console_input: usize,
lp_buffer: *mut InputRecord,
n_length: u32,
lp_number_of_events_read: *mut u32,
) -> i32;
fn WaitForMultipleObjects(
n_count: u32,
lp_handles: *mut usize,
b_wait_all: i32,
dw_wait_time: u32,
) -> u32;
}
pub fn poll_input(timeout: Duration) -> Option<io::Result<Event>> {
let handle = get_std_handle(STD_INPUT_HANDLE).ok()?;
let mut record: InputRecord = unsafe { mem::zeroed() };
let mut read = 0;
let wait_time_millis = timeout.as_millis() as u32;
let mut handles = [handle];
let result = unsafe { WaitForMultipleObjects(1, handles.as_mut_ptr(), 0, wait_time_millis) };
if result != 0 {
return None;
}
let result = unsafe { ReadConsoleInputW(handle, &mut record, 1, &mut read) };
if result == 0 {
return Some(Err(io::Error::last_os_error()));
}
if record.event_type == 1 {
let key_event = unsafe { record.event.key_event };
if key_event.key_down == 0 {
return Some(Ok(Event::Key(KeyEvent::Null)));
}
return Some(Ok(Event::Key(parse_key_event(&key_event))));
}
None
}
fn parse_key_event(event: &KeyEventRecord) -> KeyEvent {
let ctrl = event.control_key_state & (0x0008 | 0x0004) != 0; // LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED
let shift = event.control_key_state & 0x0010 != 0; // SHIFT_PRESSED
match event.virtual_key_code {
0x08 => KeyEvent::Backspace, // VK_BACK
0x09 => {
if shift {
KeyEvent::BackTab
} else {
KeyEvent::Tab
}
}
0x0D => KeyEvent::Char('\n'),
0x1B => KeyEvent::Escape,
0x21 => KeyEvent::PageUp,
0x22 => KeyEvent::PageDown,
0x23 => KeyEvent::End,
0x24 => KeyEvent::Home,
0x25 => KeyEvent::Left,
0x26 => KeyEvent::Up,
0x27 => KeyEvent::Right,
0x28 => KeyEvent::Down,
0x2D => KeyEvent::Insert,
0x2E => KeyEvent::Delete,
0x70..=0x87 => KeyEvent::F((event.virtual_key_code - 0x6F) as u8), // F1-F24
_ => {
let c = unsafe { event.u_char.unicode_char } as u8 as char;
if ctrl && c.is_ascii_alphabetic() {
KeyEvent::Ctrl(c)
} else {
KeyEvent::Char(c)
}
}
}
}