diff options
Diffstat (limited to 'src/sc.rs')
| -rw-r--r-- | src/sc.rs | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/src/sc.rs b/src/sc.rs new file mode 100644 index 0000000..cb20c68 --- /dev/null +++ b/src/sc.rs @@ -0,0 +1,134 @@ +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<dyn Error>> { + write!(stdout(), "\x1b[{};{}H", row + 1, col + 1)?; + Ok(()) +} + +pub fn clear() -> Result<(), Box<dyn Error>> { + 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<Key>, +} + +impl Screen { + pub fn init() -> Result<Screen, Box<dyn Error>> { + 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<Key> { + 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); + } + } +} |
