AxlInput — Input Substrate
Toolkit-agnostic input substrate: mouse, keyboard, and touch as raw
event sources on an AxlLoop. Unified AxlInputEvent callback type
for consumers that want a single dispatch path; per-device wrappers
that bridge UEFI input protocols (EFI_SIMPLE_POINTER_PROTOCOL,
EFI_SIMPLE_TEXT_INPUT_PROTOCOL, EFI_ABSOLUTE_POINTER_PROTOCOL)
into the loop’s existing source pattern.
Header: <axl/axl-input.h>
Role
This module is the input substrate for higher-level toolkits.
Per docs/AGT-Design.md §”Substrate
discipline rules”, axl-input is pure C and paradigm-agnostic — it
produces raw events, not widget messages. Toolkits translate
AxlInputEvent into their own dispatch model (AGT message maps,
GTK-style signals, immediate-mode polling, etc.) at the layer above.
Sibling of axl-gfx in axl-sdk core. Neither depends on the other,
and neither depends on a toolkit.
Loop integration
axl-input does not introduce its own queue, poll, or wait API.
Each input source registers with AxlLoop using the same primitives
the loop already exposes for any other UEFI event source:
axl_input_attach_mouse/axl_input_attach_touchwrapaxl_loop_add_eventaround the protocol’sWaitForInputevent. The dispatch trampoline callsGetState, translates the result into one or moreAxlInputEventvalues, and invokes the callback.axl_input_attach_keywrapsaxl_loop_add_key_press(which internally registersEFI_SIMPLE_TEXT_INPUT_PROTOCOL’sWaitForKey) and translates eachAxlInputKeyinto anAxlInputEventwithtype = AXL_INPUT_KEY_DOWN.
The benefit is uniformity: any code path that already understands
axl_loop_run, axl_loop_remove_source, timeouts, and idle callbacks
“just works” with input — no parallel event-pump for the consumer to
remember to drive.
Unified event
typedef struct {
AxlInputType type; // discriminator (mouse / key / touch / ...)
uint64_t timestamp_us; // wall-clock microseconds since boot
int32_t x, y; // cursor or touch position
uint32_t buttons; // AXL_INPUT_BUTTON_* bitmask
int32_t wheel_dx; // horizontal wheel delta (ticks)
int32_t wheel_dy; // vertical wheel delta
uint32_t keycode; // raw scan code (key events)
uint32_t unicode; // translated codepoint (0 if none)
uint32_t modifiers; // AXL_INPUT_MOD_* bitmask
} AxlInputEvent;
Fields are populated based on .type; unused fields are zero. The
pointer passed to the callback is valid only for the duration of the
call — copy out anything you want to keep.
Event kinds shipped in v0.1:
|
Emitted by |
When |
|---|---|---|
|
mouse |
non-zero relative motion |
|
mouse |
button-state transition |
|
mouse |
non-zero Z motion |
|
keyboard |
each key press |
|
(deferred) |
requires |
|
touch |
first contact (active-buttons 0 → non-zero) |
|
touch |
contact end (non-zero → 0) |
|
touch |
active contact, position changed |
KEY_DOWN events carry modifier + lock state in modifiers:
held SHIFT / CTRL / ALT / META (left/right-distinct bits plus
side-agnostic masks — AXL_INPUT_MOD_SHIFT == LSHIFT | RSHIFT) and
CAPS_LOCK / NUM_LOCK / SCROLL_LOCK. These come from
EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL; when the firmware doesn’t publish
it (e.g. a serial console), modifiers == 0 — treat absent modifiers
as “none”. Only KEY_DOWN fires: UEFI delivers no key-up or
standalone-modifier events. (The live keyboard read is exercised on
real hardware, not in the QEMU serial test harness.)
Usage
A single callback can demultiplex across all three sources:
#include <axl.h>
static bool
on_input(const AxlInputEvent *ev, void *data)
{
AxlLoop *loop = (AxlLoop *)data;
switch (ev->type) {
case AXL_INPUT_MOUSE_MOVE:
ui_cursor_to(ev->x, ev->y);
break;
case AXL_INPUT_MOUSE_BUTTON_DOWN:
ui_click(ev->buttons);
break;
case AXL_INPUT_KEY_DOWN:
if (ev->unicode == 'q') {
axl_loop_quit(loop);
return AXL_SOURCE_REMOVE;
}
break;
case AXL_INPUT_TOUCH_DOWN:
ui_touch_begin(ev->x, ev->y);
break;
default:
break;
}
return AXL_SOURCE_CONTINUE;
}
int main(void) {
AxlLoop *loop = axl_loop_new();
axl_input_attach_mouse(loop, on_input, loop);
axl_input_attach_key (loop, on_input, loop);
axl_input_attach_touch(loop, on_input, loop);
axl_loop_run(loop);
axl_loop_unref(loop);
return 0;
}
Each attach_* returns the loop source ID (use with
axl_loop_remove_source to detach), or 0 on failure: the protocol
isn’t available, a source of that kind is already attached, or
arguments were NULL.
Per-device notes
Mouse (axl_input_attach_mouse). Locates
EFI_SIMPLE_POINTER_PROTOCOL via LocateProtocol. Cursor positions
are accumulated relative deltas starting at (0, 0) — most firmware
reports motion as deltas, not absolute screen coordinates. Callers
wanting screen-bounded positions should clamp in the callback. Each
dispatch may produce multiple discrete events (motion + button +
wheel) from a single GetState call.
Keyboard (axl_input_attach_key). Thin translator over
axl_loop_add_key_press — the existing loop primitive does the
protocol handling. The wrapper exists so a consumer that wants one
callback for mouse + keyboard + touch doesn’t have to maintain a
separate AxlKeyCallback. Callers who already have a key-only flow
can keep using axl_loop_add_key_press directly.
Touch (axl_input_attach_touch). Locates
EFI_ABSOLUTE_POINTER_PROTOCOL. Positions are reported in the
device’s native (CurrentX, CurrentY) range — see
EFI_ABSOLUTE_POINTER_MODE’s AbsoluteMin/Max for the coordinate
system. Callers wanting screen pixels should rescale in the callback.
v0.1 constraints
Single source per device kind per process. Multi-device or hotplug support can be added when a consumer requires it.
Mouse / touch positions are not screen-clamped — the substrate doesn’t know what the consumer’s coordinate system is.
Visual demo
sdk/examples/input-demo.c
attaches all three sources, runs the loop for 5 seconds, and renders
a live status panel via axl-gfx. Run with
scripts/run-qemu.sh out/x64/input-demo.efi.
API Reference
Toolkit-agnostic input event types — codepoints, button + modifier bitfields, the unified AxlInputEvent discriminated union.
Source registration follows axl-loop’s existing pattern:
Keyboard already has
axl_loop_add_key_pressin <axl/axl-loop.h> (usesAxlInputKey— keep for legacy consumers; new code uses the unifiedAxlInputEventvia the wrappers below).Mouse + touch are added in subsequent phases via
axl_input_attach_mouse/axl_input_attach_touch, which registerEFI_SIMPLE_POINTER_PROTOCOL/EFI_ABSOLUTE_POINTER_PROTOCOLas axl-loop sources throughaxl_loop_add_event.
Per substrate discipline rule 3 (docs/AGT-Design.md): this module produces raw events only — no widget dispatch, no toolkit-specific dialect. Toolkits translate AxlInputEvent into their own model (AGT message maps, signal/slot, etc.) at the layer above.
static bool on_input(const AxlInputEvent *ev, void *data) {
(void)data;
switch (ev->type) {
case AXL_INPUT_MOUSE_MOVE: ui_move(ev->x, ev->y); break;
case AXL_INPUT_MOUSE_BUTTON_DOWN: ui_click(ev->buttons); break;
case AXL_INPUT_KEY_DOWN: ui_key(ev->unicode); break;
default: break;
}
return AXL_SOURCE_CONTINUE;
}
axl_input_attach_mouse(loop, on_input, NULL);
axl_input_attach_key(loop, on_input, NULL);
Defines
-
AXL_INPUT_BUTTON_LEFT
-
AXL_INPUT_BUTTON_RIGHT
-
AXL_INPUT_BUTTON_MIDDLE
-
AXL_INPUT_MOD_LSHIFT
-
AXL_INPUT_MOD_RSHIFT
-
AXL_INPUT_MOD_LCTRL
-
AXL_INPUT_MOD_RCTRL
-
AXL_INPUT_MOD_LALT
-
AXL_INPUT_MOD_RALT
-
AXL_INPUT_MOD_LMETA
-
AXL_INPUT_MOD_RMETA
-
AXL_INPUT_MOD_CAPS_LOCK
-
AXL_INPUT_MOD_NUM_LOCK
-
AXL_INPUT_MOD_SCROLL_LOCK
-
AXL_INPUT_MOD_SHIFT
-
AXL_INPUT_MOD_CTRL
-
AXL_INPUT_MOD_ALT
-
AXL_INPUT_MOD_META
Typedefs
-
typedef bool (*AxlInputCallback)(const AxlInputEvent *event, void *data)
Callback signature for unified input events. Return
AXL_SOURCE_CONTINUEto keep the source active orAXL_SOURCE_REMOVEto detach (same convention asAxlLoopCallback/AxlKeyCallback).
Enums
-
enum AxlInputType
Type tag for
AxlInputEvent.Values:
-
enumerator AXL_INPUT_NONE
Sentinel — not a real event.
-
enumerator AXL_INPUT_MOUSE_MOVE
Cursor moved to (.x, .y)
-
enumerator AXL_INPUT_MOUSE_BUTTON_DOWN
Mouse button pressed (see .buttons)
-
enumerator AXL_INPUT_MOUSE_BUTTON_UP
Mouse button released.
-
enumerator AXL_INPUT_MOUSE_WHEEL
Scroll wheel rotated (see .wheel_dx/dy)
-
enumerator AXL_INPUT_KEY_DOWN
Key pressed (see .keycode, .unicode, .modifiers; Ctrl+letter encoding below)
-
enumerator AXL_INPUT_KEY_UP
Key released (where the platform reports it)
-
enumerator AXL_INPUT_TOUCH_DOWN
Touch contact began at (.x, .y)
-
enumerator AXL_INPUT_TOUCH_UP
Touch contact ended.
-
enumerator AXL_INPUT_TOUCH_MOVE
Touch contact moved.
-
enumerator AXL_INPUT_NONE
Functions
-
char axl_input_ctrl_letter(uint32_t unicode, uint32_t modifiers)
Decode a Ctrl+<letter> chord from a KEY_DOWN event, collapsing the two device-dependent encodings documented above into a single letter so a consumer can match Ctrl-chord shortcuts portably. Pass the event’s
unicodeandmodifiers.- Returns:
the lowercase letter ‘a’..’z’ the chord names (Ctrl+A → ‘a’), or 0 when the inputs are not a Ctrl+<letter> chord. The four chords whose C0 codes double as dedicated editing keys — Ctrl+H / I / J / M (Backspace / Tab / LF / CR) — return 0, so a consumer never shadows those keys with a chord.
-
uint32_t axl_input_attach_mouse(AxlLoop *loop, AxlInputCallback cb, void *data)
Register mouse input as an event source on the loop.
Locates
EFI_SIMPLE_POINTER_PROTOCOL, registers itsWaitForInputevent with the loop, and on each dispatch reads cursor state viaGetState. EmitsAXL_INPUT_MOUSE_MOVEfor non-zero relative motion,AXL_INPUT_MOUSE_BUTTON_DOWN/UPon button-state transitions (debounced internally),AXL_INPUT_MOUSE_WHEELfor non-zero Z motion. Multiple discrete events may fire per dispatch.Cursor position is accumulated relative deltas starting at (0, 0); callers wanting screen-bounded positions should clamp in their callback. Only one mouse source per process for v0.1.
- Returns:
source ID for axl_loop_remove_source, or 0 on failure (NULL args, EFI_SIMPLE_POINTER_PROTOCOL not available, or a mouse source is already attached).
-
uint32_t axl_input_attach_key(AxlLoop *loop, AxlInputCallback cb, void *data)
Register keyboard input as an event source on the loop.
Thin wrapper over
axl_loop_add_key_pressthat translates eachAxlInputKey(scan_code + unicode_char) into a unifiedAxlInputEvent(type = AXL_INPUT_KEY_DOWN,keycode = scan_code,unicode = unicode_char,modifiers = AXL_INPUT_MOD_*). This lets callers register a singleAxlInputCallbackfor mouse + keyboard + (future) touch instead of separate per-device callbacks.event.modifierscarries held shift/ctrl/alt/meta (left/right distinct, plus side-agnostic masks) and caps/num/scroll lock state when the firmware publishesEFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL; it is 0 when modifiers can’t be read (no ex protocol, e.g. serial). OnlyAXL_INPUT_KEY_DOWNfires — UEFI delivers no key-up or standalone-modifier events.For how a held Ctrl is encoded — it differs between the physical keyboard (letter +
AXL_INPUT_MOD_CTRL) and the serial console (folded C0 control code, no modifiers) — see the Ctrl+letter note onAxlInputEvent.unicodeabove; a portable consumer must handle both.Only one keyboard source per process for v0.1.
- Returns:
source ID for axl_loop_remove_source, or 0 on failure (NULL args or a keyboard source already attached).
-
uint32_t axl_input_attach_touch(AxlLoop *loop, AxlInputCallback cb, void *data)
Register touch input as an event source on the loop.
Locates
EFI_ABSOLUTE_POINTER_PROTOCOL, registers itsWaitForInputevent with the loop, and on each dispatch reads absolute position viaGetState. EmitsAXL_INPUT_TOUCH_DOWNon first contact (ActiveButtons transition 0 → non-zero),AXL_INPUT_TOUCH_UPon contact end (non-zero → 0),AXL_INPUT_TOUCH_MOVEwhile contact is active and position changes.Position is reported in the protocol’s
(CurrentX, CurrentY)range — seeEFI_ABSOLUTE_POINTER_MODE’s AbsoluteMin/Max for the device’s native coordinate system. Callers wanting screen pixels should rescale in their callback.Only one touch source per process for v0.1.
- Returns:
source ID for axl_loop_remove_source, or 0 on failure (NULL args, EFI_ABSOLUTE_POINTER_PROTOCOL not available, or a touch source already attached).
-
struct AxlInputEvent
- #include <axl-input.h>
Raw input event — unified across keyboard / mouse / touch. Fields are populated based on
.type; unused fields are zero. Passed byconst AxlInputEvent *into the registered callback; the pointer is valid only for the duration of the call.Public Members
-
AxlInputType type
Event kind (discriminator)
-
uint64_t timestamp_us
Wall-clock microseconds since boot.
-
int32_t x
Cursor / touch x in pixels.
-
int32_t y
Cursor / touch y in pixels.
-
uint32_t buttons
Current button state (AXL_INPUT_BUTTON_*)
-
int32_t wheel_dx
Horizontal wheel delta (notch ticks)
-
int32_t wheel_dy
Vertical wheel delta.
-
uint32_t keycode
Raw scan code (key events)
-
uint32_t unicode
Translated codepoint (0 if none) — see Ctrl+letter note below.
-
uint32_t modifiers
Modifier state (AXL_INPUT_MOD_*)
-
AxlInputType type