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

Owns the local terminal: raw mode, alternate screen, mouse reporting, input decoding.

A singleton registered under this module's name. `setup/1` puts the terminal into
raw mode, switches to the alternate screen, hides the cursor and enables mouse and
bracketed-paste reporting. `cleanup/0` undoes all of it and restores the saved
terminal mode; it also runs from `terminate/2`.

Input is not read until `start_input/0` is called, so a caller can drain whatever
the terminal echoed during startup first:

    Drafter.Terminal.Driver.setup()
    Drafter.Terminal.Driver.drain_pending_input()
    Drafter.Terminal.Driver.start_input()

Decoded input is cast to the event manager as `{:event, event}`; the manager then
delivers `{:tui_event, event}` to its subscribers. Event shapes are the ones
`Drafter.Terminal.ANSI` produces, plus `{:resize, {cols, rows}}` emitted on
`SIGWINCH`.

The event manager is chosen at `init/1` by the `:event_manager` option, defaulting
to `Drafter.Event.Manager`.

Raw mode is entered through the termios NIF where it loads, and through `stty`
otherwise. On a non-unix system neither runs and the terminal mode is left alone,
while the rest of `setup/1` still happens.

Terminal size is read by `TIOCGWINSZ`, falling back to Erlang's IO dimensions and
then to `tput`; `80` by `24` if all of them fail. Setting `DRAFTER_TPUT_SIZE` to a
non-empty value forces the `tput` path. On Windows the size comes from PowerShell,
and `80` by `24` if that fails.

# `state`

```elixir
@type state() :: %Drafter.Terminal.Driver{
  alt_screen: boolean(),
  buffer: Drafter.Terminal.InputBuffer.t(),
  key_release: boolean(),
  mouse_enabled: boolean(),
  probe_replies: term(),
  probe_result: term(),
  probe_waiters: term(),
  probing: term(),
  queries: term(),
  raw_mode: boolean(),
  shell_pid: :ok | {:error, term()} | nil,
  size: {pos_integer(), pos_integer()},
  stdin_reader_pid: pid() | nil,
  terminal_mode: terminal_mode()
}
```

The driver's own state.

`shell_pid` holds whatever `:shell.start_interactive/1` returned, which is `:ok`
or an error tuple rather than a pid.

# `terminal_mode`

```elixir
@type terminal_mode() :: :nif | {:stty, binary()} | nil
```

How raw mode was entered, and so how it is undone.

`:nif` means the termios NIF holds the saved settings, `{:stty, saved}` means
`stty` does and `saved` is the `stty -g` string, and `nil` means raw mode is not
in force.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `cleanup`

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

Restore the terminal to the state it was in before `setup/1`.

Disables bracketed paste, mouse reporting and the kitty keyboard protocol when
`setup/1` turned it on, shows the cursor, leaves the
alternate screen, restores the saved terminal mode and stops the stdin reader.
Safe to call when `setup/1` was never called or already undone.

Mouse reporting is turned off as `Drafter.Terminal.ANSI.disable_mouse/1` does
with its defaults, so it clears the any-motion mode that `setup/1` enables when
`:hover` is left at `true`. Everything except the stdin reader is skipped when
raw mode is not in force. The sequences are written straight to `/dev/tty` by
`write_synchronously/1`, so they land even while the driver is shutting down.

Also runs from `terminate/2`.

# `disable_mouse`

```elixir
@spec disable_mouse(keyword()) :: :ok
```

Turn mouse reporting off, if the terminal is in raw mode and it is currently on.

`opts` defaults to `[]` and is passed to `Drafter.Terminal.ANSI.disable_mouse/1`,
whose only key is `:hover`; it must match what `enable_mouse/1` was given, or the
mode that was set is not the mode that is cleared. Asynchronous, and silently does
nothing when raw mode is not in force or reporting is already off.

# `drain_pending_input`

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

Discard stdin that has arrived but not been turned into events.

