use libc::{termios, winsize}; use std::error::Error; use std::io::{stdin, stdout, Read, Write}; use std::sync::mpsc::{Receiver, SyncSender, TryRecvError}; pub fn move_cursor(row: u16, col: u16) -> Result<(), Box> { write!(stdout(), "\x1b[{};{}H", row + 1, col + 1)?; Ok(()) } pub fn clear() -> Result<(), Box> { write!(stdout(), "\x1b[2J")?; Ok(()) } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Key { Up, Down, Left, Right, } pub struct Screen { pub rows: u16, pub cols: u16, old_attr: termios, cancel: SyncSender<()>, keys: Receiver, } impl Screen { pub fn init() -> Result> { let mut size: winsize; unsafe { size = std::mem::zeroed(); if libc::ioctl(1, libc::TIOCGWINSZ, &mut size) < 0 { return Err(Box::from("ioctl failed")); } } if size.ws_col > 60 { size.ws_col = 60; } if size.ws_row > 30 { size.ws_row = 30; } let mut attr; unsafe { attr = std::mem::zeroed(); if libc::tcgetattr(1, &mut attr) < 0 { return Err(Box::from("tcgetattr failed")); } } unsafe { let mut raw = std::mem::zeroed(); libc::cfmakeraw(&mut raw); raw.c_cc[libc::VMIN] = 0; raw.c_cc[libc::VTIME] = 1; if libc::tcsetattr(1, libc::TCSANOW, &raw) < 0 { return Err(Box::from("tcsetattr failed")); } } clear()?; move_cursor(0, 0)?; for _ in 0..size.ws_col { write!(stdout(), "-")?; } for i in 1..size.ws_row - 1 { move_cursor(i, 0)?; write!(stdout(), "|")?; move_cursor(i, size.ws_col - 1)?; write!(stdout(), "|")?; } move_cursor(size.ws_row - 1, 0)?; for _ in 0..size.ws_col { write!(stdout(), "-")?; } let (cancel_tx, cancel_rx) = std::sync::mpsc::sync_channel(0); let (key_tx, key_rx) = std::sync::mpsc::sync_channel(4); std::thread::spawn(move || { let mut buf = [0; 3]; while let Err(TryRecvError::Empty) = cancel_rx.try_recv() { let Ok(n) = stdin().read(&mut buf) else { continue; }; if n < 3 || buf[0] != 27 || buf[1] != b'[' { continue; } let key = match buf[2] { b'A' => Key::Up, b'B' => Key::Down, b'C' => Key::Right, b'D' => Key::Left, _ => { continue; } }; let _ = key_tx.try_send(key); } }); Ok(Screen { rows: size.ws_row, cols: size.ws_col, old_attr: attr, cancel: cancel_tx, keys: key_rx, }) } pub fn read_key(&self) -> Option { if let Ok(key) = self.keys.try_recv() { return Some(key); } None } pub fn in_bounds(&self, row: u16, col: u16) -> bool { (1..self.rows - 1).contains(&row) && (1..self.cols - 1).contains(&col) } } impl Drop for Screen { fn drop(&mut self) { self.cancel.send(()).unwrap(); unsafe { libc::tcsetattr(1, libc::TCSANOW, &self.old_attr); } } }