Now supports Redox!

This commit is contained in:
Ticki 2016-03-07 22:19:35 +01:00
parent 45b1136f75
commit fd2d74d41a
10 changed files with 150 additions and 81 deletions

View File

@ -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. 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 Features
-------- --------

View File

@ -11,7 +11,7 @@ fn main() {
stdout.write(b"password: ").unwrap(); stdout.write(b"password: ").unwrap();
stdout.flush().unwrap(); stdout.flush().unwrap();
let pass = stdin.read_passwd(); let pass = stdin.read_passwd(&mut stdout);
if let Some(pass) = pass { if let Some(pass) = pass {
stdout.write(pass.as_bytes()).unwrap(); stdout.write(pass.as_bytes()).unwrap();

View File

@ -1,12 +1,11 @@
extern crate libterm; 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}; use std::io::{Read, Write, stdout, stdin};
fn main() { fn main() {
let _raw = raw_mode();
let stdout = stdout(); let stdout = stdout();
let mut stdout = stdout.lock(); let mut stdout = stdout.lock().into_raw_mode().unwrap();
let mut stdin = stdin(); let mut stdin = stdin();
stdout.goto(5, 5).unwrap(); stdout.goto(5, 5).unwrap();

View File

@ -3,12 +3,15 @@ use {Color, Mode};
/// Controlling terminals. /// Controlling terminals.
pub trait TermControl { pub trait TermControl {
/// Print the CSI (control sequence introducer) followed by a byte string. /// Print the CSI (control sequence introducer) followed by a byte string.
fn csi(&mut self, b: &[u8]) -> IoResult<usize>; fn csi(&mut self, b: &[u8]) -> IoResult<usize>;
/// Print OSC (operating system command) followed by a byte string. /// Print OSC (operating system command) followed by a byte string.
fn osc(&mut self, b: &[u8]) -> IoResult<usize>; fn osc(&mut self, b: &[u8]) -> IoResult<usize>;
/// Print OSC (device control string) followed by a byte string. /// Print OSC (device control string) followed by a byte string.
fn dsc(&mut self, b: &[u8]) -> IoResult<usize>; fn dsc(&mut self, b: &[u8]) -> IoResult<usize>;
/// Clear the terminal. /// Clear the terminal.
fn clear(&mut self) -> IoResult<usize> { fn clear(&mut self) -> IoResult<usize> {
self.csi(b"2J") self.csi(b"2J")

38
src/error.rs Normal file
View File

@ -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())
}
}

View File

@ -1,15 +1,19 @@
use std::io::Read; use std::io::{Read, Write};
use raw_mode; use IntoRawMode;
/// Extension to `Read` trait. /// Extension to `Read` trait.
pub trait ReadExt { pub trait ReadExt {
/// Read a password. /// Read a password.
fn read_passwd(&mut self) -> Option<String>; fn read_passwd<W: Write>(&mut self, writer: &mut W) -> Option<String>;
} }
impl<R: Read> ReadExt for R { impl<R: Read> ReadExt for R {
fn read_passwd(&mut self) -> Option<String> { fn read_passwd<W: Write>(&mut self, writer: &mut W) -> Option<String> {
let _raw = raw_mode(); let _raw = if let Ok(x) = writer.into_raw_mode() {
x
} else {
return None;
};
let mut string = String::with_capacity(30); let mut string = String::with_capacity(30);
for c in self.chars() { for c in self.chars() {

View File

@ -3,15 +3,20 @@
#[warn(missing_docs)] #[warn(missing_docs)]
#[cfg(not(target_os = "redox"))]
extern crate libc; extern crate libc;
#[cfg(not(target_os = "redox"))]
mod termios; mod termios;
mod control; mod control;
pub use control::TermControl; pub use control::TermControl;
mod error;
pub use error::TerminalError;
mod raw; mod raw;
pub use raw::{raw_mode, TerminalRestorer}; pub use raw::{IntoRawMode, TerminalRestorer};
mod size; mod size;
pub use size::termsize; pub use size::termsize;

View File

@ -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. use TerminalError;
///
/// 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<TerminalRestorer, TermiosError> {
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);
}
}
/// A terminal restorer, which keeps the previous state of the terminal, and restores it, when /// A terminal restorer, which keeps the previous state of the terminal, and restores it, when
/// dropped. /// dropped.
pub struct TerminalRestorer { #[cfg(target_os = "redox")]
prev_ios: Termios pub struct TerminalRestorer<W> {
output: W,
} }
impl Drop for TerminalRestorer { #[cfg(target_os = "redox")]
impl<W: Write> Drop for TerminalRestorer<W> {
fn drop(&mut self) { 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<W> {
prev_ios: Termios,
output: W,
}
#[cfg(not(target_os = "redox"))]
impl<W> Drop for TerminalRestorer<W> {
fn drop(&mut self) {
use termios::set_terminal_attr;
set_terminal_attr(&mut self.prev_ios as *mut _); set_terminal_attr(&mut self.prev_ios as *mut _);
} }
} }
impl<W> Deref for TerminalRestorer<W> {
type Target = W;
fn deref(&self) -> &W {
&self.output
}
}
impl<W> DerefMut for TerminalRestorer<W> {
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<TerminalRestorer<Self>, TerminalError>;
}
impl<W: Write> IntoRawMode for W {
#[cfg(not(target_os = "redox"))]
fn into_raw_mode(self) -> Result<TerminalRestorer<W>, 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<TerminalRestorer<W>, TerminalError> {
if let Err(_) = self.csi("r") {
Err(TerminalError::StdoutError)
} else {
Ok(TerminalRestorer {
output: self,
})
}
}
}

View File

@ -3,7 +3,8 @@ use libc::{c_ushort, STDOUT_FILENO};
use std::mem; use std::mem;
use termios::{TermiosError, tiocgwinsz}; use termios::tiocgwinsz;
use TerminalError;
#[repr(C)] #[repr(C)]
struct TermSize { 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 /// Get the size of the terminal. If the program isn't running in a terminal, it will return
/// `None`. /// `None`.
pub fn termsize() -> Result<(usize, usize), TermiosError> { pub fn termsize() -> Result<(usize, usize), TerminalError> {
unsafe { unsafe {
let mut size: TermSize = mem::zeroed(); let mut size: TermSize = mem::zeroed();
if ioctl(STDOUT_FILENO, tiocgwinsz as u64, &mut size as *mut _) == 0 { if ioctl(STDOUT_FILENO, tiocgwinsz as u64, &mut size as *mut _) == 0 {
Ok((size.col as usize, size.row as usize)) Ok((size.col as usize, size.row as usize))
} else { } else {
Err(TermiosError::TermSizeError) Err(TerminalError::TermSizeError)
} }
} }
} }

View File

@ -1,8 +1,5 @@
use libc::{c_int, c_uint, c_uchar}; use libc::{c_int, c_uint, c_uchar};
use std::error::Error;
use std::fmt::{Display, Formatter, Error as FmtError};
extern { extern {
pub static tiocgwinsz: c_int; pub static tiocgwinsz: c_int;
@ -47,36 +44,3 @@ pub fn set_terminal_attr(ios: *mut Termios) -> c_int {
tcsetattr(0, 0, ios) 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())
}
}