# `FrenchCurve.Capability`
[🔗](https://github.com/jaman/french_curve/blob/v0.2.0/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.

`FRENCH_CURVE_ITERM2=kitty` sends iTerm2 to `:kitty` instead; other terminals ignore
it.

`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.

## iTerm2

iTerm2 gets its own inline images. Forced to `:kitty`, it speaks transmit-and-display
and delete only: `placements?/1` is `false` for it at every version, and
`FrenchCurve.frame/3` retransmits each frame. What each path costs on iTerm2 is set
out in the README.

    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`, `:braille` and `:cells`; 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

# `iterm2?`

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

Whether `env` describes an iTerm2 session, locally or over ssh.

    iex> FrenchCurve.Capability.iterm2?(%{"LC_TERMINAL" => "iTerm2"})
    true
    iex> FrenchCurve.Capability.iterm2?(%{"TERM" => "xterm-kitty"})
    false

# `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", "TERM_PROGRAM_VERSION" => "3.7.2"})
    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*
