#![cfg_attr(
not(any(
unix,
windows,
target_os = "wasi",
target_os = "hermit",
target_os = "unknown"
)),
no_std
)]
#[cfg(target_os = "wasi")]
use std::os::fd::{AsFd, AsRawFd};
#[cfg(target_os = "hermit")]
use std::os::hermit::io::AsFd;
#[cfg(unix)]
use std::os::unix::io::{AsFd, AsRawFd};
#[cfg(windows)]
use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle};
#[cfg(windows)]
use windows_sys::Win32::Foundation::HANDLE;
pub trait IsTerminal {
fn is_terminal(&self) -> bool;
}
pub fn is_terminal<T: IsTerminal>(this: T) -> bool {
this.is_terminal()
}
#[cfg(not(any(windows, target_os = "unknown")))]
impl<Stream: AsFd> IsTerminal for Stream {
#[inline]
fn is_terminal(&self) -> bool {
#[cfg(any(unix, target_os = "wasi"))]
{
let fd = self.as_fd();
unsafe { libc::isatty(fd.as_raw_fd()) != 0 }
}
#[cfg(target_os = "hermit")]
{
use std::os::hermit::io::AsRawFd;
hermit_abi::isatty(self.as_fd().as_fd().as_raw_fd())
}
}
}
#[cfg(windows)]
impl<Stream: AsHandle> IsTerminal for Stream {
#[inline]
fn is_terminal(&self) -> bool {
handle_is_console(self.as_handle())
}
}
#[cfg(windows)]
fn handle_is_console(handle: BorrowedHandle<'_>) -> bool {
use windows_sys::Win32::System::Console::GetConsoleMode;
let handle = handle.as_raw_handle();
if handle.is_null() {
return false;
}
unsafe {
let mut out = 0;
if GetConsoleMode(handle as HANDLE, &mut out) != 0 {
return true;
}
msys_tty_on(handle as HANDLE)
}
}
#[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, GetFileType, FILE_TYPE_PIPE,
},
};
if GetFileType(handle) != FILE_TYPE_PIPE {
return false;
}
#[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 = match name_info
.FileName
.get(..name_info.FileNameLength as usize / 2)
{
None => return false,
Some(s) => s,
};
let name = String::from_utf16_lossy(s);
let name = name.rsplit('\\').next().unwrap_or(&name);
let is_msys = name.starts_with("msys-") || name.starts_with("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() {
assert_eq!(
atty::is(atty::Stream::Stdin),
rustix::stdio::stdin().is_terminal()
)
}
#[test]
#[cfg(any(unix, target_os = "wasi"))]
fn stdout() {
assert_eq!(
atty::is(atty::Stream::Stdout),
rustix::stdio::stdout().is_terminal()
)
}
#[test]
#[cfg(any(unix, target_os = "wasi"))]
fn stderr() {
assert_eq!(
atty::is(atty::Stream::Stderr),
rustix::stdio::stderr().is_terminal()
)
}
#[test]
#[cfg(any(unix, target_os = "wasi"))]
fn stdin_vs_libc() {
unsafe {
assert_eq!(
libc::isatty(libc::STDIN_FILENO) != 0,
rustix::stdio::stdin().is_terminal()
)
}
}
#[test]
#[cfg(any(unix, target_os = "wasi"))]
fn stdout_vs_libc() {
unsafe {
assert_eq!(
libc::isatty(libc::STDOUT_FILENO) != 0,
rustix::stdio::stdout().is_terminal()
)
}
}
#[test]
#[cfg(any(unix, target_os = "wasi"))]
fn stderr_vs_libc() {
unsafe {
assert_eq!(
libc::isatty(libc::STDERR_FILENO) != 0,
rustix::stdio::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()) });
}
}