├── .gitignore ├── examples └── stdio.rs ├── .github ├── actions │ └── install-rust │ │ ├── action.yml │ │ ├── README.md │ │ └── main.js └── workflows │ └── main.yml ├── LICENSE-MIT ├── LICENSE-MIT-atty ├── Cargo.toml ├── README.md └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.bk 4 | -------------------------------------------------------------------------------- /examples/stdio.rs: -------------------------------------------------------------------------------- 1 | use is_terminal::IsTerminal; 2 | 3 | fn main() { 4 | println!("stdin? {}", std::io::stdin().is_terminal()); 5 | println!("stdout? {}", std::io::stdout().is_terminal()); 6 | println!("stderr? {}", std::io::stderr().is_terminal()); 7 | } 8 | -------------------------------------------------------------------------------- /.github/actions/install-rust/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Install Rust toolchain' 2 | description: 'Install both `rustup` and a Rust toolchain' 3 | 4 | inputs: 5 | toolchain: 6 | description: 'Default toolchan to install' 7 | required: false 8 | default: 'stable' 9 | 10 | runs: 11 | using: node16 12 | main: 'main.js' 13 | -------------------------------------------------------------------------------- /.github/actions/install-rust/README.md: -------------------------------------------------------------------------------- 1 | # install-rust 2 | 3 | A small github action to install `rustup` and a Rust toolchain. This is 4 | generally expressed inline, but it was repeated enough in this repository it 5 | seemed worthwhile to extract. 6 | 7 | Some gotchas: 8 | 9 | * Can't `--self-update` on Windows due to permission errors (a bug in Github 10 | Actions) 11 | * `rustup` isn't installed on macOS (a bug in Github Actions) 12 | 13 | When the above are fixed we should delete this action and just use this inline: 14 | 15 | ```yml 16 | - run: rustup update $toolchain && rustup default $toolchain 17 | shell: bash 18 | ``` 19 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /LICENSE-MIT-atty: -------------------------------------------------------------------------------- 1 | Portions of this project are derived from atty, which bears the following 2 | copyright notice and permission notice: 3 | 4 | Copyright (c) 2015-2019 Doug Tangren 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "is-terminal" 3 | version = "0.4.17" 4 | authors = [ 5 | "softprops ", 6 | "Dan Gohman " 7 | ] 8 | description = "Test whether a given stream is a terminal" 9 | documentation = "https://docs.rs/is-terminal" 10 | repository = "https://github.com/sunfishcode/is-terminal" 11 | keywords = ["terminal", "tty", "isatty"] 12 | categories = ["command-line-interface"] 13 | license = "MIT" 14 | edition = "2018" 15 | include = ["src", "build.rs", "Cargo.toml", "COPYRIGHT", "LICENSE*", "/*.md"] 16 | rust-version = "1.63" 17 | 18 | [target.'cfg(any(unix, target_os = "wasi"))'.dependencies] 19 | libc = "0.2" 20 | 21 | [target.'cfg(target_os = "hermit")'.dependencies] 22 | hermit-abi = "0.5.0" 23 | 24 | [target.'cfg(windows)'.dependencies.windows-sys] 25 | version = ">=0.52, <0.62" 26 | features = [ 27 | "Win32_Foundation", 28 | "Win32_Storage_FileSystem", 29 | "Win32_System_Console", 30 | ] 31 | 32 | [dev-dependencies] 33 | atty = "0.2.14" 34 | 35 | [target.'cfg(any(unix, target_os = "wasi"))'.dev-dependencies] 36 | rustix = { version = "1.0.0", features = ["termios"] } 37 | libc = "0.2.110" 38 | 39 | [target.'cfg(not(any(windows, target_os = "hermit", target_os = "unknown")))'.dev-dependencies] 40 | rustix = { version = "1.0.0", features = ["stdio"] } 41 | 42 | [target.'cfg(windows)'.dev-dependencies] 43 | tempfile = "3" 44 | -------------------------------------------------------------------------------- /.github/actions/install-rust/main.js: -------------------------------------------------------------------------------- 1 | const child_process = require('child_process'); 2 | const toolchain = process.env.INPUT_TOOLCHAIN; 3 | const fs = require('fs'); 4 | 5 | function set_env(name, val) { 6 | fs.appendFileSync(process.env['GITHUB_ENV'], `${name}=${val}\n`) 7 | } 8 | 9 | // Needed for now to get 1.24.2 which fixes a bug in 1.24.1 that causes issues 10 | // on Windows. 11 | if (process.platform === 'win32') { 12 | child_process.execFileSync('rustup', ['self', 'update']); 13 | } 14 | 15 | child_process.execFileSync('rustup', ['set', 'profile', 'minimal']); 16 | child_process.execFileSync('rustup', ['update', toolchain, '--no-self-update']); 17 | child_process.execFileSync('rustup', ['default', toolchain]); 18 | 19 | // Deny warnings on CI to keep our code warning-free as it lands in-tree. Don't 20 | // do this on nightly though since there's a fair amount of warning churn there. 21 | if (!toolchain.startsWith('nightly')) { 22 | set_env("RUSTFLAGS", "-D warnings"); 23 | } 24 | 25 | // Save disk space by avoiding incremental compilation, and also we don't use 26 | // any caching so incremental wouldn't help anyway. 27 | set_env("CARGO_INCREMENTAL", "0"); 28 | 29 | // Turn down debuginfo from 2 to 1 to help save disk space 30 | set_env("CARGO_PROFILE_DEV_DEBUG", "1"); 31 | set_env("CARGO_PROFILE_TEST_DEBUG", "1"); 32 | 33 | if (process.platform === 'darwin') { 34 | set_env("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO", "unpacked"); 35 | set_env("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO", "unpacked"); 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | rustfmt: 11 | name: Rustfmt 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | submodules: true 17 | - uses: ./.github/actions/install-rust 18 | with: 19 | toolchain: stable 20 | - run: cargo fmt --all -- --check 21 | 22 | check: 23 | name: Check 24 | runs-on: ${{ matrix.os }} 25 | strategy: 26 | matrix: 27 | build: [msrv, stable, nightly] 28 | include: 29 | - build: msrv 30 | os: ubuntu-latest 31 | rust: 1.63 32 | - build: stable 33 | os: ubuntu-latest 34 | rust: stable 35 | - build: nightly 36 | os: ubuntu-latest 37 | rust: nightly 38 | 39 | env: 40 | # -D warnings is commented out in our install-rust action; re-add it here. 41 | RUSTFLAGS: -D warnings 42 | steps: 43 | - uses: actions/checkout@v3 44 | with: 45 | submodules: true 46 | - uses: ./.github/actions/install-rust 47 | with: 48 | toolchain: ${{ matrix.rust }} 49 | 50 | - run: rustup target add x86_64-apple-darwin wasm32-unknown-unknown 51 | - run: cargo check --workspace --release -vv --target=x86_64-apple-darwin 52 | - run: cargo check --workspace --release -vv --target=wasm32-unknown-unknown 53 | 54 | check_nightly: 55 | name: Check on Rust nightly 56 | runs-on: ${{ matrix.os }} 57 | strategy: 58 | matrix: 59 | build: [nightly] 60 | include: 61 | - build: nightly 62 | os: ubuntu-latest 63 | rust: nightly 64 | 65 | steps: 66 | - uses: actions/checkout@v3 67 | with: 68 | submodules: true 69 | - uses: ./.github/actions/install-rust 70 | with: 71 | toolchain: ${{ matrix.rust }} 72 | - run: > 73 | rustup target add 74 | wasm32-wasip2 75 | - run: cargo check --workspace --release -vv --target=wasm32-wasip2 --all-targets 76 | 77 | test: 78 | name: Test 79 | runs-on: ${{ matrix.os }} 80 | strategy: 81 | matrix: 82 | build: [ubuntu-nightly, windows-nightly, ubuntu-stable, windows-stable, macos-nightly, macos-stable] 83 | include: 84 | - build: ubuntu-nightly 85 | os: ubuntu-latest 86 | rust: nightly 87 | - build: windows-nightly 88 | os: windows-latest 89 | rust: nightly 90 | - build: macos-nightly 91 | os: macos-latest 92 | rust: nightly 93 | - build: ubuntu-stable 94 | os: ubuntu-latest 95 | rust: stable 96 | - build: windows-stable 97 | os: windows-latest 98 | rust: stable 99 | - build: macos-stable 100 | os: macos-latest 101 | rust: stable 102 | 103 | steps: 104 | - uses: actions/checkout@v3 105 | with: 106 | submodules: true 107 | - uses: ./.github/actions/install-rust 108 | with: 109 | toolchain: ${{ matrix.rust }} 110 | - run: cargo test --workspace 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

