AxlConsoleMirror — mirror the firmware console to a remote terminal
Lets a loop-owning AXL application host the real UEFI Shell (or any
console app) with its console transparently mirrored to — and driven
from — a remote terminal, including full-screen interactive apps like
the Shell’s edit.
The mirror wraps the system console protocols
(gST->ConIn/ConOut/StdErr and the ConsoleInHandle
SimpleTextInputEx): console output is translated to a terminal byte
stream (UTF-8 text + ANSI/VT control sequences) handed to a caller sink;
remote input injected via axl_console_mirror_inject_text()
(xterm/VT decode) or axl_console_mirror_inject_key() is pushed
into the console key queue and the blocked reader is woken. It pairs with
AxlImage — executable-image lifecycle (axl_shell_launch) to put a real Shell.efi in the
foreground while the loop is pumped in the background
(axl_loop_attach_driver).
Warning
A mirrored real Shell is full pre-OS control over the wire —
arbitrary firmware, block-device, and filesystem access. The
substrate ships no auth/authz; consumers MUST gate it hard
(strong authentication on the carrying channel, admin-only
authorization, transport encryption, an explicit audited action).
See docs/AXL-Console-Mirror-Design.md §9.
The mirror is a single global resource (one console ⇒ one session) and owns no pump or timer — the consumer drives the loop. See the design doc for the layering (substrate owns the mechanism; the consumer owns transport, RBAC, terminal size, and late-join policy).
API Reference
Mirror the firmware console to a byte sink and inject remote input.
Lets a loop-owning AXL application host the real UEFI Shell (or any console app, via axl_shell_launch) with its console transparently mirrored to — and driven from — a remote terminal, including full-screen interactive apps like the Shell’s edit.
The mirror wraps the system console protocols (gST->ConIn/ConOut/StdErr): every console output the foreground app makes is translated to a terminal byte stream (UTF-8 text + ANSI/VT control sequences) and handed to a caller-supplied sink; input injected from a remote terminal is pushed into the console’s key queue and the Shell’s blocked reader is woken. The physical console keeps working in parallel (local keyboard input falls through; local output is mirrored too unless disabled).
This is the reusable, finicky firmware surgery; transport and policy (which shell, terminal size from the browser, the sink⇄WebSocket bridge, RBAC) belong to the consumer. See docs/AXL-Console-Mirror- Design.md.
static void to_ws(const char *bytes, size_t len, void *user) {
axl_ws_send(user, bytes, len); // ship to the browser terminal
}
AxlConsoleMirror *m;
AxlConsoleMirrorConfig cfg = {
.sink = to_ws, .user = conn, .cols = 80, .rows = 25,
.passthrough_local = true,
};
axl_console_mirror_install(&m, &cfg);
axl_loop_attach_driver(loop, 10); // HTTP/WS pumped in the background
axl_shell_launch(NULL); // real Shell in the foreground; blocks
axl_loop_detach_driver(loop);
axl_console_mirror_uninstall(m);
Typedefs
-
typedef struct AxlConsoleMirror AxlConsoleMirror
Opaque console-mirror instance.
Created by axl_console_mirror_install, torn down by axl_console_mirror_uninstall. There is one global console, so a mirror is effectively a singleton: a second install while one is active fails.
-
typedef void (*AxlConsoleSinkFn)(const char *bytes, size_t len, void *user)
Sink for the mirrored console output stream.
Receives console output already translated to a terminal byte stream (UTF-8 text interleaved with ANSI/VT control sequences) suitable for an xterm.js / VT100 terminal. Called from within the wrapped console output path — keep it cheap and non-blocking (enqueue / async send); do not re-enter the mirror from here.
bytesis not NUL-terminated; honorlen.
Functions
-
int axl_console_mirror_install(AxlConsoleMirror **out, const AxlConsoleMirrorConfig *cfg)
Install the mirror: wrap the system console, route output to the sink.
Saves
gST->ConIn/ConOut(/StdErr) and theConsoleInHandle’s SimpleTextInputEx, swaps in AXL wrappers, and begins routing console output tocfg->sink. From here the firmware — and any app the caller starts in the foreground (e.g. via axl_shell_launch) — talks to the wrappers. Pair every install with axl_console_mirror_uninstall; a registry/atexit hook restores the console if the process exits without an explicit uninstall.The mirror does not create a pump. Drive your loop in the background (e.g.
axl_loop_attach_driver) so the sink’s transport keeps running while the foreground app blocks the thread.- Parameters:
out – [out] receives the mirror handle
cfg – configuration (copied)
- Returns:
AXL_OK on success (
*outset). AXL_ERR on bad arguments (NULLout/cfg/cfg->sink), if a mirror is already installed, or on allocation / event-creation failure.
-
void axl_console_mirror_uninstall(AxlConsoleMirror *m)
Restore the original console protocols. NULL-safe.
Always pair with axl_console_mirror_install. After this returns,
gST->ConIn/ConOut/StdErrand theConsoleInHandleSimpleTextInputEx are the originals again and the mirror handle is freed.- Parameters:
m – mirror handle (NULL-safe)
-
int axl_console_mirror_inject_key(AxlConsoleMirror *m, uint16_t scan, uint16_t unicode)
Inject one keystroke from the remote terminal.
UEFI key shape: a printable key sets
unicode(andscan== 0); a special key setsscan(andunicode== 0) — e.g. Up=0x01, Down=0x02, Right=0x03, Left=0x04, Home=0x05, End=0x06, Delete=0x08, PageUp=0x09, PageDown=0x0A, F1=0x0B, F2=0x0C … F12=0x16, Esc=0x17. The key is pushed into the wrapped ConIn ring and the WaitForKey event is signalled, so a Shell blocked inWaitForEventwakes and reads it.- Parameters:
m – mirror handle
scan – UEFI scan code (0 for printable keys)
unicode – UCS-2 char (0 for special keys)
- Returns:
AXL_OK on success, AXL_ERR on NULL
mor a full ring.
-
int axl_console_mirror_inject_text(AxlConsoleMirror *m, const char *bytes, size_t len)
Inject a run of terminal input bytes (xterm/VT), decoded to keys.
Decodes CSI/SS3 escape sequences (arrows, F-keys, Home/End, PgUp/PgDn, Delete) and UTF-8 printables (BMP) from a raw terminal byte stream into the keystrokes of axl_console_mirror_inject_key. This is the reusable half that makes
editusable from a browser: feed raw xterm.js keydown bytes straight in.Each call is self-contained: feed whole escape / multi-byte UTF-8 sequences in a single call (xterm.js delivers a complete sequence per keypress). A sequence left open at end-of-call is treated as a bare Esc plus literal bytes rather than held into the next call, so a dropped final byte can’t corrupt the next keystroke.
- Parameters:
m – mirror handle
bytes – terminal input bytes (xterm/VT)
len – number of bytes
- Returns:
AXL_OK on success, AXL_ERR on NULL
m/ NULLbytes.
-
void axl_console_mirror_set_size(AxlConsoleMirror *m, uint32_t cols, uint32_t rows)
Update the remote terminal size (browser resize).
The wrapped
QueryMode/Modereport this size, so full-screen apps lay themselves out for the remote terminal rather than the physical console. Zero values fall back to the physical console size.- Parameters:
m – mirror handle (NULL-safe)
cols – remote terminal width
rows – remote terminal height
-
void axl_console_mirror_reset(AxlConsoleMirror *m)
Reset per-session mirror state. NULL-safe.
Drains the key ring and clears cursor / escape-decoder tracking. Use between Shell restarts or when a new client attaches so stale state from the previous session doesn’t bleed through. (Alt-screen enter/leave arrives with the full-screen P2 work.)
- Parameters:
m – mirror handle (NULL-safe)
-
struct AxlConsoleMirrorConfig
- #include <axl-console-mirror.h>
Console-mirror configuration.