Merge pull request #119 from redox-os/redox_termios

Move system specific features into sys module
This commit is contained in:
ticki 2017-08-03 10:19:32 +02:00 committed by GitHub
commit 792274a641
14 changed files with 245 additions and 215 deletions

View File

@ -2,7 +2,7 @@ use std::io::{self, Read};
use std::sync::mpsc; use std::sync::mpsc;
use std::thread; use std::thread;
use tty; use sys::tty::get_tty;
/// Construct an asynchronous handle to the TTY standard input. /// Construct an asynchronous handle to the TTY standard input.
/// ///
@ -17,7 +17,7 @@ use tty;
pub fn async_stdin() -> AsyncReader { pub fn async_stdin() -> AsyncReader {
let (send, recv) = mpsc::channel(); 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() { if send.send(i).is_err() {
return; return;
} }

View File

@ -9,6 +9,9 @@ use raw::CONTROL_SEQUENCE_TIMEOUT;
derive_csi_sequence!("Hide the cursor.", Hide, "?25l"); derive_csi_sequence!("Hide the cursor.", Hide, "?25l");
derive_csi_sequence!("Show the cursor.", Show, "?25h"); 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). /// Goto some position ((1,1)-based).
/// ///
/// # Why one-based? /// # Why one-based?

View File

@ -11,27 +11,20 @@
//! For more information refer to the [README](https://github.com/ticki/termion). //! For more information refer to the [README](https://github.com/ticki/termion).
#![warn(missing_docs)] #![warn(missing_docs)]
#[cfg(not(target_os = "redox"))]
extern crate libc;
#[cfg(not(target_os = "redox"))]
mod termios;
#[cfg(target_os = "redox")] #[cfg(target_os = "redox")]
extern crate redox_termios; #[path="sys/redox/mod.rs"]
mod sys;
#[cfg(target_os = "redox")] #[cfg(unix)]
extern crate syscall; #[path="sys/unix/mod.rs"]
mod sys;
pub use sys::size::terminal_size;
pub use sys::tty::{is_tty, get_tty};
mod async; mod async;
pub use async::{AsyncReader, async_stdin}; pub use async::{AsyncReader, async_stdin};
mod size;
pub use size::terminal_size;
mod tty;
pub use tty::{is_tty, get_tty};
#[macro_use] #[macro_use]
mod macros; mod macros;
pub mod clear; pub mod clear;
@ -43,3 +36,26 @@ pub mod raw;
pub mod screen; pub mod screen;
pub mod scroll; pub mod scroll;
pub mod style; 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();
}
}

View File

@ -25,6 +25,9 @@
use std::io::{self, Write}; use std::io::{self, Write};
use std::ops; 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. /// The timeout of an escape code control sequence, in milliseconds.
pub const CONTROL_SEQUENCE_TIMEOUT: u64 = 100; pub const CONTROL_SEQUENCE_TIMEOUT: u64 = 100;
@ -32,34 +35,14 @@ pub const CONTROL_SEQUENCE_TIMEOUT: u64 = 100;
/// dropped. /// dropped.
/// ///
/// Restoring will entirely bring back the old TTY state. /// Restoring will entirely bring back the old TTY state.
#[cfg(target_os = "redox")]
pub struct RawTerminal<W: Write> {
output: W,
}
#[cfg(target_os = "redox")]
impl<W: Write> Drop for RawTerminal<W> {
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<W: Write> { pub struct RawTerminal<W: Write> {
prev_ios: Termios, prev_ios: Termios,
output: W, output: W,
} }
#[cfg(not(target_os = "redox"))]
impl<W: Write> Drop for RawTerminal<W> { impl<W: Write> Drop for RawTerminal<W> {
fn drop(&mut self) { fn drop(&mut self) {
use termios::set_terminal_attr; set_terminal_attr(&self.prev_ios).unwrap();
set_terminal_attr(&mut self.prev_ios as *mut _);
} }
} }
@ -103,36 +86,18 @@ pub trait IntoRawMode: Write + Sized {
} }
impl<W: Write> IntoRawMode for W { impl<W: Write> IntoRawMode for W {
#[cfg(not(target_os = "redox"))]
fn into_raw_mode(self) -> io::Result<RawTerminal<W>> { fn into_raw_mode(self) -> io::Result<RawTerminal<W>> {
use termios::{cfmakeraw, get_terminal_attr, set_terminal_attr}; let mut ios = get_terminal_attr()?;
let (mut ios, exit) = get_terminal_attr();
let prev_ios = ios; let prev_ios = ios;
if exit != 0 {
return Err(io::Error::new(io::ErrorKind::Other, "Unable to get Termios attribute."));
}
unsafe { raw_terminal_attr(&mut ios);
cfmakeraw(&mut ios);
}
if set_terminal_attr(&mut ios as *mut _) != 0 { set_terminal_attr(&ios)?;
Err(io::Error::new(io::ErrorKind::Other, "Unable to set Termios attribute."))
} else { Ok(RawTerminal {
let res = RawTerminal {
prev_ios: prev_ios, prev_ios: prev_ios,
output: self, output: self,
}; })
Ok(res)
}
}
#[cfg(target_os = "redox")]
fn into_raw_mode(mut self) -> io::Result<RawTerminal<W>> {
write!(self, csi!("?82h"))?;
self.flush()?;
Ok(RawTerminal { output: self })
} }
} }
@ -145,6 +110,8 @@ mod test {
fn test_into_raw_mode() { fn test_into_raw_mode() {
let mut out = stdout().into_raw_mode().unwrap(); 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);
} }
} }

