# `FrenchCurve.Capability`
[🔗](https://github.com/jaman/french_curve/blob/v0.1.4/lib/french_curve/capability.ex#L1)

Chooses a graphics protocol from the environment and maps it to a backend module.

Detection reads `KITTY_WINDOW_ID`, `TERM`, `TERM_PROGRAM`, `WEZTERM_PANE`, `LC_TERMINAL` and
`KONSOLE_VERSION`. A variable exported empty counts as unset. The first matching rule wins,
in this order:

| environment | protocol |
|---|---|
| `KITTY_WINDOW_ID` set | `:kitty` |
| `TERM` contains `kitty` or `ghostty` | `:kitty` |
| `TERM_PROGRAM` is `WezTerm` | `:kitty` |
| `WEZTERM_PANE` set | `:kitty` |
| `TERM_PROGRAM` is `iTerm.app` | `:iterm2` |
| `LC_TERMINAL` is `iTerm2` | `:iterm2` |
| `KONSOLE_VERSION` set | `:sixel` |
| `TERM` contains `foot` or `mlterm` | `:sixel` |
| anything else | `:braille` |

`:braille` is the fallback because it needs no graphics support at all.

`WEZTERM_PANE` and `LC_TERMINAL` cover terminals whose `TERM_PROGRAM` did not survive the
trip: multiplexers drop it, and ssh forwards only the variables its client is configured to
send, which for iTerm2 is `LC_TERMINAL` under the usual `SendEnv LC_*`. Detecting a remote
client therefore means passing that session's environment to `detect/1`, not the
environment of the host the program runs on.

A terminal detected as `:kitty` does not necessarily speak all of the protocol — ask
`placements?/1` before storing an image under an id and placing it.

## Why iTerm is not `:kitty`

iTerm from 3.5 does understand kitty's transmit-and-display, and was detected as `:kitty` for
a while on the strength of it. That is a trap. A kitty image is an **overlay**: it is not made
of cells, so writing text over it does not rub it out, leaving the alternate screen does not
discard it, and the only thing that removes it is an explicit delete addressed to its id —
which iTerm does not implement. Images pile up over the text while the program runs and are
still on the screen after it exits.

Its own protocol has none of that problem, because an inline image *is* cells: it scrolls,
gets overwritten and disappears with the screen it was drawn on, exactly like text.

    case FrenchCurve.Capability.detect() do
      :braille -> IO.write(FrenchCurve.to_terminal(raster, :braille))
      protocol -> IO.write(FrenchCurve.to_terminal(raster, protocol))
    end

# `backend`

```elixir
@spec backend(atom()) :: module()
```

Returns the backend module implementing `protocol`.

Accepts `:kitty`, `:sixel`, `:iterm2` and `:braille`; any other value raises
`FunctionClauseError`. Every returned module exports `render/2` taking a
`FrenchCurve.Raster` and a keyword list.

# `detect`

```elixir
@spec detect() :: atom()
```

Detects the protocol for the current OS environment.

The result is computed once and cached in `:persistent_term` under
`{FrenchCurve.Capability, :detected}` for the life of the VM, so a later change to the
environment is not picked up. Use `detect/1` to detect against a specific environment.

Returns `:kitty`, `:sixel`, `:iterm2` or `:braille`.

# `detect`

```elixir
@spec detect(map()) :: atom()
```

Detects the protocol for `env`, a map of environment variable name to string value.

Uncached, and missing keys are treated as unset. Returns `:kitty`, `:sixel`, `:iterm2`
or `:braille`; `:braille` when no rule matches.

    iex> FrenchCurve.Capability.detect(%{"TERM" => "xterm-kitty"})
    :kitty

# `from_probe`

```elixir
@spec from_probe(binary()) :: atom() | nil
```

The protocol `replies` implies, or `nil` when they name nothing usable.

The terminal's own name wins: a terminal that says it is kitty, ghostty or
WezTerm gets `:kitty`, and one that says it is iTerm2 gets `:iterm2` even though
it answers kitty's queries. Only when the name is absent or unrecognised do the
device attributes decide, where a `4` among them is sixel support.

    iex> FrenchCurve.Capability.from_probe("\eP>|kitty(0.32.2)\e\\\e[?62;22c")
    :kitty

    iex> FrenchCurve.Capability.from_probe("\e[?62;4;6c")
    :sixel

    iex> FrenchCurve.Capability.from_probe("\e[?62;22c")
    nil

# `placements?`

```elixir
@spec placements?() :: boolean()
```

Whether this terminal keeps images under an id and draws them again by placement.

`detect/1` saying `:kitty` does not have to mean the whole protocol is there: a terminal can
understand transmit-and-display (`a=T`) without keeping images under an id for
`FrenchCurve.Backend.Kitty.place/2` to draw again. Sending one a store and then a placement
draws nothing at all — it stores a picture it never shows, then is handed a command it does
not understand.

`FrenchCurve.frame/3` asks this and picks accordingly; ask it yourself only if you are
building the sequence by hand.

    iex> FrenchCurve.Capability.placements?(%{"TERM" => "xterm-kitty"})
    true
    iex> FrenchCurve.Capability.placements?(%{"TERM_PROGRAM" => "iTerm.app"})
    false
    iex> FrenchCurve.Capability.placements?(%{"TERM" => "xterm-256color"})
    false

# `placements?`

```elixir
@spec placements?(map()) :: boolean()
```

Whether the terminal described by `env` keeps images under an id and places them.

As `placements?/0`, against a given environment map rather than the OS one. Uncached.

# `probe`

```elixir
@spec probe() :: binary()
```

The bytes to write to a terminal to ask what it supports.

Two queries: XTVERSION (`CSI > q`), which a terminal answers with its name, and
primary device attributes (`CSI c`), which it answers with a list of features.
The attributes reply is what `probe_complete?/1` watches for, because nearly
every terminal answers it while only some answer XTVERSION — so it arrives last
and marks the end of the exchange.

A terminal that answers neither says nothing at all, which is why a caller must
also give up on a timeout.

    iex> FrenchCurve.Capability.probe()
    "\e[>q\e[c"

# `probe_complete?`

```elixir
@spec probe_complete?(binary()) :: boolean()
```

Whether `replies` contains the device attributes answer that ends the exchange.

    iex> FrenchCurve.Capability.probe_complete?("\e[?62;4c")
    true

    iex> FrenchCurve.Capability.probe_complete?("\eP>|kitty(0.32.2)\e\\")
    false

---

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