From 0c0d5f15ef10748bc79dd550a924a1461c67b1fb Mon Sep 17 00:00:00 2001 From: Xyverle Date: Tue, 8 Jul 2025 13:18:52 -0400 Subject: [PATCH] Many Changes --- examples/input.rs | 2 +- src/ansi.rs | 92 +++++++---------------------- src/control.rs | 80 +++---------------------- src/input.rs | 63 ++++++++++---------- src/lib.rs | 63 ++++++++++++++++++++ src/unix.rs | 147 +++++++++++++++++----------------------------- src/windows.rs | 49 ++++++++-------- 7 files changed, 202 insertions(+), 294 deletions(-) diff --git a/examples/input.rs b/examples/input.rs index b097b5e..0305e3f 100644 --- a/examples/input.rs +++ b/examples/input.rs @@ -38,7 +38,7 @@ fn main() -> io::Result<()> { COLORS_BG[next(counter)] )); // q to quit - if input == Event::Key(Key::Char('q'), KeyType::Press, KeyModifiers::none()) { + if input == Event::Key(Key::Char('q'), KeyType::Press, KeyMods::NONE) { break; } counter = next(counter); diff --git a/src/ansi.rs b/src/ansi.rs index e6da106..9473d6f 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -12,6 +12,21 @@ pub use crate::unix::enable_ansi; #[cfg(windows)] pub use crate::windows::enable_ansi; +fn alt_screen(bool: bool) -> std::io::Result<()> { + if bool { + print!("{ALT_SCREEN_ENTER}"); + } else { + print!("{ALT_SCREEN_EXIT}"); + } + io::stdout().flush()?; + Ok(()) +} + +/// Creates a handler for the alt screen +pub fn alt_screen_handler() -> io::Result { + crate::Handler::new(&alt_screen) +} + /// Sets the terminal to an arbitrary 12-bit/truecolor color in the foreground when printed #[must_use] pub fn rgb_color_code_fg(red: u8, green: u8, blue: u8) -> String { @@ -86,6 +101,11 @@ pub fn move_cursor_to_position(column: u16, line: u16) -> String { ) } +// /// Enables mouse input +// pub const ENABLE_MOUSE: &str = "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h"; +// /// Disables mouse input +// pub const DISABLE_MOUSE: &str = "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l""; + /// Saves the current cursor position pub const CURSOR_POSITION_SAVE: &str = "\x1b7"; /// Restores the saved cursor position @@ -256,75 +276,3 @@ pub const COLORS: [(&str, &str); 9] = [ (COLOR_WHITE_FG, COLOR_WHITE_BG), (COLOR_DEFAULT_FG, COLOR_DEFAULT_BG), ]; - -/// Struct that prints `ALT_SCREEN_ENTER` on construction -/// and `ALT_SCREEN_EXIT` on destruction -/// -/// Prefered over function as it prints `ALT_SCREEN_EXIT` on panic -pub struct AltScreenHandler { - enabled: bool, -} - -impl AltScreenHandler { - /// Creates a new instance and sets the terminal into the alternate screen - /// - /// # Errors - /// - /// If it fails to print or flush the output - pub fn new() -> io::Result { - print!("{ALT_SCREEN_ENTER}"); - io::stdout().flush()?; - Ok(Self { enabled: true }) - } - /// Enables raw mode - /// - /// # Errors - /// - /// Never errors if the alt screen is already enabled - /// - /// If it fails to print or flush the output - pub fn enable(&mut self) -> io::Result<()> { - self.set(true) - } - /// Disables raw mode - /// - /// # Errors - /// - /// Never errors if the alt screen is already disabled - /// - /// If it fails to print or flush the output - pub fn disable(&mut self) -> io::Result<()> { - self.set(false) - } - /// Sets raw mode - /// - /// # Errors - /// - /// Never errors if the alt screen is in the same state as the boolean - /// - /// If it fails to print or flush the output - pub fn set(&mut self, alt: bool) -> io::Result<()> { - if self.enabled == alt { - return Ok(()); - } - if alt { - print!("{ALT_SCREEN_ENTER}"); - } else { - print!("{ALT_SCREEN_EXIT}"); - } - io::stdout().flush()?; - self.enabled = alt; - Ok(()) - } - /// Gets if the alt screen is enabled - #[must_use] - pub fn get(&self) -> bool { - self.enabled - } -} - -impl Drop for AltScreenHandler { - fn drop(&mut self) { - self.disable().expect("Failed to disable alternate screen"); - } -} diff --git a/src/control.rs b/src/control.rs index 8c61a83..333eb66 100644 --- a/src/control.rs +++ b/src/control.rs @@ -10,80 +10,16 @@ pub use crate::unix::{disable_raw_mode, enable_raw_mode, get_terminal_size}; #[cfg(windows)] pub use crate::windows::{disable_raw_mode, enable_raw_mode, get_terminal_size}; -/// Struct that calls `enable_raw_mode` on construction -/// and `disable_raw_mode` on destruction -/// -/// Prefered over function as it calls `disable_raw_mode` on panic -pub struct RawModeHandler { - enabled: bool, -} - -impl RawModeHandler { - /// Creates a new instance and sets the terminal to raw mode - /// - /// # Errors - /// - /// If there is no stdin, - /// stdin is not a tty, - /// or it fails to change terminal settings - pub fn new() -> io::Result { +fn raw_mode(bool: bool) -> std::io::Result<()> { + if bool { enable_raw_mode()?; - Ok(Self { enabled: true }) - } - /// Enables raw mode - /// - /// # Errors - /// - /// Never errors if raw mode is already enabled - /// - /// If there is no stdin, - /// stdin is not a tty, - /// or it fails to change terminal settings - pub fn enable(&mut self) -> io::Result<()> { - self.set(true) - } - /// Disables raw mode - /// - /// # Errors - /// - /// Never errors if raw mode is already disabled - /// - /// If there is no stdin, - /// stdin is not a tty, - /// or it fails to change terminal settings - pub fn disable(&mut self) -> io::Result<()> { - self.set(false) - } - /// Sets raw mode - /// - /// # Errors - /// - /// Never errors if raw mode is in the same state as the boolean - /// - /// If there is no stdin, - /// stdin is not a tty, - /// or it fails to change terminal settings - pub fn set(&mut self, raw: bool) -> io::Result<()> { - if self.enabled == raw { - return Ok(()); - } - if raw { - enable_raw_mode()?; - } else { - disable_raw_mode()?; - } - self.enabled = raw; - Ok(()) - } - /// Gets if raw mode is enabled - #[must_use] - pub fn get(&self) -> bool { - self.enabled + } else { + disable_raw_mode()?; } + Ok(()) } -impl Drop for RawModeHandler { - fn drop(&mut self) { - self.disable().expect("Failed to disable terminal raw mode"); - } +/// Creates a handler for raw mode +pub fn raw_mode_handler() -> io::Result { + crate::Handler::new(&raw_mode) } diff --git a/src/input.rs b/src/input.rs index 57da89f..8df10b7 100644 --- a/src/input.rs +++ b/src/input.rs @@ -12,7 +12,7 @@ #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Event { /// An event that happens upon a key being pressed - Key(Key, KeyType, KeyModifiers), + Key(Key, KeyType, KeyMods), /// An event that happens upon focus to the terminal window being gained FocusGained, /// An event that happens upon focus to the terminal window being lost @@ -52,54 +52,47 @@ pub enum Key { Char(char), /// The Escape key Escape, - /// A null byte sent to the terminal - /// - /// Can mean several different things - Null, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] #[allow(clippy::struct_excessive_bools)] -pub struct KeyModifiers { +pub struct KeyMods { pub shift: bool, pub alt: bool, pub ctrl: bool, pub meta: bool, } -impl KeyModifiers { +impl KeyMods { + pub const NONE: Self = Self { + shift: false, + alt: false, + ctrl: false, + meta: false, + }; + pub const SHIFT: Self = Self::NONE.shift(true); + pub const ALT: Self = Self::NONE.alt(true); + pub const CTRL: Self = Self::NONE.ctrl(true); + pub const META: Self = Self::NONE.meta(true); #[must_use] - pub const fn none() -> Self { - Self { - shift: false, - alt: false, - ctrl: false, - meta: false, - } + pub const fn shift(mut self, on: bool) -> Self { + self.shift = on; + self } #[must_use] - pub const fn shift(self) -> Self { - let mut value = self; - value.shift = true; - value + pub const fn alt(mut self, on: bool) -> Self { + self.alt = on; + self } #[must_use] - pub const fn alt(self) -> Self { - let mut value = self; - value.alt = true; - value + pub const fn ctrl(mut self, on: bool) -> Self { + self.ctrl = on; + self } #[must_use] - pub const fn ctrl(self) -> Self { - let mut value = self; - value.ctrl = true; - value - } - #[must_use] - pub const fn meta(self) -> Self { - let mut value = self; - value.meta = true; - value + pub const fn meta(mut self, on: bool) -> Self { + self.meta = on; + self } } @@ -113,6 +106,10 @@ pub enum KeyType { Release, } +pub fn press_key(key: Key, key_mods: KeyMods) -> Event { + Event::Key(key, KeyType::Press, key_mods) +} + #[cfg(unix)] pub use crate::unix::poll_input; diff --git a/src/lib.rs b/src/lib.rs index e3a53f7..b32e9b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,8 @@ //! //! \* Do not have full support for advanced input +use std::io; + #[cfg(unix)] mod unix; @@ -56,3 +58,64 @@ pub mod prelude { pub use crate::control::*; pub use crate::input::*; } + +/// Struct that calls `func(true)` on construction +/// and `func(false)` on destruction +pub struct Handler { + enabled: bool, + func: &'static dyn Fn(bool) -> io::Result<()>, +} + +impl Handler { + /// Creates a new instance and turns it on + /// + /// # Errors + /// + /// If the function errors + pub fn new(func: &'static dyn Fn(bool) -> io::Result<()>) -> io::Result { + let mut handler = Self { + enabled: true, + func, + }; + handler.set(true)?; + return Ok(handler); + } + /// Calls `func(true)` + /// + /// # Errors + /// + /// If the function errors + pub fn enable(&mut self) -> io::Result<()> { + self.set(true) + } + /// Calls `func(false)` + /// + /// # Errors + /// + /// If the function errors + pub fn disable(&mut self) -> io::Result<()> { + self.set(false) + } + /// Calls `func(set)` + /// + /// # Errors + /// + /// If the function errors + pub fn set(&mut self, set: bool) -> io::Result<()> { + if self.enabled != set { + (self.func)(set)?; + } + Ok(()) + } + /// Gets if it is enabled + #[must_use] + pub fn get(&self) -> bool { + self.enabled + } +} + +impl Drop for Handler { + fn drop(&mut self) { + self.disable().expect("Failed to disable terminal raw mode"); + } +} diff --git a/src/unix.rs b/src/unix.rs index 8f8c080..5b77e35 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -2,7 +2,7 @@ use std::ffi::{c_int, c_uint, c_ulong, c_ushort}; use std::io; use std::sync::LazyLock; -use crate::input::{Event, Key, KeyModifiers, KeyType}; +use crate::input::{Event, Key, KeyMods, KeyType, press_key}; use std::ffi::{c_short, c_void}; use std::time::Duration; @@ -133,6 +133,41 @@ pub fn get_terminal_size() -> io::Result<(u16, u16)> { } } +// 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 mut fds = [PollFD { + fd: STDIN_FILENO, + events: POLLIN, + revents: 0, + }]; + let result = unsafe { + #[allow(clippy::cast_possible_truncation)] + poll( + fds.as_mut_ptr(), + fds.len() as c_ulong, + timeout.as_millis() as c_int, + ) + }; + let mut read_iter = ReadIterator::new(STDIN_FILENO); + + 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()), + } +} + 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; @@ -171,87 +206,25 @@ impl Iterator for ReadIterator { } } -/// 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 mut fds = [PollFD { - fd: STDIN_FILENO, - events: POLLIN, - revents: 0, - }]; - let result = unsafe { - #[allow(clippy::cast_possible_truncation)] - poll( - fds.as_mut_ptr(), - fds.len() as c_ulong, - timeout.as_millis() as c_int, - ) - }; - let mut read_iter = ReadIterator::new(STDIN_FILENO); - - 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 try_parse_event(item: u8, iter: &mut I) -> io::Result where I: Iterator>, { match item { b'\x1b' => try_parse_ansi_sequence(iter), - b'\r' => Ok(Event::Key( - Key::Char('\n'), - KeyType::Press, - KeyModifiers::none(), - )), - b'\n' => Ok(Event::Key( - Key::Char('j'), - KeyType::Press, - KeyModifiers::none().ctrl(), - )), - b'\t' => Ok(Event::Key( - Key::Char('\t'), - KeyType::Press, - KeyModifiers::none(), - )), - b'\x7f' => Ok(Event::Key( - Key::Backspace, - KeyType::Press, - KeyModifiers::none(), - )), - b'\0' => Ok(Event::Key(Key::Null, KeyType::Press, KeyModifiers::none())), - c @ b'\x01'..=b'\x1a' => Ok(Event::Key( - Key::Char((c + 96) as char), - KeyType::Press, - KeyModifiers::none().ctrl(), - )), - c @ b'\x1c'..=b'\x1f' => Ok(Event::Key( - Key::Char((c + 24) as char), - KeyType::Press, - KeyModifiers::none().ctrl(), - )), + b'\r' => Ok(press_key(Key::Char('\r'), KeyMods::NONE)), + b'\n' => Ok(press_key(Key::Char('j'), KeyMods::CTRL)), + b'\t' => Ok(press_key(Key::Char('\t'), KeyMods::NONE)), + b'\x7f' => Ok(press_key(Key::Backspace, KeyMods::NONE)), + b'\0' => Ok(press_key(Key::Char(' '), KeyMods::CTRL)), + c @ b'\x01'..=b'\x1a' => Ok(press_key(Key::Char((c + 96) as char), KeyMods::CTRL)), + c @ b'\x1c'..=b'\x1f' => Ok(press_key(Key::Char((c + 24) as char), KeyMods::CTRL)), c => { let character = parse_utf8_char(c, iter)?; Ok(Event::Key( - Key::Char(parse_utf8_char(c, iter)?), + Key::Char(character), KeyType::Press, - KeyModifiers { - shift: character.is_uppercase(), - alt: false, - ctrl: false, - meta: false, - }, + KeyMods::NONE.shift(character.is_uppercase()), )) } } @@ -280,11 +253,7 @@ where let error = io::Error::other("Could not parse event"); match iter.next() { Some(Ok(b'O')) => match iter.next() { - Some(Ok(val @ b'P'..=b's')) => Ok(Event::Key( - Key::F(1 + val - b'P'), - KeyType::Press, - KeyModifiers::none(), - )), + Some(Ok(val @ b'P'..=b's')) => Ok(press_key(Key::F(1 + val - b'P'), KeyMods::NONE)), _ => Err(error), }, Some(Ok(b'[')) => try_parse_csi_sequence(iter).ok_or(error), @@ -298,24 +267,16 @@ where { match iter.next() { Some(Ok(b'[')) => match iter.next() { - Some(Ok(val @ b'A'..=b'E')) => Some(Event::Key( - Key::F(1 + val - b'A'), - KeyType::Press, - KeyModifiers::none(), - )), + Some(Ok(val @ b'A'..=b'E')) => Some(press_key(Key::F(1 + val - b'A'), KeyMods::NONE)), _ => None, }, - Some(Ok(b'D')) => Some(Event::Key(Key::Left, KeyType::Press, KeyModifiers::none())), - Some(Ok(b'C')) => Some(Event::Key(Key::Right, KeyType::Press, KeyModifiers::none())), - Some(Ok(b'A')) => Some(Event::Key(Key::Up, KeyType::Press, KeyModifiers::none())), - Some(Ok(b'B')) => Some(Event::Key(Key::Down, KeyType::Press, KeyModifiers::none())), - Some(Ok(b'H')) => Some(Event::Key(Key::Home, KeyType::Press, KeyModifiers::none())), - Some(Ok(b'F')) => Some(Event::Key(Key::End, KeyType::Press, KeyModifiers::none())), - Some(Ok(b'Z')) => Some(Event::Key( - Key::Tab, - KeyType::Press, - KeyModifiers::none().shift(), - )), + Some(Ok(b'D')) => Some(press_key(Key::Left, KeyMods::NONE)), + Some(Ok(b'C')) => Some(press_key(Key::Right, KeyMods::NONE)), + Some(Ok(b'A')) => Some(press_key(Key::Up, KeyMods::NONE)), + Some(Ok(b'B')) => Some(press_key(Key::Down, KeyMods::NONE)), + Some(Ok(b'H')) => Some(press_key(Key::Home, KeyMods::NONE)), + Some(Ok(b'F')) => Some(press_key(Key::End, KeyMods::NONE)), + Some(Ok(b'Z')) => Some(press_key(Key::Tab, KeyMods::SHIFT)), _ => None, } } diff --git a/src/windows.rs b/src/windows.rs index 9d91f1d..4483a70 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -1,4 +1,4 @@ -use crate::input::{Event, Key, KeyModifiers, KeyType}; +use crate::input::{Event, Key, KeyMods, KeyType, press_key}; use std::os::windows::raw::HANDLE; use std::{io, mem, time::Duration}; @@ -16,6 +16,7 @@ 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; @@ -112,6 +113,12 @@ pub fn enable_ansi() -> io::Result<()> { get_console_mode(handle, &mut mode)?; mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; set_console_mode(handle, mode)?; + + let handle = get_stdin_handle()?; + let mut mode = 0; + get_console_mode(handle, &mut mode)?; + mode &= !ENABLE_VIRTUAL_TERMINAL_INPUT; + set_console_mode(handle, mode)?; Ok(()) } @@ -237,40 +244,36 @@ fn parse_key_event(event: &KeyEventRecord) -> Event { let shift = event.control_key_state & 0x0010 != 0; // SHIFT_PRESSED match event.virtual_key_code { - 0x08 => Event::Key(Key::Backspace, KeyType::Press, KeyModifiers::none()), + 0x08 => press_key(Key::Backspace, KeyMods::NONE), 0x09 => { if shift { - Event::Key(Key::Tab, KeyType::Press, KeyModifiers::none().shift()) + press_key(Key::Tab, KeyMods::SHIFT) } else { - Event::Key(Key::Tab, KeyType::Press, KeyModifiers::none()) + press_key(Key::Tab, KeyMods::NONE) } } - 0x0D => Event::Key(Key::Char('\n'), KeyType::Press, KeyModifiers::none()), - 0x1B => Event::Key(Key::Escape, KeyType::Press, KeyModifiers::none()), - 0x21 => Event::Key(Key::PageUp, KeyType::Press, KeyModifiers::none()), - 0x22 => Event::Key(Key::PageDown, KeyType::Press, KeyModifiers::none()), - 0x23 => Event::Key(Key::End, KeyType::Press, KeyModifiers::none()), - 0x24 => Event::Key(Key::Home, KeyType::Press, KeyModifiers::none()), - 0x25 => Event::Key(Key::Left, KeyType::Press, KeyModifiers::none()), - 0x26 => Event::Key(Key::Up, KeyType::Press, KeyModifiers::none()), - 0x27 => Event::Key(Key::Right, KeyType::Press, KeyModifiers::none()), - 0x28 => Event::Key(Key::Down, KeyType::Press, KeyModifiers::none()), - 0x2D => Event::Key(Key::Insert, KeyType::Press, KeyModifiers::none()), - 0x2E => Event::Key(Key::Delete, KeyType::Press, KeyModifiers::none()), + 0x0D => press_key(Key::Char('\n'), KeyMods::NONE), + 0x1B => press_key(Key::Escape, KeyMods::NONE), + 0x21 => press_key(Key::PageUp, KeyMods::NONE), + 0x22 => press_key(Key::PageDown, KeyMods::NONE), + 0x23 => press_key(Key::End, KeyMods::NONE), + 0x24 => press_key(Key::Home, KeyMods::NONE), + 0x25 => press_key(Key::Left, KeyMods::NONE), + 0x26 => press_key(Key::Up, KeyMods::NONE), + 0x27 => press_key(Key::Right, KeyMods::NONE), + 0x28 => press_key(Key::Down, KeyMods::NONE), + 0x2D => press_key(Key::Insert, KeyMods::NONE), + 0x2E => press_key(Key::Delete, KeyMods::NONE), // I don't think anybody is going to try to press F256 clippy #[allow(clippy::cast_possible_truncation)] - 0x70..=0x87 => Event::Key( - Key::F((event.virtual_key_code - 0x6F) as u8), - KeyType::Press, - KeyModifiers::none(), - ), // F1-F24 + 0x70..=0x87 => press_key(Key::F(event.virtual_key_code - 0x6F) as u8), KeyMods::NONE), _ => { let num = u32::from(unsafe { event.u_char.unicode_char }); let c = char::from_u32(num).unwrap_or(' '); if ctrl && c.is_ascii_alphabetic() { - Event::Key(Key::Char(c), KeyType::Press, KeyModifiers::none().ctrl()) + press_key(Key::Char(c), KeyMods::CTRL) } else { - Event::Key(Key::Char(c), KeyType::Press, KeyModifiers::none()) + press_key(Key::Char(c), KeyMods::NONE) } } }