summaryrefslogtreecommitdiffstats
path: root/src/sc.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/sc.rs')
-rw-r--r--src/sc.rs134
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);
+ }
+ }
+}