add key event parser; handles basic ascii and ctrl/alt combinations

This commit is contained in:
Crispy 2023-03-22 20:02:20 +01:00
parent 6ffb32c543
commit 8ffd34dcfa
3 changed files with 149 additions and 30 deletions

View file

@ -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;
}
}
}

View file

@ -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<u8>),
// 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<u8>,
}
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::Item> {
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]),
})
}
}

View file

@ -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<Termios> = 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;