Table of Contents
Automation
The automation engine (automation/) runs on its own dedicated std::thread (not the tokio runtime), driven by an mpsc channel of EngineMessages.
Each enabled [[automation.scripts]] entry is a Lua script (mlua, lua54) given an alfred global.
The alfred Lua global
| API | Purpose |
|---|---|
on / once |
register event handlers (per-event, or one-shot) |
defer |
schedule deferred work |
| vars | script-local variables |
audio table |
audio control helpers |
state |
persistent key/value backed by SQLite (store.rs) |
hyprland table |
Hyprland control (hypr.rs) |
state survives restarts (SQLite-backed). Use it for anything that must persist across daemon reloads.
Hyprland integration
automation/hypr.rs runs a second thread with a Hyprland EventListener that translates WM events into AutomationEvents your scripts can handle via on(...).
Note the two different Hyprland mechanisms in the codebase:
serve/server.rscontrol uses thehyprctlsubprocess,automation/hypr.rsuses the nativehyprlandcrate.
Reloading
curl -X POST --unix-socket "$XDG_RUNTIME_DIR/alfred.sock" http://localhost/automation/reload
or the equivalent client path. Scripts are re-read from the on-disk config.
Threading rule
Blocking work (subprocess calls, sqlite, sysfs) belongs on the automation thread or in spawn_blocking — never inline in an async module collect(). The automation engine is intentionally off the tokio runtime so long-running Lua can't stall async tasks.
Seeded from the repo's README.md and CLAUDE.md — those remain the in-tree source of truth.