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