1#![cfg_attr(unix, no_std)]
19
20#[cfg(unix)]
21extern crate libc;
22#[cfg(windows)]
23extern crate winapi;
24
25#[cfg(windows)]
26use winapi::shared::minwindef::DWORD;
27#[cfg(windows)]
28use winapi::shared::ntdef::WCHAR;
29
30#[derive(Clone, Copy, Debug)]
32pub enum Stream {
33 Stdout,
34 Stderr,
35 Stdin,
36}
37
38#[cfg(all(unix, not(target_arch = "wasm32")))]
40pub fn is(stream: Stream) -> bool {
41 extern crate libc;
42
43 let fd = match stream {
44 Stream::Stdout => libc::STDOUT_FILENO,
45 Stream::Stderr => libc::STDERR_FILENO,
46 Stream::Stdin => libc::STDIN_FILENO,
47 };
48 unsafe { libc::isatty(fd) != 0 }
49}
50
51#[cfg(windows)]
53pub fn is(stream: Stream) -> bool {
54 use winapi::um::winbase::{
55 STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT,
56 STD_OUTPUT_HANDLE as STD_OUTPUT,
57 };
58
59 let (fd, others) = match stream {
60 Stream::Stdin => (STD_INPUT, [STD_ERROR, STD_OUTPUT]),
61 Stream::Stderr => (STD_ERROR, [STD_INPUT, STD_OUTPUT]),
62 Stream::Stdout => (STD_OUTPUT, [STD_INPUT, STD_ERROR]),
63 };
64 if unsafe { console_on_any(&[fd]) } {
65 return true;
68 }
69
70 if unsafe { console_on_any(&others) } {
75 return false;
76 }
77
78 unsafe { msys_tty_on(fd) }
81}
82
83pub fn isnt(stream: Stream) -> bool {
85 !is(stream)
86}
87
88#[cfg(windows)]
90unsafe fn console_on_any(fds: &[DWORD]) -> bool {
91 use winapi::um::{consoleapi::GetConsoleMode, processenv::GetStdHandle};
92
93 for &fd in fds {
94 let mut out = 0;
95 let handle = GetStdHandle(fd);
96 if GetConsoleMode(handle, &mut out) != 0 {
97 return true;
98 }
99 }
100 false
101}
102
103#[cfg(windows)]
105unsafe fn msys_tty_on(fd: DWORD) -> bool {
106 use std::{mem, slice};
107
108 use winapi::{
109 ctypes::c_void,
110 shared::minwindef::MAX_PATH,
111 um::{
112 fileapi::FILE_NAME_INFO, minwinbase::FileNameInfo, processenv::GetStdHandle,
113 winbase::GetFileInformationByHandleEx,
114 },
115 };
116
117 let size = mem::size_of::<FILE_NAME_INFO>();
118 let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::<WCHAR>()];
119 let res = GetFileInformationByHandleEx(
120 GetStdHandle(fd),
121 FileNameInfo,
122 &mut *name_info_bytes as *mut _ as *mut c_void,
123 name_info_bytes.len() as u32,
124 );
125 if res == 0 {
126 return false;
127 }
128 let name_info: &FILE_NAME_INFO = &*(name_info_bytes.as_ptr() as *const FILE_NAME_INFO);
129 let s = slice::from_raw_parts(
130 name_info.FileName.as_ptr(),
131 name_info.FileNameLength as usize / 2,
132 );
133 let name = String::from_utf16_lossy(s);
134 let is_msys = name.contains("msys-") || name.contains("cygwin-");
139 let is_pty = name.contains("-pty");
140 is_msys && is_pty
141}
142
143#[cfg(target_arch = "wasm32")]
145pub fn is(_stream: Stream) -> bool {
146 false
147}
148
149#[cfg(test)]
150mod tests {
151 use super::{is, Stream};
152
153 #[test]
154 #[cfg(windows)]
155 fn is_err() {
156 assert!(!is(Stream::Stderr))
158 }
159
160 #[test]
161 #[cfg(windows)]
162 fn is_out() {
163 assert!(!is(Stream::Stdout))
165 }
166
167 #[test]
168 #[cfg(windows)]
169 fn is_in() {
170 assert!(is(Stream::Stdin))
171 }
172
173 #[test]
174 #[cfg(unix)]
175 fn is_err() {
176 assert!(is(Stream::Stderr))
177 }
178
179 #[test]
180 #[cfg(unix)]
181 fn is_out() {
182 assert!(is(Stream::Stdout))
183 }
184
185 #[test]
186 #[cfg(target_os = "macos")]
187 fn is_in() {
188 assert!(is(Stream::Stdin))
190 }
191
192 #[test]
193 #[cfg(all(not(target_os = "macos"), unix))]
194 fn is_in() {
195 assert!(is(Stream::Stdin))
196 }
197}