From 8ffd34dcfa296dcfa10ff5ba9cb4605c6acbc10c Mon Sep 17 00:00:00 2001 From: CrispyPin Date: Wed, 22 Mar 2023 20:02:20 +0100 Subject: [PATCH] add key event parser; handles basic ascii and ctrl/alt combinations --- examples/print.rs | 24 +++++------ src/event.rs | 107 ++++++++++++++++++++++++++++++++++++++++++++-- src/raw_mode.rs | 48 +++++++++++++++------ 3 files changed, 149 insertions(+), 30 deletions(-) diff --git a/examples/print.rs b/examples/print.rs index 0e7a7a2..5c456b4 100644 --- a/examples/print.rs +++ b/examples/print.rs @@ -1,21 +1,17 @@ -use ants::{event::Events, raw_mode}; -use std::{ - io::{stdin, Read}, - time::{Duration, SystemTime}, +use ants::{ + event::{Event, Events, Key, Mod}, + raw_mode, }; fn main() { raw_mode::enter().unwrap(); - - let start_time = SystemTime::now(); - - let mut buf = Vec::new(); - while start_time.elapsed().unwrap() < Duration::from_secs(5) { - let mut b = [0u8]; - if let Ok(_n) = stdin().read(&mut b) { - if b[0] != 0 { - buf.push(b[0]); - print!("[{}]:{}\n\r", b[0], String::from_utf8_lossy(&buf)); + print!("Press Ctrl+q to exit.\r\n"); + let mut events = Events::new(); + loop { + if let Some(event) = events.next() { + print!("{:?}\r\n", event); + if let Event::Key(Key::Char('q'), Mod::Ctrl) = event { + break; } } } diff --git a/src/event.rs b/src/event.rs index c236374..f887b15 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,11 +1,112 @@ -use std::io::{stdin, Stdin}; +use std::io::{stdin, Read, Stdin}; + +const BS: u8 = 8; +const ESC: u8 = 27; +const TAB: u8 = b'\t'; +const LF: u8 = b'\n'; +const CR: u8 = b'\r'; +const DEL: u8 = 127; + +#[derive(Debug)] +pub enum Event { + Key(Key, Mod), + Unsupported(Vec), + // may add mouse events at some point +} + +#[derive(Debug)] +pub enum Mod { + None, + Shift, + Ctrl, + Alt, + /// only works for some specific keys + ShiftCtrl, + /// only works for some specific keys + ShiftAlt, + /// only works for some specific keys + CtrlAlt, + /// only works for some specific keys + ShiftCtrlAlt, +} + +#[non_exhaustive] +#[derive(Debug)] +pub enum Key { + Char(char), + Right, + Left, + Up, + Down, + Home, + End, + PageUp, + PageDown, + /// also Ctrl+H + Backspace, + Delete, + /// also Ctrl+[ + Escape, + /// \r or \n or Ctrl+M or Ctrl+J + Enter, + /// \t or Ctrl+I + Tab, + F(u8), +} pub struct Events { - bytes: Stdin, + source: Stdin, + buffer: Vec, } impl Events { pub fn new() -> Self { - Self { bytes: stdin() } + Self { + source: stdin(), + buffer: Vec::new(), + } + } + + fn parse_esc(&mut self) -> Event { + if self.buffer.is_empty() { + return Event::Key(Key::Escape, Mod::None); + } + + let byte = self.buffer[0]; + self.buffer = self.buffer[1..].to_vec(); + match byte { + ESC => Event::Key(Key::Escape, Mod::None), + BS => Event::Key(Key::Backspace, Mod::Alt), + DEL => Event::Key(Key::Delete, Mod::Alt), + LF | CR => Event::Key(Key::Enter, Mod::Alt), + TAB => Event::Key(Key::Tab, Mod::Alt), + c @ 1..=26 => Event::Key(Key::Char((c + 96).into()), Mod::CtrlAlt), // ctrl + letter + c @ b' '..=b'~' => Event::Key(Key::Char(c.into()), Mod::Alt), // regular ascii charaters + _ => Event::Unsupported(vec![ESC, byte]), + } + } +} + +impl Iterator for Events { + type Item = Event; + + fn next(&mut self) -> Option { + self.source.read_to_end(&mut self.buffer).ok()?; + if self.buffer.is_empty() { + return None; + } + + let byte = self.buffer[0]; + self.buffer = self.buffer[1..].to_vec(); + Some(match byte { + ESC => self.parse_esc(), + BS => Event::Key(Key::Backspace, Mod::None), + DEL => Event::Key(Key::Delete, Mod::None), + LF | CR => Event::Key(Key::Enter, Mod::None), + TAB => Event::Key(Key::Tab, Mod::None), + c @ 1..=26 => Event::Key(Key::Char((c + 96).into()), Mod::Ctrl), // ctrl + letter + c @ b' '..=b'~' => Event::Key(Key::Char(c.into()), Mod::None), // regular ascii charaters + _ => Event::Unsupported(vec![byte]), + }) } } diff --git a/src/raw_mode.rs b/src/raw_mode.rs index 74e5afa..70e3377 100644 --- a/src/raw_mode.rs +++ b/src/raw_mode.rs @@ -1,12 +1,13 @@ +#![allow(unused)] use std::{ ffi::{c_char, c_int, c_uint}, - io::{self, Result}, + io, os::fd::{self, AsFd}, }; static mut PREV_TERMINAL_STATE: Option = None; -pub fn enter() -> Result<()> { +pub fn enter() -> io::Result<()> { if unsafe { PREV_TERMINAL_STATE.is_some() } { return Ok(()); } @@ -20,20 +21,22 @@ pub fn enter() -> Result<()> { PREV_TERMINAL_STATE = Some(state.clone()); } // https://man7.org/linux/man-pages/man3/termios.3.html - // termios.c_iflag &= !(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); - // termios.c_oflag &= !OPOST; - // termios.c_lflag &= !(ECHO | ECHONL | ICANON | ISIG | IEXTEN); - // termios.c_cflag &= !(CSIZE | PARENB); - // termios.c_cflag |= CS8; + state.c_iflag &= !(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); + state.c_oflag &= !OPOST; + state.c_lflag &= !(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + state.c_cflag &= !(CSIZE | PARENB); + state.c_cflag |= CS8; + state.c_cc[VMIN] = 0; // minimum bytes read before read() returns + state.c_cc[VTIME] = 0; // timeout in 100ms increments (set to none) unsafe { // set to raw mode - cfmakeraw(&state); + // cfmakeraw(&state); check_err(tcsetattr(stdin.as_fd(), 0, &state))?; } Ok(()) } -pub fn exit() -> Result<()> { +pub fn exit() -> io::Result<()> { unsafe { if let Some(termios) = PREV_TERMINAL_STATE.as_ref() { let stdin = io::stdin(); @@ -43,7 +46,7 @@ pub fn exit() -> Result<()> { Ok(()) } -fn check_err(val: c_int) -> Result<()> { +fn check_err(val: c_int) -> io::Result<()> { if val == -1 { Err(io::Error::last_os_error()) } else { @@ -61,8 +64,8 @@ struct Termios { pub c_line: c_char, pub c_cc: [c_char; 32], // these two seem to be platform specific and don't need to be used - // pub c_ispeed: c_int, - // pub c_ospeed: c_int, + pub c_ispeed: c_int, + pub c_ospeed: c_int, } #[link(name = "c")] @@ -74,7 +77,7 @@ extern "C" { /// set terminal flags to raw mode fn cfmakeraw(termios_p: *const Termios); } -/* +//* const TCIOFF: c_int = 2; const TCION: c_int = 3; const TCOOFF: c_int = 0; @@ -115,6 +118,25 @@ const ONLRET: c_uint = 0o000040; const OFILL: c_uint = 0o000100; const OFDEL: c_uint = 0o000200; +const SIGSTKSZ: usize = 8192; +const MINSIGSTKSZ: usize = 2048; +const CBAUD: c_uint = 0o0010017; +const TAB1: c_int = 0x00000800; +const TAB2: c_int = 0x00001000; +const TAB3: c_int = 0x00001800; +const CR1: c_int = 0x00000200; +const CR2: c_int = 0x00000400; +const CR3: c_int = 0x00000600; +const FF1: c_int = 0x00008000; +const BS1: c_int = 0x00002000; +const VT1: c_int = 0x00004000; +const VWERASE: usize = 14; +const VREPRINT: usize = 12; +const VSUSP: usize = 10; +const VSTART: usize = 8; +const VSTOP: usize = 9; +const VDISCARD: usize = 13; +const VTIME: usize = 5; const IXON: c_uint = 0x00000400; const IXOFF: c_uint = 0x00001000; const ONLCR: c_uint = 0x4;