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_touch wrap axl_loop_add_event around the protocol’s WaitForInput event. The dispatch trampoline calls GetState, translates the result into one or more AxlInputEvent values, and invokes the callback.

  • axl_input_attach_key wraps axl_loop_add_key_press (which internally registers EFI_SIMPLE_TEXT_INPUT_PROTOCOL’s WaitForKey) and translates each AxlInputKey into an AxlInputEvent with type = 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:

AxlInputType

Emitted by

When

AXL_INPUT_MOUSE_MOVE

mouse

non-zero relative motion

AXL_INPUT_MOUSE_BUTTON_DOWN/UP

mouse

button-state transition

AXL_INPUT_MOUSE_WHEEL

mouse

non-zero Z motion

AXL_INPUT_KEY_DOWN

keyboard

each key press

AXL_INPUT_KEY_UP

(deferred)

requires EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL

AXL_INPUT_TOUCH_DOWN

touch

first contact (active-buttons 0 → non-zero)

AXL_INPUT_TOUCH_UP

touch

contact end (non-zero → 0)

AXL_INPUT_TOUCH_MOVE

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_count is 1 / 2 / 3 for single / double / triple click within the multi-click window and movement threshold, and dragging latches once a held-button press moves past the drag threshold (until release). Tune with axl_input_set_click_tuning(multi_click_ms, drag_px); run the recognizer standalone with axl_input_gesture_feed.

  • Held-button auto-repeat synthesizes a MOUSE_BUTTON_DOWN (with repeat == true) after a delay, then at an interval, while a button stays held — for scrollbar arrows, spinners, press-and-hold. Off by default; enable with axl_input_set_button_repeat(delay_ms, interval_ms). (Firmware does not auto-repeat mouse buttons, unlike keys.)

  • Keyboard debounce drops a same-key KEY_DOWN that 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 with axl_input_set_key_debounce(min_repeat_ms, printable_only), or run it standalone with axl_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_press in <axl/axl-loop.h> (uses AxlInputKey — keep for legacy consumers; new code uses the unified AxlInputEvent via the wrappers below).

  • Mouse + touch are added in subsequent phases via axl_input_attach_mouse / axl_input_attach_touch, which register EFI_SIMPLE_POINTER_PROTOCOL / EFI_ABSOLUTE_POINTER_PROTOCOL as axl-loop sources through axl_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’s x / y are rescaled from the device’s native EFI_ABSOLUTE_POINTER_MODE AbsoluteMin/Max into [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_CONTINUE to keep the source active or AXL_SOURCE_REMOVE to detach (same convention as AxlLoopCallback / AxlKeyCallback).

typedef struct AxlLoop AxlLoop

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.

enum AxlInputTouchMethod

How axl_input_attach_touch reads 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.

Functions

void axl_input_gesture_feed(AxlGesture *g, AxlInputEvent *ev)

Feed one event through the recognizer, annotating ev->click_count and ev->dragging in place and updating g. Pure: it reads only ev->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_ms is the largest gap between successive BUTTON_DOWNs that still counts as a double/triple click; drag_threshold_px is how far the pointer must move under a held button before dragging latches. 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 after delay_ms, then every interval_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 only ev->type / keycode / unicode / timestamp_us and updates d — 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_ms is the minimum gap below which a repeat of the same key is dropped; 0 disables (the default). When printable_only is 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, from EFI_ABSOLUTE_POINTER_MODE) to [0, AXL_INPUT_ABS_RANGE). This is the exact mapping the touch source applies to each TOUCH_* event; exposed so it can be unit-tested and reused. value is 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 unicode and modifiers.

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 its WaitForInput event with the loop, and on each dispatch reads cursor state via GetState. Emits AXL_INPUT_MOUSE_MOVE for non-zero relative motion, AXL_INPUT_MOUSE_BUTTON_DOWN/UP on button-state transitions (debounced internally), AXL_INPUT_MOUSE_WHEEL for 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_press that translates each AxlInputKey (scan_code + unicode_char) into a unified AxlInputEvent (type = AXL_INPUT_KEY_DOWN, keycode = scan_code, unicode = unicode_char, modifiers = AXL_INPUT_MOD_*). This lets callers register a single AxlInputCallback for mouse + keyboard + (future) touch instead of separate per-device callbacks.

event.modifiers carries held shift/ctrl/alt/meta (left/right distinct, plus side-agnostic masks) and caps/num/scroll lock state when the firmware publishes EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL; it is 0 when modifiers can’t be read (no ex protocol, e.g. serial). Only AXL_INPUT_KEY_DOWN fires — 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 on AxlInputEvent.unicode above; 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 its WaitForInput event with the loop, and on each dispatch reads absolute position via GetState. Emits AXL_INPUT_TOUCH_DOWN on first contact (ActiveButtons transition 0 → non-zero), AXL_INPUT_TOUCH_UP on contact end (non-zero → 0), and AXL_INPUT_TOUCH_MOVE whenever 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.) buttons on a MOVE is the live ActiveButtons (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 native EFI_ABSOLUTE_POINTER_MODE range — display-independent; the caller maps it onto its surface. See AXL_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 a WaitForInput event source (the efficient path), plus a low-rate poll fallback for firmware whose WaitForInput never signals — so the cursor works either way. GetState consumes, 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 single axl_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_states per 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 loop and frees the single-touch-per-process slot so a later axl_input_attach_touch can 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, if log_path is non-NULL, to that file. For each of EFI_SIMPLE_POINTER_PROTOCOL and EFI_ABSOLUTE_POINTER_PROTOCOL it lists all handles, flags which is the ConsoleInHandle aggregator, whether HandleProtocol(ConsoleInHandle, …) succeeds, the device’s mode (resolution / absolute range), and a live GetState result. 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 by const 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.

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

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.

Public Members

uint32_t last_keycode

keycode of the last key seen

uint32_t last_unicode

unicode of the last key seen

uint64_t last_us

timestamp of the last key seen