use libc::{termios, winsize}; use std::error::Error; use std::io::{stdout, BufRead, BufReader, Read, Stdin, Write}; const ESC: u8 = 27; pub fn size() -> Result> { unsafe { let mut size = std::mem::zeroed(); if libc::ioctl(1, libc::TIOCGWINSZ, &mut size) < 0 { return Err(Box::from("ioctl failed")); } Ok(size) } } pub struct RawHandle { old_attr: termios, } impl Drop for RawHandle { fn drop(&mut self) { unsafe { libc::tcsetattr(1, libc::TCSANOW, &self.old_attr); } let _ = write!(stdout(), "\x1b[?7h\x1b[2J"); let _ = stdout().flush(); } } pub fn raw_mode() -> Result> { unsafe { let mut attr = std::mem::zeroed(); if libc::tcgetattr(1, &mut attr) < 0 { return Err(Box::from("tcgetattr failed")); } let mut raw = std::mem::zeroed(); libc::cfmakeraw(&mut raw); raw.c_cc[libc::VTIME] = 1; raw.c_cc[libc::VMIN] = 0; if libc::tcsetattr(1, libc::TCSANOW, &raw) < 0 { return Err(Box::from("tcsetattr failed")); } write!(stdout(), "\x1b[?7l")?; stdout().flush()?; Ok(RawHandle { old_attr: attr }) } } pub enum Key { Timeout, Up, Down, Left, Right, Byte(u8), } pub fn read_key(stdin: &mut BufReader) -> Result> { loop { let mut buf = vec![0; 1]; let n = stdin.read(&mut buf)?; if n == 0 { return Ok(Key::Timeout); } if buf[0] != ESC { return Ok(Key::Byte(buf[0])); } let buf = stdin.fill_buf()?; if buf.is_empty() { continue; } if buf[0] != b'[' || buf.len() == 1 { stdin.consume(1); continue; } let mut n = buf.len(); for (i, &c) in buf.iter().enumerate() { match c { b'A'..=b'Z' | b'a'..=b'z' => { n = i + 1; break; } _ => (), } } let c = buf[1]; stdin.consume(n); match c { b'A' => { return Ok(Key::Up); } b'B' => { return Ok(Key::Down); } b'C' => { return Ok(Key::Right); } b'D' => { return Ok(Key::Left); } _ => { continue; } } } } // cursor_pos *WILL* fail if the user is typing on the keyboard. Be sure to handle // errors appropriately. pub fn cursor_pos(stdin: &mut BufReader) -> Result<(u16, u16), Box> { if !stdin.buffer().is_empty() { // If there is any buffered input, the below call to fill_buf will just return the buffered // data and not actually read the terminal response. return Err(Box::from("interrupted")); } write!(stdout(), "\x1b[6n")?; stdout().flush()?; let buf = stdin.fill_buf()?; let mut semicolon = buf.len(); let mut r = buf.len(); for (i, &c) in buf.iter().enumerate() { if c == b';' { semicolon = i; } if c == b'R' { r = i; break; } } if buf.len() < 6 || buf[0] != ESC || buf[1] != b'[' || semicolon < 2 || semicolon + 1 > r || r == buf.len() { return Err(Box::from("invalid response")); } let row = std::str::from_utf8(&buf[2..semicolon])?.parse::()?; let col = std::str::from_utf8(&buf[semicolon + 1..r])?.parse::()?; stdin.consume(r + 1); Ok((row - 1, col - 1)) }