diff --git a/README.md b/README.md index 4dab977..4c9cda7 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ libterm A pure Rust library for handling, manipulating and reading information about terminals. This provides a full-featured alternative to Termbox. +Supports Redox and POSIX. Untested on Windows. + Features -------- diff --git a/examples/read.rs b/examples/read.rs index b31816c..6d0dd33 100644 --- a/examples/read.rs +++ b/examples/read.rs @@ -11,7 +11,7 @@ fn main() { stdout.write(b"password: ").unwrap(); stdout.flush().unwrap(); - let pass = stdin.read_passwd(); + let pass = stdin.read_passwd(&mut stdout); if let Some(pass) = pass { stdout.write(pass.as_bytes()).unwrap(); diff --git a/examples/simple.rs b/examples/simple.rs index 55cd65f..dbdb693 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,12 +1,11 @@ extern crate libterm; -use libterm::{TermControl, raw_mode, Color, Mode, ReadExt}; +use libterm::{TermControl, IntoRawMode, Color, Mode, ReadExt}; use std::io::{Read, Write, stdout, stdin}; fn main() { - let _raw = raw_mode(); let stdout = stdout(); - let mut stdout = stdout.lock(); + let mut stdout = stdout.lock().into_raw_mode().unwrap(); let mut stdin = stdin(); stdout.goto(5, 5).unwrap(); diff --git a/src/control.rs b/src/control.rs index 65f9d3c..0ce86de 100644 --- a/src/control.rs +++ b/src/control.rs @@ -3,12 +3,15 @@ use {Color, Mode}; /// Controlling terminals. pub trait TermControl { + /// Print the CSI (control sequence introducer) followed by a byte string. fn csi(&mut self, b: &[u8]) -> IoResult; /// Print OSC (operating system command) followed by a byte string. fn osc(&mut self, b: &[u8]) -> IoResult; /// Print OSC (device control string) followed by a byte string. fn dsc(&mut self, b: &[u8]) -> IoResult; + + /// Clear the terminal. fn clear(&mut self) -> IoResult { self.csi(b"2J") diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..76a029d --- /dev/null +++ b/src/error.rs @@ -0,0 +1,38 @@ +use std::error::Error; +use std::fmt::{Display, Formatter, Error as FmtError}; + +/// An terminal error. +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum TerminalError { + /// Failed to load attributes. + LoadAttrError, + /// Failed to set attributes. + SetAttrError, + /// Failed to get terminal size. + TermSizeError, + /// Failed to write to stdout. + StdoutError, +} + +impl TerminalError { + fn msg(self) -> &'static str { + match self { + TerminalError::LoadAttrError => "Failed to load Terminal attributes.", + TerminalError::SetAttrError => "Failed to set Terminal attribute.", + TerminalError::TermSizeError => "Failed to get terminal size.", + TerminalError::StdoutError => "Failed to write to stdout.", + } + } +} + +impl Error for TerminalError { + fn description(&self) -> &str { + self.msg() + } +} + +impl Display for TerminalError { + fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { + f.write_str(self.msg()) + } +} diff --git a/src/extra.rs b/src/extra.rs index e7c6410..13ff74b 100644 --- a/src/extra.rs +++ b/src/extra.rs @@ -1,15 +1,19 @@ -use std::io::Read; -use raw_mode; +use std::io::{Read, Write}; +use IntoRawMode; /// Extension to `Read` trait. pub trait ReadExt { /// Read a password. - fn read_passwd(&mut self) -> Option; + fn read_passwd(&mut self, writer: &mut W) -> Option; } impl ReadExt for R { - fn read_passwd(&mut self) -> Option { - let _raw = raw_mode(); + fn read_passwd(&mut self, writer: &mut W) -> Option { + let _raw = if let Ok(x) = writer.into_raw_mode() { + x + } else { + return None; + }; let mut string = String::with_capacity(30); for c in self.chars() { diff --git a/src/lib.rs b/src/lib.rs index 03f76b2..208852d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,15 +3,20 @@ #[warn(missing_docs)] +#[cfg(not(target_os = "redox"))] extern crate libc; +#[cfg(not(target_os = "redox"))] mod termios; mod control; pub use control::TermControl; +mod error; +pub use error::TerminalError; + mod raw; -pub use raw::{raw_mode, TerminalRestorer}; +pub use raw::{IntoRawMode, TerminalRestorer}; mod size; pub use size::termsize; diff --git a/src/raw.rs b/src/raw.rs index c792ce6..9fa3e9c 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -1,42 +1,95 @@ -use termios::{cfmakeraw, Termios, TermiosError, get_terminal_attr, set_terminal_attr}; +use std::io::Write; +use std::ops::{Deref, DerefMut}; -/// 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. -pub fn raw_mode() -> Result { - let (mut ios, err) = get_terminal_attr(); - let prev_ios = ios.clone(); - if err != 0 { - return Err(TermiosError::LoadAttrError); - } - - make_raw(&mut ios); - - if set_terminal_attr(&mut ios as *mut _) != 0 { - Err(TermiosError::SetAttrError) - } else { - Ok(TerminalRestorer { - prev_ios: prev_ios, - }) - } -} - -fn make_raw(ios: &mut Termios) { - unsafe { - cfmakeraw(&mut *ios); - } -} +use TerminalError; /// A terminal restorer, which keeps the previous state of the terminal, and restores it, when /// dropped. -pub struct TerminalRestorer { - prev_ios: Termios +#[cfg(target_os = "redox")] +pub struct TerminalRestorer { + output: W, } -impl Drop for TerminalRestorer { +#[cfg(target_os = "redox")] +impl Drop for TerminalRestorer { fn drop(&mut self) { + use TermControl; + self.csi(b"R"); + } +} + +#[cfg(not(target_os = "redox"))] +use termios::Termios; +/// A terminal restorer, which keeps the previous state of the terminal, and restores it, when +/// dropped. +#[cfg(not(target_os = "redox"))] +pub struct TerminalRestorer { + prev_ios: Termios, + output: W, +} + +#[cfg(not(target_os = "redox"))] +impl Drop for TerminalRestorer { + fn drop(&mut self) { + use termios::set_terminal_attr; set_terminal_attr(&mut self.prev_ios as *mut _); } } + +impl Deref for TerminalRestorer { + type Target = W; + + fn deref(&self) -> &W { + &self.output + } +} +impl DerefMut for TerminalRestorer { + fn deref_mut(&mut self) -> &mut W { + &mut self.output + } +} + +pub trait IntoRawMode: Sized { + /// 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. + fn into_raw_mode(self) -> Result, TerminalError>; +} + +impl IntoRawMode for W { + #[cfg(not(target_os = "redox"))] + fn into_raw_mode(self) -> Result, TerminalError> { + use termios::{cfmakeraw, get_terminal_attr, set_terminal_attr}; + + let (mut ios, err) = get_terminal_attr(); + let prev_ios = ios.clone(); + if err != 0 { + return Err(TerminalError::LoadAttrError); + } + + unsafe { + cfmakeraw(&mut ios); + } + + if set_terminal_attr(&mut ios as *mut _) != 0 { + Err(TerminalError::SetAttrError) + } else { + Ok(TerminalRestorer { + prev_ios: prev_ios, + output: self, + }) + } + } + #[cfg(target_os = "redox")] + fn into_raw_mode(self) -> Result, TerminalError> { + if let Err(_) = self.csi("r") { + Err(TerminalError::StdoutError) + } else { + Ok(TerminalRestorer { + output: self, + }) + } + } +} diff --git a/src/size.rs b/src/size.rs index 58b6ed1..5449587 100644 --- a/src/size.rs +++ b/src/size.rs @@ -3,7 +3,8 @@ use libc::{c_ushort, STDOUT_FILENO}; use std::mem; -use termios::{TermiosError, tiocgwinsz}; +use termios::tiocgwinsz; +use TerminalError; #[repr(C)] struct TermSize { @@ -15,14 +16,14 @@ struct TermSize { /// Get the size of the terminal. If the program isn't running in a terminal, it will return /// `None`. -pub fn termsize() -> Result<(usize, usize), TermiosError> { +pub fn termsize() -> Result<(usize, usize), TerminalError> { unsafe { let mut size: TermSize = mem::zeroed(); if ioctl(STDOUT_FILENO, tiocgwinsz as u64, &mut size as *mut _) == 0 { Ok((size.col as usize, size.row as usize)) } else { - Err(TermiosError::TermSizeError) + Err(TerminalError::TermSizeError) } } } diff --git a/src/termios.rs b/src/termios.rs index 76c6aba..0ee4fa9 100644 --- a/src/termios.rs +++ b/src/termios.rs @@ -1,8 +1,5 @@ use libc::{c_int, c_uint, c_uchar}; -use std::error::Error; -use std::fmt::{Display, Formatter, Error as FmtError}; - extern { pub static tiocgwinsz: c_int; @@ -47,36 +44,3 @@ pub fn set_terminal_attr(ios: *mut Termios) -> c_int { tcsetattr(0, 0, ios) } } - -/// Termios error. -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -pub enum TermiosError { - /// Failed to load attributes. - LoadAttrError, - /// Failed to set attributes. - SetAttrError, - /// Failed to get terminal size. - TermSizeError, -} - -impl TermiosError { - fn msg(self) -> &'static str { - match self { - TermiosError::LoadAttrError => "Failed to load Termios attributes.", - TermiosError::SetAttrError => "Failed to set Termios attribute.", - TermiosError::TermSizeError => "Failed to get terminal size.", - } - } -} - -impl Error for TermiosError { - fn description(&self) -> &str { - self.msg() - } -} - -impl Display for TermiosError { - fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { - f.write_str(self.msg()) - } -}