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 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}}
endKeys 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"]}
Summary
Functions
A keyboard with no keys held and caps lock off.
Translates one evdev event. Events other than :ev_key (:ev_syn, :ev_msc, …) and keys with no mapping produce no keys.
Translates a list of evdev events in order, as delivered in one {:input_event, path, events} message.
Types
Functions
A keyboard with no keys held and caps lock off.
Options
:layout—:us(default) or a map ofkey_code => {unshifted, shifted}merged over the US table:emit_release— also emitkind: "release"events (defaultfalse, like a terminal without keyboard enhancement)
Examples
iex> RasterExRatatui.Input.Evdev.new(layout: %{key_z: {"y", "Y"}}).chars.key_z
{"y", "Y"}
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)
[]
@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