211 lines
5.3 KiB
Rust
211 lines
5.3 KiB
Rust
//! atty is a simple utility that answers one question
|
|
//! > is this a tty?
|
|
//!
|
|
//! usage is just as simple
|
|
//!
|
|
//! ```
|
|
//! if atty::is(atty::Stream::Stdout) {
|
|
//! println!("i'm a tty")
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! ```
|
|
//! if atty::isnt(atty::Stream::Stdout) {
|
|
//! println!("i'm not a tty")
|
|
//! }
|
|
//! ```
|
|
|
|
#![cfg_attr(unix, no_std)]
|
|
|
|
#[cfg(unix)]
|
|
extern crate libc;
|
|
#[cfg(windows)]
|
|
extern crate winapi;
|
|
|
|
#[cfg(windows)]
|
|
use winapi::shared::minwindef::DWORD;
|
|
#[cfg(windows)]
|
|
use winapi::shared::ntdef::WCHAR;
|
|
|
|
/// possible stream sources
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub enum Stream {
|
|
Stdout,
|
|
Stderr,
|
|
Stdin,
|
|
}
|
|
|
|
/// returns true if this is a tty
|
|
#[cfg(all(unix, not(target_arch = "wasm32")))]
|
|
pub fn is(stream: Stream) -> bool {
|
|
extern crate libc;
|
|
|
|
let fd = match stream {
|
|
Stream::Stdout => libc::STDOUT_FILENO,
|
|
Stream::Stderr => libc::STDERR_FILENO,
|
|
Stream::Stdin => libc::STDIN_FILENO,
|
|
};
|
|
unsafe { libc::isatty(fd) != 0 }
|
|
}
|
|
|
|
/// returns true if this is a tty
|
|
#[cfg(target_os = "hermit")]
|
|
pub fn is(stream: Stream) -> bool {
|
|
extern crate hermit_abi;
|
|
|
|
let fd = match stream {
|
|
Stream::Stdout => hermit_abi::STDOUT_FILENO,
|
|
Stream::Stderr => hermit_abi::STDERR_FILENO,
|
|
Stream::Stdin => hermit_abi::STDIN_FILENO,
|
|
};
|
|
hermit_abi::isatty(fd)
|
|
}
|
|
|
|
/// returns true if this is a tty
|
|
#[cfg(windows)]
|
|
pub fn is(stream: Stream) -> bool {
|
|
use winapi::um::winbase::{
|
|
STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT,
|
|
STD_OUTPUT_HANDLE as STD_OUTPUT,
|
|
};
|
|
|
|
let (fd, others) = match stream {
|
|
Stream::Stdin => (STD_INPUT, [STD_ERROR, STD_OUTPUT]),
|
|
Stream::Stderr => (STD_ERROR, [STD_INPUT, STD_OUTPUT]),
|
|
Stream::Stdout => (STD_OUTPUT, [STD_INPUT, STD_ERROR]),
|
|
};
|
|
if unsafe { console_on_any(&[fd]) } {
|
|
// False positives aren't possible. If we got a console then
|
|
// we definitely have a tty on stdin.
|
|
return true;
|
|
}
|
|
|
|
// 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.
|
|
if unsafe { console_on_any(&others) } {
|
|
return false;
|
|
}
|
|
|
|
// Otherwise, we fall back to a very strange msys hack to see if we can
|
|
// sneakily detect the presence of a tty.
|
|
unsafe { msys_tty_on(fd) }
|
|
}
|
|
|
|
/// returns true if this is _not_ a tty
|
|
pub fn isnt(stream: Stream) -> bool {
|
|
!is(stream)
|
|
}
|
|
|
|
/// Returns true if any of the given fds are on a console.
|
|
#[cfg(windows)]
|
|
unsafe fn console_on_any(fds: &[DWORD]) -> bool {
|
|
use winapi::um::{consoleapi::GetConsoleMode, processenv::GetStdHandle};
|
|
|
|
for &fd in fds {
|
|
let mut out = 0;
|
|
let handle = GetStdHandle(fd);
|
|
if GetConsoleMode(handle, &mut out) != 0 {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// Returns true if there is an MSYS tty on the given handle.
|
|
#[cfg(windows)]
|
|
unsafe fn msys_tty_on(fd: DWORD) -> bool {
|
|
use std::{mem, slice};
|
|
|
|
use winapi::{
|
|
ctypes::c_void,
|
|
shared::minwindef::MAX_PATH,
|
|
um::{
|
|
fileapi::FILE_NAME_INFO, minwinbase::FileNameInfo, processenv::GetStdHandle,
|
|
winbase::GetFileInformationByHandleEx,
|
|
},
|
|
};
|
|
|
|
let size = mem::size_of::<FILE_NAME_INFO>();
|
|
let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::<WCHAR>()];
|
|
let res = GetFileInformationByHandleEx(
|
|
GetStdHandle(fd),
|
|
FileNameInfo,
|
|
&mut *name_info_bytes as *mut _ as *mut c_void,
|
|
name_info_bytes.len() as u32,
|
|
);
|
|
if res == 0 {
|
|
return false;
|
|
}
|
|
let name_info: &FILE_NAME_INFO = &*(name_info_bytes.as_ptr() as *const FILE_NAME_INFO);
|
|
let s = slice::from_raw_parts(
|
|
name_info.FileName.as_ptr(),
|
|
name_info.FileNameLength as usize / 2,
|
|
);
|
|
let name = String::from_utf16_lossy(s);
|
|
// This checks whether 'pty' exists in the file name, which indicates that
|
|
// a pseudo-terminal is attached. To mitigate against false positives
|
|
// (e.g., an actual file name that contains 'pty'), we also require that
|
|
// either the strings 'msys-' or 'cygwin-' are in the file name as well.)
|
|
let is_msys = name.contains("msys-") || name.contains("cygwin-");
|
|
let is_pty = name.contains("-pty");
|
|
is_msys && is_pty
|
|
}
|
|
|
|
/// returns true if this is a tty
|
|
#[cfg(target_arch = "wasm32")]
|
|
pub fn is(_stream: Stream) -> bool {
|
|
false
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{is, Stream};
|
|
|
|
#[test]
|
|
#[cfg(windows)]
|
|
fn is_err() {
|
|
// appveyor pipes its output
|
|
assert!(!is(Stream::Stderr))
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(windows)]
|
|
fn is_out() {
|
|
// appveyor pipes its output
|
|
assert!(!is(Stream::Stdout))
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(windows)]
|
|
fn is_in() {
|
|
assert!(is(Stream::Stdin))
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn is_err() {
|
|
assert!(is(Stream::Stderr))
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(unix)]
|
|
fn is_out() {
|
|
assert!(is(Stream::Stdout))
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "macos")]
|
|
fn is_in() {
|
|
// macos on travis seems to pipe its input
|
|
assert!(is(Stream::Stdin))
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(all(not(target_os = "macos"), unix))]
|
|
fn is_in() {
|
|
assert!(is(Stream::Stdin))
|
|
}
|
|
}
|