use std::collections::VecDeque;
use std::sync::Mutex;
const HIDE_CURSOR: &[u8] = b"\x1b[?25l";
const SHOW_CURSOR: &[u8] = b"\x1b[?25h";
static SAVED: Mutex<Option<libc::termios>> = Mutex::new(None);
static EVENTS: Mutex<VecDeque<Key>> = Mutex::new(VecDeque::new());
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Key {
Char(u8),
Up,
Down,
PageUp,
PageDown,
Home,
End,
Left,
Right,
Enter,
Esc,
}
pub fn restore() {
unsafe {
libc::write(
libc::STDOUT_FILENO,
SHOW_CURSOR.as_ptr() as *const libc::c_void,
SHOW_CURSOR.len(),
);
}
if let Ok(mut guard) = SAVED.lock() {
if let Some(settings) = guard.take() {
unsafe {
libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &settings);
}
}
}
}
extern "C" fn on_signal(_signal: libc::c_int) {
restore();
unsafe { libc::_exit(0) }
}
pub fn watch() -> bool {
if !crate::sys::stdin_is_terminal() {
return false;
}
let mut settings: libc::termios = unsafe { std::mem::zeroed() };
if unsafe { libc::tcgetattr(libc::STDIN_FILENO, &mut settings) } != 0 {
return false;
}
if let Ok(mut guard) = SAVED.lock() {
*guard = Some(settings);
}
let mut raw = settings;
raw.c_lflag &= !(libc::ICANON | libc::ECHO);
raw.c_cc[libc::VMIN] = 1;
raw.c_cc[libc::VTIME] = 0;
if unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &raw) } != 0 {
return false;
}
unsafe {
libc::write(
libc::STDOUT_FILENO,
HIDE_CURSOR.as_ptr() as *const libc::c_void,
HIDE_CURSOR.len(),
);
}
unsafe {
let handler = on_signal as *const () as libc::sighandler_t;
libc::signal(libc::SIGINT, handler);
libc::signal(libc::SIGTERM, handler);
libc::signal(libc::SIGHUP, handler);
}
std::thread::spawn(|| {
while let Some(key) = read_key() {
push(key);
}
});
true
}
pub fn take() -> Vec<Key> {
match EVENTS.lock() {
Ok(mut q) => q.drain(..).collect(),
Err(_) => Vec::new(),
}
}
fn push(key: Key) {
if let Ok(mut q) = EVENTS.lock() {
q.push_back(key);
}
}
fn decode_plain(byte: u8) -> Key {
match byte {
b'\n' | b'\r' => Key::Enter,
other => Key::Char(other),
}
}
fn read_key() -> Option<Key> {
let first = read_fd()?;
if first != 0x1b {
return Some(decode_plain(first));
}
if !stdin_ready(50) {
return Some(Key::Esc);
}
let mut buf = vec![0x1b];
while buf.len() < 16 {
let Some(byte) = read_fd() else {
break;
};
buf.push(byte);
if sequence_done(&buf) {
break;
}
if !stdin_ready(20) {
break;
}
}
Some(decode(&buf).map(|(key, _)| key).unwrap_or(Key::Esc))
}
fn sequence_done(buf: &[u8]) -> bool {
if buf.len() < 3 || buf[0] != 0x1b {
return false;
}
match buf[1] {
b'[' => (0x40..=0x7e).contains(buf.last().unwrap()),
b'O' => buf.len() >= 3,
_ => true,
}
}
fn read_fd() -> Option<u8> {
let mut byte = [0u8; 1];
loop {
let n = unsafe {
libc::read(
libc::STDIN_FILENO,
byte.as_mut_ptr() as *mut libc::c_void,
1,
)
};
if n == 1 {
return Some(byte[0]);
}
if n < 0 {
let err = std::io::Error::last_os_error();
if err.kind() == std::io::ErrorKind::Interrupted {
continue;
}
}
return None;
}
}
fn stdin_ready(timeout_ms: i32) -> bool {
let mut fd = libc::pollfd {
fd: libc::STDIN_FILENO,
events: libc::POLLIN,
revents: 0,
};
unsafe { libc::poll(&mut fd, 1, timeout_ms) > 0 }
}
pub fn decode(bytes: &[u8]) -> Option<(Key, usize)> {
let first = *bytes.first()?;
if first != 0x1b {
return Some((decode_plain(first), 1));
}
if bytes.len() >= 3 && (bytes[1] == b'[' || bytes[1] == b'O') {
if let Some(key) = decode_csi(bytes) {
return Some((key, bytes.len()));
}
}
Some((Key::Esc, 1))
}
fn decode_csi(bytes: &[u8]) -> Option<Key> {
match *bytes.last()? {
b'A' => Some(Key::Up),
b'B' => Some(Key::Down),
b'C' => Some(Key::Right),
b'D' => Some(Key::Left),
b'H' => Some(Key::Home),
b'F' => Some(Key::End),
b'~' => match csi_number(bytes) {
5 => Some(Key::PageUp),
6 => Some(Key::PageDown),
1 | 7 => Some(Key::Home),
4 | 8 => Some(Key::End),
_ => None,
},
_ => None,
}
}
fn csi_number(bytes: &[u8]) -> u32 {
let mid = bytes.get(2..bytes.len().saturating_sub(1)).unwrap_or(&[]);
let digits: String = mid
.iter()
.take_while(|b| b.is_ascii_digit())
.map(|b| *b as char)
.collect();
digits.parse().unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_letter_is_a_character() {
assert_eq!(decode(b"q"), Some((Key::Char(b'q'), 1)));
assert_eq!(decode(b"j"), Some((Key::Char(b'j'), 1)));
}
#[test]
fn enter_and_return_are_the_same_key() {
assert_eq!(decode(b"\n"), Some((Key::Enter, 1)));
assert_eq!(decode(b"\r"), Some((Key::Enter, 1)));
}
#[test]
fn an_arrow_is_one_key() {
assert_eq!(decode(b"\x1b[A"), Some((Key::Up, 3)));
assert_eq!(decode(b"\x1b[B"), Some((Key::Down, 3)));
assert_eq!(decode(b"\x1bOA"), Some((Key::Up, 3)));
assert_eq!(decode(b"\x1bOB"), Some((Key::Down, 3)));
assert_eq!(decode(b"\x1b[1;5A"), Some((Key::Up, 6)));
assert_eq!(decode(b"\x1b[1;2B"), Some((Key::Down, 6)));
assert_eq!(decode(b"\x1b[C"), Some((Key::Right, 3)));
assert_eq!(decode(b"\x1b[D"), Some((Key::Left, 3)));
}
#[test]
fn page_and_home_keys_decode() {
assert_eq!(decode(b"\x1b[5~"), Some((Key::PageUp, 4)));
assert_eq!(decode(b"\x1b[6~"), Some((Key::PageDown, 4)));
assert_eq!(decode(b"\x1b[H"), Some((Key::Home, 3)));
assert_eq!(decode(b"\x1b[F"), Some((Key::End, 3)));
assert_eq!(decode(b"\x1b[1~"), Some((Key::Home, 4)));
assert_eq!(decode(b"\x1b[4~"), Some((Key::End, 4)));
}
#[test]
fn a_lone_escape_is_escape() {
assert_eq!(decode(b"\x1b"), Some((Key::Esc, 1)));
}
}