diff --git a/src/input.rs b/src/input.rs index eeda390..2754c18 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,6 +1,12 @@ //! Various input functions, structs, etc. //! //! Very incomplete currently +//! +//! # Support +//! This library attempts to support as much on all platforms but many platforms are very weird +//! +//! In general the best support will be on Kitty-like linux terminals and Windows, due to historical +//! reasons input on normal *nix terminals are limited /// Different events that can happen through the terminal #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -15,7 +21,11 @@ pub enum Event { /// An event that happens upon a key being pressed #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum KeyEvent { +pub struct KeyEvent(pub Key, pub KeyType, pub KeyModifiers); + +/// An event that happens upon a key being pressed +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum Key { /// The Backspace key Backspace, /// The Up arrow key @@ -46,8 +56,6 @@ pub enum KeyEvent { F(u8), /// Any character inputted by the keyboard Char(char), - /// Ctrl + Char - Ctrl(char), /// The Escape key Escape, /// A null byte sent to the terminal @@ -56,6 +64,61 @@ pub enum KeyEvent { Null, } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[allow(clippy::struct_excessive_bools)] +pub struct KeyModifiers { + pub shift: bool, + pub alt: bool, + pub ctrl: bool, + pub meta: bool, +} + +impl KeyModifiers { + #[must_use] + pub const fn none() -> Self { + Self { + shift: false, + alt: false, + ctrl: false, + meta: false, + } + } + #[must_use] + pub const fn shift(self) -> Self { + let mut value = self; + value.shift = true; + value + } + #[must_use] + pub const fn alt(self) -> Self { + let mut value = self; + value.alt = true; + value + } + #[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 + } +} + +/// This is the type of the key that is sent to the terminal +/// +/// This is implemented in Windows, and Kitty-like Terminals +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum KeyType { + Press, + Repeat, + Release, +} + impl From for Event { fn from(value: KeyEvent) -> Self { Self::Key(value) diff --git a/src/lib.rs b/src/lib.rs index fbd4a2b..e52626d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,29 @@ //! - [ ] Mouse input (Unix) //! - [ ] Mouse input (Windows) //! - [ ] Feature completeness / API cleanup +//! +//! ## Support +//! This library generally attempts to have as much functionality as it can but sadly many terminal +//! emulators are heavily limited, there are a few protocols I have decided not to support but for +//! the most part this holds true +//! +//! ### Protocol Support +//! This is a list of terminal protocols and whether they will be supported, they still might not +//! work as this library is work in progress but eventually will be +//! +//! Just because a protocol is listed as not planned doesn't mean it definetly won't be added but +//! it is most likely a no without good reason +//! - Standard Windows terminals (Full support planned)* +//! - WinPTY (Windows psuedo-terminals) (Full support planned) +//! - Standard *nix terminals (Full support planned)** +//! - OSC 52 system clipboard (Full support planned) +//! - Kitty comprehensive keyboard handling (Full support planned) +//! - Kitty colored and styled underlines (Full support planned) +//! - Other Kitty protocols (there are a lot of them) (Not planned) +//! +//! \* Standard Windows termian +//! +//! \** Standard *nix terminals do not have support for some advanced input #[cfg(unix)] mod unix; diff --git a/src/unix.rs b/src/unix.rs index 92069d3..775a2da 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -3,7 +3,7 @@ use std::io; unsafe extern "C" { fn ioctl(fd: c_int, request: c_ulong, argp: *mut u8) -> c_int; - safe fn cfmakeraw(termios: *mut Termios); + fn cfmakeraw(termios: *mut Termios); fn tcgetattr(fd: c_int, termios: *mut Termios) -> c_int; fn tcsetattr(fd: c_int, optional_actions: c_int, termios: *mut Termios) -> c_int; } @@ -57,14 +57,16 @@ fn set_attributes(fd: c_int, termios: &mut Termios) -> io::Result<()> { } fn make_raw(termios: &mut Termios) { - cfmakeraw(termios); - termios.iflag |= !(ICRNL); + unsafe { + cfmakeraw(termios); + } + // termios.iflag |= !(ICRNL); } pub mod os { use super::{STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ}; use super::{Termios, Winsize}; - use super::{get_attributes, set_attributes, make_raw, ioctl}; + use super::{get_attributes, ioctl, make_raw, set_attributes}; use std::io; use std::sync::LazyLock; @@ -144,7 +146,7 @@ pub mod os { pub mod input { use super::{POLLIN, STDIN_FILENO}; - use crate::input::{Event, KeyEvent}; + use crate::input::{Event, Key, KeyEvent, KeyModifiers, KeyType}; use std::ffi::{c_int, c_short, c_ulong, c_void}; use std::io; use std::time::Duration; @@ -226,13 +228,54 @@ pub mod input { { match item { b'\x1b' => try_parse_ansi_sequence(iter), - b'\n' | b'\r' => Ok(Event::Key(KeyEvent::Char('\n'))), - b'\t' => Ok(Event::Key(KeyEvent::Tab)), - b'\x7f' => Ok(Event::Key(KeyEvent::Backspace)), - b'\0' => Ok(Event::Key(KeyEvent::Null)), - c @ b'\x01'..=b'\x1a' => Ok(Event::Key(KeyEvent::Ctrl((c + 96) as char))), - c @ b'\x1c'..=b'\x1f' => Ok(Event::Key(KeyEvent::Ctrl((c + 24) as char))), - c => Ok(Event::Key(KeyEvent::Char(parse_utf8_char(c, iter)?))), + b'\r' => Ok(Event::Key(KeyEvent( + Key::Char('\n'), + KeyType::Press, + KeyModifiers::none(), + ))), + b'\n' => Ok(Event::Key(KeyEvent( + Key::Char('j'), + KeyType::Press, + KeyModifiers::none().ctrl(), + ))), + b'\t' => Ok(Event::Key(KeyEvent( + Key::Char('\t'), + KeyType::Press, + KeyModifiers::none(), + ))), + b'\x7f' => Ok(Event::Key(KeyEvent( + Key::Backspace, + KeyType::Press, + KeyModifiers::none(), + ))), + b'\0' => Ok(Event::Key(KeyEvent( + Key::Null, + KeyType::Press, + KeyModifiers::none(), + ))), + c @ b'\x01'..=b'\x1a' => Ok(Event::Key(KeyEvent( + Key::Char((c + 96) as char), + KeyType::Press, + KeyModifiers::none().ctrl(), + ))), + c @ b'\x1c'..=b'\x1f' => Ok(Event::Key(KeyEvent( + Key::Char((c + 24) as char), + KeyType::Press, + KeyModifiers::none().ctrl(), + ))), + c => { + let character = parse_utf8_char(c, iter)?; + Ok(Event::Key(KeyEvent( + Key::Char(parse_utf8_char(c, iter)?), + KeyType::Press, + KeyModifiers { + shift: character.is_uppercase(), + alt: false, + ctrl: false, + meta: false, + }, + ))) + } } } @@ -259,7 +302,11 @@ pub mod input { 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(KeyEvent::F(1 + val - b'P'))), + Some(Ok(val @ b'P'..=b's')) => Ok(Event::Key(KeyEvent( + Key::F(1 + val - b'P'), + KeyType::Press, + KeyModifiers::none(), + ))), _ => Err(error), }, Some(Ok(b'[')) => try_parse_csi_sequence(iter).ok_or(error), @@ -273,16 +320,48 @@ pub mod input { { match iter.next() { Some(Ok(b'[')) => 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( + Key::F(1 + val - b'A'), + KeyType::Press, + KeyModifiers::none(), + ))), _ => None, }, - Some(Ok(b'D')) => Some(Event::Key(KeyEvent::Left)), - Some(Ok(b'C')) => Some(Event::Key(KeyEvent::Right)), - Some(Ok(b'A')) => Some(Event::Key(KeyEvent::Up)), - Some(Ok(b'B')) => Some(Event::Key(KeyEvent::Down)), - Some(Ok(b'H')) => Some(Event::Key(KeyEvent::Home)), - Some(Ok(b'F')) => Some(Event::Key(KeyEvent::End)), - Some(Ok(b'Z')) => Some(Event::Key(KeyEvent::ShiftTab)), + Some(Ok(b'D')) => Some(Event::Key(KeyEvent( + Key::Left, + KeyType::Press, + KeyModifiers::none(), + ))), + Some(Ok(b'C')) => Some(Event::Key(KeyEvent( + Key::Right, + KeyType::Press, + KeyModifiers::none(), + ))), + Some(Ok(b'A')) => Some(Event::Key(KeyEvent( + Key::Up, + KeyType::Press, + KeyModifiers::none(), + ))), + Some(Ok(b'B')) => Some(Event::Key(KeyEvent( + Key::Down, + KeyType::Press, + KeyModifiers::none(), + ))), + Some(Ok(b'H')) => Some(Event::Key(KeyEvent( + Key::Home, + KeyType::Press, + KeyModifiers::none(), + ))), + Some(Ok(b'F')) => Some(Event::Key(KeyEvent( + Key::End, + KeyType::Press, + KeyModifiers::none(), + ))), + Some(Ok(b'Z')) => Some(Event::Key(KeyEvent( + Key::ShiftTab, + KeyType::Press, + KeyModifiers::none(), + ))), _ => None, } } diff --git a/src/windows.rs b/src/windows.rs index baf79dc..670e7b2 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -63,7 +63,10 @@ fn get_console_mode(handle: HANDLE, mode: &mut u32) -> io::Result<()> { } pub mod os { - use super::{GetConsoleScreenBufferInfo, ConsoleScreenBufferInfo, get_console_mode, get_stdin_handle, get_stdout_handle, set_console_mode}; + use super::{ + ConsoleScreenBufferInfo, GetConsoleScreenBufferInfo, get_console_mode, get_stdin_handle, + get_stdout_handle, set_console_mode, + }; use super::{ ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT, ENABLE_VIRTUAL_TERMINAL_PROCESSING, @@ -145,10 +148,10 @@ pub mod os { pub mod input { use super::get_stdin_handle; - use crate::input::{Event, KeyEvent}; + use crate::input::{Event, KeyEvent, Key, KeyModifiers, KeyType}; - use std::{io, mem, time::Duration}; use std::os::windows::raw::HANDLE; + use std::{io, mem, time::Duration}; #[repr(C)] #[derive(Copy, Clone)] @@ -252,36 +255,36 @@ pub mod input { let shift = event.control_key_state & 0x0010 != 0; // SHIFT_PRESSED match event.virtual_key_code { - 0x08 => KeyEvent::Backspace, // VK_BACK + 0x08 => KeyEvent(Key::Backspace, KeyType::Press, KeyModifiers::none()), 0x09 => { if shift { - KeyEvent::ShiftTab + KeyEvent(Key::ShiftTab, KeyType::Press, KeyModifiers::none().shift()) } else { - KeyEvent::Tab + KeyEvent(Key::Tab, KeyType::Press, KeyModifiers::none()) } } - 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, + 0x0D => KeyEvent(Key::Char('\n'), KeyType::Press, KeyModifiers::none()), + 0x1B => KeyEvent(Key::Escape, KeyType::Press, KeyModifiers::none()), + 0x21 => KeyEvent(Key::PageUp, KeyType::Press, KeyModifiers::none()), + 0x22 => KeyEvent(Key::PageDown, KeyType::Press, KeyModifiers::none()), + 0x23 => KeyEvent(Key::End, KeyType::Press, KeyModifiers::none()), + 0x24 => KeyEvent(Key::Home, KeyType::Press, KeyModifiers::none()), + 0x25 => KeyEvent(Key::Left, KeyType::Press, KeyModifiers::none()), + 0x26 => KeyEvent(Key::Up, KeyType::Press, KeyModifiers::none()), + 0x27 => KeyEvent(Key::Right, KeyType::Press, KeyModifiers::none()), + 0x28 => KeyEvent(Key::Down, KeyType::Press, KeyModifiers::none()), + 0x2D => KeyEvent(Key::Insert, KeyType::Press, KeyModifiers::none()), + 0x2E => KeyEvent(Key::Delete, KeyType::Press, KeyModifiers::none()), // I don't think anybody is going to try to press F256 clippy #[allow(clippy::cast_possible_truncation)] - 0x70..=0x87 => KeyEvent::F((event.virtual_key_code - 0x6F) as u8), // F1-F24 + 0x70..=0x87 => KeyEvent(Key::F((event.virtual_key_code - 0x6F) as u8), KeyType::Press, KeyModifiers::none()), // F1-F24 _ => { let c = char::from_u32(u32::from(unsafe { event.u_char.unicode_char })).unwrap_or(' '); if ctrl && c.is_ascii_alphabetic() { - KeyEvent::Ctrl(c) + KeyEvent(Key::Char(c), KeyType::Press, KeyModifiers::none().ctrl()) } else { - KeyEvent::Char(c) + KeyEvent(Key::Char(c), KeyType::Press, KeyModifiers::none()) } } }