diff --git a/src/control.rs b/src/control.rs index 4af902b..87c4078 100644 --- a/src/control.rs +++ b/src/control.rs @@ -2,14 +2,65 @@ //! //! These are built to work on Windows, Linux, and MacOS +use std::io; + #[cfg(unix)] pub use crate::unix::{ - disable_kitty_keyboard, disable_mouse_input, disable_raw_mode, enable_kitty_keyboard, - enable_mouse_input, enable_raw_mode, get_terminal_size, + disable_ansi, disable_mouse_input, disable_raw_mode, enable_mouse_input, enable_raw_mode, + get_terminal_size, }; #[cfg(windows)] pub use crate::windows::{ - disable_kitty_keyboard, disable_mouse_input, disable_raw_mode, enable_kitty_keyboard, - enable_mouse_input, enable_raw_mode, get_terminal_size, + disable_ansi, disable_mouse_input, disable_raw_mode, enable_mouse_input, enable_raw_mode, + get_terminal_size, }; + +const ENABLE_KITTY_KEYBOARD: &str = "\x1b[>31u"; +const DISABLE_KITTY_KEYBOARD: &str = "\x1b[<31u"; + +/// Enable kitty comprehensive keyboard handling protocol +pub fn enable_kitty_keyboard() { + print!("{ENABLE_KITTY_KEYBOARD}"); +} + +/// Disable kitty comprehensive keyboard handling protocol +pub fn disable_kitty_keyboard() { + print!("{DISABLE_KITTY_KEYBOARD}"); +} + +use crate::prelude::{ALT_SCREEN_ENTER, ALT_SCREEN_EXIT, enable_ansi}; + +pub fn tui_init() -> io::Result<()> { + enable_ansi()?; + enable_raw_mode()?; + enable_mouse_input()?; + print!("{ALT_SCREEN_ENTER}"); + enable_kitty_keyboard(); + Ok(()) +} + +pub fn tui_deinit() -> io::Result<()> { + disable_kitty_keyboard(); + print!("{ALT_SCREEN_EXIT}"); + disable_mouse_input()?; + disable_raw_mode()?; + disable_ansi()?; + Ok(()) +} + +pub fn cli_init() -> io::Result<()> { + enable_ansi()?; + enable_raw_mode()?; + enable_mouse_input()?; + enable_kitty_keyboard(); + Ok(()) +} + +pub fn cli_deinit() -> io::Result<()> { + disable_ansi()?; + disable_raw_mode()?; + disable_mouse_input()?; + disable_kitty_keyboard(); + Ok(()) +} diff --git a/src/input.rs b/src/input.rs index dfc0807..f954d96 100644 --- a/src/input.rs +++ b/src/input.rs @@ -90,7 +90,7 @@ pub struct Modifiers { } impl Modifiers { - pub const fn new(shift: bool, alt: bool, ctrl: bool) -> Self { + #[must_use] pub const fn new(shift: bool, alt: bool, ctrl: bool) -> Self { Self { shift, alt, ctrl } } @@ -131,7 +131,6 @@ pub enum ButtonType { Release, } -#[inline(always)] pub(crate) const fn key_helper(mods: &str, key: Key) -> Event { let mut key_mods = Modifiers::NONE; let mut key_type = ButtonType::Press; @@ -139,9 +138,9 @@ pub(crate) const fn key_helper(mods: &str, key: Key) -> Event { let string = mods.as_bytes(); let mut i = 0; while i < string.len() { - key_mods.alt = key_mods.alt | (string[i] == b'A'); - key_mods.ctrl = key_mods.ctrl | (string[i] == b'C'); - key_mods.shift = key_mods.shift | (string[i] == b'S'); + key_mods.alt |= string[i] == b'A'; + key_mods.ctrl |= string[i] == b'C'; + key_mods.shift |= string[i] == b'S'; if string[i] == b'-' { key_type = ButtonType::Release; } @@ -159,10 +158,10 @@ pub(crate) const fn simple_key(key: Key, shift: bool, alt: bool, ctrl: bool) -> } #[cfg(unix)] -pub use crate::unix::poll_input; +pub use crate::unix_input::poll_input; #[cfg(windows)] -pub use crate::windows::poll_input; +pub use crate::windows_input::poll_input; #[test] fn test_key_helper() { diff --git a/src/lib.rs b/src/lib.rs index 5ba0a80..f9e036d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,12 @@ mod unix; #[cfg(windows)] mod windows; +#[cfg(unix)] +mod unix_input; + +#[cfg(windows)] +mod windows_input; + pub mod ansi; pub mod control; pub mod input; diff --git a/src/unix.rs b/src/unix.rs index 06ae7be..b687377 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -1,17 +1,10 @@ -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::sync::LazyLock; -use crate::input::{ButtonType, Event, Key, Modifiers, MouseButton, key_helper, simple_key}; -use std::ffi::{c_short, c_void}; -use std::time::Duration; - const ENABLE_MOUSE: &str = "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h\x1b[?1003h"; const DISABLE_MOUSE: &str = "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l\x1b[?1003l"; -const ENABLE_KITTY_KEYBOARD: &str = "\x1b[>31u"; -const DISABLE_KITTY_KEYBOARD: &str = "\x1b[<31u"; - unsafe extern "C" { fn ioctl(fd: c_int, request: c_ulong, argp: *mut u8) -> c_int; fn cfmakeraw(termios: *mut Termios); @@ -19,9 +12,9 @@ unsafe extern "C" { fn tcsetattr(fd: c_int, optional_actions: c_int, termios: *const Termios) -> c_int; } -const STDIN_FILENO: c_int = 0; -const STDOUT_FILENO: c_int = 1; -const POLLIN: c_short = 1; +pub(crate) const STDIN_FILENO: c_int = 0; +pub(crate) const STDOUT_FILENO: c_int = 1; +pub(crate) const POLLIN: c_short = 1; #[cfg(not(target_os = "macos"))] const TIOCGWINSZ: c_ulong = 0x5413; @@ -79,17 +72,6 @@ static TERMIOS: LazyLock> = LazyLock::new(|| { } }); -/// CSI>31u -/// Enable kitty comprehensive keyboard handling protocol -pub fn enable_kitty_keyboard() { - print!("{ENABLE_KITTY_KEYBOARD}"); -} - -/// Disable kitty comprehensive keyboard handling protocol -pub fn disable_kitty_keyboard() { - print!("{DISABLE_KITTY_KEYBOARD}"); -} - /// Enable mouse input, if available /// /// # Errors @@ -118,9 +100,9 @@ pub fn disable_mouse_input() -> io::Result<()> { /// stdin is not a tty, /// or it fails to change terminal settings pub fn enable_raw_mode() -> io::Result<()> { - let mut termios = (*TERMIOS).map_err(|e| io::Error::from_raw_os_error(e))?; + let mut termios = (*TERMIOS).map_err(io::Error::from_raw_os_error)?; unsafe { - cfmakeraw(&mut termios); + cfmakeraw(&raw mut termios); } set_attributes(STDIN_FILENO, &mut termios)?; Ok(()) @@ -134,29 +116,45 @@ pub fn enable_raw_mode() -> io::Result<()> { /// stdin is not a tty, /// or it fails to change terminal settings pub fn disable_raw_mode() -> io::Result<()> { - let mut termios = (*TERMIOS).map_err(|e| io::Error::from_raw_os_error(e))?; + let mut termios = (*TERMIOS).map_err(io::Error::from_raw_os_error)?; set_attributes(STDIN_FILENO, &mut termios)?; Ok(()) } /// Enables ANSI support on Windows terminals /// -/// ANSI is on by default on *nix machines but still exists on them for simpler usage +/// ANSI is always enabled on *nix machines but these function still exist for simpler usage /// /// # Errors /// /// Never on *nix /// -/// If There is no stdout, +/// On Windows, if There is no stdout, /// if stdout isn't a TTY, or -/// if it cannot change terminal properties on Windows -#[cfg(unix)] +/// if it cannot change terminal properties pub fn enable_ansi() -> io::Result<()> { // ANSI is on by default on unix platforms // This is here for compatibility with the windows version of this API Ok(()) } +/// Disables ANSI support on Windows terminals +/// +/// ANSI is always enabled on *nix machines but these function still exist for simpler usage +/// +/// # Errors +/// +/// Never on *nix +/// +/// On Windows, if There is no stdout, +/// if stdout isn't a TTY, or +/// if it cannot change terminal properties +pub fn disable_ansi() -> io::Result<()> { + // ANSI is on by default on unix platforms + // This is here for compatibility with the windows version of this API + Ok(()) +} + /// Gets the size of the terminal /// /// Returns in (width, height) format @@ -176,419 +174,3 @@ pub fn get_terminal_size() -> io::Result<(u16, u16)> { Err(io::Error::last_os_error()) } } - -// Some of this input code has been modified from [termion](https://github.com/redox-os/termion) - -/// Attempts to fetch input from stdin -/// -/// # Errors -/// If the timeout has expired or -/// there was an error getting the data -pub fn poll_input(timeout: Duration) -> io::Result { - let result = poll_timeout(timeout); - let mut read_iter = ReadIterator::new(); - - let timed_out: io::Error = io::ErrorKind::TimedOut.into(); - - match result { - 1.. => { - let item = read_iter.next().ok_or(timed_out)??; - try_parse_event(item, &mut read_iter) - } - 0 => Err(timed_out), - _ => Err(io::Error::last_os_error()), - } -} - -fn poll_timeout(timeout: Duration) -> i32 { - let mut fds = [PollFD { - fd: STDIN_FILENO, - events: POLLIN, - revents: 0, - }]; - unsafe { - #[allow(clippy::cast_possible_truncation)] - poll( - fds.as_mut_ptr(), - fds.len() as c_ulong, - timeout.as_millis() as c_int, - ) - } -} - -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 { - buf: u8, -} - -impl ReadIterator { - fn new() -> Self { - Self { buf: 0 } - } -} - -impl Iterator for ReadIterator { - type Item = io::Result; - - fn next(&mut self) -> Option { - let bytes_poll = poll_timeout(Duration::ZERO); - let bytes_read = match bytes_poll { - 1.. => Some(Ok(unsafe { - read(STDIN_FILENO, (&raw mut self.buf).cast::(), 1) - })), - 0 => None, - _ => Some(Err(io::Error::last_os_error())), - }; - match bytes_read? { - Ok(1..) => Some(Ok(self.buf)), - Ok(0) => None, - _ => Some(Err(io::Error::last_os_error())), - } - } -} - -fn try_parse_event(item: u8, iter: &mut I) -> io::Result -where - I: Iterator>, -{ - match item { - b'\x1b' => parse_ansi_sequence(iter), - b'\r' => Ok(key_helper("", Key::Char('\r'))), - b'\n' => Ok(key_helper("C", Key::Char('j'))), - b'\t' => Ok(key_helper("", Key::Char('\t'))), - b'\x7f' => Ok(key_helper("", Key::Backspace)), - b'\0' => Ok(key_helper("C", Key::Char(' '))), - c @ b'\x01'..=b'\x1a' => Ok(key_helper("C", Key::Char((c + 96) as char))), - c @ b'\x1c'..=b'\x1f' => Ok(key_helper("C", Key::Char((c + 24) as char))), - c => { - let character = parse_utf8_char(c, iter)?; - Ok(Event::Key( - Key::Char(character), - ButtonType::Press, - Modifiers::NONE.shift(character.is_uppercase()), - )) - } - } -} - -fn parse_utf8_char(c: u8, iter: &mut I) -> io::Result -where - I: Iterator>, -{ - let error = || io::Error::new(io::ErrorKind::InvalidData, "Input char is not valid UTF-8"); - let mut bytes = vec![c]; - - for _ in 1..=4 { - if let Ok(string) = std::str::from_utf8(&bytes) { - return Ok(string.chars().next().unwrap()); - } - bytes.push(iter.next().ok_or_else(error)??); - } - Err(error()) -} - -fn parse_ansi_sequence(iter: &mut I) -> io::Result -where - I: Iterator>, -{ - let error = io::Error::other("Could not parse event"); - match iter.next() { - None => Ok(key_helper("", Key::Escape)), - Some(Ok(b'O')) => match iter.next() { - Some(Ok(val @ b'P'..=b's')) => Ok(key_helper("", Key::F(1 + val - b'P'))), - _ => Err(error), - }, - Some(Ok(b'[')) => parse_csi_sequence(iter).ok_or(error), - Some(Ok(c)) => match c { - b'\r' => Ok(key_helper("A", Key::Char('\r'))), - b'\n' => Ok(key_helper("CA", Key::Char('j'))), - b'\t' => Ok(key_helper("A", Key::Char('\t'))), - b'\x7f' => Ok(key_helper("A", Key::Backspace)), - b'\0' => Ok(key_helper("CA", Key::Char(' '))), - c @ b'\x01'..=b'\x1a' => Ok(key_helper("CA", Key::Char((c + 96) as char))), - c @ b'\x1c'..=b'\x1f' => Ok(key_helper("CA", Key::Char((c + 24) as char))), - c => { - let character = parse_utf8_char(c, iter)?; - Ok(Event::Key( - Key::Char(character), - ButtonType::Press, - Modifiers::NONE.shift(character.is_uppercase()).alt(true), - )) - } - }, - _ => Err(error), - } -} - -fn parse_csi_sequence(iter: &mut I) -> Option -where - I: Iterator>, -{ - match iter.next() { - Some(Ok(b'[')) => match iter.next() { - Some(Ok(val @ b'A'..=b'E')) => Some(key_helper("", Key::F(1 + val - b'A'))), - _ => None, - }, - Some(Ok(b'D')) => Some(key_helper("", Key::Left)), - Some(Ok(b'C')) => Some(key_helper("", Key::Right)), - Some(Ok(b'A')) => Some(key_helper("", Key::Up)), - Some(Ok(b'B')) => Some(key_helper("", Key::Down)), - Some(Ok(b'H')) => Some(key_helper("", Key::Home)), - Some(Ok(b'F')) => Some(key_helper("", Key::End)), - Some(Ok(b'Z')) => Some(key_helper("", Key::Tab)), - Some(Ok(b'<')) => parse_xterm_mouse(iter), - Some(Ok(b'M')) => parse_x10_mouse(iter), - Some(Ok(c @ b'0'..=b'9')) => parse_numbered_escape(iter, c), - None => Some(key_helper("A", Key::Char('['))), - _ => None, - } -} - -fn parse_numbered_escape(iter: &mut I, c: u8) -> Option -where - I: Iterator>, -{ - let mut buf = Vec::new(); - buf.push(c); - let mut c = iter.next().unwrap().unwrap(); - // The final byte of a CSI sequence can be in the range 64-126, so let's keep reading - // anything else. - while c < 64 || c > 126 { - buf.push(c); - c = iter.next().unwrap().unwrap(); - } - match c { - // rxvt mouse encoding: - // ESC [ Cb ; Cx ; Cy ; M - b'M' => { - let str_buf = String::from_utf8(buf).unwrap(); - - let nums: Vec = str_buf.split(';').map(|n| n.parse().unwrap()).collect(); - - let cb = nums[0]; - let cx = nums[1]; - let cy = nums[2]; - - let mods = Modifiers::NONE; - - let event = match cb { - 32 => Event::Mouse(mods, MouseButton::Left, ButtonType::Press, cx, cy), - 33 => Event::Mouse(mods, MouseButton::Middle, ButtonType::Press, cx, cy), - 34 => Event::Mouse(mods, MouseButton::Right, ButtonType::Press, cx, cy), - 35 => Event::Mouse(mods, MouseButton::Unknown, ButtonType::Release, cx, cy), - 64 => Event::Mouse(mods, MouseButton::Unknown, ButtonType::Held, cx, cy), - 96 | 97 => Event::Mouse(mods, MouseButton::WheelUp, ButtonType::Press, cx, cy), - _ => return None, - }; - - Some(event) - } - // Special key code. - b'~' => { - let str_buf = String::from_utf8(buf).unwrap(); - - // This CSI sequence can be a list of semicolon-separated - // numbers. - let nums: Vec = str_buf.split(';').map(|n| n.parse().unwrap()).collect(); - - if nums.is_empty() { - return None; - } - - // TODO: handle multiple values for key modififiers (ex: values - // [3, 2] means Shift+Delete) - if nums.len() > 1 { - return None; - } - - match nums[0] { - 1 | 7 => Some(key_helper("", Key::Home)), - 2 => Some(key_helper("", Key::Insert)), - 3 => Some(key_helper("", Key::Delete)), - 4 | 8 => Some(key_helper("", Key::End)), - 5 => Some(key_helper("", Key::PageUp)), - 6 => Some(key_helper("", Key::PageDown)), - v @ 11..=15 => Some(key_helper("", Key::F(v - 10))), - v @ 17..=21 => Some(key_helper("", Key::F(v - 11))), - v @ 23..=24 => Some(key_helper("", Key::F(v - 12))), - _ => return None, - } - } - b'u' => { - let str_buf = String::from_utf8(buf).unwrap(); - let mut iter = str_buf.split(';'); - let key_code: u32 = iter.next()?.parse().ok()?; - let mut iter = iter.next().unwrap_or("0:1").split(':'); - let modifier: u32 = iter.next()?.parse().ok()?; - let key_type: u32 = iter.next().unwrap_or("1").parse().ok()?; - println!("{str_buf}\r"); - println!("{modifier}\r"); - - let char = char::from_u32(key_code); - // let shift = modifier & 1 == 1; - // let alt = modifier & 2 == 2; - // let ctrl = modifier & 4 == 4; - let button_type = match key_type { - 1 => ButtonType::Press, - 2 => ButtonType::Held, - 3 => ButtonType::Release, - _ => return None, - }; - - Some(Event::Key(Key::Char(char?), button_type, Modifiers::NONE)) - } - b'A' | b'B' | b'C' | b'D' | b'F' | b'H' => { - let str_buf = String::from_utf8(buf).unwrap(); - - // This CSI sequence can be a list of semicolon-separated - // numbers. - let nums: Vec = str_buf.split(';').map(|n| n.parse().unwrap()).collect(); - - if !(nums.len() == 2 && nums[0] == 1) { - return None; - } - let mods = nums[1] - 1; - let shift = mods & 1 == 1; - let alt = mods & 2 == 2; - let ctrl = mods & 4 == 4; - match c { - b'D' => Some(simple_key(Key::Left, shift, alt, ctrl)), - b'C' => Some(simple_key(Key::Right, shift, alt, ctrl)), - b'A' => Some(simple_key(Key::Up, shift, alt, ctrl)), - b'B' => Some(simple_key(Key::Down, shift, alt, ctrl)), - b'H' => Some(simple_key(Key::Home, shift, alt, ctrl)), - b'F' => Some(simple_key(Key::End, shift, alt, ctrl)), - _ => return None, - } - } - - _ => None, - } -} - -fn parse_x10_mouse(iter: &mut I) -> Option -where - I: Iterator>, -{ - // X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only). - let mut next = || iter.next().unwrap().unwrap(); - - let cb = next() as i8 - 32; - // (0, 0) are the coords for upper left. - let cx = next().saturating_sub(33) as u16; - let cy = next().saturating_sub(33) as u16; - - let mods = Modifiers::NONE; - Some(match cb & 0b11 { - 0 => { - if cb & 0x40 != 0 { - Event::Mouse(mods, MouseButton::WheelUp, ButtonType::Press, cx, cy) - } else { - Event::Mouse(mods, MouseButton::WheelUp, ButtonType::Press, cx, cy) - } - } - 1 => { - if cb & 0x40 != 0 { - Event::Mouse(mods, MouseButton::WheelDown, ButtonType::Press, cx, cy) - } else { - Event::Mouse(mods, MouseButton::Middle, ButtonType::Press, cx, cy) - } - } - 2 => { - if cb & 0x40 != 0 { - Event::Mouse(mods, MouseButton::WheelLeft, ButtonType::Press, cx, cy) - } else { - Event::Mouse(mods, MouseButton::Right, ButtonType::Press, cx, cy) - } - } - 3 => { - if cb & 0x40 != 0 { - Event::Mouse(mods, MouseButton::WheelRight, ButtonType::Press, cx, cy) - } else { - Event::Mouse(mods, MouseButton::Unknown, ButtonType::Release, cx, cy) - } - } - _ => unreachable!(), - }) -} - -fn parse_xterm_mouse(iter: &mut I) -> Option -where - I: Iterator>, -{ - // xterm mouse encoding: - // ESC [ < Cb ; Cx ; Cy (;) (M or m) - let mut buf = Vec::new(); - let mut c = iter.next().unwrap().unwrap(); - while match c { - b'm' | b'M' => false, - _ => true, - } { - buf.push(c); - c = iter.next().unwrap().unwrap(); - } - let str_buf = String::from_utf8(buf).unwrap(); - let nums = &mut str_buf.split(';'); - - let cb = nums.next()?.parse::().unwrap(); - let cx = nums.next()?.parse::().unwrap().saturating_sub(1); - let cy = nums.next()?.parse::().unwrap().saturating_sub(1); - - let shift = cb & 4 == 4; - let alt = cb & 8 == 8; - let ctrl = cb & 16 == 16; - let mods = Modifiers::new(shift, alt, ctrl); - let trimmed_cb = cb ^ (cb & 0b00011100); - - let event = match trimmed_cb { - 0..=2 | 64..=67 => { - let button = match trimmed_cb { - 0 => MouseButton::Left, - 1 => MouseButton::Middle, - 2 => MouseButton::Right, - 64 => MouseButton::WheelUp, - 65 => MouseButton::WheelDown, - 66 => MouseButton::WheelLeft, - 67 => MouseButton::WheelRight, - _ => unreachable!(), - }; - match c { - b'M' => Event::Mouse(mods, button, ButtonType::Press, cx, cy), - b'm' => Event::Mouse(mods, button, ButtonType::Release, cx, cy), - _ => return None, - } - } - 32 => Event::Mouse(mods, MouseButton::Left, ButtonType::Held, cx, cy), - 33 => Event::Mouse(mods, MouseButton::Middle, ButtonType::Held, cx, cy), - 34 => Event::Mouse(mods, MouseButton::Right, ButtonType::Held, cx, cy), - 35 => Event::Mouse(mods, MouseButton::None, ButtonType::Held, cx, cy), - 3 => Event::Mouse(mods, MouseButton::Unknown, ButtonType::Release, cx, cy), - _ => return None, - }; - - Some(event) -} - -#[test] -fn test_parse_utf8() { - let string = "abcéŷ¤£€ù%323"; - let ref mut bytes = string.bytes().map(|x| Ok(x)); - let chars = string.chars(); - for c in chars { - let b = bytes.next().unwrap().unwrap(); - let character = parse_utf8_char(b, bytes).unwrap(); - assert!(c == character); - } -} diff --git a/src/unix_input.rs b/src/unix_input.rs new file mode 100644 index 0000000..c66ff6b --- /dev/null +++ b/src/unix_input.rs @@ -0,0 +1,417 @@ +use std::ffi::{c_int, c_short, c_ulong, c_void}; +use std::io; +use std::time::Duration; + +use crate::input::{ButtonType, Event, Key, Modifiers, MouseButton, key_helper, simple_key}; +use crate::unix::{POLLIN, STDIN_FILENO}; +// Some of this input code has been modified from [termion](https://github.com/redox-os/termion) + +/// Attempts to fetch input from stdin +/// +/// # Errors +/// If the timeout has expired or +/// there was an error getting the data +pub fn poll_input(timeout: Duration) -> io::Result { + let result = poll_timeout(timeout); + let mut read_iter = ReadIterator::new(); + + let timed_out: io::Error = io::ErrorKind::TimedOut.into(); + + match result { + 1.. => { + let item = read_iter.next().ok_or(timed_out)??; + parse_event(item, &mut read_iter) + } + 0 => Err(timed_out), + _ => Err(io::Error::last_os_error()), + } +} + +fn poll_timeout(timeout: Duration) -> i32 { + let mut fds = [PollFD { + fd: STDIN_FILENO, + events: POLLIN, + revents: 0, + }]; + unsafe { + #[allow(clippy::cast_possible_truncation)] + poll( + fds.as_mut_ptr(), + fds.len() as c_ulong, + timeout.as_millis() as c_int, + ) + } +} + +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 { + buf: u8, +} + +impl ReadIterator { + fn new() -> Self { + Self { buf: 0 } + } +} + +impl Iterator for ReadIterator { + type Item = io::Result; + + fn next(&mut self) -> Option { + let bytes_poll = poll_timeout(Duration::ZERO); + let bytes_read = match bytes_poll { + 1.. => Some(Ok(unsafe { + read(STDIN_FILENO, (&raw mut self.buf).cast::(), 1) + })), + 0 => None, + _ => Some(Err(io::Error::last_os_error())), + }; + match bytes_read? { + Ok(1..) => Some(Ok(self.buf)), + Ok(0) => None, + _ => Some(Err(io::Error::last_os_error())), + } + } +} + +pub(crate) fn parse_event(item: u8, iter: &mut I) -> io::Result +where + I: Iterator>, +{ + match item { + b'\x1b' => parse_ansi_sequence(iter), + b'\r' => Ok(key_helper("", Key::Char('\r'))), + b'\n' => Ok(key_helper("C", Key::Char('j'))), + b'\t' => Ok(key_helper("", Key::Char('\t'))), + b'\x7f' => Ok(key_helper("", Key::Backspace)), + b'\0' => Ok(key_helper("C", Key::Char(' '))), + c @ b'\x01'..=b'\x1a' => Ok(key_helper("C", Key::Char((c + 96) as char))), + c @ b'\x1c'..=b'\x1f' => Ok(key_helper("C", Key::Char((c + 24) as char))), + c => { + let character = parse_utf8_char(c, iter)?; + Ok(Event::Key( + Key::Char(character), + ButtonType::Press, + Modifiers::NONE.shift(character.is_uppercase()), + )) + } + } +} + +fn parse_utf8_char(c: u8, iter: &mut I) -> io::Result +where + I: Iterator>, +{ + let error = || io::Error::new(io::ErrorKind::InvalidData, "Input char is not valid UTF-8"); + let mut bytes = vec![c]; + + for _ in 1..=4 { + if let Ok(string) = std::str::from_utf8(&bytes) { + return Ok(string.chars().next().unwrap()); + } + bytes.push(iter.next().ok_or_else(error)??); + } + Err(error()) +} + +fn parse_ansi_sequence(iter: &mut I) -> io::Result +where + I: Iterator>, +{ + let error = io::Error::other("Could not parse event"); + match iter.next() { + None => Ok(key_helper("", Key::Escape)), + Some(Ok(b'O')) => match iter.next() { + Some(Ok(val @ b'P'..=b's')) => Ok(key_helper("", Key::F(1 + val - b'P'))), + _ => Err(error), + }, + Some(Ok(b'[')) => parse_csi_sequence(iter).ok_or(error), + Some(Ok(c)) => match c { + b'\r' => Ok(key_helper("A", Key::Char('\r'))), + b'\n' => Ok(key_helper("CA", Key::Char('j'))), + b'\t' => Ok(key_helper("A", Key::Char('\t'))), + b'\x7f' => Ok(key_helper("A", Key::Backspace)), + b'\0' => Ok(key_helper("CA", Key::Char(' '))), + c @ b'\x01'..=b'\x1a' => Ok(key_helper("CA", Key::Char((c + 96) as char))), + c @ b'\x1c'..=b'\x1f' => Ok(key_helper("CA", Key::Char((c + 24) as char))), + c => { + let character = parse_utf8_char(c, iter)?; + Ok(Event::Key( + Key::Char(character), + ButtonType::Press, + Modifiers::NONE.shift(character.is_uppercase()).alt(true), + )) + } + }, + _ => Err(error), + } +} + +fn parse_csi_sequence(iter: &mut I) -> Option +where + I: Iterator>, +{ + match iter.next() { + Some(Ok(b'[')) => match iter.next() { + Some(Ok(val @ b'A'..=b'E')) => Some(key_helper("", Key::F(1 + val - b'A'))), + _ => None, + }, + Some(Ok(b'D')) => Some(key_helper("", Key::Left)), + Some(Ok(b'C')) => Some(key_helper("", Key::Right)), + Some(Ok(b'A')) => Some(key_helper("", Key::Up)), + Some(Ok(b'B')) => Some(key_helper("", Key::Down)), + Some(Ok(b'H')) => Some(key_helper("", Key::Home)), + Some(Ok(b'F')) => Some(key_helper("", Key::End)), + Some(Ok(b'Z')) => Some(key_helper("", Key::Tab)), + Some(Ok(b'<')) => parse_xterm_mouse(iter), + Some(Ok(b'M')) => Some(parse_x10_mouse(iter)), + Some(Ok(c @ b'0'..=b'9')) => parse_numbered_escape(iter, c), + None => Some(key_helper("A", Key::Char('['))), + _ => None, + } +} + +fn parse_numbered_escape(iter: &mut I, c: u8) -> Option +where + I: Iterator>, +{ + let mut buf = Vec::new(); + buf.push(c); + let mut c = iter.next().unwrap().unwrap(); + // The final byte of a CSI sequence can be in the range 64-126, so let's keep reading + // anything else. + while !(64..=126).contains(&c) { + buf.push(c); + c = iter.next().unwrap().unwrap(); + } + match c { + // rxvt mouse encoding: + // ESC [ Cb ; Cx ; Cy ; M + b'M' => { + let str_buf = String::from_utf8(buf).unwrap(); + + let nums: Vec = str_buf.split(';').map(|n| n.parse().unwrap()).collect(); + + let cb = nums[0]; + let cx = nums[1]; + let cy = nums[2]; + + let mods = Modifiers::NONE; + + let event = match cb { + 32 => Event::Mouse(mods, MouseButton::Left, ButtonType::Press, cx, cy), + 33 => Event::Mouse(mods, MouseButton::Middle, ButtonType::Press, cx, cy), + 34 => Event::Mouse(mods, MouseButton::Right, ButtonType::Press, cx, cy), + 35 => Event::Mouse(mods, MouseButton::Unknown, ButtonType::Release, cx, cy), + 64 => Event::Mouse(mods, MouseButton::Unknown, ButtonType::Held, cx, cy), + 96 | 97 => Event::Mouse(mods, MouseButton::WheelUp, ButtonType::Press, cx, cy), + _ => return None, + }; + + Some(event) + } + // Special key code. + b'~' => { + let str_buf = String::from_utf8(buf).unwrap(); + + // This CSI sequence can be a list of semicolon-separated + // numbers. + let nums: Vec = str_buf.split(';').map(|n| n.parse().unwrap()).collect(); + + if nums.is_empty() { + return None; + } + + // TODO: handle multiple values for key modififiers (ex: values + // [3, 2] means Shift+Delete) + if nums.len() > 1 { + return None; + } + + match nums[0] { + 1 | 7 => Some(key_helper("", Key::Home)), + 2 => Some(key_helper("", Key::Insert)), + 3 => Some(key_helper("", Key::Delete)), + 4 | 8 => Some(key_helper("", Key::End)), + 5 => Some(key_helper("", Key::PageUp)), + 6 => Some(key_helper("", Key::PageDown)), + v @ 11..=15 => Some(key_helper("", Key::F(v - 10))), + v @ 17..=21 => Some(key_helper("", Key::F(v - 11))), + v @ 23..=24 => Some(key_helper("", Key::F(v - 12))), + _ => None, + } + } + b'u' => { + let str_buf = String::from_utf8(buf).unwrap(); + let mut iter = str_buf.split(';'); + let key_code: u32 = iter.next()?.parse().ok()?; + let mut iter = iter.next().unwrap_or("0:1").split(':'); + let modifier: u32 = iter.next()?.parse().ok()?; + let key_type: u32 = iter.next().unwrap_or("1").parse().ok()?; + println!("{str_buf}\r"); + println!("{modifier}\r"); + + let char = char::from_u32(key_code); + // let shift = modifier & 1 == 1; + // let alt = modifier & 2 == 2; + // let ctrl = modifier & 4 == 4; + let button_type = match key_type { + 1 => ButtonType::Press, + 2 => ButtonType::Held, + 3 => ButtonType::Release, + _ => return None, + }; + + Some(Event::Key(Key::Char(char?), button_type, Modifiers::NONE)) + } + b'A' | b'B' | b'C' | b'D' | b'F' | b'H' => { + let str_buf = String::from_utf8(buf).unwrap(); + + // This CSI sequence can be a list of semicolon-separated + // numbers. + let nums: Vec = str_buf.split(';').map(|n| n.parse().unwrap()).collect(); + + if !(nums.len() == 2 && nums[0] == 1) { + return None; + } + let mods = nums[1] - 1; + let shift = mods & 1 == 1; + let alt = mods & 2 == 2; + let ctrl = mods & 4 == 4; + match c { + b'D' => Some(simple_key(Key::Left, shift, alt, ctrl)), + b'C' => Some(simple_key(Key::Right, shift, alt, ctrl)), + b'A' => Some(simple_key(Key::Up, shift, alt, ctrl)), + b'B' => Some(simple_key(Key::Down, shift, alt, ctrl)), + b'H' => Some(simple_key(Key::Home, shift, alt, ctrl)), + b'F' => Some(simple_key(Key::End, shift, alt, ctrl)), + _ => None, + } + } + + _ => None, + } +} + +fn parse_x10_mouse(iter: &mut I) -> Event +where + I: Iterator>, +{ + // X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only). + let mut next = || iter.next().unwrap().unwrap(); + + let cb = next().wrapping_sub(32); + // (0, 0) are the coords for upper left. + let cx = u16::from(next().saturating_sub(33)); + let cy = u16::from(next().saturating_sub(33)); + + let mods = Modifiers::NONE; + match cb & 0b11 { + 0 => { + if cb & 0x40 != 0 { + Event::Mouse(mods, MouseButton::WheelUp, ButtonType::Press, cx, cy) + } else { + Event::Mouse(mods, MouseButton::Left, ButtonType::Press, cx, cy) + } + } + 1 => { + if cb & 0x40 != 0 { + Event::Mouse(mods, MouseButton::WheelDown, ButtonType::Press, cx, cy) + } else { + Event::Mouse(mods, MouseButton::Middle, ButtonType::Press, cx, cy) + } + } + 2 => { + if cb & 0x40 != 0 { + Event::Mouse(mods, MouseButton::WheelLeft, ButtonType::Press, cx, cy) + } else { + Event::Mouse(mods, MouseButton::Right, ButtonType::Press, cx, cy) + } + } + 3 => { + if cb & 0x40 != 0 { + Event::Mouse(mods, MouseButton::WheelRight, ButtonType::Press, cx, cy) + } else { + Event::Mouse(mods, MouseButton::Unknown, ButtonType::Release, cx, cy) + } + } + _ => unreachable!(), + } +} + +fn parse_xterm_mouse(iter: &mut I) -> Option +where + I: Iterator>, +{ + // xterm/SGR mouse encoding: + let mut buf = Vec::new(); + let mut c = iter.next().unwrap().unwrap(); + while !matches!(c, b'm' | b'M') { + buf.push(c); + c = iter.next().unwrap().unwrap(); + } + let str_buf = String::from_utf8(buf).unwrap(); + let nums = &mut str_buf.split(';'); + + let cb = nums.next()?.parse::().unwrap(); + let cx = nums.next()?.parse::().unwrap().saturating_sub(1); + let cy = nums.next()?.parse::().unwrap().saturating_sub(1); + + let shift = cb & 4 == 4; + let alt = cb & 8 == 8; + let ctrl = cb & 16 == 16; + let mods = Modifiers::new(shift, alt, ctrl); + let trimmed_cb = cb ^ (cb & 0b0001_1100); + + let event = match trimmed_cb { + 0..=2 | 64..=67 => { + let button = match trimmed_cb { + 0 => MouseButton::Left, + 1 => MouseButton::Middle, + 2 => MouseButton::Right, + 64 => MouseButton::WheelUp, + 65 => MouseButton::WheelDown, + 66 => MouseButton::WheelLeft, + 67 => MouseButton::WheelRight, + _ => unreachable!(), + }; + match c { + b'M' => Event::Mouse(mods, button, ButtonType::Press, cx, cy), + b'm' => Event::Mouse(mods, button, ButtonType::Release, cx, cy), + _ => return None, + } + } + 32 => Event::Mouse(mods, MouseButton::Left, ButtonType::Held, cx, cy), + 33 => Event::Mouse(mods, MouseButton::Middle, ButtonType::Held, cx, cy), + 34 => Event::Mouse(mods, MouseButton::Right, ButtonType::Held, cx, cy), + 35 => Event::Mouse(mods, MouseButton::None, ButtonType::Held, cx, cy), + 3 => Event::Mouse(mods, MouseButton::Unknown, ButtonType::Release, cx, cy), + _ => return None, + }; + + Some(event) +} + +#[test] +fn test_parse_utf8() { + let string = "abcéŷ¤£€ù%323"; + let ref mut bytes = string.bytes().map(|x| Ok(x)); + let chars = string.chars(); + for c in chars { + let b = bytes.next().unwrap().unwrap(); + let character = parse_utf8_char(b, bytes).unwrap(); + assert!(c == character); + } +} diff --git a/src/windows.rs b/src/windows.rs index d3d30b9..2c8aa7c 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -1,9 +1,8 @@ -use crate::input::{Event, Key, key_helper}; +use std::io; use std::os::windows::raw::HANDLE; -use std::{io, mem, time::Duration}; #[link(name = "kernel32")] -unsafe extern "system" { +unsafe extern "C" { fn GetStdHandle(std_handle: i32) -> HANDLE; fn GetConsoleMode(console_handle: HANDLE, mode: *mut u32) -> u32; fn SetConsoleMode(console_handle: HANDLE, mode: u32) -> u32; @@ -16,7 +15,6 @@ unsafe extern "system" { const STD_INPUT_HANDLE: i32 = -10; const STD_OUTPUT_HANDLE: i32 = -11; const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 4; -const ENABLE_VIRTUAL_TERMINAL_INPUT: u32 = 0x200; const ENABLE_ECHO_INPUT: u32 = 4; const ENABLE_LINE_INPUT: u32 = 2; const ENABLE_PROCESSED_INPUT: u32 = 1; @@ -30,7 +28,7 @@ struct ConsoleScreenBufferInfo { _unused: [u16; 9], } -fn get_stdin_handle() -> io::Result { +pub(crate) fn get_stdin_handle() -> io::Result { let handle = unsafe { GetStdHandle(STD_INPUT_HANDLE) }; if handle == INVALID_HANDLE_VALUE { Err(io::Error::last_os_error()) @@ -39,7 +37,7 @@ fn get_stdin_handle() -> io::Result { } } -fn get_stdout_handle() -> io::Result { +pub(crate) fn get_stdout_handle() -> io::Result { let handle = unsafe { GetStdHandle(STD_OUTPUT_HANDLE) }; if handle == INVALID_HANDLE_VALUE { Err(io::Error::last_os_error()) @@ -64,12 +62,6 @@ fn get_console_mode(handle: HANDLE, mode: &mut u32) -> io::Result<()> { } } -/// Enable kitty comprehensive keyboard handling protocol -pub fn enable_kitty_keyboard() {} - -/// Disable kitty comprehensive keyboard handling protocol -pub fn disable_kitty_keyboard() {} - /// Enable mouse input, if available /// /// # Errors @@ -122,7 +114,7 @@ pub fn disable_raw_mode() -> io::Result<()> { /// Enables ANSI support on Windows terminals /// -/// ANSI is on by default on *nix machines but still exists on them for simpler usage +/// ANSI is always enabled on *nix machines but these function still exist for simpler usage /// /// # Errors /// @@ -137,11 +129,25 @@ pub fn enable_ansi() -> io::Result<()> { get_console_mode(handle, &mut mode)?; mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; set_console_mode(handle, mode)?; + Ok(()) +} - let handle = get_stdin_handle()?; +/// Disables ANSI support on Windows terminals +/// +/// ANSI is always enabled on *nix machines but these function still exist for simpler usage +/// +/// # Errors +/// +/// Never on *nix +/// +/// On Windows, if There is no stdout, +/// if stdout isn't a TTY, or +/// if it cannot change terminal properties +pub fn disable_ansi() -> io::Result<()> { + let handle = get_stdout_handle()?; let mut mode = 0; get_console_mode(handle, &mut mode)?; - mode &= !ENABLE_VIRTUAL_TERMINAL_INPUT; + mode &= !ENABLE_VIRTUAL_TERMINAL_PROCESSING; set_console_mode(handle, mode)?; Ok(()) } @@ -158,147 +164,10 @@ pub fn enable_ansi() -> io::Result<()> { pub fn get_terminal_size() -> io::Result<(u16, u16)> { let handle = get_stdout_handle()?; let mut csbi = ConsoleScreenBufferInfo::default(); - if unsafe { GetConsoleScreenBufferInfo(handle, &mut csbi) != 0 } { + if unsafe { GetConsoleScreenBufferInfo(handle, &raw mut csbi) != 0 } { let width = csbi.x; let height = csbi.y; return Ok((width, height)); } Err(io::Error::last_os_error()) } - -#[repr(C)] -#[derive(Copy, Clone)] -struct InputRecord { - event_type: u16, - event: EventRecord, -} - -#[repr(C)] -#[derive(Copy, Clone)] -union EventRecord { - key: KeyEventRecord, - focus: FocusEventRecord, -} - -#[repr(C)] -#[derive(Copy, Clone)] -struct KeyEventRecord { - key_down: i32, - repeat_count: u16, - virtual_key_code: u16, - virtual_scan_code: u16, - u_char: CharUnion, - control_key_state: u32, -} - -#[repr(C)] -#[derive(Copy, Clone)] -struct FocusEventRecord { - set_focus: i32, -} - -#[repr(C)] -#[derive(Copy, Clone)] -union CharUnion { - unicode_char: u16, - ascii_char: u8, -} - -unsafe extern "system" { - fn ReadConsoleInputW( - console_input: HANDLE, - buffer: *mut InputRecord, - length: u32, - number_of_events_read: *mut u32, - ) -> i32; - fn WaitForSingleObject(handle: HANDLE, wait_time_ms: u32) -> u32; -} - -/// Attempts to fetch input from stdin -/// -/// # Errors -/// If the timeout has expired or -/// there was an error getting the data -pub fn poll_input(timeout: Duration) -> io::Result { - let handle = get_stdin_handle()?; - let mut record: InputRecord = unsafe { mem::zeroed() }; - let mut read = 0; - - // shut up clippy no reasonable person would expect to be able to have a poll longer than a - // month - #[allow(clippy::cast_possible_truncation)] - let wait_time_millis = timeout.as_millis() as u32; - let result = unsafe { WaitForSingleObject(handle, wait_time_millis) }; - - // The function timed out - if result != 0 { - return Err(io::ErrorKind::TimedOut.into()); - } - - let result = unsafe { ReadConsoleInputW(handle, &mut record, 1, &mut read) }; - - if result == 0 { - Err(io::Error::last_os_error())?; - } - match record.event_type { - 0x10 => { - // Focus Event - Err(io::ErrorKind::InvalidData.into()) - } - 0x1 => { - // Key Event - let key_event: KeyEventRecord = unsafe { record.event.key }; - if key_event.key_down == 0 { - // return Ok(Event::Key(KeyEvent::Null)); - // I don't quite know why but this seems to happen a lot, until I investigate - // more this will have to do - return Err(io::ErrorKind::Other.into()); - } - Ok(parse_key_event(&key_event)) - } - _ => { - //TODO Make this better - Err(io::ErrorKind::InvalidData.into()) - } - } -} - -fn parse_key_event(event: &KeyEventRecord) -> Event { - 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 => key_helper("", Key::Backspace), - 0x09 => { - if shift { - key_helper("S", Key::Tab) - } else { - key_helper("", Key::Tab) - } - } - 0x0D => key_helper("", Key::Char('\n')), - 0x1B => key_helper("", Key::Escape), - 0x21 => key_helper("", Key::PageUp), - 0x22 => key_helper("", Key::PageDown), - 0x23 => key_helper("", Key::End), - 0x24 => key_helper("", Key::Home), - 0x25 => key_helper("", Key::Left), - 0x26 => key_helper("", Key::Up), - 0x27 => key_helper("", Key::Right), - 0x28 => key_helper("", Key::Down), - 0x2D => key_helper("", Key::Insert), - 0x2E => key_helper("", Key::Delete), - // I don't think anybody is going to try to press F256 clippy - #[allow(clippy::cast_possible_truncation)] - 0x70..=0x87 => key_helper("", Key::F((event.virtual_key_code - 0x6F) as u8)), - _ => { - let num = u32::from(unsafe { event.u_char.unicode_char }); - let c = char::from_u32(num).unwrap_or(' '); - if ctrl && c.is_ascii_alphabetic() { - key_helper("C", Key::Char(c)) - } else { - key_helper("", Key::Char(c)) - } - } - } -} diff --git a/src/windows_input.rs b/src/windows_input.rs new file mode 100644 index 0000000..d5329bd --- /dev/null +++ b/src/windows_input.rs @@ -0,0 +1,144 @@ +use crate::input::{Event, key_helper, Key}; +use crate::windows::get_stdin_handle; + +use std::io; +use std::mem; +use std::os::windows::raw::HANDLE; +use std::time::Duration; + +#[repr(C)] +#[derive(Copy, Clone)] +struct InputRecord { + event_type: u16, + event: EventRecord, +} + +#[repr(C)] +#[derive(Copy, Clone)] +union EventRecord { + key: KeyEventRecord, + focus: FocusEventRecord, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct KeyEventRecord { + key_down: i32, + repeat_count: u16, + virtual_key_code: u16, + virtual_scan_code: u16, + u_char: CharUnion, + control_key_state: u32, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct FocusEventRecord { + set_focus: i32, +} + +#[repr(C)] +#[derive(Copy, Clone)] +union CharUnion { + unicode_char: u16, + ascii_char: u8, +} + +unsafe extern "system" { + fn ReadConsoleInputW( + console_input: HANDLE, + buffer: *mut InputRecord, + length: u32, + number_of_events_read: *mut u32, + ) -> i32; + fn WaitForSingleObject(handle: HANDLE, wait_time_ms: u32) -> u32; +} + +/// Attempts to fetch input from stdin +/// +/// # Errors +/// If the timeout has expired or +/// there was an error getting the data +pub fn poll_input(timeout: Duration) -> io::Result { + let handle = get_stdin_handle()?; + let mut record: InputRecord = unsafe { mem::zeroed() }; + let mut read = 0; + + // shut up clippy no reasonable person would expect to be able to have a poll longer than a + // month + #[allow(clippy::cast_possible_truncation)] + let wait_time_millis = timeout.as_millis() as u32; + let result = unsafe { WaitForSingleObject(handle, wait_time_millis) }; + + // The function timed out + if result != 0 { + return Err(io::ErrorKind::TimedOut.into()); + } + + let result = unsafe { ReadConsoleInputW(handle, &raw mut record, 1, &raw mut read) }; + + if result == 0 { + Err(io::Error::last_os_error())?; + } + match record.event_type { + 0x10 => { + // Focus Event + Err(io::ErrorKind::InvalidData.into()) + } + 0x1 => { + // Key Event + let key_event: KeyEventRecord = unsafe { record.event.key }; + if key_event.key_down == 0 { + // return Ok(Event::Key(KeyEvent::Null)); + // I don't quite know why but this seems to happen a lot, until I investigate + // more this will have to do + return Err(io::ErrorKind::Other.into()); + } + Ok(parse_key_event(&key_event)) + } + _ => { + //TODO Make this better + Err(io::ErrorKind::InvalidData.into()) + } + } +} + +fn parse_key_event(event: &KeyEventRecord) -> Event { + 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 => key_helper("", Key::Backspace), + 0x09 => { + if shift { + key_helper("S", Key::Tab) + } else { + key_helper("", Key::Tab) + } + } + 0x0D => key_helper("", Key::Char('\n')), + 0x1B => key_helper("", Key::Escape), + 0x21 => key_helper("", Key::PageUp), + 0x22 => key_helper("", Key::PageDown), + 0x23 => key_helper("", Key::End), + 0x24 => key_helper("", Key::Home), + 0x25 => key_helper("", Key::Left), + 0x26 => key_helper("", Key::Up), + 0x27 => key_helper("", Key::Right), + 0x28 => key_helper("", Key::Down), + 0x2D => key_helper("", Key::Insert), + 0x2E => key_helper("", Key::Delete), + // I don't think anybody is going to try to press F256 clippy + #[allow(clippy::cast_possible_truncation)] + 0x70..=0x87 => key_helper("", Key::F((event.virtual_key_code - 0x6F) as u8)), + _ => { + let num = u32::from(unsafe { event.u_char.unicode_char }); + let c = char::from_u32(num).unwrap_or(' '); + if ctrl && c.is_ascii_alphabetic() { + key_helper("C", Key::Char(c)) + } else { + key_helper("", Key::Char(c)) + } + } + } +}