# `RasterExRatatui.Input.Evdev`
[🔗](https://github.com/mcass19/raster_ex_ratatui/blob/v0.4.0/lib/raster_ex_ratatui/input/evdev.ex#L1)

Translates Linux evdev keyboard events into `ExRatatui.Event.Key` structs.

A terminal hands an app finished key events: `"A"` with `["shift"]`, `"back_tab"`, `"c"` with `["ctrl"]`. A USB keyboard read through evdev (with the [`input_event`](https://hex.pm/packages/input_event) library, for instance) only reports physical keys going down, repeating, and coming up, so something has to remember which modifiers are held and which character a key makes. This module is that something: pure, no process, no dependency on `input_event`.

It consumes the `{type, code, value}` tuples `input_event` delivers in `{:input_event, path, events}` messages and keeps only `:ev_key` ones:

    {:ok, _pid} = InputEvent.start_link(path: "/dev/input/event0", grab: true)
    keyboard = Evdev.new()

    # in the process receiving the messages (a surface's handle_info/2):
    def handle_info({:input_event, _path, events}, %{keyboard: keyboard} = state) do
      {keyboard, keys} = Evdev.translate_all(keyboard, events)
      {:events, keys, %{state | keyboard: keyboard}}
    end

Keys come out the way crossterm reports them in a terminal, so apps behave the same on both: character keys carry the character they type (`"A"` when shifted or under caps lock, `"!"` for shift+1) with `"shift"`, `"ctrl"`, `"alt"`, `"super"` modifiers in that order; special keys use the codes listed in `ExRatatui.Event.Key`; shift+tab is `"back_tab"`. Modifier keys alone produce no events.

## Layouts

The built-in character table is a US layout. Pass `layout:` a map of `key_code => {unshifted, shifted}` to override or add keys (a German layout swaps `:key_y` and `:key_z`, for example).

## Examples

    iex> alias RasterExRatatui.Input.Evdev
    iex> {keyboard, []} = Evdev.translate(Evdev.new(), {:ev_key, :key_leftshift, 1})
    iex> {_keyboard, [key]} = Evdev.translate(keyboard, {:ev_key, :key_1, 1})
    iex> key
    %ExRatatui.Event.Key{code: "!", kind: "press", modifiers: ["shift"]}

# `t`

```elixir
@type t() :: %RasterExRatatui.Input.Evdev{
  caps_lock: boolean(),
  chars: %{required(atom()) =&gt; {String.t(), String.t()}},
  emit_release: boolean(),
  held: MapSet.t(atom())
}
```

# `new`

```elixir
@spec new(keyword()) :: t()
```

A keyboard with no keys held and caps lock off.

## Options

  * `:layout` — `:us` (default) or a map of `key_code => {unshifted, shifted}` merged over the US table
  * `:emit_release` — also emit `kind: "release"` events (default `false`, like a terminal without keyboard enhancement)

## Examples

    iex> RasterExRatatui.Input.Evdev.new(layout: %{key_z: {"y", "Y"}}).chars.key_z
    {"y", "Y"}

# `translate`

```elixir
@spec translate(t(), {atom(), atom(), integer()}) :: {t(), [ExRatatui.Event.Key.t()]}
```

Translates one evdev event. Events other than `:ev_key` (`:ev_syn`, `:ev_msc`, …) and keys with no mapping produce no keys.

## Examples

    iex> alias RasterExRatatui.Input.Evdev
    iex> {_keyboard, keys} = Evdev.translate(Evdev.new(), {:ev_key, :key_enter, 1})
    iex> keys
    [%ExRatatui.Event.Key{code: "enter", kind: "press", modifiers: []}]

    iex> alias RasterExRatatui.Input.Evdev
    iex> Evdev.translate(Evdev.new(), {:ev_syn, :syn_report, 0}) |> elem(1)
    []

# `translate_all`

```elixir
@spec translate_all(t(), [{atom(), atom(), integer()}] | :disconnect) ::
  {t(), [ExRatatui.Event.Key.t()]}
```

Translates a list of evdev events in order, as delivered in one `{:input_event, path, events}` message.

`input_event` sends `:disconnect` in place of the list when the device goes away (unplugged, or grabbed by another reader). That releases every held modifier and produces no keys; the consumer is the one to look for a keyboard again.

## Examples

    iex> alias RasterExRatatui.Input.Evdev
    iex> events = [{:ev_key, :key_leftctrl, 1}, {:ev_msc, :msc_scan, 29}, {:ev_key, :key_c, 1}, {:ev_syn, :syn_report, 0}]
    iex> {_keyboard, keys} = Evdev.translate_all(Evdev.new(), events)
    iex> keys
    [%ExRatatui.Event.Key{code: "c", kind: "press", modifiers: ["ctrl"]}]
    iex> {keyboard, []} = Evdev.translate_all(Evdev.new(), [{:ev_key, :key_leftshift, 1}])
    iex> {keyboard, []} = Evdev.translate_all(keyboard, :disconnect)
    iex> MapSet.size(keyboard.held)
    0

---

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