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
uint32_t click_count; // 1/2/3 = single/double/triple click (mouse buttons)
bool dragging; // held-button press has crossed the drag threshold
bool repeat; // synthetic held-button auto-repeat, not a fresh press
} 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. The last three fields are
filled by the built-in recognizers (see Recognizers below).
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 |
position changed — including hover (no contact) |
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.)
Ctrl+<letter> chords are device-dependent. A physical keyboard
reports them as the printable letter plus AXL_INPUT_MOD_CTRL (Ctrl+A →
'a' + the Ctrl bit); a serial console (TerminalDxe) sends the folded
C0 control byte (Ctrl+A → 0x01) with modifiers == 0. Match both with
axl_input_ctrl_letter(unicode, modifiers), which collapses the two
encodings into a single lowercase letter (and returns 0 for the four
chords whose C0 codes are dedicated editing keys — Ctrl+H/I/J/M).
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. Detach the mouse with axl_input_detach_mouse
(frees the single-mouse slot and cancels any auto-repeat timer) and the
touch / absolute pointer with axl_input_detach_touch (touch binds several
loop sources, so it needs its own teardown rather than one
axl_loop_remove_source).
Recognizers: gestures, debounce, auto-repeat
axl_input_attach_mouse and axl_input_attach_key run small built-in
recognizers that annotate events and, where useful, synthesize new
ones. All are pure functions over the event stream, so a consumer with
its own stream (or a unit test) can run the same logic directly.
Click gestures annotate each mouse-button event in place:
click_countis 1 / 2 / 3 for single / double / triple click within the multi-click window and movement threshold, anddragginglatches once a held-button press moves past the drag threshold (until release). Tune withaxl_input_set_click_tuning(multi_click_ms, drag_px); run the recognizer standalone withaxl_input_gesture_feed.Held-button auto-repeat synthesizes a
MOUSE_BUTTON_DOWN(withrepeat == true) after a delay, then at an interval, while a button stays held — for scrollbar arrows, spinners, press-and-hold. Off by default; enable withaxl_input_set_button_repeat(delay_ms, interval_ms). (Firmware does not auto-repeat mouse buttons, unlike keys.)Keyboard debounce drops a same-key
KEY_DOWNthat repeats faster than a human would — the fix for a high-latency remote console (a BMC virtual console, IPMI SOL) where one keypress registers as held long enough that firmware typematic fires it several times. Off by default; enable withaxl_input_set_key_debounce(min_repeat_ms, printable_only), or run it standalone withaxl_input_key_accept. Held-key repeat itself comes from firmware typematic (UEFI delivers no key-up, so software can’t synthesize it) — the debounce only suppresses the spurious extras.
Modifier state on pointer events
UEFI delivers keyboard modifier state (KeyShiftState) only with a
keystroke, so “Shift held while scrolling” or “Ctrl held while
clicking” would otherwise be invisible to a mouse callback. The substrate
keeps a live modifier state and stamps it onto every pointer event
(ev->modifiers on mouse + touch), so a consumer reads Shift+wheel /
Ctrl+click directly. To stay current between character keystrokes the
keyboard backend enables EFI_KEY_STATE_EXPOSED, which makes the
firmware deliver modifier-only partial keystrokes on shift/ctrl/alt
down+up; the substrate tracks those to update the live state and filters
them out of the KEY_DOWN stream (a partial carries no character).
Caveats: the live state is 0 until a keyboard source is attached
(axl_input_attach_key) and the firmware publishes
EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL — a serial console (no Ex protocol)
reports no modifiers, so pointer events carry modifiers == 0 there.
Enabling EFI_KEY_STATE_EXPOSED also resets the caps/num/scroll-lock
toggle LEDs (UEFI offers no read-modify-write for them). The partial-
keystroke path is firmware-gated and validated on real hardware (the
serial QEMU test harness has no Ex protocol to exercise it).
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 / absolute pointer (axl_input_attach_touch). Drives the seat
from EFI_ABSOLUTE_POINTER_PROTOCOL — a touchscreen, pen / digitizer, VNC
absolute pointer, or the virtual mouse a BMC remote console presents. On
modern firmware (UEFI ≥ 2.30) the BIOS multiplexes pointer devices through
gST->ConsoleInHandle, and that is where a remote-console pointer’s events
actually arrive — so attach_touch binds every absolute-pointer handle,
ConsoleInHandle first, not a single located one. Each handle gets a
WaitForInput event source (the efficient path — idle means no wakeups),
plus a low-rate poll fallback for firmware whose WaitForInput never
signals; a dispatch reads the first handle with data and stops (GetState
consumes, so the sources never double-count). Tear it all down with
axl_input_detach_touch — not a single axl_loop_remove_source, since it
registers several sources. (The compositor wraps this as
axl_compositor_attach_touch, scaling the normalized position onto the
output and running the click/drag recognizer so an absolute pointer gets
double-click + drag.)
Positions are normalized from the device’s native
EFI_ABSOLUTE_POINTER_MODE range into [0, AXL_INPUT_ABS_RANGE) on both
axes, so the value is display-independent — the consumer maps it onto its
own surface (px = ev->x * surface_w / AXL_INPUT_ABS_RANGE);
axl_input_abs_normalize exposes the exact mapping. A TOUCH_MOVE fires
whenever the position changes, including hover (no contact), so a pen /
tablet / VNC absolute pointer that reports position without a button still
drives a pointer; buttons on a move is the live contact state (0 on
hover), so a drag is distinguishable from a hover.
Tune the read path with axl_input_set_touch_config when a given firmware
misbehaves: pick the mechanism (AXL_INPUT_TOUCH_EVENT_AND_POLL default /
EVENT_ONLY / POLL_ONLY), the poll interval (some BMC consoles flicker or
stall the pointer protocol if polled too fast), and whether to bind only
ConsoleInHandle (skipping a separate physical handle that would otherwise
deliver the same device twice). To see what a platform actually exposes,
axl_input_probe_pointers enumerates every simple / absolute pointer handle,
flags the ConsoleInHandle aggregator, and runs a live event-vs-poll
comparison — handy when a remote-console pointer “doesn’t move.”
v0.1 constraints
Single source per device kind per process. Multi-device or hotplug support can be added when a consumer requires it.
Mouse positions are relative deltas accumulated from
(0, 0)(not screen-clamped — the substrate doesn’t know the consumer’s coordinate system); touch positions are normalized to[0, AXL_INPUT_ABS_RANGE).
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
-
AXL_INPUT_ABS_RANGE
Normalized coordinate span for absolute-pointer (touch) events. A
TOUCH_*event’sx/yare rescaled from the device’s nativeEFI_ABSOLUTE_POINTER_MODEAbsoluteMin/Maxinto[0, AXL_INPUT_ABS_RANGE), so the value is display-independent: the consumer maps it onto its own surface (px = x * surface_w / AXL_INPUT_ABS_RANGE). axl-input stays a sibling of axl-gfx with no dependency on it — it never learns the screen resolution; mapping to pixels is the (display-owning) caller’s job.
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
-
enum AxlInputTouchMethod
How
axl_input_attach_touchreads the absolute pointer — tunable so a consumer can find what a given platform’s firmware handles best.Values:
-
enumerator AXL_INPUT_TOUCH_EVENT_AND_POLL
WaitForInput sources + poll fallback (default)
-
enumerator AXL_INPUT_TOUCH_EVENT_ONLY
WaitForInput event sources only.
-
enumerator AXL_INPUT_TOUCH_POLL_ONLY
GetState poll timer only.
-
enumerator AXL_INPUT_TOUCH_EVENT_AND_POLL
Functions
-
void axl_input_gesture_feed(AxlGesture *g, AxlInputEvent *ev)
Feed one event through the recognizer, annotating
ev->click_countandev->draggingin place and updatingg. Pure: it reads onlyev->type/timestamp_us/x/y/buttons, so synthetic events drive it deterministically in tests. Uses the global tuning (axl_input_set_click_tuning).- Parameters:
g – recognizer state (zero-initialized before first call)
ev – [in,out] event to annotate
-
void axl_input_set_click_tuning(uint32_t multi_click_ms, int32_t drag_threshold_px)
Set the multi-click window and drag threshold the recognizer uses.
multi_click_msis the largest gap between successive BUTTON_DOWNs that still counts as a double/triple click;drag_threshold_pxis how far the pointer must move under a held button beforedragginglatches. Pass 0 for either to restore its default (400 ms / 4 px).- Parameters:
multi_click_ms – max inter-click gap in ms (0 = default 400)
drag_threshold_px – drag-latch distance in px (0 = default 4)
-
void axl_input_set_button_repeat(uint32_t delay_ms, uint32_t interval_ms)
Enable held-pointer-button auto-repeat for axl_input_attach_mouse. While a button stays held, a synthetic MOUSE_BUTTON_DOWN (with
repeat == true) is emitted afterdelay_ms, then everyinterval_ms— for scrollbar arrows, spinners, press-and-hold.delay_ms== 0 disables repeat (the default).- Parameters:
delay_ms – delay before the first repeat in ms (0 = disabled)
interval_ms – gap between repeats in ms
-
bool axl_input_key_accept(AxlKeyDebounce *d, AxlInputEvent *ev)
Decide whether to deliver
ev(a KEY_DOWN). Returns true to deliver, false to drop it as a too-fast same-key repeat. Pure — reads onlyev->type/keycode/unicode/timestamp_usand updatesd— so synthetic events drive it in tests. Non-KEY_DOWN events always return true. Uses the global tuning (axl_input_set_key_debounce).- Parameters:
d – state (zero-initialized before first call)
ev – [in] event to test (not modified)
-
void axl_input_set_key_debounce(uint32_t min_repeat_ms, bool printable_only)
Configure keyboard debounce.
min_repeat_msis the minimum gap below which a repeat of the same key is dropped; 0 disables (the default). Whenprintable_onlyis true, the filter applies only to printable characters (unicode >= 0x20) — navigation/editing keys (arrows, Backspace, Delete, Page/Home/End, whose unicode is 0 or a control code) keep their firmware repeat, so held-key navigation still works. Recommended starting point: ~40 ms (above the ~20 ms USB typematic rate, below the ~60 ms human same-key minimum); tune from a capture.- Parameters:
min_repeat_ms – min same-key gap in ms (0 = disabled)
printable_only – true: exempt navigation/editing keys
-
int32_t axl_input_abs_normalize(int64_t value, uint64_t lo, uint64_t hi)
Normalize a native absolute coordinate
value(in the device’s[lo, hi]range, fromEFI_ABSOLUTE_POINTER_MODE) to[0, AXL_INPUT_ABS_RANGE). This is the exact mapping the touch source applies to eachTOUCH_*event; exposed so it can be unit-tested and reused.valueis clamped to[lo, hi]; a degenerate range (hi <= lo) yields 0.
-
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).
-
void axl_input_detach_mouse(AxlLoop *loop)
Detach the mouse source previously attached with axl_input_attach_mouse. Removes the loop event source, cancels any pending held-button auto-repeat timer, and frees the single-mouse-per-process slot so a later axl_input_attach_mouse can succeed. NULL-safe and idempotent (no-op if no mouse is attached).
- Parameters:
loop – the loop the mouse was attached to
-
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).
-
void axl_input_detach_key(AxlLoop *loop)
Detach the keyboard source previously attached with axl_input_attach_key. Removes the loop event source and frees the single-keyboard-per-process slot so a later axl_input_attach_key can succeed — the mirror of axl_input_detach_mouse. Pass the same loop you gave axl_input_attach_key (the source is removed from it). Idempotent: a no-op if no keyboard is attached.
- Parameters:
loop – the loop the keyboard was attached to
-
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), andAXL_INPUT_TOUCH_MOVEwhenever the position changes — INCLUDING while no button is held (hover), so a pen / tablet / VNC-tablet that reports position without contact still drives a pointer. (A bare touchscreen simply never emits the hover moves.)buttonson a MOVE is the liveActiveButtons(0 on hover), so a consumer can tell a drag from a hover.Position is normalized to
[0, AXL_INPUT_ABS_RANGE)from the device’s nativeEFI_ABSOLUTE_POINTER_MODErange — display-independent; the caller maps it onto its surface. SeeAXL_INPUT_ABS_RANGE.Binds EVERY handle publishing
EFI_ABSOLUTE_POINTER_PROTOCOL, ConsoleInHandle first — on modern firmware (UEFI >= 2.30) the BIOS multiplexes pointers, including a BMC remote-console virtual mouse, through ConIn, and that is where live events arrive. Each handle gets aWaitForInputevent source (the efficient path), plus a low-rate poll fallback for firmware whoseWaitForInputnever signals — so the cursor works either way.GetStateconsumes, so the readers never double-count.Only one touch source per process for v0.1. Tear it down with
axl_input_detach_touch(NOT a singleaxl_loop_remove_source, since it registers several sources).- Returns:
a non-zero source ID on success (the first source bound), or 0 on failure (NULL args, no absolute pointer, or already attached).
-
void axl_input_set_touch_config(AxlInputTouchMethod method, bool console_only, uint32_t poll_ms)
Configure the NEXT
axl_input_attach_touch(a process-global setting; call before attaching). Defaults:EVENT_AND_POLL, all handles, 30 ms poll.- Parameters:
method – which read mechanism(s) to use.
console_only – bind ONLY
gST->ConsoleInHandle(skip the separate physical handle — avoids double events if both deliver the same device independently).poll_ms – poll-fallback interval (0 = keep the 30 ms default).
-
void axl_input_set_touch_drain(uint32_t max_states)
Set how many queued absolute-pointer states a single read drains, coalescing them to the LATEST position (a process-global setting; call before attaching). Default 1 = read one state per dispatch (legacy behavior).
Some firmware — notably a BMC/remote-console virtual mouse — queues pointer states FIFO: a fast move enqueues many, and at one-read-per-poll the cursor drains the backlog slowly and lags seconds behind. Raising this drains up to
max_statesper read and reports only the final position, so a slow, protocol-safe poll still catches up in one tick. A value of 0 or 1 keeps the single-read behavior.Coalescing reports only the last drained state’s buttons, so a full press+ release that lands entirely within one drained batch can be missed — fine for tracking, so keep the default (1) where click latency matters.
- Parameters:
max_states – max queued states to coalesce per read (0/1 = no coalesce)
-
void axl_input_detach_touch(AxlLoop *loop)
Detach the touch source: removes every WaitForInput event source + the poll fallback from
loopand frees the single-touch-per-process slot so a lateraxl_input_attach_touchcan succeed. Pass the loop it was attached to.
-
void axl_input_probe_pointers(const char *log_path)
Diagnostic: enumerate every pointer protocol the firmware publishes and report it to the console (
axl_printf) and, iflog_pathis non-NULL, to that file. For each ofEFI_SIMPLE_POINTER_PROTOCOLandEFI_ABSOLUTE_POINTER_PROTOCOLit lists all handles, flags which is theConsoleInHandleaggregator, whetherHandleProtocol(ConsoleInHandle, …)succeeds, the device’s mode (resolution / absolute range), and a liveGetStateresult. Use it to see which pointer path a given platform (real hardware, a BMC remote console, QEMU) actually exposes — call it BEFORE the GUI takes the framebuffer so the console output is visible. Best-effort: silently does nothing if boot services are unavailable.- Parameters:
log_path – optional file to also write the report to (NULL = console only)
-
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_*)
-
uint32_t click_count
Mouse-button events: 1/2/3 for single/double/triple click within the multi-click window+threshold; 0 otherwise.
-
bool dragging
Mouse events: true once a held-button press moves past the drag threshold, until release.
-
bool repeat
Mouse-button events: true if this is a synthetic held-button auto-repeat, not a fresh press.
-
AxlInputType type
-
struct AxlGesture
- #include <axl-input.h>
Click-gesture recognizer state. A plain value type — zero-initialize (
AxlGesture g = {0};) before the first feed. One instance per pointer stream. axl_input_attach_mouse keeps one internally; this is exposed so a consumer with its own event stream (or a unit test) can run the same recognition.Public Members
-
uint64_t last_down_us
timestamp of the last BUTTON_DOWN
-
int32_t last_down_x
x of the last BUTTON_DOWN
-
int32_t last_down_y
y of the last BUTTON_DOWN
-
uint32_t streak
current consecutive-click count
-
uint32_t held
buttons currently held (AXL_INPUT_BUTTON_*)
-
int32_t press_x
x where the current press began
-
int32_t press_y
y where the current press began
-
bool dragging
drag latched for the current press
-
uint64_t last_down_us
-
struct AxlKeyDebounce
- #include <axl-input.h>
Key-debounce recognizer state. Zero-initialize (
AxlKeyDebounce d = {0};) before the first call; one per key stream. axl_input_attach_key keeps one internally; exposed for reuse / unit testing.