Linux input devices from /dev/input/event*: keyboards, sticks and
gamepads. It lists and opens devices, keeps each one's identity and axis
calibration, and normalizes axis readings. It never decides what a key or
an axis means.
A program reads a device in one of two ways:
dev:read() once a frame and apply the events to its own state.dev:fileno() with lev.core.wait_readable in a task of its own.nodes and capabilities read what is attached without opening a node.
The module exports EV_SYN, EV_KEY, EV_ABS, ABS_X and ABS_Y, the
codes a reader of dev:read() compares against. Every other code is a
number from the kernel's input-event-codes.h, and a binding table holds
its own.
| Name | Signature |
|---|---|
| normalize | normalize(ai, raw, deadzone) -> v |
| curve | curve(v, kind, amount) -> shaped |
| looks_like_stick | looks_like_stick(dev) -> stick |
| looks_like_keyboard | looks_like_keyboard(dev) -> keyboard |
| open | open(path, nonblock) -> dev, err, errno |
| capabilities | capabilities(name, root) -> caps |
| nodes | nodes(root) -> names |
| ignored | ignored(info) -> ignore |
| list | list() -> devices |
| list_keyboards | list_keyboards() -> devices |
normalize(
ai,raw,deadzone) ->v
Map a raw axis reading to [-1, 1]
ai is the axis's calibration from dev.axes. The deadzone is the larger
of the hardware flat and deadzone, a fraction of the half-range. A
reading inside it returns 0. Outside it, the rest of the range is scaled
so the value rises from 0 at the deadzone's edge.
curve(
v,kind,amount) ->shaped
Shape a normalized axis value
kind = "expo" softens the centre and keeps the ends:
(1 - amount) * v + amount * v³. amount runs from 0, linear, to 1,
cubic, and defaults to 0.5. The sign is kept. Any other kind returns v.
looks_like_stick(
dev) ->stick
Check whether a device is a stick or a gamepad
dev needs axes and buttons, each keyed by code, as an open device
has them. Any true value marks a code, so plain tables serve.
True when the device reports ABS_X, ABS_Y and one key udev counts as a
joystick button:
BTN_JOYSTICK up to BTN_DIGI: the joystick and
gamepad names;BTN_DPAD_UP to BTN_DPAD_RIGHT;BTN_TRIGGER_HAPPY1 to BTN_TRIGGER_HAPPY40: the
spares a stick with more than sixteen buttons falls back on.A touchscreen and a touchpad report X and Y too, but their keys sit in the
digitizer block, 0x140 to 0x14f, so neither passes. A mouse with more than
sixteen buttons runs into the joystick range. It reports 0x11f, the code
below BTN_JOYSTICK, and a device with that code is never a stick.
looks_like_keyboard(
dev) ->keyboard
Check whether an open device is a keyboard
True when the device reports KEY_A, KEY_ENTER and KEY_SPACE and is
not stick-like. The key test rules out power buttons, lid switches and
media-key nodes, which also report EV_KEY. The stick test keeps a
gamepad with a keyboard profile out.
open(
path,nonblock) ->dev,err,errno
Open one input device
Opens path, non-blocking unless nonblock is false, and reads the
device's identity and capabilities once. Returns nil, an error and the
errno when the node does not open, and when it answers no identity: a
regular file refuses with ENOTTY.
| field | description |
|---|---|
dev.path | the node's path |
dev.info | { name, bustype, vendor, product, version } |
dev.axes | ABS code to { code, value, min, max, fuzz, flat, resolution } |
dev.buttons | the set of EV_KEY codes the device reports |
| method | description |
|---|---|
dev:read() | the pending events, each { type, code, value }; an empty table when none wait; nil, an error and the errno when the device is gone |
dev:fileno() | the descriptor, for lev.core.wait_readable |
dev:close() | close the node; safe to call twice |
One read returns up to 64 events.
capabilities(
name,root) ->caps
Read one event node's identity and capabilities from sysfs
name is the node's name under /dev/input, such as event4, as nodes
lists them. Reads <root>/<name>/device/uevent, root being
/sys/class/input by default, and opens nothing. Returns nil when the
file does not read or names no device.
| field | description |
|---|---|
caps.info | { name, bustype, vendor, product, version }, as in dev.info |
caps.axes | the set of EV_ABS codes the device reports |
caps.buttons | the set of EV_KEY codes the device reports |
looks_like_stick and looks_like_keyboard take caps as they take an
open device, and ignored takes caps.info. An axis carries no
calibration here: that needs the node open.
nodes(
root) ->names
List the event nodes without opening them
The event* entries under root, /dev/input by default, in number
order. Nothing is opened, so a node this process may not open is still
listed. A root that does not read gives an empty table.
capabilities takes a name from here, and open takes <root>/<name>.
ignored(
info) ->ignore
Check whether the environment asks a game to ignore a device
info is a device's dev.info. The rule is SDL3's, from the variables
Steam sets for the games it launches. They are read on every call.
0x28de/0x11ff, is kept only when
SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD is set and not empty,
does not start with 0, and does not read false in any case. The two
lists do not apply to it.SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT holds an entry, a
device on it is kept and every other device is ignored.
SDL_GAMECONTROLLER_IGNORE_DEVICES is not read then.SDL_GAMECONTROLLER_IGNORE_DEVICES is ignored.A list holds 0xVVVV/0xPPPP entries split by commas or whitespace. The
hex digits match in either case, up to four of them. Any other entry is
skipped. A value that starts with @ names a file that holds the list. A
file that does not read gives an empty list. A missing vendor or product
reads as 0.
list() ->
devices
Open every stick and gamepad
Returns the open, non-blocking devices under /dev/input that
looks_like_stick accepts, in node number order: list()[1] is the
lowest-numbered stick. A node the process cannot open is skipped, so with
nothing attached the list is empty.
A device ignored accepts is left out. open on its path still opens it.
looks_like_stick and ignored run on each node's capabilities first,
and only a node that passes is opened. A close waits out an RCU grace period, tens of
milliseconds on some kernels. So a node turned down costs one sysfs read
and no close.
list_keyboards() ->
devices
Open every keyboard
Returns the open, non-blocking devices under /dev/input that
looks_like_keyboard accepts, in node number order. A node the process
cannot open is skipped. As in list, only a node whose capabilities
pass is opened.