#![cfg_attr(unix, no_std)]
#[cfg(not(target_os = "unknown"))]
use io_lifetimes::AsFilelike;
#[cfg(windows)]
use io_lifetimes::BorrowedHandle;
#[cfg(windows)]
use windows_sys::Win32::Foundation::HANDLE;
#[cfg(windows)]
use windows_sys::Win32::System::Console::STD_HANDLE;
pub trait IsTerminal {
fn is_terminal(&self) -> bool;
}
#[cfg(not(target_os = "unknown"))]
impl<Stream: AsFilelike> IsTerminal for Stream {
#[inline]
fn is_terminal(&self) -> bool {
#[cfg(any(unix, target_os = "wasi"))]
{
rustix::termios::isatty(self)
}
#[cfg(target_os = "hermit")]
{
hermit_abi::isatty(self.as_filelike().as_fd())
}
#[cfg(windows)]
{
_is_terminal(self.as_filelike())
}
}
}
#[cfg(windows)]
fn _is_terminal(stream: BorrowedHandle<'_>) -> bool {
use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::System::Console::GetStdHandle;
use windows_sys::Win32::System::Console::{
STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT,
STD_OUTPUT_HANDLE as STD_OUTPUT,
};
let (fd, others) = unsafe {
if stream.as_raw_handle() == GetStdHandle(STD_INPUT) as _ {
(STD_INPUT, [STD_ERROR, STD_OUTPUT])
} else if stream.as_raw_handle() == GetStdHandle(STD_OUTPUT) as _ {
(STD_OUTPUT, [STD_INPUT, STD_ERROR])
} else if stream.as_raw_handle() == GetStdHandle(STD_ERROR) as _ {
(STD_ERROR, [STD_INPUT, STD_OUTPUT])
} else {
return false;
}
};
if unsafe { console_on_any(&[fd]) } {
return true;
}
if unsafe { console_on_any(&others) } {
return false;
}
let handle = unsafe { GetStdHandle(fd) };
unsafe { msys_tty_on(handle) }
}
#[cfg(windows)]
unsafe fn console_on_any(fds: &[STD_HANDLE]) -> bool {
use windows_sys::Win32::System::Console::{GetConsoleMode, GetStdHandle};
for &fd in fds {
let mut out = 0;
let handle = GetStdHandle(fd);
if GetConsoleMode(handle, &mut out) != 0 {
return true;
}
}
false
}
#[cfg(windows)]
unsafe fn msys_tty_on(handle: HANDLE) -> bool {
use std::ffi::c_void;
use windows_sys::Win32::{
Foundation::MAX_PATH,
Storage::FileSystem::{FileNameInfo, GetFileInformationByHandleEx},
};
#[repr(C)]
#[allow(non_snake_case)]
struct FILE_NAME_INFO {
FileNameLength: u32,
FileName: [u16; MAX_PATH as usize],
}
let mut name_info = FILE_NAME_INFO {
FileNameLength: 0,
FileName: [0; MAX_PATH as usize],
};
let res = GetFileInformationByHandleEx(
handle,
FileNameInfo,
&mut name_info as *mut _ as *mut c_void,
std::mem::size_of::<FILE_NAME_INFO>() as u32,
);
if res == 0 {
return false;
}
let s = &name_info.FileName[..name_info.FileNameLength as usize / 2];
let name = String::from_utf16_lossy(s);
let is_msys = name.contains("msys-") || name.contains("cygwin-");
let is_pty = name.contains("-pty");
is_msys && is_pty
}
#[cfg(target_os = "unknown")]
impl IsTerminal for std::io::Stdin {
#[inline]
fn is_terminal(&self) -> bool {
false
}
}
#[cfg(target_os = "unknown")]
impl IsTerminal for std::io::Stdout {
#[inline]
fn is_terminal(&self) -> bool {
false
}
}
#[cfg(target_os = "unknown")]
impl IsTerminal for std::io::Stderr {
#[inline]
fn is_terminal(&self) -> bool {
false
}
}
#[cfg(target_os = "unknown")]
impl<'a> IsTerminal for std::io::StdinLock<'a> {
#[inline]
fn is_terminal(&self) -> bool {
false
}
}
#[cfg(target_os = "unknown")]
impl<'a> IsTerminal for std::io::StdoutLock<'a> {
#[inline]
fn is_terminal(&self) -> bool {
false
}
}
#[cfg(target_os = "unknown")]
impl<'a> IsTerminal for std::io::StderrLock<'a> {
#[inline]
fn is_terminal(&self) -> bool {
false
}
}
#[cfg(target_os = "unknown")]
impl<'a> IsTerminal for std::fs::File {
#[inline]
fn is_terminal(&self) -> bool {
false
}
}
#[cfg(target_os = "unknown")]
impl IsTerminal for std::process::ChildStdin {
#[inline]
fn is_terminal(&self) -> bool {
false
}
}
#[cfg(target_os = "unknown")]
impl IsTerminal for std::process::ChildStdout {
#[inline]
fn is_terminal(&self) -> bool {
false
}
}
#[cfg(target_os = "unknown")]
impl IsTerminal for std::process::ChildStderr {
#[inline]
fn is_terminal(&self) -> bool {
false
}
}
#[cfg(test)]
mod tests {
#[cfg(not(target_os = "unknown"))]
use super::IsTerminal;
#[test]
#[cfg(windows)]
fn stdin() {
assert_eq!(
atty::is(atty::Stream::Stdin),
std::io::stdin().is_terminal()
)
}
#[test]
#[cfg(windows)]
fn stdout() {
assert_eq!(
atty::is(atty::Stream::Stdout),
std::io::stdout().is_terminal()
)
}
#[test]
#[cfg(windows)]
fn stderr() {
assert_eq!(
atty::is(atty::Stream::Stderr),
std::io::stderr().is_terminal()
)
}
#[test]
#[cfg(any(unix, target_os = "wasi"))]
fn stdin() {
unsafe {
assert_eq!(
atty::is(atty::Stream::Stdin),
rustix::io::stdin().is_terminal()
)
}
}
#[test]
#[cfg(any(unix, target_os = "wasi"))]
fn stdout() {
unsafe {
assert_eq!(
atty::is(atty::Stream::Stdout),
rustix::io::stdout().is_terminal()
)
}
}
#[test]
#[cfg(any(unix, target_os = "wasi"))]
fn stderr() {
unsafe {
assert_eq!(
atty::is(atty::Stream::Stderr),
rustix::io::stderr().is_terminal()
)
}
}
#[test]
#[cfg(any(unix, target_os = "wasi"))]
fn stdin_vs_libc() {
unsafe {
assert_eq!(
libc::isatty(libc::STDIN_FILENO) != 0,
rustix::io::stdin().is_terminal()
)
}
}
#[test]
#[cfg(any(unix, target_os = "wasi"))]
fn stdout_vs_libc() {
unsafe {
assert_eq!(
libc::isatty(libc::STDOUT_FILENO) != 0,
rustix::io::stdout().is_terminal()
)
}
}
#[test]
#[cfg(any(unix, target_os = "wasi"))]
fn stderr_vs_libc() {
unsafe {
assert_eq!(
libc::isatty(libc::STDERR_FILENO) != 0,
rustix::io::stderr().is_terminal()
)
}
}
#[test]
#[cfg(windows)]
fn msys_tty_on_path_length() {
use std::{fs::File, os::windows::io::AsRawHandle};
use windows_sys::Win32::Foundation::MAX_PATH;
let dir = tempfile::tempdir().expect("Unable to create temporary directory");
let file_path = dir.path().join("ten_chars_".repeat(25));
assert!(file_path.to_string_lossy().len() > MAX_PATH as usize);
let file = File::create(file_path).expect("Unable to create file");
assert!(!unsafe { crate::msys_tty_on(file.as_raw_handle() as isize) });
}
}