use std::io::{self, Read, Write, IsTerminal}; use std::os::unix::io::AsRawFd; use termios::*; struct ScopedRawMode { fd: i32, settings: Termios, } impl Drop for ScopedRawMode { fn drop(&mut self) { self.disable(); } } impl ScopedRawMode { fn on_fd(fd: i32) -> Self { let mut termios = Termios::from_fd(fd).unwrap(); let settings = termios.clone(); cfmakeraw(&mut termios); tcsetattr(fd, TCSANOW, &termios).unwrap(); Self { fd, settings } } fn disable(&self) { tcsetattr(self.fd, TCSANOW, &self.settings).unwrap(); } } macro_rules! print { ($($x:tt)*) => {{ write!(io::stdout(), $($x)*).unwrap(); io::stdout().flush().unwrap(); }} } fn main() { let stdin = io::stdin(); let stdout = io::stdout(); if !stdin.is_terminal() { println!("need to run in a tty"); return; } let fd = stdin.as_raw_fd(); let _scoped_raw = ScopedRawMode::on_fd(fd); // needs to be a var, gets dropped on scope exit, // even if something panics let mut stdin = stdin.lock(); let mut stdout = stdout.lock(); let mut buffer = [0u8; 1]; loop { let Ok(_) = stdin.read_exact(&mut buffer) else { break; }; match buffer[0] { // EOF 4 => { break; } // Enter b'\r' => { println!("\r"); } // Backspace (127 on most systems) 127 => { write!(stdout, "\x08 \x08").unwrap(); stdout.flush().unwrap(); } // Escape sequence 27 => { let mut seq = [0u8; 2]; stdin.read_exact(&mut seq).unwrap(); if seq[0] == b'[' { match seq[1] { b'A' => print!("Up"), b'B' => print!("Down"), b'C' => print!("Right"), b'D' => print!("Left"), _ => {} } } } // Normal character x => { stdout.write(&[x]).unwrap(); stdout.flush().unwrap(); } } } }