is-terminal

3 | 4 |

5 | Test whether a given stream is a terminal 6 |

7 | 8 |

9 | Github Actions CI Status 10 | crates.io page 11 | docs.rs docs 12 |

13 |
14 | 15 | As of Rust 1.70, most users should use the [`IsTerminal`] trait in the Rust 16 | standard library instead of this crate. 17 | 18 | On Unix platforms, this crate now uses libc, so that the implementation 19 | matches what's in std. Users wishing to use the rustix-based implementation 20 | can use the [rustix-is-terminal] crate instead. 21 | 22 | [rustix-is-terminal]: https://crates.io/crates/rustix-is-terminal 23 | 24 |
25 | 26 | is-terminal is a simple utility that answers one question: 27 | 28 | > Is this a terminal? 29 | 30 | A "terminal", also known as a "tty", is an I/O device which may be interactive 31 | and may support color and other special features. This crate doesn't provide 32 | any of those features; it just answers this one question. 33 | 34 | On Unix-family platforms, this is effectively the same as the [`isatty`] 35 | function for testing whether a given stream is a terminal, though it accepts 36 | high-level stream types instead of raw file descriptors. 37 | 38 | On Windows, it uses a variety of techniques to determine whether the given 39 | stream is a terminal. 40 | 41 | This crate is derived from [the atty crate] with [PR \#51] bug fix and 42 | [PR \#54] port to windows-sys applied. The only additional difference is that 43 | the atty crate only accepts stdin, stdout, or stderr, while this crate accepts 44 | any stream. In particular, this crate does not access any stream that is not 45 | passed to it, in accordance with [I/O safety]. 46 | 47 | [PR \#51]: https://github.com/softprops/atty/pull/51 48 | [PR \#54]: https://github.com/softprops/atty/pull/54 49 | 50 | ## Example 51 | 52 | ```rust 53 | use is_terminal::IsTerminal; 54 | 55 | fn main() { 56 | if std::io::stdout().is_terminal() { 57 | println!("Stdout is a terminal"); 58 | } else { 59 | println!("Stdout is not a terminal"); 60 | } 61 | } 62 | ``` 63 | 64 | ## Testing 65 | 66 | This library is tested on both Unix-family and Windows platforms. 67 | 68 | To test it on a platform manually, use the provided `stdio` example program. 69 | When run normally, it prints this: 70 | 71 | ```bash 72 | $ cargo run --example stdio 73 | stdin? true 74 | stdout? true 75 | stderr? true 76 | ``` 77 | 78 | To test stdin, pipe some text to the program: 79 | 80 | ```bash 81 | $ cat | cargo run --example stdio 82 | stdin? false 83 | stdout? true 84 | stderr? true 85 | ``` 86 | 87 | To test stdout, pipe the program to something: 88 | 89 | ```bash 90 | $ cargo run --example stdio | cat 91 | stdin? true 92 | stdout? false 93 | stderr? true 94 | ``` 95 | 96 | To test stderr, pipe the program to something redirecting stderr: 97 | 98 | ```bash 99 | $ cargo run --example stdio 2>&1 | cat 100 | stdin? true 101 | stdout? false 102 | stderr? false 103 | ``` 104 | 105 | # Minimum Supported Rust Version (MSRV) 106 | 107 | This crate currently works on the version of [Rust on Debian stable], which is 108 | currently Rust 1.63. This policy may change in the future, in minor version 109 | releases, so users using a fixed version of Rust should pin to a specific 110 | version of this crate. 111 | 112 | [`isatty`]: https://man7.org/linux/man-pages/man3/isatty.3.html 113 | [the atty crate]: https://crates.io/crates/atty 114 | [I/O safety]: https://github.com/rust-lang/rfcs/blob/master/text/3128-io-safety.md 115 | [Rust on Debian stable]: https://packages.debian.org/stable/rust/rustc 116 | [`IsTerminal`]: https://doc.rust-lang.org/stable/std/io/trait.IsTerminal.html 117 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! is-terminal is a simple utility that answers one question: 2 | //! 3 | //! > Is this a terminal? 4 | //! 5 | //! A "terminal", also known as a "tty", is an I/O device which may be 6 | //! interactive and may support color and other special features. This crate 7 | //! doesn't provide any of those features; it just answers this one question. 8 | //! 9 | //! On Unix-family platforms, this is effectively the same as the [`isatty`] 10 | //! function for testing whether a given stream is a terminal, though it 11 | //! accepts high-level stream types instead of raw file descriptors. 12 | //! 13 | //! On Windows, it uses a variety of techniques to determine whether the 14 | //! given stream is a terminal. 15 | //! 16 | //! # Example 17 | //! 18 | //! ```rust 19 | //! use is_terminal::IsTerminal; 20 | //! 21 | //! if std::io::stdout().is_terminal() { 22 | //! println!("stdout is a terminal") 23 | //! } 24 | //! ``` 25 | //! 26 | //! [`isatty`]: https://man7.org/linux/man-pages/man3/isatty.3.html 27 | 28 | #![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")] 40 | use std::os::fd::{AsFd, AsRawFd}; 41 | #[cfg(target_os = "hermit")] 42 | use std::os::hermit::io::AsFd; 43 | #[cfg(unix)] 44 | use std::os::unix::io::{AsFd, AsRawFd}; 45 | #[cfg(windows)] 46 | use std::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle}; 47 | #[cfg(windows)] 48 | use windows_sys::Win32::Foundation::HANDLE; 49 | 50 | /// Extension trait to check whether something is a terminal. 51 | pub trait IsTerminal { 52 | /// Returns true if this is a terminal. 53 | /// 54 | /// # Example 55 | /// 56 | /// ``` 57 | /// use is_terminal::IsTerminal; 58 | /// 59 | /// if std::io::stdout().is_terminal() { 60 | /// println!("stdout is a terminal") 61 | /// } 62 | /// ``` 63 | fn is_terminal(&self) -> bool; 64 | } 65 | 66 | /// Returns `true` if `this` is a terminal. 67 | /// 68 | /// This is equivalent to calling `this.is_terminal()` and exists only as a 69 | /// convenience to calling the trait method [`IsTerminal::is_terminal`] 70 | /// without importing the trait. 71 | /// 72 | /// # Example 73 | /// 74 | /// ``` 75 | /// if is_terminal::is_terminal(&std::io::stdout()) { 76 | /// println!("stdout is a terminal") 77 | /// } 78 | /// ``` 79 | pub fn is_terminal(this: T) -> bool { 80 | this.is_terminal() 81 | } 82 | 83 | #[cfg(not(any(windows, target_os = "unknown")))] 84 | impl 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)] 102 | impl IsTerminal for Stream { 103 | #[inline] 104 | fn is_terminal(&self) -> bool { 105 | handle_is_console(self.as_handle()) 106 | } 107 | } 108 | 109 | // The Windows implementation here is copied from `handle_is_console` in 110 | // library/std/src/sys/pal/windows/io.rs in Rust at revision 111 | // e74c667a53c6368579867a74494e6fb7a7f17d13. 112 | 113 | #[cfg(windows)] 114 | fn handle_is_console(handle: BorrowedHandle<'_>) -> bool { 115 | use windows_sys::Win32::System::Console::GetConsoleMode; 116 | 117 | let handle = handle.as_raw_handle(); 118 | 119 | // A null handle means the process has no console. 120 | 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 | // False positives aren't possible. If we got a console then we definitely have a console. 128 | return true; 129 | } 130 | 131 | // Otherwise, we fall back to an msys hack to see if we can detect the presence of a pty. 132 | msys_tty_on(handle as HANDLE) 133 | } 134 | } 135 | 136 | /// Returns true if there is an MSYS tty on the given handle. 137 | #[cfg(windows)] 138 | unsafe 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 | // Early return if the handle is not a pipe. 148 | if GetFileType(handle) != FILE_TYPE_PIPE { 149 | return false; 150 | } 151 | 152 | /// Mirrors windows_sys::Win32::Storage::FileSystem::FILE_NAME_INFO, giving 153 | /// it a fixed length that we can stack allocate 154 | #[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 | // Safety: buffer length is fixed. 165 | let res = GetFileInformationByHandleEx( 166 | handle, 167 | FileNameInfo, 168 | &mut name_info as *mut _ as *mut c_void, 169 | std::mem::size_of::() as u32, 170 | ); 171 | if res == 0 { 172 | return false; 173 | } 174 | 175 | // Use `get` because `FileNameLength` can be out of range. 176 | 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 | // Get the file name only. 185 | let name = name.rsplit('\\').next().unwrap_or(&name); 186 | // This checks whether 'pty' exists in the file name, which indicates that 187 | // a pseudo-terminal is attached. To mitigate against false positives 188 | // (e.g., an actual file name that contains 'pty'), we also require that 189 | // the file name begins with either the strings 'msys-' or 'cygwin-'.) 190 | 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")] 196 | impl IsTerminal for std::io::Stdin { 197 | #[inline] 198 | fn is_terminal(&self) -> bool { 199 | false 200 | } 201 | } 202 | 203 | #[cfg(target_os = "unknown")] 204 | impl IsTerminal for std::io::Stdout { 205 | #[inline] 206 | fn is_terminal(&self) -> bool { 207 | false 208 | } 209 | } 210 | 211 | #[cfg(target_os = "unknown")] 212 | impl IsTerminal for std::io::Stderr { 213 | #[inline] 214 | fn is_terminal(&self) -> bool { 215 | false 216 | } 217 | } 218 | 219 | #[cfg(target_os = "unknown")] 220 | impl<'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")] 228 | impl<'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")] 236 | impl<'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")] 244 | impl<'a> IsTerminal for std::fs::File { 245 | #[inline] 246 | fn is_terminal(&self) -> bool { 247 | false 248 | } 249 | } 250 | 251 | #[cfg(target_os = "unknown")] 252 | impl IsTerminal for std::process::ChildStdin { 253 | #[inline] 254 | fn is_terminal(&self) -> bool { 255 | false 256 | } 257 | } 258 | 259 | #[cfg(target_os = "unknown")] 260 | impl IsTerminal for std::process::ChildStdout { 261 | #[inline] 262 | fn is_terminal(&self) -> bool { 263 | false 264 | } 265 | } 266 | 267 | #[cfg(target_os = "unknown")] 268 | impl IsTerminal for std::process::ChildStderr { 269 | #[inline] 270 | fn is_terminal(&self) -> bool { 271 | false 272 | } 273 | } 274 | 275 | #[cfg(test)] 276 | mod 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 | // Verify that the msys_tty_on function works with long path. 368 | #[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 | // Ensure that the path is longer than MAX_PATH. 377 | 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 | } 383 | --------------------------------------------------------------------------------