From cd455e835842831125df0ca23507384f5ae06c8b Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Mon, 31 Jul 2017 21:17:47 -0600 Subject: [PATCH 1/2] Move system specific features into sys module --- src/async.rs | 4 +- src/lib.rs | 46 ++++++++++++++------- src/raw.rs | 61 +++++++-------------------- src/size.rs | 85 -------------------------------------- src/sys/redox/attr.rs | 33 +++++++++++++++ src/sys/redox/mod.rs | 15 +++++++ src/sys/redox/size.rs | 18 ++++++++ src/{ => sys/redox}/tty.rs | 23 +---------- src/sys/unix/attr.rs | 29 +++++++++++++ src/sys/unix/mod.rs | 33 +++++++++++++++ src/sys/unix/size.rs | 48 +++++++++++++++++++++ src/sys/unix/tty.rs | 17 ++++++++ src/termios.rs | 45 -------------------- 13 files changed, 242 insertions(+), 215 deletions(-) delete mode 100644 src/size.rs create mode 100644 src/sys/redox/attr.rs create mode 100644 src/sys/redox/mod.rs create mode 100644 src/sys/redox/size.rs rename src/{ => sys/redox}/tty.rs (51%) create mode 100644 src/sys/unix/attr.rs create mode 100644 src/sys/unix/mod.rs create mode 100644 src/sys/unix/size.rs create mode 100644 src/sys/unix/tty.rs delete mode 100644 src/termios.rs diff --git a/src/async.rs b/src/async.rs index 6255b78..f58b044 100644 --- a/src/async.rs +++ b/src/async.rs @@ -2,7 +2,7 @@ use std::io::{self, Read}; use std::sync::mpsc; use std::thread; -use tty; +use sys::tty::get_tty; /// Construct an asynchronous handle to the TTY standard input. /// @@ -17,7 +17,7 @@ use tty; pub fn async_stdin() -> AsyncReader { let (send, recv) = mpsc::channel(); - thread::spawn(move || for i in tty::get_tty().unwrap().bytes() { + thread::spawn(move || for i in get_tty().unwrap().bytes() { if send.send(i).is_err() { return; } diff --git a/src/lib.rs b/src/lib.rs index 8dd4d2f..b284edd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,27 +11,20 @@ //! For more information refer to the [README](https://github.com/ticki/termion). #![warn(missing_docs)] -#[cfg(not(target_os = "redox"))] -extern crate libc; - -#[cfg(not(target_os = "redox"))] -mod termios; - #[cfg(target_os = "redox")] -extern crate redox_termios; +#[path="sys/redox/mod.rs"] +mod sys; -#[cfg(target_os = "redox")] -extern crate syscall; +#[cfg(unix)] +#[path="sys/unix/mod.rs"] +mod sys; + +pub use sys::size::terminal_size; +pub use sys::tty::{is_tty, get_tty}; mod async; pub use async::{AsyncReader, async_stdin}; -mod size; -pub use size::terminal_size; - -mod tty; -pub use tty::{is_tty, get_tty}; - #[macro_use] mod macros; pub mod clear; @@ -43,3 +36,26 @@ pub mod raw; pub mod screen; pub mod scroll; pub mod style; + +#[cfg(test)] +mod test { + use super::sys; + + #[test] + fn test_get_terminal_attr() { + sys::attr::get_terminal_attr().unwrap(); + sys::attr::get_terminal_attr().unwrap(); + sys::attr::get_terminal_attr().unwrap(); + } + + #[test] + fn test_set_terminal_attr() { + let ios = sys::attr::get_terminal_attr().unwrap(); + sys::attr::set_terminal_attr(&ios).unwrap(); + } + + #[test] + fn test_size() { + sys::size::terminal_size().unwrap(); + } +} diff --git a/src/raw.rs b/src/raw.rs index bd9c8ec..5421d56 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -25,6 +25,9 @@ use std::io::{self, Write}; use std::ops; +use sys::Termios; +use sys::attr::{get_terminal_attr, raw_terminal_attr, set_terminal_attr}; + /// The timeout of an escape code control sequence, in milliseconds. pub const CONTROL_SEQUENCE_TIMEOUT: u64 = 100; @@ -32,34 +35,14 @@ pub const CONTROL_SEQUENCE_TIMEOUT: u64 = 100; /// dropped. /// /// Restoring will entirely bring back the old TTY state. -#[cfg(target_os = "redox")] -pub struct RawTerminal { - output: W, -} - -#[cfg(target_os = "redox")] -impl Drop for RawTerminal { - fn drop(&mut self) { - let _ = write!(self, csi!("?82l")); - let _ = self.flush(); - } -} - -#[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 RawTerminal { prev_ios: Termios, output: W, } -#[cfg(not(target_os = "redox"))] impl Drop for RawTerminal { fn drop(&mut self) { - use termios::set_terminal_attr; - set_terminal_attr(&mut self.prev_ios as *mut _); + set_terminal_attr(&self.prev_ios).unwrap(); } } @@ -103,36 +86,18 @@ pub trait IntoRawMode: Write + Sized { } impl IntoRawMode for W { - #[cfg(not(target_os = "redox"))] fn into_raw_mode(self) -> io::Result> { - use termios::{cfmakeraw, get_terminal_attr, set_terminal_attr}; - - let (mut ios, exit) = get_terminal_attr(); + let mut ios = get_terminal_attr()?; let prev_ios = ios; - if exit != 0 { - return Err(io::Error::new(io::ErrorKind::Other, "Unable to get Termios attribute.")); - } - unsafe { - cfmakeraw(&mut ios); - } + raw_terminal_attr(&mut ios); - if set_terminal_attr(&mut ios as *mut _) != 0 { - Err(io::Error::new(io::ErrorKind::Other, "Unable to set Termios attribute.")) - } else { - let res = RawTerminal { - prev_ios: prev_ios, - output: self, - }; - Ok(res) - } - } + set_terminal_attr(&ios)?; - #[cfg(target_os = "redox")] - fn into_raw_mode(mut self) -> io::Result> { - write!(self, csi!("?82h"))?; - self.flush()?; - Ok(RawTerminal { output: self }) + Ok(RawTerminal { + prev_ios: prev_ios, + output: self, + }) } } @@ -145,6 +110,8 @@ mod test { fn test_into_raw_mode() { let mut out = stdout().into_raw_mode().unwrap(); - out.write_all(b"this is a test, muahhahahah").unwrap(); + out.write_all(b"this is a test, muahhahahah\r\n").unwrap(); + + drop(out); } } diff --git a/src/size.rs b/src/size.rs deleted file mode 100644 index 3317ce5..0000000 --- a/src/size.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::io; - -#[cfg(not(target_os = "redox"))] -use libc::c_ushort; - -#[cfg(not(target_os = "redox"))] -#[repr(C)] -struct TermSize { - row: c_ushort, - col: c_ushort, - _x: c_ushort, - _y: c_ushort, -} - -// Since attributes on non-item statements is not stable yet, we use a function. -#[cfg(not(target_os = "android"))] -#[cfg(not(target_os = "redox"))] -#[cfg(target_pointer_width = "64")] -#[cfg(not(target_env = "musl"))] -fn tiocgwinsz() -> u64 { - use termios::TIOCGWINSZ; - TIOCGWINSZ as u64 -} -#[cfg(not(target_os = "android"))] -#[cfg(not(target_os = "redox"))] -#[cfg(target_pointer_width = "32")] -#[cfg(not(target_env = "musl"))] -fn tiocgwinsz() -> u32 { - use termios::TIOCGWINSZ; - TIOCGWINSZ as u32 -} - -#[cfg(any(target_env = "musl", target_os = "android"))] -fn tiocgwinsz() -> i32 { - use termios::TIOCGWINSZ; - TIOCGWINSZ as i32 -} - -/// Get the size of the terminal. -#[cfg(not(target_os = "redox"))] -pub fn terminal_size() -> io::Result<(u16, u16)> { - use libc::ioctl; - use libc::STDOUT_FILENO; - - use std::mem; - unsafe { - let mut size: TermSize = mem::zeroed(); - - if ioctl(STDOUT_FILENO, tiocgwinsz(), &mut size as *mut _) == 0 { - Ok((size.col as u16, size.row as u16)) - } else { - Err(io::Error::new(io::ErrorKind::Other, "Unable to get the terminal size.")) - } - } -} - -/// Get the size of the terminal. -#[cfg(target_os = "redox")] -pub fn terminal_size() -> io::Result<(u16, u16)> { - use redox_termios; - use syscall; - - if let Ok(fd) = syscall::dup(1, b"winsize") { - let mut winsize = redox_termios::Winsize::default(); - let res = syscall::read(fd, &mut winsize); - let _ = syscall::close(fd); - if let Ok(count) = res { - if count == winsize.len() { - return Ok((winsize.ws_col, winsize.ws_row)); - } - } - } - - Err(io::Error::new(io::ErrorKind::Other, "Unable to get the terminal size.")) -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_size() { - assert!(terminal_size().is_ok()); - } -} diff --git a/src/sys/redox/attr.rs b/src/sys/redox/attr.rs new file mode 100644 index 0000000..c6489a5 --- /dev/null +++ b/src/sys/redox/attr.rs @@ -0,0 +1,33 @@ +use std::io; + +use super::{cvt, syscall, Termios}; + +pub fn get_terminal_attr() -> io::Result { + let mut termios = Termios::default(); + + let fd = cvt(syscall::dup(0, b"termios"))?; + let res = cvt(syscall::read(fd, &mut termios)); + let _ = syscall::close(fd); + + if res? == termios.len() { + Ok(termios) + } else { + Err(io::Error::new(io::ErrorKind::Other, "Unable to get the terminal attributes.")) + } +} + +pub fn set_terminal_attr(termios: &Termios) -> io::Result<()> { + let fd = cvt(syscall::dup(0, b"termios"))?; + let res = cvt(syscall::write(fd, termios)); + let _ = syscall::close(fd); + + if res? == termios.len() { + Ok(()) + } else { + Err(io::Error::new(io::ErrorKind::Other, "Unable to set the terminal attributes.")) + } +} + +pub fn raw_terminal_attr(ios: &mut Termios) { + ios.make_raw() +} diff --git a/src/sys/redox/mod.rs b/src/sys/redox/mod.rs new file mode 100644 index 0000000..2a9b875 --- /dev/null +++ b/src/sys/redox/mod.rs @@ -0,0 +1,15 @@ +extern crate redox_termios; +extern crate syscall; + +use std::io; + +pub use self::redox_termios::Termios; + +pub mod attr; +pub mod size; +pub mod tty; + +// Support function for converting syscall error to io error +fn cvt(result: Result) -> io::Result { + result.map_err(|err| io::Error::from_raw_os_error(err.errno)) +} diff --git a/src/sys/redox/size.rs b/src/sys/redox/size.rs new file mode 100644 index 0000000..07f64a2 --- /dev/null +++ b/src/sys/redox/size.rs @@ -0,0 +1,18 @@ +use std::io; + +use super::{cvt, redox_termios, syscall}; + +/// Get the size of the terminal. +pub fn terminal_size() -> io::Result<(u16, u16)> { + let mut winsize = redox_termios::Winsize::default(); + + let fd = cvt(syscall::dup(1, b"winsize"))?; + let res = cvt(syscall::read(fd, &mut winsize)); + let _ = syscall::close(fd); + + if res? == winsize.len() { + Ok((winsize.ws_col, winsize.ws_row)) + } else { + Err(io::Error::new(io::ErrorKind::Other, "Unable to get the terminal size.")) + } +} diff --git a/src/tty.rs b/src/sys/redox/tty.rs similarity index 51% rename from src/tty.rs rename to src/sys/redox/tty.rs index f153e0e..9179b39 100644 --- a/src/tty.rs +++ b/src/sys/redox/tty.rs @@ -1,19 +1,10 @@ -use std::{fs, io}; +use std::{env, fs, io}; use std::os::unix::io::AsRawFd; -/// Is this stream a TTY? -#[cfg(not(target_os = "redox"))] -pub fn is_tty(stream: &T) -> bool { - use libc; - - unsafe { libc::isatty(stream.as_raw_fd()) == 1 } -} +use super::syscall; /// Is this stream a TTY? -#[cfg(target_os = "redox")] pub fn is_tty(stream: &T) -> bool { - use syscall; - if let Ok(fd) = syscall::dup(stream.as_raw_fd(), b"termios") { let _ = syscall::close(fd); true @@ -25,17 +16,7 @@ pub fn is_tty(stream: &T) -> bool { /// Get the TTY device. /// /// This allows for getting stdio representing _only_ the TTY, and not other streams. -#[cfg(target_os = "redox")] pub fn get_tty() -> io::Result { - use std::env; let tty = try!(env::var("TTY").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x))); fs::OpenOptions::new().read(true).write(true).open(tty) } - -/// Get the TTY device. -/// -/// This allows for getting stdio representing _only_ the TTY, and not other streams. -#[cfg(not(target_os = "redox"))] -pub fn get_tty() -> io::Result { - fs::OpenOptions::new().read(true).write(true).open("/dev/tty") -} diff --git a/src/sys/unix/attr.rs b/src/sys/unix/attr.rs new file mode 100644 index 0000000..5e21fba --- /dev/null +++ b/src/sys/unix/attr.rs @@ -0,0 +1,29 @@ +use std::{io, mem}; + +use super::{cvt, Termios}; +use super::libc::c_int; + +pub fn get_terminal_attr() -> io::Result { + extern "C" { + pub fn tcgetattr(fd: c_int, termptr: *mut Termios) -> c_int; + } + unsafe { + let mut termios = mem::zeroed(); + cvt(tcgetattr(0, &mut termios))?; + Ok(termios) + } +} + +pub fn set_terminal_attr(termios: &Termios) -> io::Result<()> { + extern "C" { + pub fn tcsetattr(fd: c_int, opt: c_int, termptr: *const Termios) -> c_int; + } + cvt(unsafe { tcsetattr(0, 0, termios) }).and(Ok(())) +} + +pub fn raw_terminal_attr(termios: &mut Termios) { + extern "C" { + pub fn cfmakeraw(termptr: *mut Termios); + } + unsafe { cfmakeraw(termios) } +} diff --git a/src/sys/unix/mod.rs b/src/sys/unix/mod.rs new file mode 100644 index 0000000..08d73fe --- /dev/null +++ b/src/sys/unix/mod.rs @@ -0,0 +1,33 @@ +extern crate libc; + +use std::io; + +pub use self::libc::termios as Termios; + +pub mod attr; +pub mod size; +pub mod tty; + +// Support functions for converting libc return values to io errors { +trait IsMinusOne { + fn is_minus_one(&self) -> bool; +} + +macro_rules! impl_is_minus_one { + ($($t:ident)*) => ($(impl IsMinusOne for $t { + fn is_minus_one(&self) -> bool { + *self == -1 + } + })*) + } + +impl_is_minus_one! { i8 i16 i32 i64 isize } + +fn cvt(t: T) -> io::Result { + if t.is_minus_one() { + Err(io::Error::last_os_error()) + } else { + Ok(t) + } +} +// } End of support functions diff --git a/src/sys/unix/size.rs b/src/sys/unix/size.rs new file mode 100644 index 0000000..9c2aaf1 --- /dev/null +++ b/src/sys/unix/size.rs @@ -0,0 +1,48 @@ +use std::{io, mem}; + +use super::cvt; +use super::libc::{c_ushort, ioctl, STDOUT_FILENO}; + +#[repr(C)] +struct TermSize { + row: c_ushort, + col: c_ushort, + _x: c_ushort, + _y: c_ushort, +} + +#[cfg(target_os = "linux")] +pub const TIOCGWINSZ: usize = 0x00005413; + +#[cfg(not(target_os = "linux"))] +pub const TIOCGWINSZ: usize = 0x40087468; + +// Since attributes on non-item statements is not stable yet, we use a function. +#[cfg(not(target_os = "android"))] +#[cfg(not(target_os = "redox"))] +#[cfg(target_pointer_width = "64")] +#[cfg(not(target_env = "musl"))] +fn tiocgwinsz() -> u64 { + TIOCGWINSZ as u64 +} +#[cfg(not(target_os = "android"))] +#[cfg(not(target_os = "redox"))] +#[cfg(target_pointer_width = "32")] +#[cfg(not(target_env = "musl"))] +fn tiocgwinsz() -> u32 { + TIOCGWINSZ as u32 +} + +#[cfg(any(target_env = "musl", target_os = "android"))] +fn tiocgwinsz() -> i32 { + TIOCGWINSZ as i32 +} + +/// Get the size of the terminal. +pub fn terminal_size() -> io::Result<(u16, u16)> { + unsafe { + let mut size: TermSize = mem::zeroed(); + cvt(ioctl(STDOUT_FILENO, tiocgwinsz(), &mut size as *mut _))?; + Ok((size.col as u16, size.row as u16)) + } +} diff --git a/src/sys/unix/tty.rs b/src/sys/unix/tty.rs new file mode 100644 index 0000000..2be9363 --- /dev/null +++ b/src/sys/unix/tty.rs @@ -0,0 +1,17 @@ +use std::{fs, io}; +use std::os::unix::io::AsRawFd; + +use super::libc; + + +/// Is this stream a TTY? +pub fn is_tty(stream: &T) -> bool { + unsafe { libc::isatty(stream.as_raw_fd()) == 1 } +} + +/// Get the TTY device. +/// +/// This allows for getting stdio representing _only_ the TTY, and not other streams. +pub fn get_tty() -> io::Result { + fs::OpenOptions::new().read(true).write(true).open("/dev/tty") +} diff --git a/src/termios.rs b/src/termios.rs deleted file mode 100644 index 420408f..0000000 --- a/src/termios.rs +++ /dev/null @@ -1,45 +0,0 @@ -use libc::c_int; -use std::mem; - -pub use libc::termios as Termios; - -#[cfg(target_os = "linux")] -pub const TIOCGWINSZ: usize = 0x00005413; - -#[cfg(not(target_os = "linux"))] -pub const TIOCGWINSZ: usize = 0x40087468; - -extern "C" { - pub fn tcgetattr(fd: c_int, termptr: *mut Termios) -> c_int; - pub fn tcsetattr(fd: c_int, opt: c_int, termptr: *mut Termios) -> c_int; - pub fn cfmakeraw(termptr: *mut Termios); -} - -pub fn get_terminal_attr() -> (Termios, c_int) { - unsafe { - let mut ios = mem::zeroed(); - let attr = tcgetattr(0, &mut ios); - (ios, attr) - } -} - -pub fn set_terminal_attr(ios: *mut Termios) -> c_int { - unsafe { tcsetattr(0, 0, ios) } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_get_terminal_attr() { - get_terminal_attr(); - get_terminal_attr(); - get_terminal_attr(); - } - #[test] - fn test_set_terminal_attr() { - let mut ios = get_terminal_attr().0; - set_terminal_attr(&mut ios as *mut _); - } -} From 18e589b9d92e8f93ae75475389e234bbe5deb109 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 2 Aug 2017 20:07:37 -0600 Subject: [PATCH 2/2] Add cursor position save/restore --- src/cursor.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cursor.rs b/src/cursor.rs index 5cfa72e..6296da9 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -9,6 +9,9 @@ use raw::CONTROL_SEQUENCE_TIMEOUT; derive_csi_sequence!("Hide the cursor.", Hide, "?25l"); derive_csi_sequence!("Show the cursor.", Show, "?25h"); +derive_csi_sequence!("Restore the cursor.", Restore, "u"); +derive_csi_sequence!("Save the cursor.", Save, "s"); + /// Goto some position ((1,1)-based). /// /// # Why one-based?