Lua Runtime Overview¶
This section documents how EasyBarKit runs Lua widgets for either frontend. It is for contributors changing the runtime implementation, not for normal widget authoring. Use Lua Widgets for the public API and guides.
Process boundary¶
EasyBarKit does not embed Lua in either frontend process. It starts a separate Lua child and communicates with it over a dedicated Unix socket. stderr remains reserved for structured runtime logs.
The separation provides:
- crash isolation;
- deterministic full resets on runtime restart;
- explicit JSON transport between Swift and Lua;
- independent process supervision and backpressure;
- a clear trust boundary around widget execution.
Widget code is still trusted local code. A separate process is isolation, not a security sandbox.
High-level flow¶
flowchart TD
Start["Swift starts the Lua child"]
Load["Lua loads managed entrypoints and manual widgets"]
Subscribe["Lua reports required events"]
Sources["Swift enables required event sources"]
Events["Swift sends normalized events"]
State["Lua mutates registry state"]
Trees["Lua renders node trees"]
Store["Swift applies trees to WidgetStore"]
Start --> Load
Load --> Subscribe
Subscribe --> Sources
Sources --> Events
Events --> State
State --> Trees
Trees --> Store
Managed package entrypoints and manual widget files have different discovery rules. See Widget Loading. The package manager owns version selection and activation; see Package Store Internals.
Swift responsibilities¶
The main Swift pieces are:
LuaProcessController.swiftstarts and stops the child process group;LuaTransport.swiftowns the Lua socket and stderr handling;EasyBarLuaRuntimeconnects the configured socket and execs Lua;LuaRuntime.swiftis the runtime facade;WidgetEngine.swiftowns handshake, subscriptions, tree updates, and request routing;LuaCommandService.swiftandLuaCommandRunner.swiftown bounded external commands;LuaTimerService.swiftowns cancellable one-shot timers;EventHub.swiftforwards normalized events;EventManager.swiftenables native sources from merged subscription demand;RuntimeCoordinator.swiftowns startup, shutdown, reload, and runtime orchestration;WidgetStore.swiftowns the latest decoded node trees.
Swift owns process lifecycle, command limits, timers, transport health, and native event sources. Lua owns widget state and rendering decisions.
Lua responsibilities¶
The main Lua pieces are:
runtime.luabootstraps the runtime and owns the transport read loop;loader.luacreates per-entrypoint environments and executes widget code transactionally;api.luaexposes the publiceasybarAPI and discovery helpers;registry.luastores node state;subscriptions.luastores event and interval handlers;events.luanormalizes and dispatches incoming events;render.luaderives flat output trees from registry state;json.luaencodes and decodes protocol payloads;log.luawrites structured records to stderr.
Registry and rendering model¶
Widgets mutate registry state through node handles. The renderer derives output from that state; it does not send incremental UI mutation commands to Swift.
At a high level:
- widget code adds or updates registry nodes;
render.luabuilds the current nested tree;- popup relationships and interactions are attached;
- the result is flattened;
- output identical to the last tree for a root is skipped;
- Swift replaces the previous root nodes in
WidgetStorewith the new tree.
This keeps the Lua model simple: mutable widget state in Lua, derived UI state across the process boundary.
Host-owned requests¶
The Lua process delegates operations that need host lifecycle control to Swift:
command_requestandcommand_cancelfor external processes;timer_requestandtimer_cancelfor one-shot scheduling;storage_requestfor validated reads and writes below a widget's config namespace.
Swift returns the corresponding responses or timer events over the same transport. Public behavior for these APIs belongs in Commands and Widget Settings; this page only defines the ownership boundary.
Backpressure¶
Lua event delivery keeps bounded action and coalescing queues. Must-deliver actions are never silently evicted. If the action queue reaches its hard limit, the host records the overflow, suspends the failed session, and restarts Lua through normal supervision.
The host also bounds complete Lua protocol lines waiting for actor-side processing. A full input queue is treated as an unhealthy child instead of dropping an ordered protocol message.
These mechanisms keep slow or wedged Lua execution from growing host memory without bound.
Logging boundary¶
Lua uses stderr for structured logs while the socket carries runtime protocol messages.
LuaLogBridge.swift translates Lua records into the shared host logger. Runtime records receive
runtime context; widget records receive the stable widget source identity supplied by the loader.
Public log levels, file-backed widget logs, and filtering belong in Lua Logging. Contributor debugging commands belong in Contributor Notes.