I’m trying to wrap my head around the difference between TTY detection for #tty? (recently edited in Implement `IO#tty?` in Win32 by HertzDevil · Pull Request #14421 · crystal-lang/crystal · GitHub) and ConsoleUtils.console? (used for #unbuffered_read).
Both _isatty and GetFileType(windows_handle) == LibC::FILE_TYPE_CHAR check if the file is a character device.
console? uses GetConsoleMode (and assumes with a non-error return code that the file is a console).
I’m not sure what the exact differences in semantics are. I presume the console mode check is relevant for buffering behaviour respective whether reads are expected to return immedately. And I suppose a character device on Windows alwasy means it’s a tty (or is it just the closes we get?).
/cc @HertzDevil I hope you can shed some light on this.
From what I could gather from c - Detect NUL file descriptor (isatty is bogus) - Stack Overflow, _isatty checks for a character device despite its name, GetConsoleMode checks for a terminal, and on Windows the latter is more specific than the former…? They would probably make a difference for C file descriptors in general, which we are now going to avoid.
Wine’s implementation doesn’t tell a lot: _isatty, GetFileType
1 Like
Actually if you scroll down a bit, msvcrt_init_io sets WX_TTY if GetFileType returns FILE_TYPE_UNKNOWN. That’s presumably where the difference for NUL comes from; it isn’t a terminal on Win32, but the Microsoft C runtime likely treats it as a character device for consistency with /dev/null
This indeed affects NUL and therefore the behavior of Colorize.on_tty_only!:
# test.cr
{% if flag?(:GetConsoleMode) %}
module Crystal::System::FileDescriptor
private def system_tty?
LibC.GetConsoleMode(windows_handle, out _) != 0
end
end
{% end %}
p! STDIN.tty?, STDERR.tty?
> bin\crystal run test.cr
STDIN.tty? # => true
STDERR.tty? # => true
> bin\crystal run test.cr -DGetConsoleMode
STDIN.tty? # => true
STDERR.tty? # => true
> bin\crystal run test.cr <nul 2>nul
STDIN.tty? # => true
STDERR.tty? # => true
> bin\crystal run test.cr -DGetConsoleMode <nul 2>nul
STDIN.tty? # => false
STDERR.tty? # => false
GetConsoleMode is consistent with /dev/null’s behavior:
$ bin/crystal run test.cr
STDIN.tty? # => true
STDERR.tty? # => true
$ bin/crystal run test.cr </dev/null 2>/dev/null
STDIN.tty? # => false
STDERR.tty? # => false