From bf7ca5c1439191b8a342f3442793d76b1cef3c60 Mon Sep 17 00:00:00 2001 From: Ticki Date: Sun, 6 Mar 2016 14:54:26 +0100 Subject: [PATCH] Initial commit --- src/lib.rs | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 src/lib.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..892fe69 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,155 @@ +#![feature(libc)] +extern crate libc; + +use std::mem; +use self::libc::{c_int, c_uint, c_ushort, c_uchar, STDOUT_FILENO}; +use self::libc::ioctl; + +use std::io::{Write, Result as IoResult}; + +extern { + static tiocgwinsz: c_int; + + fn tcgetattr(filedes: c_int, termptr: *mut Termios) -> c_int; + fn tcsetattr(filedes: c_int, opt: c_int, termptr: *mut Termios) -> c_int; + fn cfmakeraw(termptr: *mut Termios); +} + +#[repr(C)] +struct TermSize { + row: c_ushort, + col: c_ushort, + _x: c_ushort, + _y: c_ushort, +} + +/// Get the size of the terminal. If the program isn't running in a terminal, it will return +/// `None`. +pub fn termsize() -> Option<(usize, usize)> { + unsafe { + let mut size: TermSize = mem::zeroed(); + + if ioctl(STDOUT_FILENO, tiocgwinsz as u64, &mut size as *mut _) == 0 { + Some((size.col as usize, size.row as usize)) + } else { + None + } + } +} + +#[derive(Clone)] +#[repr(C)] +struct Termios { + c_iflag: c_uint, + c_oflag: c_uint, + c_cflag: c_uint, + c_lflag: c_uint, + c_line: c_uchar, + c_cc: [c_uchar; 32], + c_ispeed: c_uint, + c_ospeed: c_uint, +} + +fn get_terminal_attr() -> (Termios, c_int) { + unsafe { + let mut ios = Termios { + c_iflag: 0, + c_oflag: 0, + c_cflag: 0, + c_lflag: 0, + c_line: 0, + c_cc: [0; 32], + c_ispeed: 0, + c_ospeed: 0 + }; + + let attr = tcgetattr(0, &mut ios); + (ios, attr) + } +} + +fn make_raw(ios: &mut Termios) { + unsafe { + cfmakeraw(&mut *ios); + } +} + +fn set_terminal_attr(ios: *mut Termios) -> c_int { + unsafe { + tcsetattr(0, 0, ios) + } +} + +/// A terminal restorer, which keeps the previous state of the terminal, and restores it, when +/// dropped. +pub struct TerminalRestorer { + prev_ios: Termios +} + +impl Drop for TerminalRestorer { + fn drop(&mut self) { + set_terminal_attr(&mut self.prev_ios as *mut _); + } +} + +/// Switch to raw mode. +/// +/// Raw mode means that stdin won't be printed (it will instead have to be written manually by the +/// program). Furthermore, the input isn't canonicalised or buffered (that is, you can read from +/// stdin one byte of a time). The output is neither modified in any way. +/// +/// Panics +/// ------ +/// +/// This may panic if the Termios settings can be set or loaded properly. +pub fn raw_mode() -> TerminalRestorer { + let (mut ios, err) = get_terminal_attr(); + let prev_ios = ios.clone(); + if err != 0 { + panic!("Failed to load termios settings properly."); + } + + make_raw(&mut ios); + + if set_terminal_attr(&mut ios as *mut _) != 0 { + panic!("Failed to init termios raw mode properly."); + } + + TerminalRestorer { + prev_ios: prev_ios, + } +} + +/// Controlling terminals. +pub trait TermControl { + /// Print the CSI (control sequence introducer) followed by a byte string. + fn csi(&mut self, b: &[u8]) -> IoResult; + /// Clear the terminal. + fn clear(&mut self) -> IoResult { + self.csi(b"2J") + } + /// Show the cursor. + fn show(&mut self) -> IoResult { + self.csi(b"?25h") + } + /// Hide the cursor. + fn hide(&mut self) -> IoResult { + self.csi(b"?25l") + } + /// Reset the style of the cursor. + fn reset_style(&mut self) -> IoResult { + self.csi(b"0m") + } + /// Go to a given position. + fn goto(&mut self, x: usize, y: usize) -> IoResult { + self.csi(format!("{};{}H", x, y).as_bytes()) + } +} + +impl TermControl for W { + fn csi(&mut self, b: &[u8]) -> IoResult { + self.write(b"\x1b[").and_then(|x| { + self.write(b).map(|y| x + y) + }) + } +}