# TTY on Windows

**URL:** https://forum.crystal-lang.org/t/tty-on-windows/6769
**Category:** Crystal Contrib
**Created:** [April 16, 2024, 6:41pm UTC](https://forum.crystal-lang.org/t/tty-on-windows/6769 "2024-04-16T18:41:56Z")
**Posts on this page:** 4
**Page:** 1

<div class="post-metadata">

### Author: ![straight-shoota](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/straight-shoota/32/36_2.png) [@straight-shoota](https://forum.crystal-lang.org/u/straight-shoota)
#### Post date: [April 16, 2024, 6:41pm UTC](https://forum.crystal-lang.org/t/tty-on-windows/6769/1 "2024-04-16T18:41:57Z")

</div>

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](https://github.com/crystal-lang/crystal/pull/14421)) and `ConsoleUtils.console?` (used for `#unbuffered_read`).

Both [`_isatty`](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/isatty?view=msvc-170) and [`GetFileType(windows_handle) == LibC::FILE_TYPE_CHAR`](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfiletype) check if the file is a character device.  
`console?` uses [`GetConsoleMode`](https://learn.microsoft.com/en-us/windows/console/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.

---

<div class="post-metadata">

### Author: ![HertzDevil](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/hertzdevil/32/1023_2.png) [@HertzDevil](https://forum.crystal-lang.org/u/HertzDevil)
#### Post date: [April 16, 2024, 6:51pm UTC](https://forum.crystal-lang.org/t/tty-on-windows/6769/2 "2024-04-16T18:51:18Z")

</div>

From what I could gather from [c - Detect NUL file descriptor (isatty is bogus) - Stack Overflow](https://stackoverflow.com/a/3650507), `_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`](https://gitlab.winehq.org/wine/wine/-/blob/30a70548796e46c07bb508ac9aee38b30db60e39/dlls/msvcrt/file.c#L823-828), [`GetFileType`](https://gitlab.winehq.org/wine/wine/-/blob/30a70548796e46c07bb508ac9aee38b30db60e39/dlls/kernelbase/file.c#L3264-3292)

---

<div class="post-metadata">

### Author: ![HertzDevil](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/hertzdevil/32/1023_2.png) [@HertzDevil](https://forum.crystal-lang.org/u/HertzDevil)
#### Post date: [April 16, 2024, 7:32pm UTC](https://forum.crystal-lang.org/t/tty-on-windows/6769/3 "2024-04-16T19:32:42Z")

</div>

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

---

<div class="post-metadata">

### Author: ![HertzDevil](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/hertzdevil/32/1023_2.png) [@HertzDevil](https://forum.crystal-lang.org/u/HertzDevil)
#### Post date: [April 19, 2024, 3:18pm UTC](https://forum.crystal-lang.org/t/tty-on-windows/6769/4 "2024-04-19T15:18:49Z")

</div>

This indeed affects NUL and therefore the behavior of `Colorize.on_tty_only!`:

```crystal
# 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?

```

```cmd
> 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:

```sh
$ 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

```
