Skip to content

Commit

Permalink
Support of reading the is_tty status of stdin
Browse files Browse the repository at this point in the history
  • Loading branch information
Gordon01 committed Jan 21, 2024
1 parent de2f15a commit 198462d
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 97 deletions.
103 changes: 77 additions & 26 deletions src/term.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,13 @@ impl<'a> TermFeatures<'a> {
/// Check if this is a real user attended terminal (`isatty`)
#[inline]
pub fn is_attended(&self) -> bool {
is_a_terminal(self.0)
self.0.tty.is_tty
}

/// Check if input is a terminal
#[inline]
pub fn is_input_a_tty(&self) -> bool {
self.0.tty.input
}

/// Check if colors are supported by this terminal.
Expand All @@ -82,16 +88,10 @@ impl<'a> TermFeatures<'a> {
///
/// This is sometimes useful to disable features that are known to not
/// work on msys terminals or require special handling.
#[cfg(windows)]
#[inline]
pub fn is_msys_tty(&self) -> bool {
#[cfg(windows)]
{
msys_tty_on(self.0)
}
#[cfg(not(windows))]
{
false
}
self.0.tty.msys
}

/// Check if this terminal wants emojis.
Expand Down Expand Up @@ -121,28 +121,79 @@ impl<'a> TermFeatures<'a> {
}
}

pub(crate) enum Stream {
Stdin,
Stdout,
Stderr,
}

#[derive(Clone, Debug, Default)]
pub(crate) struct Tty {
/// Only considers output streams. Left for compatibility with `Term.is_tty`
pub(crate) is_tty: bool,
input: bool,
output: bool,
error: bool,
#[cfg(windows)]
pub(crate) msys: bool,
}

impl From<&TermTarget> for Tty {
fn from(target: &TermTarget) -> Self {
let mut tty = Self {
is_tty: false,
input: is_a_tty(Stream::Stdin),
output: is_a_tty(Stream::Stdout),
error: is_a_tty(Stream::Stderr),
#[cfg(windows)]
msys: msys_tty_on(&target),
};
tty.is_tty = match target {
TermTarget::Stdout => tty.output,
TermTarget::Stderr => tty.error,
#[cfg(unix)]
TermTarget::ReadWritePair(_) => false,
};

#[cfg(windows)]
{
if !tty.is_tty {
// At this point, we *could* have a false negative. We can determine that
// this is true negative if we can detect the presence of a console on
// any of the other streams. If another stream has a console, then we know
// we're in a Windows console and can therefore trust the negative.
tty.is_tty = if match target {
TermTarget::Stdout => tty.input || tty.error,
TermTarget::Stderr => tty.input || tty.output,
} {
false
} else {
tty.msys
}
}
}

tty
}
}

/// Abstraction around a terminal.
///
/// A terminal can be cloned. If a buffer is used it's shared across all
/// clones which means it largely acts as a handle.
#[derive(Clone, Debug)]
pub struct Term {
inner: Arc<TermInner>,
pub(crate) is_msys_tty: bool,
pub(crate) is_tty: bool,
pub(crate) tty: Tty,
}

impl Term {
fn with_inner(inner: TermInner) -> Term {
let mut term = Term {
let tty = Tty::from(&inner.target);
Term {
inner: Arc::new(inner),
is_msys_tty: false,
is_tty: false,
};

term.is_msys_tty = term.features().is_msys_tty();
term.is_tty = term.features().is_attended();
term
tty,
}
}

/// Return a new unbuffered terminal.
Expand Down Expand Up @@ -265,7 +316,7 @@ impl Term {
/// or complete key chord is entered. If the terminal is not user attended
/// the return value will be an error.
pub fn read_char(&self) -> io::Result<char> {
if !self.is_tty {
if !self.tty.is_tty {
return Err(io::Error::new(
io::ErrorKind::NotConnected,
"Not a terminal",
Expand All @@ -289,15 +340,15 @@ impl Term {
/// This does not echo anything. If the terminal is not user attended
/// the return value will always be the unknown key.
pub fn read_key(&self) -> io::Result<Key> {
if !self.is_tty {
if !self.tty.is_tty {
Ok(Key::Unknown)
} else {
read_single_key(false)
}
}

pub fn read_key_raw(&self) -> io::Result<Key> {
if !self.is_tty {
if !self.tty.is_tty {
Ok(Key::Unknown)
} else {
read_single_key(true)
Expand All @@ -319,7 +370,7 @@ impl Term {
/// This does not include the trailing newline. If the terminal is not
/// user attended the return value will always be an empty string.
pub fn read_line_initial_text(&self, initial: &str) -> io::Result<String> {
if !self.is_tty {
if !self.tty.is_tty {
return Ok("".into());
}
*self.inner.prompt.write().unwrap() = initial.to_string();
Expand Down Expand Up @@ -369,7 +420,7 @@ impl Term {
/// also switches the terminal into a different mode where not all
/// characters might be accepted.
pub fn read_secure_line(&self) -> io::Result<String> {
if !self.is_tty {
if !self.tty.is_tty {
return Ok("".into());
}
match read_secure() {
Expand Down Expand Up @@ -400,7 +451,7 @@ impl Term {
/// Check if the terminal is indeed a terminal.
#[inline]
pub fn is_term(&self) -> bool {
self.is_tty
self.tty.is_tty
}

/// Check for common terminal features.
Expand Down Expand Up @@ -509,7 +560,7 @@ impl Term {

/// Set the terminal title.
pub fn set_title<T: Display>(&self, title: T) {
if !self.is_tty {
if !self.tty.is_tty {
return;
}
set_title(title);
Expand Down
54 changes: 30 additions & 24 deletions src/unix_term.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::os::unix::io::AsRawFd;
use std::str;

use crate::kb::Key;
use crate::term::Term;
use crate::term::{Stream, Term};

pub use crate::common_term::*;

Expand All @@ -19,6 +19,16 @@ pub fn is_a_terminal(out: &Term) -> bool {
unsafe { libc::isatty(out.as_raw_fd()) != 0 }
}

#[inline]
pub fn is_a_tty(stream: Stream) -> bool {
let fd = match stream {
Stream::Stdin => libc::STDIN_FILENO,
Stream::Stdout => libc::STDOUT_FILENO,
Stream::Stderr => libc::STDERR_FILENO,
};
unsafe { libc::isatty(fd) == 1 }
}

pub fn is_a_color_terminal(out: &Term) -> bool {
if !is_a_terminal(out) {
return false;
Expand Down Expand Up @@ -65,19 +75,17 @@ pub fn terminal_size(out: &Term) -> Option<(u16, u16)> {

pub fn read_secure() -> io::Result<String> {
let f_tty;
let fd = unsafe {
if libc::isatty(libc::STDIN_FILENO) == 1 {
f_tty = None;
libc::STDIN_FILENO
} else {
let f = fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")?;
let fd = f.as_raw_fd();
f_tty = Some(BufReader::new(f));
fd
}
let fd = if is_a_tty(Stream::Stdin) {
f_tty = None;
libc::STDIN_FILENO
} else {
let f = fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")?;
let fd = f.as_raw_fd();
f_tty = Some(BufReader::new(f));
fd
};

let mut termios = mem::MaybeUninit::uninit();
Expand Down Expand Up @@ -296,16 +304,14 @@ fn read_single_key_impl(fd: i32) -> Result<Key, io::Error> {

pub fn read_single_key(ctrlc_key: bool) -> io::Result<Key> {
let tty_f;
let fd = unsafe {
if libc::isatty(libc::STDIN_FILENO) == 1 {
libc::STDIN_FILENO
} else {
tty_f = fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")?;
tty_f.as_raw_fd()
}
let fd = if is_a_tty(Stream::Stdin) {
libc::STDIN_FILENO
} else {
tty_f = fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")?;
tty_f.as_raw_fd()
};
let mut termios = core::mem::MaybeUninit::uninit();
c_result(|| unsafe { libc::tcgetattr(fd, termios.as_mut_ptr()) })?;
Expand Down
Loading

0 comments on commit 198462d

Please sign in to comment.