How to tell that stdout is a color terminal?

I know that it's a color terminal and insert escape ANSI codes in my print out. However if I need to capture stdout some other app and redirect to my terminal, it will come in black and white. So, I use some special command flag to get a colorized out from the app. I looked at how to set stdout:

pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command

I looked at StdoutStdout and didn't find how to tell it supports color. I know, there is Rust Shell, so I need just look in its code. But if someone can tell me without looking in its code, I appreciate the help.

there's std::io::IsTerminal, just be aware the platform specific behavior, specifically, on Windows, some heuristics are used to also detect the cygwin and msys environment.

2 Likes

Thank you, it looks exactly what I need. However AI got stuck explaining how can I implement the trait for piped out:

For piped stdout, the is_terminal method of the std::io::IsTerminal trait will return false. There is no need for a manual IsTerminal implementation for this specific case because the standard library provides this functionality automatically. The operating system determines if stdout is connected to a terminal or a pipe, and the Rust standard library's built-in IsTerminal implementation reflects this.

It simply tell it's already implemented for me. But if I want to implement it myself, what should I do? Again, my pipe is connected to a terminal.

Just be aware that ANSI output feature support is very by much heuristic and by convention, and if it matters you should allow the caller to override the value.

As a simple example where no library can help you, a user redirecting the output to a file might want to include color or not depending on what they're doing with it.

3 Likes

Sure, I implemented ANSI support and tested it with crate "owo-colors" . Everything works flawlessly. I also tested it with rustc, and other apps forcing color output, and so far, everything worked good. So, I want to eliminate the enforcement to use the color mode now.

Some illustration of my terminal:

Terminals are a mess of historical cruft. I can only really speak about the Unix/Linux side of things here.

After determining that you are indeed connected to a terminal, traditionally you would look at the TERM environment variable to determine the capabilities of the current terminal by looking up the TERM value in the system terminfo database (/usr/share/terminfo). The format is arcane so you would generally use a library to do this.

However none of that matters anymore. These days we don't use hardware terminals with widely varying control codes. Basically everything is compatible with xterm, with maybe some extensions.

If all you want to do is colours and bold you can basically bet on the terminal supporting ANSI colours and 256 colours and even RGB colours. The extensions only really come into play for advanced features such as showing embedded images (yes, many terminals support either the Kitty image protocol or sixels).

However the user may or may not actually want colours. Standard for ANSI Colors in Terminals is a "standard" for how the user can tell you about this. I would recommend https://docs.rs/anstream/latest/anstream/struct.AutoStream.html for dealing with figuring out if the colour should be stripped or not. I believe cargo uses this specific library (the project lead for Cargo is involved in the anstyle/anstream libraries at least).

3 Likes

You know exactly what I need. I was looking for a crate like that for an year. Now, I need to solve the initial task how tell all apps using my terminal that it supports ANSI colours.

Right, this is on Unix/Linux generally this is via setting the TERM variable to suitable value. If it is unset, most programs assume the terminal is "dumb" (i.e. a plain old serial port connected to a something that doesn't understand control codes at all, such as a printer.[1]).

Generally everything claims to be xterm these days. So setting TERM to xterm or one of the xterm-color variants (depending on what colour combos you support) makes sense:

❯ ls /usr/share/terminfo/x/xterm-*color*
/usr/share/terminfo/x/xterm-16color  /usr/share/terminfo/x/xterm-256color  /usr/share/terminfo/x/xterm-88color  /usr/share/terminfo/x/xterm-color  /usr/share/terminfo/x/xterm-pcolor

E.g. konsole (the KDE terminal) claims to be xterm-256color. I use kitty which claims to be xterm-kitty and that causes issues when the kitty terminfo isn't installed. But even it claims to be some variant of xterm to try to work with tools that do string matching on xterm...

Also I went digging into the history of ANSI escape codes and apparently COLORTERM is also a thing that exist.


  1. Look it made perfect sense in the 70s! â†Šī¸Ž

I have checked how the variable set in my terminal:

xterm-256color

But still all apps consider it as "dumb". I think that the default is_terminal is set to `false' and it's the cause.

Does isatty return true for programs running in your terminal? If not that is probably your issue.

(I'm assuming Unix/Linux here, on Windows I have no idea.)

Windows has GetFileType(handle) == FILE_TYPE_CHAR, but here you actually care about if the attached console has ANSI code support enabled, which is GetConsoleMode(output_handle) & ENABLE_VIRTUAL_TERMINAL_PROCESSING.

1 Like

afaik that doesn't cover when you're using alternative terminals on windows, e.g. cygwin or msys2 terminals where afaik they don't go through the windows terminal APIs.

They should be using pseudo consoles / ConPTY now, or at least have the option to. They finally got around to it!

It returns false:

pub fn is_terminal(fd: &impl AsFd) -> bool {
    let fd = fd.as_fd();
    unsafe { libc::isatty(fd.as_raw_fd()) != 0 }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let stdout_lock = io::stdout().lock();
    println!("terminal={}", stdout_lock.is_terminal());
    
    Ok(())
}

is_terminal implemented as shown above the main, exactly as you told. So it looks like I need to dig in Linux internals to understand how the isatty implemented.

you need to create a pseudo terminal pair, pass the slave end to the child process, and use the master end to implement your emulator. regular pipes are not sufficient. see the man page for /dev/ptmx for example.

It will be the next step in my journey. Since most Windows commands implemented in my shell using Rust, I can assure a color for them. Perhaps only rustc is my worry.

Thank you. Now I need to find a reference implementation. It should be a piece of cake after.

The main problem will be creating a pseudo console on Windows, the API is pretty annoying in comparison to pseudo terminals on Linux. There's some crates out there, but I wasn't super impressed by them when I last looked a while ago for some reason. Maybe I was trying to get a tokio awaitable handle as well....

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.