Drops queued `{:stdin, _}` messages, flushes the operating system's input queue
and empties the partial-sequence buffer. Nothing is emitted to the event manager.

# `enable_mouse`

```elixir
@spec enable_mouse(keyword()) :: :ok
```

Turn mouse reporting on, if the terminal is in raw mode and it is currently off.

`opts` defaults to `[]` and is passed to `Drafter.Terminal.ANSI.enable_mouse/1`,
whose only key is `:hover`. Asynchronous, and silently does nothing when raw mode
is not in force or reporting is already on.

# `get_size`

```elixir
@spec get_size() :: {pos_integer(), pos_integer()}
```

The last known terminal size as `{cols, rows}`, without re-measuring.

# `probe`

```elixir
@spec probe(timeout()) :: {:ok, atom() | nil} | :unprobed
```

The graphics protocol this terminal answered the startup probe with.

Returns `{:ok, protocol}` where `protocol` is `:kitty`, `:iterm2`, `:sixel`, or
`nil` for a terminal that answered naming none. Returns `:unprobed` when there
was no terminal to ask, which is the caller's cue to fall back to guessing from
the environment.

Asking writes to the terminal and reads its answer, so call this after
`start_input/0` and after any `drain_pending_input/0`, which would otherwise
discard the answer. Blocks until the terminal answers or `timeout` passes, and
never longer: a driver with no terminal replies at once, and one that fails to
reply at all is reported as `:unprobed` rather than taking the caller down.

# `query_terminal`

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

Write the terminal queries `setup/1` was given: the kitty keyboard query for
`key_release: true` and the cell size query for `cell_size: true`.

Call after `start_input/0` and after the last `drain_pending_input/0`; the replies
arrive as input events. Writes nothing when there are no queries or they were already
sent.

# `refresh_size`

```elixir
@spec refresh_size() :: {pos_integer(), pos_integer()}
```

Measure the terminal again and return the new `{cols, rows}`.

# `setup`

```elixir
@spec setup(keyword()) :: :ok | {:error, term()}
```

Put the terminal into TUI mode.

Enters raw mode, switches to the alternate screen, hides the cursor, clears it,
and enables mouse and bracketed-paste reporting. `opts` defaults to `[]`; its
`:hover` key is passed to `Drafter.Terminal.ANSI.enable_mouse/1`, and
`key_release: true` turns on the kitty keyboard protocol with
`Drafter.Terminal.KittyKeyboard.push/0` and asks the terminal whether it took,
so key presses are followed by `:key_down` and releases arrive as `:key_up`.

Terminal size is measured again on success, and `SIGWINCH` starts being watched.
On a system where the signal cannot be watched, size is polled instead and a
change is reported the same way.

Returns `{:error, reason}` if raw mode could not be entered, leaving the terminal
untouched. Input reading does not begin here; call `start_input/0`.

# `start_input`

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

Start reading stdin and emitting events.

Idempotent: a second call while a reader is running does nothing.

# `start_link`

```elixir
@spec start_link(keyword()) :: GenServer.on_start()
```

Start the driver and register it under this module's name.

The name is fixed, so only one driver runs per node and every function in this
module addresses it without being told which.

Options:

  * `:event_manager` — process name or pid decoded events are cast to, default
    `Drafter.Event.Manager`

The terminal is measured here but not altered; `setup/1` does that.

# `write`

```elixir
@spec write(iodata()) :: :ok
```

Write bytes to the terminal.

Asynchronous. Bytes are discarded unless the terminal is in raw mode.

# `write_synchronously`

```elixir
@spec write_synchronously(iodata()) :: :ok
```

Write bytes to the controlling terminal and return once they have been handed over.

Opens `/dev/tty` directly, bypassing the driver process and the Erlang IO server,
so the bytes land even while the driver is shutting down. Falls back to `IO.write/1`
when `/dev/tty` cannot be opened. An empty list writes nothing.

---

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