# `Drafter.Terminal.TermiosNif`
[🔗](https://github.com/jaman/drafter/blob/main/lib/drafter/terminal/termios_nif.ex#L1)

Terminal and pseudoterminal control through the `termios_nif` native library.

Covers what cannot be done from the BEAM: putting the controlling terminal into
raw mode, reading and setting window size by ioctl, discarding the operating
system's input queue, and allocating pseudoterminal pairs.

    {:ok, {cols, rows, _xpixel, _ypixel}} = Drafter.Terminal.TermiosNif.get_winsize()

The library is loaded from `priv/termios_nif` on module load. A failed load is not
an error: the Elixir bodies stay in place, so `disable_flow_control/0`,
`enable_flow_control/0`, `enter_raw_mode/0`, `exit_raw_mode/0`,
`set_tui_active/0`, `set_tui_inactive/0` and `flush_stdin/0` return
`:nif_not_loaded`, while every other function raises `ErlangError` with
`:nif_not_loaded`. Callers must handle whichever applies to the function they use.

Raw mode is a process-independent property of the terminal. Enter it once and
leave it once; `Drafter.Terminal.Driver` owns that lifecycle for the local
terminal.

# `termios_result`

```elixir
@type termios_result() :: :ok | :error | :nif_not_loaded
```

What a function with an Elixir fallback returns.

`:ok` on success, `:error` when the underlying `tcgetattr`/`tcsetattr` failed,
and `:nif_not_loaded` when the native library is absent.

# `close_fd`

```elixir
@spec close_fd(integer()) :: :ok | {:error, binary()}
```

Close a descriptor returned by `open_pty/2`.

On failure the error term carries the `strerror` text for `errno` as a binary,
so closing the same descriptor twice gives `{:error, "Bad file descriptor"}`.

# `disable_flow_control`

```elixir
@spec disable_flow_control() :: termios_result()
```

Clear `IXON`/`IXOFF` on the controlling terminal, so `Ctrl+S` and `Ctrl+Q` reach
the application.

The settings in force at the first call are saved, if `enter_raw_mode/0` has not
already saved them, and are what `exit_raw_mode/0` restores.

# `enable_flow_control`

```elixir
@spec enable_flow_control() :: termios_result()
```

Restore `IXON`/`IXOFF` software flow control on the controlling terminal.

Sets both bits regardless of whether they were set before, and saves nothing.

# `enter_raw_mode`

```elixir
@spec enter_raw_mode() :: termios_result()
```

Put the controlling terminal into raw mode, saving the previous settings.

Input is delivered unbuffered and unechoed, and control characters are passed
through instead of generating signals. `exit_raw_mode/0` restores what was saved.
The settings are saved only on the first call that saves them, so entering raw
mode twice does not lose the original ones.

# `exit_raw_mode`

```elixir
@spec exit_raw_mode() :: termios_result()
```

Restore the terminal settings saved by `enter_raw_mode/0`.

Returns `:ok` when nothing was ever saved, having changed nothing.

# `flush_stdin`

```elixir
@spec flush_stdin() :: :ok | :nif_not_loaded
```

Discard the bytes the operating system has queued on standard input but not
delivered.

Bytes already read into the emulator are unaffected.

# `get_winsize`

```elixir
@spec get_winsize() ::
  {:ok,
   {non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer()}}
  | {:error, :not_a_tty}
```

The controlling terminal's size via `TIOCGWINSZ`, as `{cols, rows, xpixel, ypixel}`.

Standard output is asked first, standard input second. `{:error, :not_a_tty}`
means neither is a terminal. The pixel dimensions are zero on terminals that do
not report them.

# `get_winsize`

```elixir
@spec get_winsize(integer()) ::
  {:ok,
   {non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer()}}
  | {:error, binary()}
```

The size of the terminal behind `fd`, as `{cols, rows, xpixel, ypixel}`.

On failure the error term carries the `strerror` text for `errno` as a binary.

# `killpg`

```elixir
@spec killpg(pos_integer(), non_neg_integer()) :: :ok | {:error, binary()}
```

Send `signal` to the process group led by `pid`.

`pid` is a process group leader's id, which for a program started by
`Drafter.Pty.spawn/2` is the program's own pid — it is made a session and group
leader. Signalling the group reaches the children it started as well.

Signal `0` delivers nothing and reports only whether the group still exists,
giving `{:error, "No such process"}` when it does not. A `pid` of zero or less
raises `ArgumentError` rather than signalling the caller's own group.

# `load_error`

```elixir
@spec load_error() :: binary() | nil
```

Why the native library is not in use, or `nil` when it loaded.

The functions with an Elixir fallback answer `:nif_not_loaded` when the library
is absent, and the ones without it raise the same. Neither says why; this does,
carrying the path that was tried and the reason the runtime gave for refusing it.

# `load_error_key`

```elixir
@spec load_error_key() :: {module(), :load_error}
```

The `:persistent_term` key `load_error/0` reads.

Exposed so a test can stand in a load failure on a machine where the library
loads.

# `load_nif`

```elixir
@spec load_nif() :: :ok
```

Load the native library, leaving the Elixir fallbacks in place if it is missing.

Runs on module load. Always returns `:ok`, so a missing library never stops the
module from loading. A failure is recorded for `load_error/0` to report.

# `open_pty`

```elixir
@spec open_pty(non_neg_integer(), non_neg_integer()) ::
  {:ok, {integer(), integer(), binary()}} | {:error, binary()}
```

Allocate a pseudo-terminal pair sized `cols` by `rows`.

Returns `{master_fd, slave_fd, slave_path}`. The master is the side this
process reads and writes — hand it to `:erlang.open_port({:fd, master, master},
[:binary])` to get ordinary Erlang message-passing I/O. The slave is what a
child process uses as its controlling terminal, either by inheriting the
descriptor or by opening `slave_path`.

Does no forking or exec'ing. Both descriptors must be released with
`close_fd/1`. On failure the error term carries the `strerror` text for `errno`
as a binary.

# `set_tui_active`

```elixir
@spec set_tui_active() :: :ok | :nif_not_loaded
```

Mark the terminal as held in TUI mode and install a `SIGINT` handler.

While the mark is set, a `SIGINT` or a normal exit of the emulator restores the
saved terminal settings and writes the sequences that leave the alternate screen,
show the cursor and turn mouse reporting off, so a crash does not leave the
terminal unusable.

# `set_tui_inactive`

```elixir
@spec set_tui_inactive() :: :ok | :nif_not_loaded
```

Clear the TUI-mode mark set by `set_tui_active/0` and restore the default
`SIGINT` handling.

# `set_winsize`

```elixir
@spec set_winsize(
  integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer(),
  non_neg_integer()
) :: :ok | {:error, :ioctl_failed}
```

Set the window size of the pty behind `fd` via `TIOCSWINSZ`.

The kernel then reports the new dimensions to processes on the other side of
the pty and signals `SIGWINCH` to its foreground process group. Each dimension
is truncated to 16 bits. A failed ioctl gives `{:error, :ioctl_failed}`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
