Key Processing Refactor

This commit is contained in:
2025-06-22 11:45:50 -04:00
parent 55012807f0
commit 6e4039bba6
4 changed files with 213 additions and 45 deletions

View File

@@ -1,6 +1,12 @@
//! Various input functions, structs, etc. //! Various input functions, structs, etc.
//! //!
//! Very incomplete currently //! 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 /// Different events that can happen through the terminal
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -15,7 +21,11 @@ pub enum Event {
/// An event that happens upon a key being pressed /// An event that happens upon a key being pressed
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[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 /// The Backspace key
Backspace, Backspace,
/// The Up arrow key /// The Up arrow key
@@ -46,8 +56,6 @@ pub enum KeyEvent {
F(u8), F(u8),
/// Any character inputted by the keyboard /// Any character inputted by the keyboard
Char(char), Char(char),
/// Ctrl + Char
Ctrl(char),
/// The Escape key /// The Escape key
Escape, Escape,
/// A null byte sent to the terminal /// A null byte sent to the terminal
@@ -56,6 +64,61 @@ pub enum KeyEvent {
Null, 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<KeyEvent> for Event { impl From<KeyEvent> for Event {
fn from(value: KeyEvent) -> Self { fn from(value: KeyEvent) -> Self {
Self::Key(value) Self::Key(value)

View File

@@ -16,6 +16,29 @@
//! - [ ] Mouse input (Unix) //! - [ ] Mouse input (Unix)
//! - [ ] Mouse input (Windows) //! - [ ] Mouse input (Windows)
//! - [ ] Feature completeness / API cleanup //! - [ ] 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)] #[cfg(unix)]
mod unix; mod unix;

View File

@@ -3,7 +3,7 @@ use std::io;
unsafe extern "C" { unsafe extern "C" {
fn ioctl(fd: c_int, request: c_ulong, argp: *mut u8) -> c_int; 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 tcgetattr(fd: c_int, termios: *mut Termios) -> c_int;
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;
} }
@@ -57,14 +57,16 @@ fn set_attributes(fd: c_int, termios: &mut Termios) -> io::Result<()> {
} }
fn make_raw(termios: &mut Termios) { fn make_raw(termios: &mut Termios) {
cfmakeraw(termios); unsafe {
termios.iflag |= !(ICRNL); cfmakeraw(termios);
}
// termios.iflag |= !(ICRNL);
} }
pub mod os { pub mod os {
use super::{STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ}; use super::{STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ};
use super::{Termios, Winsize}; 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::io;
use std::sync::LazyLock; use std::sync::LazyLock;
@@ -144,7 +146,7 @@ pub mod os {
pub mod input { pub mod input {
use super::{POLLIN, STDIN_FILENO}; 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::ffi::{c_int, c_short, c_ulong, c_void};
use std::io; use std::io;
use std::time::Duration; use std::time::Duration;
@@ -226,13 +228,54 @@ pub mod input {
{ {
match item { match item {
b'\x1b' => try_parse_ansi_sequence(iter), b'\x1b' => try_parse_ansi_sequence(iter),
b'\n' | b'\r' => Ok(Event::Key(KeyEvent::Char('\n'))), b'\r' => Ok(Event::Key(KeyEvent(
b'\t' => Ok(Event::Key(KeyEvent::Tab)), Key::Char('\n'),
b'\x7f' => Ok(Event::Key(KeyEvent::Backspace)), KeyType::Press,
b'\0' => Ok(Event::Key(KeyEvent::Null)), KeyModifiers::none(),
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))), b'\n' => Ok(Event::Key(KeyEvent(
c => Ok(Event::Key(KeyEvent::Char(parse_utf8_char(c, iter)?))), 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"); let error = io::Error::other("Could not parse event");
match iter.next() { match iter.next() {
Some(Ok(b'O')) => 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), _ => Err(error),
}, },
Some(Ok(b'[')) => try_parse_csi_sequence(iter).ok_or(error), Some(Ok(b'[')) => try_parse_csi_sequence(iter).ok_or(error),
@@ -273,16 +320,48 @@ pub mod input {
{ {
match iter.next() { match iter.next() {
Some(Ok(b'[')) => 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, _ => None,
}, },
Some(Ok(b'D')) => Some(Event::Key(KeyEvent::Left)), Some(Ok(b'D')) => Some(Event::Key(KeyEvent(
Some(Ok(b'C')) => Some(Event::Key(KeyEvent::Right)), Key::Left,
Some(Ok(b'A')) => Some(Event::Key(KeyEvent::Up)), KeyType::Press,
Some(Ok(b'B')) => Some(Event::Key(KeyEvent::Down)), KeyModifiers::none(),
Some(Ok(b'H')) => Some(Event::Key(KeyEvent::Home)), ))),
Some(Ok(b'F')) => Some(Event::Key(KeyEvent::End)), Some(Ok(b'C')) => Some(Event::Key(KeyEvent(
Some(Ok(b'Z')) => Some(Event::Key(KeyEvent::ShiftTab)), 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, _ => None,
} }
} }

View File

@@ -63,7 +63,10 @@ fn get_console_mode(handle: HANDLE, mode: &mut u32) -> io::Result<()> {
} }
pub mod os { 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::{ use super::{
ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT,
ENABLE_VIRTUAL_TERMINAL_PROCESSING, ENABLE_VIRTUAL_TERMINAL_PROCESSING,
@@ -145,10 +148,10 @@ pub mod os {
pub mod input { pub mod input {
use super::get_stdin_handle; 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::os::windows::raw::HANDLE;
use std::{io, mem, time::Duration};
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@@ -252,36 +255,36 @@ pub mod input {
let shift = event.control_key_state & 0x0010 != 0; // SHIFT_PRESSED let shift = event.control_key_state & 0x0010 != 0; // SHIFT_PRESSED
match event.virtual_key_code { match event.virtual_key_code {
0x08 => KeyEvent::Backspace, // VK_BACK 0x08 => KeyEvent(Key::Backspace, KeyType::Press, KeyModifiers::none()),
0x09 => { 0x09 => {
if shift { if shift {
KeyEvent::ShiftTab KeyEvent(Key::ShiftTab, KeyType::Press, KeyModifiers::none().shift())
} else { } else {
KeyEvent::Tab KeyEvent(Key::Tab, KeyType::Press, KeyModifiers::none())
} }
} }
0x0D => KeyEvent::Char('\n'), 0x0D => KeyEvent(Key::Char('\n'), KeyType::Press, KeyModifiers::none()),
0x1B => KeyEvent::Escape, 0x1B => KeyEvent(Key::Escape, KeyType::Press, KeyModifiers::none()),
0x21 => KeyEvent::PageUp, 0x21 => KeyEvent(Key::PageUp, KeyType::Press, KeyModifiers::none()),
0x22 => KeyEvent::PageDown, 0x22 => KeyEvent(Key::PageDown, KeyType::Press, KeyModifiers::none()),
0x23 => KeyEvent::End, 0x23 => KeyEvent(Key::End, KeyType::Press, KeyModifiers::none()),
0x24 => KeyEvent::Home, 0x24 => KeyEvent(Key::Home, KeyType::Press, KeyModifiers::none()),
0x25 => KeyEvent::Left, 0x25 => KeyEvent(Key::Left, KeyType::Press, KeyModifiers::none()),
0x26 => KeyEvent::Up, 0x26 => KeyEvent(Key::Up, KeyType::Press, KeyModifiers::none()),
0x27 => KeyEvent::Right, 0x27 => KeyEvent(Key::Right, KeyType::Press, KeyModifiers::none()),
0x28 => KeyEvent::Down, 0x28 => KeyEvent(Key::Down, KeyType::Press, KeyModifiers::none()),
0x2D => KeyEvent::Insert, 0x2D => KeyEvent(Key::Insert, KeyType::Press, KeyModifiers::none()),
0x2E => KeyEvent::Delete, 0x2E => KeyEvent(Key::Delete, KeyType::Press, KeyModifiers::none()),
// I don't think anybody is going to try to press F256 clippy // I don't think anybody is going to try to press F256 clippy
#[allow(clippy::cast_possible_truncation)] #[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 = let c =
char::from_u32(u32::from(unsafe { event.u_char.unicode_char })).unwrap_or(' '); char::from_u32(u32::from(unsafe { event.u_char.unicode_char })).unwrap_or(' ');
if ctrl && c.is_ascii_alphabetic() { if ctrl && c.is_ascii_alphabetic() {
KeyEvent::Ctrl(c) KeyEvent(Key::Char(c), KeyType::Press, KeyModifiers::none().ctrl())
} else { } else {
KeyEvent::Char(c) KeyEvent(Key::Char(c), KeyType::Press, KeyModifiers::none())
} }
} }
} }