View File

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

33
src/sys/redox/attr.rs Normal file
View File

@ -0,0 +1,33 @@
use std::io;
use super::{cvt, syscall, Termios};
pub fn get_terminal_attr() -> io::Result<Termios> {
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()
}

15
src/sys/redox/mod.rs Normal file
View File

@ -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<usize, syscall::Error>) -> io::Result<usize> {
result.map_err(|err| io::Error::from_raw_os_error(err.errno))
}

18
src/sys/redox/size.rs Normal file
View File

@ -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."))
}
}

View File

@ -1,19 +1,10 @@
use std::{fs, io}; use std::{env, fs, io};
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
/// Is this stream a TTY? use super::syscall;
#[cfg(not(target_os = "redox"))]
pub fn is_tty<T: AsRawFd>(stream: &T) -> bool {
use libc;
unsafe { libc::isatty(stream.as_raw_fd()) == 1 }
}
/// Is this stream a TTY? /// Is this stream a TTY?
#[cfg(target_os = "redox")]
pub fn is_tty<T: AsRawFd>(stream: &T) -> bool { pub fn is_tty<T: AsRawFd>(stream: &T) -> bool {
use syscall;
if let Ok(fd) = syscall::dup(stream.as_raw_fd(), b"termios") { if let Ok(fd) = syscall::dup(stream.as_raw_fd(), b"termios") {
let _ = syscall::close(fd); let _ = syscall::close(fd);
true true
@ -25,17 +16,7 @@ pub fn is_tty<T: AsRawFd>(stream: &T) -> bool {
/// Get the TTY device. /// Get the TTY device.
/// ///
/// This allows for getting stdio representing _only_ the TTY, and not other streams. /// This allows for getting stdio representing _only_ the TTY, and not other streams.
#[cfg(target_os = "redox")]
pub fn get_tty() -> io::Result<fs::File> { pub fn get_tty() -> io::Result<fs::File> {
use std::env;
let tty = try!(env::var("TTY").map_err(|x| io::Error::new(io::ErrorKind::NotFound, x))); 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) 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::File> {
fs::OpenOptions::new().read(true).write(true).open("/dev/tty")
}

29
src/sys/unix/attr.rs Normal file
View File

@ -0,0 +1,29 @@
use std::{io, mem};
use super::{cvt, Termios};
use super::libc::c_int;
pub fn get_terminal_attr() -> io::Result<Termios> {
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) }
}

33
src/sys/unix/mod.rs Normal file
View File

@ -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: IsMinusOne>(t: T) -> io::Result<T> {
if t.is_minus_one() {
Err(io::Error::last_os_error())
} else {
Ok(t)
}
}
// } End of support functions

48
src/sys/unix/size.rs Normal file
View File

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

17
src/sys/unix/tty.rs Normal file
View File

@ -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<T: AsRawFd>(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::File> {
fs::OpenOptions::new().read(true).write(true).open("/dev/tty")
}

View File

@ -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 _);
}
}