From 2f97c69a5c3ddee228d9def514173121e3d786b1 Mon Sep 17 00:00:00 2001 From: ftilde Date: Thu, 13 Jul 2017 01:30:41 +0200 Subject: [PATCH 01/22] Add EventsAndRaw iter and implement it for Read - In addition to Events it preserves the byte sequence that created an event. This is useful, e.g., for implementing a terminal multiplexer where the raw input should in some cases be passed on to another tty. - In order to ensure backwards compatibility, the function that creates the trait is implemented in a separate extension trait. --- src/input.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/src/input.rs b/src/input.rs index a9e874d..6f6dd17 100644 --- a/src/input.rs +++ b/src/input.rs @@ -27,15 +27,28 @@ impl Iterator for Keys { } /// An iterator over input events. -pub struct Events { - source: R, - leftover: Option, +pub struct Events { + inner: EventsAndRaw } impl Iterator for Events { type Item = Result; fn next(&mut self) -> Option> { + self.inner.next().map(|tuple| tuple.map(|(event, _raw)| event)) + } +} + +/// An iterator over input events and the bytes that define them. +pub struct EventsAndRaw { + source: R, + leftover: Option, +} + +impl Iterator for EventsAndRaw { + type Item = Result<(Event, Vec), io::Error>; + + fn next(&mut self) -> Option), io::Error>> { let mut source = &mut self.source; if let Some(c) = self.leftover { @@ -53,7 +66,7 @@ impl Iterator for Events { Ok(0) => return None, Ok(1) => { match buf[0] { - b'\x1B' => Ok(Event::Key(Key::Esc)), + b'\x1B' => Ok((Event::Key(Key::Esc), vec![b'\x1B'])), c => parse_event(c, &mut source.bytes()), } } @@ -75,7 +88,7 @@ impl Iterator for Events { } } -fn parse_event(item: u8, iter: &mut I) -> Result +fn parse_event(item: u8, iter: &mut I) -> Result<(Event, Vec), io::Error> where I: Iterator> { let mut buf = vec![item]; @@ -85,7 +98,7 @@ fn parse_event(item: u8, iter: &mut I) -> Result }); event::parse_event(item, &mut iter) }; - result.or(Ok(Event::Unsupported(buf))) + result.or(Ok(Event::Unsupported(buf.clone()))).map(|e| (e, buf)) } @@ -113,11 +126,11 @@ pub trait TermRead { } } -impl TermRead for R { + +impl TermRead for R { fn events(self) -> Events { Events { - source: self, - leftover: None, + inner: self.events_and_raw() } } fn keys(self) -> Keys { @@ -145,6 +158,21 @@ impl TermRead for R { } } +/// Extension to `TermRead` trait. A separate trait in order to maintain backwards compatibility. +pub trait TermReadEventsAndRaw { + /// An iterator over input events and the bytes that define them. + fn events_and_raw(self) -> EventsAndRaw where Self: Sized; +} + +impl TermReadEventsAndRaw for R { + fn events_and_raw(self) -> EventsAndRaw { + EventsAndRaw { + source: self, + leftover: None, + } + } +} + /// A sequence of escape codes to enable terminal mouse support. const ENTER_MOUSE_SEQUENCE: &'static str = csi!("?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h"); @@ -241,6 +269,38 @@ mod test { assert!(i.next().is_none()); } + #[test] + fn test_events_and_raw() { + let input = b"\x1B[\x00bc\x7F\x1B[D\ + \x1B[M\x00\x22\x24\x1B[<0;2;4;M\x1B[32;2;4M\x1B[<0;2;4;m\x1B[35;2;4Mb"; + let mut output = Vec::::new(); + { + let mut i = input.events_and_raw().map(|res| res.unwrap()) + .inspect(|&(_, ref raw)| { output.extend(raw); }).map(|(event, _)| event); + + assert_eq!(i.next().unwrap(), + Event::Unsupported(vec![0x1B, b'[', 0x00])); + assert_eq!(i.next().unwrap(), Event::Key(Key::Char('b'))); + assert_eq!(i.next().unwrap(), Event::Key(Key::Char('c'))); + assert_eq!(i.next().unwrap(), Event::Key(Key::Backspace)); + assert_eq!(i.next().unwrap(), Event::Key(Key::Left)); + assert_eq!(i.next().unwrap(), + Event::Mouse(MouseEvent::Press(MouseButton::WheelUp, 2, 4))); + assert_eq!(i.next().unwrap(), + Event::Mouse(MouseEvent::Press(MouseButton::Left, 2, 4))); + assert_eq!(i.next().unwrap(), + Event::Mouse(MouseEvent::Press(MouseButton::Left, 2, 4))); + assert_eq!(i.next().unwrap(), + Event::Mouse(MouseEvent::Release(2, 4))); + assert_eq!(i.next().unwrap(), + Event::Mouse(MouseEvent::Release(2, 4))); + assert_eq!(i.next().unwrap(), Event::Key(Key::Char('b'))); + assert!(i.next().is_none()); + } + + assert_eq!(input.iter().map(|b| *b).collect::>(), output) + } + #[test] fn test_function_keys() { let mut st = b"\x1BOP\x1BOQ\x1BOR\x1BOS".keys(); From 5fdabb4320fad53aa5fb54dc5c9f1e7e6bea965a Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Sun, 23 Jul 2017 11:38:21 -0600 Subject: [PATCH 02/22] Implement size and is_tty with termios on Redox --- Cargo.toml | 5 +++++ src/lib.rs | 6 ++++++ src/size.rs | 21 +++++++++++++-------- src/tty.rs | 11 +++++++++-- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dd2495a..9f5bbeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,8 @@ exclude = ["target", "CHANGELOG.md", "image.png", "Cargo.lock"] [target.'cfg(not(target_os = "redox"))'.dependencies] libc = "0.2.8" + + +[target.'cfg(target_os = "redox")'.dependencies] +redox_syscall = "0.1" +redox_termios = "0.1" diff --git a/src/lib.rs b/src/lib.rs index 7e286bb..8dd4d2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,12 @@ extern crate libc; #[cfg(not(target_os = "redox"))] mod termios; +#[cfg(target_os = "redox")] +extern crate redox_termios; + +#[cfg(target_os = "redox")] +extern crate syscall; + mod async; pub use async::{AsyncReader, async_stdin}; diff --git a/src/size.rs b/src/size.rs index 19f377b..3317ce5 100644 --- a/src/size.rs +++ b/src/size.rs @@ -57,16 +57,21 @@ pub fn terminal_size() -> io::Result<(u16, u16)> { /// Get the size of the terminal. #[cfg(target_os = "redox")] pub fn terminal_size() -> io::Result<(u16, u16)> { - use std::env; + use redox_termios; + use syscall; - let width = try!(env::var("COLUMNS").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x))) - .parse() - .unwrap_or(0); - let height = try!(env::var("LINES").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x))) - .parse() - .unwrap_or(0); + 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)); + } + } + } - Ok((width, height)) + Err(io::Error::new(io::ErrorKind::Other, "Unable to get the terminal size.")) } #[cfg(test)] diff --git a/src/tty.rs b/src/tty.rs index 9788e54..90a97e0 100644 --- a/src/tty.rs +++ b/src/tty.rs @@ -11,8 +11,15 @@ pub fn is_tty(stream: &T) -> bool { /// This will panic. #[cfg(target_os = "redox")] -pub fn is_tty(_stream: &T) -> bool { - unimplemented!(); +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 + } else { + false + } } /// Get the TTY device. From 5ad83b76ec8cf0b67675346db20673f08f2e958b Mon Sep 17 00:00:00 2001 From: ticki Date: Mon, 24 Jul 2017 18:55:46 +0200 Subject: [PATCH 03/22] Bump to 1.5.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index dd2495a..0be9051 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "termion" -version = "1.4.0" +version = "1.5.0" authors = ["ticki ", "gycos ", "IGI-111 "] description = "A bindless library for manipulating terminals." repository = "https://github.com/ticki/termion" From da9a604c40dc59aa816b4b3ac78293f788cd3490 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 27 Jul 2017 07:00:48 -0600 Subject: [PATCH 04/22] Update tty.rs --- src/tty.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tty.rs b/src/tty.rs index 90a97e0..f153e0e 100644 --- a/src/tty.rs +++ b/src/tty.rs @@ -1,7 +1,7 @@ use std::{fs, io}; use std::os::unix::io::AsRawFd; -/// Is this stream an TTY? +/// Is this stream a TTY? #[cfg(not(target_os = "redox"))] pub fn is_tty(stream: &T) -> bool { use libc; @@ -9,7 +9,7 @@ pub fn is_tty(stream: &T) -> bool { unsafe { libc::isatty(stream.as_raw_fd()) == 1 } } -/// This will panic. +/// Is this stream a TTY? #[cfg(target_os = "redox")] pub fn is_tty(stream: &T) -> bool { use syscall; From cd455e835842831125df0ca23507384f5ae06c8b Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Mon, 31 Jul 2017 21:17:47 -0600 Subject: [PATCH 05/22] 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 06/22] 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? From 52a22ea87314535245898580a2e2613a884f93d6 Mon Sep 17 00:00:00 2001 From: ticki Date: Fri, 4 Aug 2017 00:13:00 +0200 Subject: [PATCH 07/22] Bump to 1.5.1. --- Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3d1d394..a441571 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "termion" -version = "1.5.0" +version = "1.5.1" authors = ["ticki ", "gycos ", "IGI-111 "] description = "A bindless library for manipulating terminals." repository = "https://github.com/ticki/termion" @@ -12,7 +12,6 @@ exclude = ["target", "CHANGELOG.md", "image.png", "Cargo.lock"] [target.'cfg(not(target_os = "redox"))'.dependencies] libc = "0.2.8" - [target.'cfg(target_os = "redox")'.dependencies] redox_syscall = "0.1" redox_termios = "0.1" From 00f87e5ed85433616d435b50a8f1b8e16acd7401 Mon Sep 17 00:00:00 2001 From: Niv Kaminer Date: Wed, 11 Oct 2017 13:06:09 +0300 Subject: [PATCH 08/22] use `TIOCGWINSZ` directly from `libc` --- src/sys/unix/size.rs | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/src/sys/unix/size.rs b/src/sys/unix/size.rs index 9c2aaf1..9979280 100644 --- a/src/sys/unix/size.rs +++ b/src/sys/unix/size.rs @@ -1,7 +1,7 @@ use std::{io, mem}; use super::cvt; -use super::libc::{c_ushort, ioctl, STDOUT_FILENO}; +use super::libc::{c_ushort, ioctl, STDOUT_FILENO, TIOCGWINSZ}; #[repr(C)] struct TermSize { @@ -10,39 +10,11 @@ struct TermSize { _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 _))?; + cvt(ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size as *mut _))?; Ok((size.col as u16, size.row as u16)) } } From 7d97b6424f3694716c8f903eb48ed5c8eef34b2a Mon Sep 17 00:00:00 2001 From: Robert Metcalf Date: Mon, 7 May 2018 01:32:39 +0100 Subject: [PATCH 09/22] Update references to repository --- Cargo.toml | 2 +- README.md | 4 ++-- src/lib.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a441571..617be70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "termion" version = "1.5.1" authors = ["ticki ", "gycos ", "IGI-111 "] description = "A bindless library for manipulating terminals." -repository = "https://github.com/ticki/termion" +repository = "https://github.com/redox-os/termion" documentation = "https://docs.rs/termion" license = "MIT" keywords = ["tty", "color", "terminal", "password", "tui"] diff --git a/README.md b/README.md index 7bda9d3..25346d9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@

