]>
Commit | Line | Data |
---|---|---|
041b39d2 XL |
1 | //! atty is a simple utility that answers one question |
2 | //! > is this a tty? | |
3 | //! | |
4 | //! usage is just as simple | |
5 | //! | |
6 | //! ``` | |
7 | //! if atty::is(atty::Stream::Stdout) { | |
8 | //! println!("i'm a tty") | |
9 | //! } | |
10 | //! ``` | |
11 | //! | |
12 | //! ``` | |
13 | //! if atty::isnt(atty::Stream::Stdout) { | |
14 | //! println!("i'm not a tty") | |
15 | //! } | |
16 | //! ``` | |
17 | ||
abe05a73 XL |
18 | #![cfg_attr(unix, no_std)] |
19 | ||
abe05a73 | 20 | #[cfg(unix)] |
041b39d2 XL |
21 | extern crate libc; |
22 | #[cfg(windows)] | |
23 | extern crate winapi; | |
24 | ||
25 | #[cfg(windows)] | |
2c00a5a8 | 26 | use winapi::shared::minwindef::DWORD; |
8faf50e0 XL |
27 | #[cfg(windows)] |
28 | use winapi::shared::ntdef::WCHAR; | |
041b39d2 XL |
29 | |
30 | /// possible stream sources | |
31 | #[derive(Clone, Copy, Debug)] | |
32 | pub enum Stream { | |
33 | Stdout, | |
34 | Stderr, | |
35 | Stdin, | |
36 | } | |
37 | ||
38 | /// returns true if this is a tty | |
8faf50e0 | 39 | #[cfg(all(unix, not(target_arch = "wasm32")))] |
041b39d2 XL |
40 | pub 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 | ||
72b1a166 FG |
51 | /// returns true if this is a tty |
52 | #[cfg(target_os = "hermit")] | |
53 | pub fn is(stream: Stream) -> bool { | |
54 | extern crate hermit_abi; | |
55 | ||
56 | let fd = match stream { | |
57 | Stream::Stdout => hermit_abi::STDOUT_FILENO, | |
58 | Stream::Stderr => hermit_abi::STDERR_FILENO, | |
59 | Stream::Stdin => hermit_abi::STDIN_FILENO, | |
60 | }; | |
61 | hermit_abi::isatty(fd) | |
62 | } | |
63 | ||
041b39d2 XL |
64 | /// returns true if this is a tty |
65 | #[cfg(windows)] | |
66 | pub fn is(stream: Stream) -> bool { | |
72b1a166 FG |
67 | use winapi::um::winbase::{ |
68 | STD_ERROR_HANDLE as STD_ERROR, STD_INPUT_HANDLE as STD_INPUT, | |
69 | STD_OUTPUT_HANDLE as STD_OUTPUT, | |
70 | }; | |
041b39d2 XL |
71 | |
72 | let (fd, others) = match stream { | |
73 | Stream::Stdin => (STD_INPUT, [STD_ERROR, STD_OUTPUT]), | |
74 | Stream::Stderr => (STD_ERROR, [STD_INPUT, STD_OUTPUT]), | |
75 | Stream::Stdout => (STD_OUTPUT, [STD_INPUT, STD_ERROR]), | |
76 | }; | |
77 | if unsafe { console_on_any(&[fd]) } { | |
78 | // False positives aren't possible. If we got a console then | |
79 | // we definitely have a tty on stdin. | |
80 | return true; | |
81 | } | |
82 | ||
83 | // At this point, we *could* have a false negative. We can determine that | |
84 | // this is true negative if we can detect the presence of a console on | |
85 | // any of the other streams. If another stream has a console, then we know | |
86 | // we're in a Windows console and can therefore trust the negative. | |
87 | if unsafe { console_on_any(&others) } { | |
88 | return false; | |
89 | } | |
90 | ||
91 | // Otherwise, we fall back to a very strange msys hack to see if we can | |
92 | // sneakily detect the presence of a tty. | |
93 | unsafe { msys_tty_on(fd) } | |
94 | } | |
95 | ||
96 | /// returns true if this is _not_ a tty | |
97 | pub fn isnt(stream: Stream) -> bool { | |
98 | !is(stream) | |
99 | } | |
100 | ||
101 | /// Returns true if any of the given fds are on a console. | |
102 | #[cfg(windows)] | |
103 | unsafe fn console_on_any(fds: &[DWORD]) -> bool { | |
72b1a166 | 104 | use winapi::um::{consoleapi::GetConsoleMode, processenv::GetStdHandle}; |
2c00a5a8 | 105 | |
041b39d2 XL |
106 | for &fd in fds { |
107 | let mut out = 0; | |
2c00a5a8 XL |
108 | let handle = GetStdHandle(fd); |
109 | if GetConsoleMode(handle, &mut out) != 0 { | |
041b39d2 XL |
110 | return true; |
111 | } | |
112 | } | |
113 | false | |
114 | } | |
115 | ||
116 | /// Returns true if there is an MSYS tty on the given handle. | |
117 | #[cfg(windows)] | |
118 | unsafe fn msys_tty_on(fd: DWORD) -> bool { | |
72b1a166 FG |
119 | use std::{mem, slice}; |
120 | ||
121 | use winapi::{ | |
122 | ctypes::c_void, | |
123 | shared::minwindef::MAX_PATH, | |
124 | um::{ | |
125 | fileapi::FILE_NAME_INFO, minwinbase::FileNameInfo, processenv::GetStdHandle, | |
126 | winbase::GetFileInformationByHandleEx, | |
127 | }, | |
128 | }; | |
041b39d2 XL |
129 | |
130 | let size = mem::size_of::<FILE_NAME_INFO>(); | |
8faf50e0 | 131 | let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::<WCHAR>()]; |
041b39d2 | 132 | let res = GetFileInformationByHandleEx( |
2c00a5a8 | 133 | GetStdHandle(fd), |
041b39d2 XL |
134 | FileNameInfo, |
135 | &mut *name_info_bytes as *mut _ as *mut c_void, | |
abe05a73 XL |
136 | name_info_bytes.len() as u32, |
137 | ); | |
041b39d2 | 138 | if res == 0 { |
0531ce1d | 139 | return false; |
041b39d2 | 140 | } |
8faf50e0 XL |
141 | let name_info: &FILE_NAME_INFO = &*(name_info_bytes.as_ptr() as *const FILE_NAME_INFO); |
142 | let s = slice::from_raw_parts( | |
143 | name_info.FileName.as_ptr(), | |
144 | name_info.FileNameLength as usize / 2, | |
abe05a73 | 145 | ); |
8faf50e0 XL |
146 | let name = String::from_utf16_lossy(s); |
147 | // This checks whether 'pty' exists in the file name, which indicates that | |
148 | // a pseudo-terminal is attached. To mitigate against false positives | |
149 | // (e.g., an actual file name that contains 'pty'), we also require that | |
150 | // either the strings 'msys-' or 'cygwin-' are in the file name as well.) | |
151 | let is_msys = name.contains("msys-") || name.contains("cygwin-"); | |
152 | let is_pty = name.contains("-pty"); | |
153 | is_msys && is_pty | |
041b39d2 XL |
154 | } |
155 | ||
2c00a5a8 XL |
156 | /// returns true if this is a tty |
157 | #[cfg(target_arch = "wasm32")] | |
158 | pub fn is(_stream: Stream) -> bool { | |
159 | false | |
160 | } | |
161 | ||
041b39d2 XL |
162 | #[cfg(test)] |
163 | mod tests { | |
72b1a166 | 164 | use super::{is, Stream}; |
041b39d2 XL |
165 | |
166 | #[test] | |
167 | #[cfg(windows)] | |
168 | fn is_err() { | |
169 | // appveyor pipes its output | |
170 | assert!(!is(Stream::Stderr)) | |
171 | } | |
172 | ||
173 | #[test] | |
174 | #[cfg(windows)] | |
175 | fn is_out() { | |
176 | // appveyor pipes its output | |
177 | assert!(!is(Stream::Stdout)) | |
178 | } | |
179 | ||
180 | #[test] | |
181 | #[cfg(windows)] | |
182 | fn is_in() { | |
183 | assert!(is(Stream::Stdin)) | |
184 | } | |
185 | ||
186 | #[test] | |
187 | #[cfg(unix)] | |
188 | fn is_err() { | |
189 | assert!(is(Stream::Stderr)) | |
190 | } | |
191 | ||
192 | #[test] | |
193 | #[cfg(unix)] | |
194 | fn is_out() { | |
195 | assert!(is(Stream::Stdout)) | |
196 | } | |
197 | ||
198 | #[test] | |
199 | #[cfg(target_os = "macos")] | |
200 | fn is_in() { | |
201 | // macos on travis seems to pipe its input | |
abe05a73 | 202 | assert!(is(Stream::Stdin)) |
041b39d2 XL |
203 | } |
204 | ||
205 | #[test] | |
206 | #[cfg(all(not(target_os = "macos"), unix))] | |
207 | fn is_in() { | |
208 | assert!(is(Stream::Stdin)) | |
209 | } | |
210 | } |