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 ants::{
|
||||||
use std::{
|
event::{Event, Events, Key, Mod},
|
||||||
io::{stdin, Read},
|
raw_mode,
|
||||||
time::{Duration, SystemTime},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
raw_mode::enter().unwrap();
|
raw_mode::enter().unwrap();
|
||||||
|
print!("Press Ctrl+q to exit.\r\n");
|
||||||
let start_time = SystemTime::now();
|
let mut events = Events::new();
|
||||||
|
loop {
|
||||||
let mut buf = Vec::new();
|
if let Some(event) = events.next() {
|
||||||
while start_time.elapsed().unwrap() < Duration::from_secs(5) {
|
print!("{:?}\r\n", event);
|
||||||
let mut b = [0u8];
|
if let Event::Key(Key::Char('q'), Mod::Ctrl) = event {
|
||||||
if let Ok(_n) = stdin().read(&mut b) {
|
break;
|
||||||
if b[0] != 0 {
|
|
||||||
buf.push(b[0]);
|
|
||||||
print!("[{}]:{}\n\r", b[0], String::from_utf8_lossy(&buf));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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 {
|
pub struct Events {
|
||||||
bytes: Stdin,
|
source: Stdin,
|
||||||
|
buffer: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Events {
|
impl Events {
|
||||||
pub fn new() -> Self {
|
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::{
|
use std::{
|
||||||
ffi::{c_char, c_int, c_uint},
|
ffi::{c_char, c_int, c_uint},
|
||||||
io::{self, Result},
|
io,
|
||||||
os::fd::{self, AsFd},
|
os::fd::{self, AsFd},
|
||||||
};
|
};
|
||||||
|
|
||||||
static mut PREV_TERMINAL_STATE: Option<Termios> = None;
|
static mut PREV_TERMINAL_STATE: Option<Termios> = None;
|
||||||
|
|
||||||
pub fn enter() -> Result<()> {
|
pub fn enter() -> io::Result<()> {
|
||||||
if unsafe { PREV_TERMINAL_STATE.is_some() } {
|
if unsafe { PREV_TERMINAL_STATE.is_some() } {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -20,20 +21,22 @@ pub fn enter() -> Result<()> {
|
||||||
PREV_TERMINAL_STATE = Some(state.clone());
|
PREV_TERMINAL_STATE = Some(state.clone());
|
||||||
}
|
}
|
||||||
// https://man7.org/linux/man-pages/man3/termios.3.html
|
// https://man7.org/linux/man-pages/man3/termios.3.html
|
||||||
// termios.c_iflag &= !(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
|
state.c_iflag &= !(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
|
||||||
// termios.c_oflag &= !OPOST;
|
state.c_oflag &= !OPOST;
|
||||||
// termios.c_lflag &= !(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
state.c_lflag &= !(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
||||||
// termios.c_cflag &= !(CSIZE | PARENB);
|
state.c_cflag &= !(CSIZE | PARENB);
|
||||||
// termios.c_cflag |= CS8;
|
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 {
|
unsafe {
|
||||||
// set to raw mode
|
// set to raw mode
|
||||||
cfmakeraw(&state);
|
// cfmakeraw(&state);
|
||||||
check_err(tcsetattr(stdin.as_fd(), 0, &state))?;
|
check_err(tcsetattr(stdin.as_fd(), 0, &state))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exit() -> Result<()> {
|
pub fn exit() -> io::Result<()> {
|
||||||
unsafe {
|
unsafe {
|
||||||
if let Some(termios) = PREV_TERMINAL_STATE.as_ref() {
|
if let Some(termios) = PREV_TERMINAL_STATE.as_ref() {
|
||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
|
@ -43,7 +46,7 @@ pub fn exit() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_err(val: c_int) -> Result<()> {
|
fn check_err(val: c_int) -> io::Result<()> {
|
||||||
if val == -1 {
|
if val == -1 {
|
||||||
Err(io::Error::last_os_error())
|
Err(io::Error::last_os_error())
|
||||||
} else {
|
} else {
|
||||||
|
@ -61,8 +64,8 @@ struct Termios {
|
||||||
pub c_line: c_char,
|
pub c_line: c_char,
|
||||||
pub c_cc: [c_char; 32],
|
pub c_cc: [c_char; 32],
|
||||||
// these two seem to be platform specific and don't need to be used
|
// these two seem to be platform specific and don't need to be used
|
||||||
// pub c_ispeed: c_int,
|
pub c_ispeed: c_int,
|
||||||
// pub c_ospeed: c_int,
|
pub c_ospeed: c_int,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[link(name = "c")]
|
#[link(name = "c")]
|
||||||
|
@ -74,7 +77,7 @@ extern "C" {
|
||||||
/// set terminal flags to raw mode
|
/// set terminal flags to raw mode
|
||||||
fn cfmakeraw(termios_p: *const Termios);
|
fn cfmakeraw(termios_p: *const Termios);
|
||||||
}
|
}
|
||||||
/*
|
//*
|
||||||
const TCIOFF: c_int = 2;
|
const TCIOFF: c_int = 2;
|
||||||
const TCION: c_int = 3;
|
const TCION: c_int = 3;
|
||||||
const TCOOFF: c_int = 0;
|
const TCOOFF: c_int = 0;
|
||||||
|
@ -115,6 +118,25 @@ const ONLRET: c_uint = 0o000040;
|
||||||
const OFILL: c_uint = 0o000100;
|
const OFILL: c_uint = 0o000100;
|
||||||
const OFDEL: c_uint = 0o000200;
|
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 IXON: c_uint = 0x00000400;
|
||||||
const IXOFF: c_uint = 0x00001000;
|
const IXOFF: c_uint = 0x00001000;
|
||||||
const ONLCR: c_uint = 0x4;
|
const ONLCR: c_uint = 0x4;
|
||||||
|
|
Loading…
Reference in a new issue