add key event parser; handles basic ascii and ctrl/alt combinations
This commit is contained in:
parent
6ffb32c543
commit
8ffd34dcfa
3 changed files with 149 additions and 30 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
107
src/event.rs
107
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<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]),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue