1#![cfg_attr(
29 not(any(
30 unix,
31 windows,
32 target_os = "wasi",
33 target_os = "hermit",
34 target_os = "unknown"
35 )),
36 no_std
37)]
38
39#[cfg(target_os = "wasi")]
40use std::os::fd::{AsFd, AsRawFd};
41#[cfg(target_os = "hermit")]
42use std::os::hermit::io::AsFd;
43#[cfg(unix)]
44use std::os::unix::io::{AsFd, AsRawFd};
45#[cfg(windows)]
46use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle};
47#[cfg(windows)]
48use windows_sys::Win32::Foundation::HANDLE;
49
50pub trait IsTerminal {
52 fn is_terminal(&self) -> bool;
64}
65
66pub fn is_terminal<T: IsTerminal>(this: T) -> bool {
80 this.is_terminal()
81}
82
83#[cfg(not(any(windows, target_os = "unknown")))]
84impl<Stream: AsFd> IsTerminal for Stream {
85 #[inline]
86 fn is_terminal(&self) -> bool {
87 #[cfg(any(unix, target_os = "wasi"))]
88 {
89 let fd = self.as_fd();
90 unsafe { libc::isatty(fd.as_raw_fd()) != 0 }
91 }
92
93 #[cfg(target_os = "hermit")]
94 {
95 use std::os::hermit::io::AsRawFd;
96 hermit_abi::isatty(self.as_fd().as_fd().as_raw_fd())
97 }
98 }
99}
100
101#[cfg(windows)]
102impl<Stream: AsHandle> IsTerminal for Stream {
103 #[inline]
104 fn is_terminal(&self) -> bool {
105 handle_is_console(self.as_handle())
106 }
107}
108
109#[cfg(windows)]
114fn handle_is_console(handle: BorrowedHandle<'_>) -> bool {
115 use windows_sys::Win32::System::Console::GetConsoleMode;
116
117 let handle = handle.as_raw_handle();
118
119 if handle.is_null() {
121 return false;
122 }
123
124 unsafe {
125 let mut out = 0;
126 if GetConsoleMode(handle as HANDLE, &mut out) != 0 {
127 return true;
129 }
130
131 msys_tty_on(handle as HANDLE)
133 }
134}
135
136#[cfg(windows)]
138unsafe fn msys_tty_on(handle: HANDLE) -> bool {
139 use std::ffi::c_void;
140 use windows_sys::Win32::{
141 Foundation::MAX_PATH,
142 Storage::FileSystem::{
143 FileNameInfo, GetFileInformationByHandleEx, GetFileType, FILE_TYPE_PIPE,
144 },
145 };
146
147 if GetFileType(handle) != FILE_TYPE_PIPE {
149 return false;
150 }
151
152 #[repr(C)]
155 #[allow(non_snake_case)]
156 struct FILE_NAME_INFO {
157 FileNameLength: u32,
158 FileName: [u16; MAX_PATH as usize],
159 }
160 let mut name_info = FILE_NAME_INFO {
161 FileNameLength: 0,
162 FileName: [0; MAX_PATH as usize],
163 };
164 let res = GetFileInformationByHandleEx(
166 handle,
167 FileNameInfo,
168 &mut name_info as *mut _ as *mut c_void,
169 std::mem::size_of::<FILE_NAME_INFO>() as u32,
170 );
171 if res == 0 {
172 return false;
173 }
174
175 let s = match name_info
177 .FileName
178 .get(..name_info.FileNameLength as usize / 2)
179 {
180 None => return false,
181 Some(s) => s,
182 };
183 let name = String::from_utf16_lossy(s);
184 let name = name.rsplit('\\').next().unwrap_or(&name);
186 let is_msys = name.starts_with("msys-") || name.starts_with("cygwin-");
191 let is_pty = name.contains("-pty");
192 is_msys && is_pty
193}
194
195#[cfg(target_os = "unknown")]
196impl IsTerminal for std::io::Stdin {
197 #[inline]
198 fn is_terminal(&self) -> bool {
199 false
200 }
201}
202
203#[cfg(target_os = "unknown")]
204impl IsTerminal for std::io::Stdout {
205 #[inline]
206 fn is_terminal(&self) -> bool {
207 false
208 }
209}
210
211#[cfg(target_os = "unknown")]
212impl IsTerminal for std::io::Stderr {
213 #[inline]
214 fn is_terminal(&self) -> bool {
215 false
216 }
217}
218
219#[cfg(target_os = "unknown")]
220impl<'a> IsTerminal for std::io::StdinLock<'a> {
221 #[inline]
222 fn is_terminal(&self) -> bool {
223 false
224 }
225}
226
227#[cfg(target_os = "unknown")]
228impl<'a> IsTerminal for std::io::StdoutLock<'a> {
229 #[inline]
230 fn is_terminal(&self) -> bool {
231 false
232 }
233}
234
235#[cfg(target_os = "unknown")]
236impl<'a> IsTerminal for std::io::StderrLock<'a> {
237 #[inline]
238 fn is_terminal(&self) -> bool {
239 false
240 }
241}
242
243#[cfg(target_os = "unknown")]
244impl<'a> IsTerminal for std::fs::File {
245 #[inline]
246 fn is_terminal(&self) -> bool {
247 false
248 }
249}
250
251#[cfg(target_os = "unknown")]
252impl IsTerminal for std::process::ChildStdin {
253 #[inline]
254 fn is_terminal(&self) -> bool {
255 false
256 }
257}
258
259#[cfg(target_os = "unknown")]
260impl IsTerminal for std::process::ChildStdout {
261 #[inline]
262 fn is_terminal(&self) -> bool {
263 false
264 }
265}
266
267#[cfg(target_os = "unknown")]
268impl IsTerminal for std::process::ChildStderr {
269 #[inline]
270 fn is_terminal(&self) -> bool {
271 false
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 #[cfg(not(target_os = "unknown"))]
278 use super::IsTerminal;
279
280 #[test]
281 #[cfg(windows)]
282 fn stdin() {
283 assert_eq!(
284 atty::is(atty::Stream::Stdin),
285 std::io::stdin().is_terminal()
286 )
287 }
288
289 #[test]
290 #[cfg(windows)]
291 fn stdout() {
292 assert_eq!(
293 atty::is(atty::Stream::Stdout),
294 std::io::stdout().is_terminal()
295 )
296 }
297
298 #[test]
299 #[cfg(windows)]
300 fn stderr() {
301 assert_eq!(
302 atty::is(atty::Stream::Stderr),
303 std::io::stderr().is_terminal()
304 )
305 }
306
307 #[test]
308 #[cfg(any(unix, target_os = "wasi"))]
309 fn stdin() {
310 assert_eq!(
311 atty::is(atty::Stream::Stdin),
312 rustix::stdio::stdin().is_terminal()
313 )
314 }
315
316 #[test]
317 #[cfg(any(unix, target_os = "wasi"))]
318 fn stdout() {
319 assert_eq!(
320 atty::is(atty::Stream::Stdout),
321 rustix::stdio::stdout().is_terminal()
322 )
323 }
324
325 #[test]
326 #[cfg(any(unix, target_os = "wasi"))]
327 fn stderr() {
328 assert_eq!(
329 atty::is(atty::Stream::Stderr),
330 rustix::stdio::stderr().is_terminal()
331 )
332 }
333
334 #[test]
335 #[cfg(any(unix, target_os = "wasi"))]
336 fn stdin_vs_libc() {
337 unsafe {
338 assert_eq!(
339 libc::isatty(libc::STDIN_FILENO) != 0,
340 rustix::stdio::stdin().is_terminal()
341 )
342 }
343 }
344
345 #[test]
346 #[cfg(any(unix, target_os = "wasi"))]
347 fn stdout_vs_libc() {
348 unsafe {
349 assert_eq!(
350 libc::isatty(libc::STDOUT_FILENO) != 0,
351 rustix::stdio::stdout().is_terminal()
352 )
353 }
354 }
355
356 #[test]
357 #[cfg(any(unix, target_os = "wasi"))]
358 fn stderr_vs_libc() {
359 unsafe {
360 assert_eq!(
361 libc::isatty(libc::STDERR_FILENO) != 0,
362 rustix::stdio::stderr().is_terminal()
363 )
364 }
365 }
366
367 #[test]
369 #[cfg(windows)]
370 fn msys_tty_on_path_length() {
371 use std::{fs::File, os::windows::io::AsRawHandle};
372 use windows_sys::Win32::Foundation::MAX_PATH;
373
374 let dir = tempfile::tempdir().expect("Unable to create temporary directory");
375 let file_path = dir.path().join("ten_chars_".repeat(25));
376 assert!(file_path.to_string_lossy().len() > MAX_PATH as usize);
378 let file = File::create(file_path).expect("Unable to create file");
379
380 assert!(!unsafe { crate::msys_tty_on(file.as_raw_handle()) });
381 }
382}