-Termion logo +Termion logo

-[![Build Status](https://travis-ci.org/ticki/termion.svg?branch=master)](https://travis-ci.org/ticki/termion) [![Latest Version](https://img.shields.io/crates/v/termion.svg)](https://crates.io/crates/termion) | [Documentation](https://docs.rs/termion) | [Examples](https://github.com/Ticki/termion/tree/master/examples) | [Changelog](https://github.com/Ticki/termion/tree/master/CHANGELOG.md) | [Tutorial](http://ticki.github.io/blog/making-terminal-applications-in-rust-with-termion/) +[![Build Status](https://travis-ci.org/redox-os/termion.svg?branch=master)](https://travis-ci.org/redox-os/termion) [![Latest Version](https://img.shields.io/crates/v/termion.svg)](https://crates.io/crates/termion) | [Documentation](https://docs.rs/termion) | [Examples](https://github.com/redox-os/termion/tree/master/examples) | [Changelog](https://github.com/redox-os/termion/tree/master/CHANGELOG.md) | [Tutorial](http://ticki.github.io/blog/making-terminal-applications-in-rust-with-termion/) |----|----|----|----|---- diff --git a/src/lib.rs b/src/lib.rs index b284edd..2cb49fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ //! //! Supports Redox, Mac OS X, and Linux (or, in general, ANSI terminals). //! -//! For more information refer to the [README](https://github.com/ticki/termion). +//! For more information refer to the [README](https://github.com/redox-os/termion). #![warn(missing_docs)] #[cfg(target_os = "redox")] From 17fdd50823b0370aa0a2eca3739c9055776a3a26 Mon Sep 17 00:00:00 2001 From: Josh Mcguigan Date: Mon, 7 May 2018 17:13:55 -0700 Subject: [PATCH 10/22] Added async_stdin_until function to be used in cursor_pos --- src/async.rs | 25 +++++++++++++++++++++++++ src/cursor.rs | 7 ++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/async.rs b/src/async.rs index f58b044..94e8d81 100644 --- a/src/async.rs +++ b/src/async.rs @@ -1,9 +1,34 @@ use std::io::{self, Read}; use std::sync::mpsc; use std::thread; +use std::io::BufReader; +use std::io::BufRead; use sys::tty::get_tty; +/// Construct an asynchronous handle to the TTY standard input, with a delimiter byte. +/// +/// This has the same advantages as async_stdin(), but also allows specifying a delimiter byte. The +/// reader will stop reading after consuming the delimiter byte. +pub fn async_stdin_until(delimiter: u8) -> AsyncReader { + let (send, recv) = mpsc::channel(); + + thread::spawn(move || for i in get_tty().unwrap().bytes() { + + match i { + Ok(byte) => { + let end_of_stream = &byte == &delimiter; + let send_error = send.send(Ok(byte)).is_err(); + + if end_of_stream || send_error { return; } + }, + Err(e) => { return; } + } + }); + + AsyncReader { recv: recv } +} + /// Construct an asynchronous handle to the TTY standard input. /// /// This allows you to read from standard input _without blocking_ the current thread. diff --git a/src/cursor.rs b/src/cursor.rs index 6296da9..cf8dc86 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -2,7 +2,7 @@ use std::fmt; use std::io::{self, Write, Error, ErrorKind, Read}; -use async::async_stdin; +use async::async_stdin_until; use std::time::{SystemTime, Duration}; use raw::CONTROL_SEQUENCE_TIMEOUT; @@ -94,7 +94,8 @@ pub trait DetectCursorPos { impl DetectCursorPos for W { fn cursor_pos(&mut self) -> io::Result<(u16, u16)> { - let mut stdin = async_stdin(); + let delimiter = b'R'; + let mut stdin = async_stdin_until(delimiter); // Where is the cursor? // Use `ESC [ 6 n`. @@ -108,7 +109,7 @@ impl DetectCursorPos for W { let now = SystemTime::now(); // Either consume all data up to R or wait for a timeout. - while buf[0] != b'R' && now.elapsed().unwrap() < timeout { + while buf[0] != delimiter && now.elapsed().unwrap() < timeout { if stdin.read(&mut buf)? > 0 { read_chars.push(buf[0]); } From 047cbc0cabf51690917e6a993f3fb553c64151ca Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Tue, 8 May 2018 17:30:31 -0700 Subject: [PATCH 11/22] Remove unused 'mut' qualifier in src/input.rs (#143) This change removes an unused 'mut' qualifier of the 'source' variable in src/input.rs. > warning: variable does not need to be mutable > --> src/input.rs:52:13 > | > 52 | let mut source = &mut self.source; > | ----^^^^^^ > | | > | help: remove this `mut` > | > = note: #[warn(unused_mut)] on by default --- src/input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input.rs b/src/input.rs index 6f6dd17..5c8ecf4 100644 --- a/src/input.rs +++ b/src/input.rs @@ -49,7 +49,7 @@ impl Iterator for EventsAndRaw { type Item = Result<(Event, Vec), io::Error>; fn next(&mut self) -> Option), io::Error>> { - let mut source = &mut self.source; + let source = &mut self.source; if let Some(c) = self.leftover { // we have a leftover byte, use it From b9881e122a414f83a3f724aee132d35ec0583222 Mon Sep 17 00:00:00 2001 From: Daniel Mueller Date: Tue, 8 May 2018 17:31:47 -0700 Subject: [PATCH 12/22] Remove unused import of std::ascii::AsciiExt (#142) The import of std::ascii::AsciiExt in src/event.rs is unused. Remove it. > warning: unused import: `std::ascii::AsciiExt` > --> src/event.rs:4:5 > | > 4 | use std::ascii::AsciiExt; > | ^^^^^^^^^^^^^^^^^^^^ > | > = note: #[warn(unused_imports)] on by default --- src/event.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/event.rs b/src/event.rs index f79cb4c..6e383a1 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,7 +1,6 @@ //! Mouse and key events. use std::io::{Error, ErrorKind}; -use std::ascii::AsciiExt; use std::str; /// An event reported by the terminal. From a29929ee34e0d9f4deb26b48f6abb4c38d82462e Mon Sep 17 00:00:00 2001 From: Josh Mcguigan Date: Tue, 8 May 2018 17:34:43 -0700 Subject: [PATCH 13/22] Corrected all remaining build warnings --- src/async.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/async.rs b/src/async.rs index 94e8d81..ea02489 100644 --- a/src/async.rs +++ b/src/async.rs @@ -1,8 +1,6 @@ use std::io::{self, Read}; use std::sync::mpsc; use std::thread; -use std::io::BufReader; -use std::io::BufRead; use sys::tty::get_tty; @@ -22,7 +20,7 @@ pub fn async_stdin_until(delimiter: u8) -> AsyncReader { if end_of_stream || send_error { return; } }, - Err(e) => { return; } + Err(_) => { return; } } }); From 6c6b240961cd3212fd216b8403c86e34853c9696 Mon Sep 17 00:00:00 2001 From: Alex Yankov Date: Tue, 8 May 2018 23:44:29 -0400 Subject: [PATCH 14/22] Reset style in color example before exit (#138) --- examples/color.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/color.rs b/examples/color.rs index 16071be..72c521d 100644 --- a/examples/color.rs +++ b/examples/color.rs @@ -6,5 +6,5 @@ fn main() { println!("{}Red", color::Fg(color::Red)); println!("{}Blue", color::Fg(color::Blue)); println!("{}Blue'n'Bold{}", style::Bold, style::Reset); - println!("{}Just plain italic", style::Italic); + println!("{}Just plain italic{}", style::Italic, style::Reset); } From 96b1196ff3609981ee6f1d100e468522ecb8e7d8 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Sun, 10 Jun 2018 05:16:45 +0000 Subject: [PATCH 15/22] Add .gitlab-ci.yml --- .gitlab-ci.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..b6a5376 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,16 @@ +before_script: + - apt-get update -qq + - apt-get install -qq build-essential curl git + - curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly + - source "$HOME/.cargo/env" + - rustup toolchain add stable + - rustup toolchain add beta + +build: + script: + - cargo build --verbose + +test: + script: + - cargo test --verbose + - cargo test --release --verbose \ No newline at end of file From 5c584dbeaf4cb15dbec17e07bf069cb07e4cc694 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Sun, 10 Jun 2018 05:55:53 +0000 Subject: [PATCH 16/22] Attempt to fix GitLab CI --- .gitlab-ci.yml | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b6a5376..95e1c99 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,16 +1,34 @@ before_script: - apt-get update -qq - apt-get install -qq build-essential curl git - - curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly + - curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable - source "$HOME/.cargo/env" - - rustup toolchain add stable - - rustup toolchain add beta -build: +build_stable: script: - - cargo build --verbose + - cargo +stable build --verbose -test: +test_stable: script: - - cargo test --verbose - - cargo test --release --verbose \ No newline at end of file + - bash -c "cargo test --verbose" + - bash -c "cargo test --release --verbose" + +build_beta: + script: + - rustup toolchain add beta + - cargo +beta build --verbose + +test_beta: + script: + - bash -c "cargo +beta test --verbose" + - bash -c "cargo +beta test --release --verbose" + +build_nightly: + script: + - rustup toolchain add nightly + - cargo +nightly build --verbose + +test_nightly: + script: + - bash -c "cargo +nightly test --verbose" + - bash -c "cargo +nightly test --release --verbose" \ No newline at end of file From c652dfc91f692472c01dd784b74632327a15cc29 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Sun, 10 Jun 2018 06:08:07 +0000 Subject: [PATCH 17/22] Update .gitlab-ci.yml --- .gitlab-ci.yml | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 95e1c99..742b36f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,31 +4,22 @@ before_script: - curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable - source "$HOME/.cargo/env" -build_stable: +stable: script: - cargo +stable build --verbose + - cargo +stable test --verbose + - cargo +stable test --release --verbose -test_stable: - script: - - bash -c "cargo test --verbose" - - bash -c "cargo test --release --verbose" - -build_beta: +beta: script: - rustup toolchain add beta - cargo +beta build --verbose + - cargo +beta test --verbose + - cargo +beta test --release --verbose -test_beta: +nightly: script: - - bash -c "cargo +beta test --verbose" - - bash -c "cargo +beta test --release --verbose" - -build_nightly: - script: - - rustup toolchain add nightly + - rustup toolchain add nightly - cargo +nightly build --verbose - -test_nightly: - script: - - bash -c "cargo +nightly test --verbose" - - bash -c "cargo +nightly test --release --verbose" \ No newline at end of file + - cargo +nightly test --verbose + - cargo +nightly test --release --verbose \ No newline at end of file From 130fc9acf5e23295674bb6096f9ad6c2d9645653 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 12 Jun 2018 12:30:45 -0600 Subject: [PATCH 18/22] Update links to gitlab --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 617be70..a4dca1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "termion" version = "1.5.1" authors = ["ticki ", "gycos ", "IGI-111 "] description = "A bindless library for manipulating terminals." -repository = "https://github.com/redox-os/termion" +repository = "https://gitlab.redox-os.org/redox-os/termion" documentation = "https://docs.rs/termion" license = "MIT" keywords = ["tty", "color", "terminal", "password", "tui"] From acd94a362599beea0a6f45a4253f7f74db1a2777 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 20 Aug 2018 22:11:58 +0000 Subject: [PATCH 19/22] Extra derives & performance optimizations --- .gitlab-ci.yml | 14 ++++----- Cargo.toml | 3 ++ src/color.rs | 79 +++++++++++++++++++++++++++++++++++++++++++++----- src/cursor.rs | 49 ++++++++++++++++++++++++++----- src/lib.rs | 2 ++ src/macros.rs | 8 +++++ 6 files changed, 133 insertions(+), 22 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 742b36f..90d5e9a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,19 +7,19 @@ before_script: stable: script: - cargo +stable build --verbose - - cargo +stable test --verbose - - cargo +stable test --release --verbose + - script -q -c "cargo +stable test --verbose" + - script -q -c "cargo +stable test --release --verbose" beta: script: - rustup toolchain add beta - cargo +beta build --verbose - - cargo +beta test --verbose - - cargo +beta test --release --verbose + - script -q -c "cargo +beta test --verbose" + - script -q -c "cargo +beta test --release --verbose" nightly: script: - rustup toolchain add nightly - - cargo +nightly build --verbose - - cargo +nightly test --verbose - - cargo +nightly test --release --verbose \ No newline at end of file + - cargo +nightly build --verbose + - script -q -c "cargo +nightly test --verbose" + - script -q -c "cargo +nightly test --release --verbose" diff --git a/Cargo.toml b/Cargo.toml index a4dca1d..6e88e81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,9 @@ license = "MIT" keywords = ["tty", "color", "terminal", "password", "tui"] exclude = ["target", "CHANGELOG.md", "image.png", "Cargo.lock"] +[dependencies] +numtoa = { version = "0.1.0", features = ["std"]} + [target.'cfg(not(target_os = "redox"))'.dependencies] libc = "0.2.8" diff --git a/src/color.rs b/src/color.rs index 6f3fd88..73776c2 100644 --- a/src/color.rs +++ b/src/color.rs @@ -18,6 +18,7 @@ use std::io::{self, Write, Read}; use std::time::{SystemTime, Duration}; use async::async_stdin; use std::env; +use numtoa::NumToA; /// A terminal color. pub trait Color { @@ -36,14 +37,24 @@ macro_rules! derive_color { impl Color for $name { #[inline] fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, csi!("38;5;", $value, "m")) + f.write_str(self.fg_str()) } #[inline] fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, csi!("48;5;", $value, "m")) + f.write_str(self.bg_str()) } } + + impl $name { + #[inline] + /// Returns the ANSI escape sequence as a string. + pub fn fg_str(&self) -> &'static str { csi!("38;5;", $value, "m") } + + #[inline] + /// Returns the ANSI escape sequences as a string. + pub fn bg_str(&self) -> &'static str { csi!("48;5;", $value, "m") } + } }; } @@ -110,15 +121,31 @@ impl AnsiValue { } } +impl AnsiValue { + /// Returns the ANSI sequence as a string. + pub fn fg_string(self) -> String { + let mut x = [0u8; 20]; + let x = self.0.numtoa_str(10, &mut x); + [csi!("38;5;"), x, "m"].concat() + } + + /// Returns the ANSI sequence as a string. + pub fn bg_string(self) -> String { + let mut x = [0u8; 20]; + let x = self.0.numtoa_str(10, &mut x); + [csi!("48;5;"), x, "m"].concat() + } +} + impl Color for AnsiValue { #[inline] fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, csi!("38;5;{}m"), self.0) + f.write_str(&self.fg_string()) } #[inline] fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, csi!("48;5;{}m"), self.0) + f.write_str(&self.bg_string()) } } @@ -126,15 +153,41 @@ impl Color for AnsiValue { #[derive(Debug, Clone, Copy, PartialEq)] pub struct Rgb(pub u8, pub u8, pub u8); +impl Rgb { + /// Returns the ANSI sequence as a string. + pub fn fg_string(self) -> String { + let (mut x, mut y, mut z) = ([0u8; 20], [0u8; 20], [0u8; 20]); + let (x, y, z) = ( + self.0.numtoa_str(10, &mut x), + self.1.numtoa_str(10, &mut y), + self.2.numtoa_str(10, &mut z), + ); + + [csi!("38;2;"), x, ";", y, ";", z, "m"].concat() + } + + /// Returns the ANSI sequence as a string. + pub fn bg_string(self) -> String { + let (mut x, mut y, mut z) = ([0u8; 20], [0u8; 20], [0u8; 20]); + let (x, y, z) = ( + self.0.numtoa_str(10, &mut x), + self.1.numtoa_str(10, &mut y), + self.2.numtoa_str(10, &mut z), + ); + + [csi!("48;2;"), x, ";", y, ";", z, "m"].concat() + } +} + impl Color for Rgb { #[inline] fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, csi!("38;2;{};{};{}m"), self.0, self.1, self.2) + f.write_str(&self.fg_string()) } #[inline] fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, csi!("48;2;{};{};{}m"), self.0, self.1, self.2) + f.write_str(&self.bg_string()) } } @@ -142,15 +195,25 @@ impl Color for Rgb { #[derive(Debug, Clone, Copy)] pub struct Reset; +const RESET_FG: &str = csi!("39m"); +const RESET_BG: &str = csi!("49m"); + +impl Reset { + /// Returns the ANSI sequence as a string. + pub fn fg_str(self) -> &'static str { RESET_FG } + /// Returns the ANSI sequence as a string. + pub fn bg_str(self) -> &'static str { RESET_BG } +} + impl Color for Reset { #[inline] fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, csi!("39m")) + f.write_str(RESET_FG) } #[inline] fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, csi!("49m")) + f.write_str(RESET_BG) } } diff --git a/src/cursor.rs b/src/cursor.rs index cf8dc86..b9791a2 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -5,6 +5,7 @@ use std::io::{self, Write, Error, ErrorKind, Read}; use async::async_stdin_until; use std::time::{SystemTime, Duration}; use raw::CONTROL_SEQUENCE_TIMEOUT; +use numtoa::NumToA; derive_csi_sequence!("Hide the cursor.", Hide, "?25l"); derive_csi_sequence!("Show the cursor.", Show, "?25h"); @@ -32,6 +33,13 @@ derive_csi_sequence!("Save the cursor.", Save, "s"); #[derive(Copy, Clone, PartialEq, Eq)] pub struct Goto(pub u16, pub u16); +impl From for String { + fn from(this: Goto) -> String { + let (mut x, mut y) = ([0u8; 20], [0u8; 20]); + ["\x1B[", this.1.numtoa_str(10, &mut x), ";", this.0.numtoa_str(10, &mut y), "H"].concat() + } +} + impl Default for Goto { fn default() -> Goto { Goto(1, 1) @@ -41,8 +49,7 @@ impl Default for Goto { impl fmt::Display for Goto { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { debug_assert!(self != &Goto(0, 0), "Goto is one-based."); - - write!(f, csi!("{};{}H"), self.1, self.0) + f.write_str(&String::from(*self)) } } @@ -50,9 +57,16 @@ impl fmt::Display for Goto { #[derive(Copy, Clone, PartialEq, Eq)] pub struct Left(pub u16); +impl From for String { + fn from(this: Left) -> String { + let mut buf = [0u8; 20]; + ["\x1B[", this.0.numtoa_str(10, &mut buf), "D"].concat() + } +} + impl fmt::Display for Left { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, csi!("{}D"), self.0) + f.write_str(&String::from(*self)) } } @@ -60,9 +74,16 @@ impl fmt::Display for Left { #[derive(Copy, Clone, PartialEq, Eq)] pub struct Right(pub u16); +impl From for String { + fn from(this: Right) -> String { + let mut buf = [0u8; 20]; + ["\x1B[", this.0.numtoa_str(10, &mut buf), "C"].concat() + } +} + impl fmt::Display for Right { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, csi!("{}C"), self.0) + f.write_str(&String::from(*self)) } } @@ -70,9 +91,16 @@ impl fmt::Display for Right { #[derive(Copy, Clone, PartialEq, Eq)] pub struct Up(pub u16); +impl From for String { + fn from(this: Up) -> String { + let mut buf = [0u8; 20]; + ["\x1B[", this.0.numtoa_str(10, &mut buf), "A"].concat() + } +} + impl fmt::Display for Up { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, csi!("{}A"), self.0) + f.write_str(&String::from(*self)) } } @@ -80,9 +108,16 @@ impl fmt::Display for Up { #[derive(Copy, Clone, PartialEq, Eq)] pub struct Down(pub u16); +impl From for String { + fn from(this: Down) -> String { + let mut buf = [0u8; 20]; + ["\x1B[", this.0.numtoa_str(10, &mut buf), "B"].concat() + } +} + impl fmt::Display for Down { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, csi!("{}B"), self.0) + f.write_str(&String::from(*self)) } } @@ -115,7 +150,7 @@ impl DetectCursorPos for W { } } - if read_chars.len() == 0 { + if read_chars.is_empty() { return Err(Error::new(ErrorKind::Other, "Cursor position detection timed out.")); } diff --git a/src/lib.rs b/src/lib.rs index 2cb49fa..1d8f66f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,8 @@ //! For more information refer to the [README](https://github.com/redox-os/termion). #![warn(missing_docs)] +extern crate numtoa; + #[cfg(target_os = "redox")] #[path="sys/redox/mod.rs"] mod sys; diff --git a/src/macros.rs b/src/macros.rs index 28370e3..5fd70b9 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -15,5 +15,13 @@ macro_rules! derive_csi_sequence { write!(f, csi!($value)) } } + + impl AsRef<[u8]> for $name { + fn as_ref(&self) -> &'static [u8] { csi!($value).as_bytes() } + } + + impl AsRef for $name { + fn as_ref(&self) -> &'static str { csi!($value) } + } }; } From 377dca13042be90cb713a7501a2006c7606ba2d1 Mon Sep 17 00:00:00 2001 From: SamwiseFilmore Date: Mon, 20 Aug 2018 22:19:19 +0000 Subject: [PATCH 20/22] Reconfigure CI --- .gitlab-ci.yml | 67 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 90d5e9a..e60f6ab 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,25 +1,66 @@ -before_script: - - apt-get update -qq - - apt-get install -qq build-essential curl git - - curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable - - source "$HOME/.cargo/env" +image: "rust:latest" -stable: +stages: + - build + - test + +before_script: + - rustup toolchain add $toolchain + +cache: + paths: + - target/ + +build:stable: + stage: build + variables: + toolchain: stable script: - cargo +stable build --verbose + - cargo +stable build --release --verbose + +test:stable: + stage: test + variables: + toolchain: stable + dependencies: + - build:stable + script: - script -q -c "cargo +stable test --verbose" - script -q -c "cargo +stable test --release --verbose" -beta: +build:beta: + stage: build + variables: + toolchain: beta script: - - rustup toolchain add beta - cargo +beta build --verbose + - cargo +beta build --release --verbose + +test:beta: + stage: test + variables: + toolchain: beta + dependencies: + - build:beta + script: - script -q -c "cargo +beta test --verbose" - script -q -c "cargo +beta test --release --verbose" -nightly: +build:nightly: + stage: build + variables: + toolchain: nightly script: - - rustup toolchain add nightly - - cargo +nightly build --verbose - - script -q -c "cargo +nightly test --verbose" - - script -q -c "cargo +nightly test --release --verbose" + - cargo +nightly build --verbose + - cargo +nightly build --release --verbose + +test:nightly: + stage: test + variables: + toolchain: nightly + dependencies: + - build:nightly + script: + - script -q -c "cargo +nightly test --verbose" + - script -q -c "cargo +nightly test --release --verbose" \ No newline at end of file From b64b10f40bbdae061cde40cbad3dccd3efcec6d4 Mon Sep 17 00:00:00 2001 From: Nathan Lilienthal Date: Fri, 12 Oct 2018 01:40:18 -0400 Subject: [PATCH 21/22] Add ability to suspend/activate raw mode on RawTerminal --- src/raw.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/raw.rs b/src/raw.rs index 5421d56..0dbfb56 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -101,6 +101,20 @@ impl IntoRawMode for W { } } +impl RawTerminal { + pub fn suspend_raw_mode(&self) -> io::Result<()> { + set_terminal_attr(&self.prev_ios)?; + Ok(()) + } + + pub fn activate_raw_mode(&self) -> io::Result<()> { + let mut ios = get_terminal_attr()?; + raw_terminal_attr(&mut ios); + set_terminal_attr(&ios)?; + Ok(()) + } +} + #[cfg(test)] mod test { use super::*; From 0837ad5ab1804732e441d89161a97ad9a7d87f6c Mon Sep 17 00:00:00 2001 From: Michael Neumann Date: Sat, 29 Dec 2018 13:30:22 +0100 Subject: [PATCH 22/22] Fix TIOCGWINSZ type mismatch on DragonFly Below is the error message I got before this patch: error[E0308]: mismatched types --> src/sys/unix/size.rs:17:34 | 17 | cvt(ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size as *mut _))?; | ^^^^^^^^^^ expected u64, found u32 help: you can cast an `u32` to `u64`, which will zero-extend the source value | 17 | cvt(ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut size as *mut _))?; | ^^^^^^^^^^^^^^^^^ --- src/sys/unix/size.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sys/unix/size.rs b/src/sys/unix/size.rs index 9979280..17f2515 100644 --- a/src/sys/unix/size.rs +++ b/src/sys/unix/size.rs @@ -14,7 +14,7 @@ struct TermSize { pub fn terminal_size() -> io::Result<(u16, u16)> { unsafe { let mut size: TermSize = mem::zeroed(); - cvt(ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size as *mut _))?; + cvt(ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut size as *mut _))?; Ok((size.col as u16, size.row as u16)) } }