Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
5301d8621a
|
@ -0,0 +1,66 @@
|
||||||
|
image: "rust:latest"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
build:beta:
|
||||||
|
stage: build
|
||||||
|
variables:
|
||||||
|
toolchain: beta
|
||||||
|
script:
|
||||||
|
- 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"
|
||||||
|
|
||||||
|
build:nightly:
|
||||||
|
stage: build
|
||||||
|
variables:
|
||||||
|
toolchain: nightly
|
||||||
|
script:
|
||||||
|
- 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"
|
11
Cargo.toml
11
Cargo.toml
|
@ -1,13 +1,20 @@
|
||||||
[package]
|
[package]
|
||||||
name = "termion"
|
name = "termion"
|
||||||
version = "1.4.0"
|
version = "1.5.1"
|
||||||
authors = ["ticki <Ticki@users.noreply.github.com>", "gycos <alexandre.bury@gmail.com>", "IGI-111 <igi-111@protonmail.com>"]
|
authors = ["ticki <Ticki@users.noreply.github.com>", "gycos <alexandre.bury@gmail.com>", "IGI-111 <igi-111@protonmail.com>"]
|
||||||
description = "A bindless library for manipulating terminals."
|
description = "A bindless library for manipulating terminals."
|
||||||
repository = "https://github.com/ticki/termion"
|
repository = "https://gitlab.redox-os.org/redox-os/termion"
|
||||||
documentation = "https://docs.rs/termion"
|
documentation = "https://docs.rs/termion"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
keywords = ["tty", "color", "terminal", "password", "tui"]
|
keywords = ["tty", "color", "terminal", "password", "tui"]
|
||||||
exclude = ["target", "CHANGELOG.md", "image.png", "Cargo.lock"]
|
exclude = ["target", "CHANGELOG.md", "image.png", "Cargo.lock"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
numtoa = { version = "0.1.0", features = ["std"]}
|
||||||
|
|
||||||
[target.'cfg(not(target_os = "redox"))'.dependencies]
|
[target.'cfg(not(target_os = "redox"))'.dependencies]
|
||||||
libc = "0.2.8"
|
libc = "0.2.8"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "redox")'.dependencies]
|
||||||
|
redox_syscall = "0.1"
|
||||||
|
redox_termios = "0.1"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="Termion logo" src="https://rawgit.com/ticki/termion/master/logo.svg" />
|
<img alt="Termion logo" src="https://rawgit.com/redox-os/termion/master/logo.svg" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[![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/)
|
||||||
|----|----|----|----|----
|
|----|----|----|----|----
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,5 +6,5 @@ fn main() {
|
||||||
println!("{}Red", color::Fg(color::Red));
|
println!("{}Red", color::Fg(color::Red));
|
||||||
println!("{}Blue", color::Fg(color::Blue));
|
println!("{}Blue", color::Fg(color::Blue));
|
||||||
println!("{}Blue'n'Bold{}", style::Bold, style::Reset);
|
println!("{}Blue'n'Bold{}", style::Bold, style::Reset);
|
||||||
println!("{}Just plain italic", style::Italic);
|
println!("{}Just plain italic{}", style::Italic, style::Reset);
|
||||||
}
|
}
|
||||||
|
|
27
src/async.rs
27
src/async.rs
|
@ -2,7 +2,30 @@ 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, 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(_) => { return; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AsyncReader { recv: recv }
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct an asynchronous handle to the TTY standard input.
|
/// Construct an asynchronous handle to the TTY standard input.
|
||||||
///
|
///
|
||||||
|
@ -17,7 +40,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;
|
||||||
}
|
}
|
||||||
|
|
79
src/color.rs
79
src/color.rs
|
@ -18,6 +18,7 @@ use std::io::{self, Write, Read};
|
||||||
use std::time::{SystemTime, Duration};
|
use std::time::{SystemTime, Duration};
|
||||||
use async::async_stdin;
|
use async::async_stdin;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use numtoa::NumToA;
|
||||||
|
|
||||||
/// A terminal color.
|
/// A terminal color.
|
||||||
pub trait Color {
|
pub trait Color {
|
||||||
|
@ -36,14 +37,24 @@ macro_rules! derive_color {
|
||||||
impl Color for $name {
|
impl Color for $name {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, csi!("38;5;", $value, "m"))
|
f.write_str(self.fg_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
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 {
|
impl Color for AnsiValue {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
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]
|
#[inline]
|
||||||
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Rgb(pub u8, pub u8, pub u8);
|
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 {
|
impl Color for Rgb {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
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]
|
#[inline]
|
||||||
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
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)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Reset;
|
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 {
|
impl Color for Reset {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn write_fg(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, csi!("39m"))
|
f.write_str(RESET_FG)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn write_bg(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, csi!("49m"))
|
f.write_str(RESET_BG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,17 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops;
|
use std::ops;
|
||||||
use std::io::{self, Write, Error, ErrorKind, Read};
|
use std::io::{self, Write, Error, ErrorKind, Read};
|
||||||
use async::async_stdin;
|
use async::async_stdin_until;
|
||||||
use std::time::{SystemTime, Duration};
|
use std::time::{SystemTime, Duration};
|
||||||
use raw::CONTROL_SEQUENCE_TIMEOUT;
|
use raw::CONTROL_SEQUENCE_TIMEOUT;
|
||||||
|
use numtoa::NumToA;
|
||||||
|
|
||||||
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?
|
||||||
|
@ -30,6 +34,13 @@ derive_csi_sequence!("Show the cursor.", Show, "?25h");
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct Goto(pub u16, pub u16);
|
pub struct Goto(pub u16, pub u16);
|
||||||
|
|
||||||
|
impl From<Goto> 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 {
|
impl Default for Goto {
|
||||||
fn default() -> Goto {
|
fn default() -> Goto {
|
||||||
Goto(1, 1)
|
Goto(1, 1)
|
||||||
|
@ -39,8 +50,7 @@ impl Default for Goto {
|
||||||
impl fmt::Display for Goto {
|
impl fmt::Display for Goto {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
debug_assert!(self != &Goto(0, 0), "Goto is one-based.");
|
debug_assert!(self != &Goto(0, 0), "Goto is one-based.");
|
||||||
|
f.write_str(&String::from(*self))
|
||||||
write!(f, csi!("{};{}H"), self.1, self.0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,9 +58,16 @@ impl fmt::Display for Goto {
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct Left(pub u16);
|
pub struct Left(pub u16);
|
||||||
|
|
||||||
|
impl From<Left> 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 {
|
impl fmt::Display for Left {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, csi!("{}D"), self.0)
|
f.write_str(&String::from(*self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,9 +75,16 @@ impl fmt::Display for Left {
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct Right(pub u16);
|
pub struct Right(pub u16);
|
||||||
|
|
||||||
|
impl From<Right> 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 {
|
impl fmt::Display for Right {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, csi!("{}C"), self.0)
|
f.write_str(&String::from(*self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,9 +92,16 @@ impl fmt::Display for Right {
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct Up(pub u16);
|
pub struct Up(pub u16);
|
||||||
|
|
||||||
|
impl From<Up> 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 {
|
impl fmt::Display for Up {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, csi!("{}A"), self.0)
|
f.write_str(&String::from(*self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,9 +109,16 @@ impl fmt::Display for Up {
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct Down(pub u16);
|
pub struct Down(pub u16);
|
||||||
|
|
||||||
|
impl From<Down> 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 {
|
impl fmt::Display for Down {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, csi!("{}B"), self.0)
|
f.write_str(&String::from(*self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +130,8 @@ pub trait DetectCursorPos {
|
||||||
|
|
||||||
impl<W: Write> DetectCursorPos for W {
|
impl<W: Write> DetectCursorPos for W {
|
||||||
fn cursor_pos(&mut self) -> io::Result<(u16, u16)> {
|
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?
|
// Where is the cursor?
|
||||||
// Use `ESC [ 6 n`.
|
// Use `ESC [ 6 n`.
|
||||||
|
@ -106,13 +145,13 @@ impl<W: Write> DetectCursorPos for W {
|
||||||
let now = SystemTime::now();
|
let now = SystemTime::now();
|
||||||
|
|
||||||
// Either consume all data up to R or wait for a timeout.
|
// 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 {
|
if stdin.read(&mut buf)? > 0 {
|
||||||
read_chars.push(buf[0]);
|
read_chars.push(buf[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if read_chars.len() == 0 {
|
if read_chars.is_empty() {
|
||||||
return Err(Error::new(ErrorKind::Other, "Cursor position detection timed out."));
|
return Err(Error::new(ErrorKind::Other, "Cursor position detection timed out."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
//! Mouse and key events.
|
//! Mouse and key events.
|
||||||
|
|
||||||
use std::io::{Error, ErrorKind};
|
use std::io::{Error, ErrorKind};
|
||||||
use std::ascii::AsciiExt;
|
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
/// An event reported by the terminal.
|
/// An event reported by the terminal.
|
||||||
|
|
78
src/input.rs
78
src/input.rs
|
@ -28,15 +28,28 @@ impl<R: Read> Iterator for Keys<R> {
|
||||||
|
|
||||||
/// An iterator over input events.
|
/// An iterator over input events.
|
||||||
pub struct Events<R> {
|
pub struct Events<R> {
|
||||||
source: R,
|
inner: EventsAndRaw<R>
|
||||||
leftover: Option<u8>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read> Iterator for Events<R> {
|
impl<R: Read> Iterator for Events<R> {
|
||||||
type Item = Result<Event, io::Error>;
|
type Item = Result<Event, io::Error>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Result<Event, io::Error>> {
|
fn next(&mut self) -> Option<Result<Event, io::Error>> {
|
||||||
let mut source = &mut self.source;
|
self.inner.next().map(|tuple| tuple.map(|(event, _raw)| event))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over input events and the bytes that define them.
|
||||||
|
pub struct EventsAndRaw<R> {
|
||||||
|
source: R,
|
||||||
|
leftover: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read> Iterator for EventsAndRaw<R> {
|
||||||
|
type Item = Result<(Event, Vec<u8>), io::Error>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Result<(Event, Vec<u8>), io::Error>> {
|
||||||
|
let source = &mut self.source;
|
||||||
|
|
||||||
if let Some(c) = self.leftover {
|
if let Some(c) = self.leftover {
|
||||||
// we have a leftover byte, use it
|
// we have a leftover byte, use it
|
||||||
|
@ -53,7 +66,7 @@ impl<R: Read> Iterator for Events<R> {
|
||||||
Ok(0) => return None,
|
Ok(0) => return None,
|
||||||
Ok(1) => {
|
Ok(1) => {
|
||||||
match buf[0] {
|
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()),
|
c => parse_event(c, &mut source.bytes()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,7 +88,7 @@ impl<R: Read> Iterator for Events<R> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_event<I>(item: u8, iter: &mut I) -> Result<Event, io::Error>
|
fn parse_event<I>(item: u8, iter: &mut I) -> Result<(Event, Vec<u8>), io::Error>
|
||||||
where I: Iterator<Item = Result<u8, io::Error>>
|
where I: Iterator<Item = Result<u8, io::Error>>
|
||||||
{
|
{
|
||||||
let mut buf = vec![item];
|
let mut buf = vec![item];
|
||||||
|
@ -85,7 +98,7 @@ fn parse_event<I>(item: u8, iter: &mut I) -> Result<Event, io::Error>
|
||||||
});
|
});
|
||||||
event::parse_event(item, &mut iter)
|
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<R: Read> TermRead for R {
|
|
||||||
|
impl<R: Read + TermReadEventsAndRaw> TermRead for R {
|
||||||
fn events(self) -> Events<Self> {
|
fn events(self) -> Events<Self> {
|
||||||
Events {
|
Events {
|
||||||
source: self,
|
inner: self.events_and_raw()
|
||||||
leftover: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn keys(self) -> Keys<Self> {
|
fn keys(self) -> Keys<Self> {
|
||||||
|
@ -145,6 +158,21 @@ impl<R: Read> 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<Self> where Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read> TermReadEventsAndRaw for R {
|
||||||
|
fn events_and_raw(self) -> EventsAndRaw<Self> {
|
||||||
|
EventsAndRaw {
|
||||||
|
source: self,
|
||||||
|
leftover: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A sequence of escape codes to enable terminal mouse support.
|
/// A sequence of escape codes to enable terminal mouse support.
|
||||||
const ENTER_MOUSE_SEQUENCE: &'static str = csi!("?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h");
|
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());
|
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::<u8>::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::<Vec<u8>>(), output)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_function_keys() {
|
fn test_function_keys() {
|
||||||
let mut st = b"\x1BOP\x1BOQ\x1BOR\x1BOS".keys();
|
let mut st = b"\x1BOP\x1BOQ\x1BOR\x1BOS".keys();
|
||||||
|
|
46
src/lib.rs
46
src/lib.rs
|
@ -8,24 +8,25 @@
|
||||||
//!
|
//!
|
||||||
//! Supports Redox, Mac OS X, and Linux (or, in general, ANSI terminals).
|
//! 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)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
#[cfg(not(target_os = "redox"))]
|
extern crate numtoa;
|
||||||
extern crate libc;
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "redox"))]
|
#[cfg(target_os = "redox")]
|
||||||
mod termios;
|
#[path="sys/redox/mod.rs"]
|
||||||
|
mod sys;
|
||||||
|
|
||||||
|
#[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;
|
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;
|
||||||
|
@ -37,3 +38,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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,5 +15,13 @@ macro_rules! derive_csi_sequence {
|
||||||
write!(f, csi!($value))
|
write!(f, csi!($value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for $name {
|
||||||
|
fn as_ref(&self) -> &'static [u8] { csi!($value).as_bytes() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for $name {
|
||||||
|
fn as_ref(&self) -> &'static str { csi!($value) }
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
67
src/raw.rs
67
src/raw.rs
|
@ -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,32 @@ 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")]
|
impl<W: Write> RawTerminal<W> {
|
||||||
fn into_raw_mode(mut self) -> io::Result<RawTerminal<W>> {
|
pub fn suspend_raw_mode(&self) -> io::Result<()> {
|
||||||
write!(self, csi!("?82h"))?;
|
set_terminal_attr(&self.prev_ios)?;
|
||||||
self.flush()?;
|
Ok(())
|
||||||
Ok(RawTerminal { output: self })
|
}
|
||||||
|
|
||||||
|
pub fn activate_raw_mode(&self) -> io::Result<()> {
|
||||||
|
let mut ios = get_terminal_attr()?;
|
||||||
|
raw_terminal_attr(&mut ios);
|
||||||
|
set_terminal_attr(&ios)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,6 +124,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
80
src/size.rs
80
src/size.rs
|
@ -1,80 +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 std::env;
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
Ok((width, height))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_size() {
|
|
||||||
assert!(terminal_size().is_ok());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
|
@ -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."))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
use std::{env, fs, io};
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
|
||||||
|
use super::syscall;
|
||||||
|
|
||||||
|
/// Is this stream a TTY?
|
||||||
|
pub fn is_tty<T: AsRawFd>(stream: &T) -> bool {
|
||||||
|
if let Ok(fd) = syscall::dup(stream.as_raw_fd(), b"termios") {
|
||||||
|
let _ = syscall::close(fd);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
||||||
|
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)
|
||||||
|
}
|
|
@ -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) }
|
||||||
|
}
|
|
@ -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
|
|
@ -0,0 +1,20 @@
|
||||||
|
use std::{io, mem};
|
||||||
|
|
||||||
|
use super::cvt;
|
||||||
|
use super::libc::{c_ushort, ioctl, STDOUT_FILENO, TIOCGWINSZ};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct TermSize {
|
||||||
|
row: c_ushort,
|
||||||
|
col: c_ushort,
|
||||||
|
_x: c_ushort,
|
||||||
|
_y: c_ushort,
|
||||||
|
}
|
||||||
|
/// 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.into(), &mut size as *mut _))?;
|
||||||
|
Ok((size.col as u16, size.row as u16))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
|
@ -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 _);
|
|
||||||
}
|
|
||||||
}
|
|
34
src/tty.rs
34
src/tty.rs
|
@ -1,34 +0,0 @@
|
||||||
use std::{fs, io};
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
|
|
||||||
/// Is this stream an TTY?
|
|
||||||
#[cfg(not(target_os = "redox"))]
|
|
||||||
pub fn is_tty<T: AsRawFd>(stream: &T) -> bool {
|
|
||||||
use libc;
|
|
||||||
|
|
||||||
unsafe { libc::isatty(stream.as_raw_fd()) == 1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This will panic.
|
|
||||||
#[cfg(target_os = "redox")]
|
|
||||||
pub fn is_tty<T: AsRawFd>(_stream: &T) -> bool {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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<fs::File> {
|
|
||||||
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::File> {
|
|
||||||
fs::OpenOptions::new().read(true).write(true).open("/dev/tty")
|
|
||||||
}
|
|
Loading…
Reference in New Issue