API Reference
Public API
keydaemon — background daemon for keyboard/mouse automation.
Public API
macro() → MacroBuilder (fluent builder) preset(name) → MacroBuilder (pre-configured) load(name) → MacroBuilder (from TOML file) stop_all() → None
macro()
Return a new empty MacroBuilder.
preset(name)
Load a named built-in preset and return its pre-configured MacroBuilder.
See keydaemon.presets.available() for the names.
Raises:
| Type | Description |
|---|---|
ImportError
|
if no preset with that name exists. |
load(name)
Load a macro or profile from a TOML file in the keydaemon data directory.
For profiles, returns a builder whose .run() starts all listed macros.
stop_all()
Kill everything. Two independent passes so nothing can survive:
- Ordered teardown through the profile tree — stops runners the clean way (releases held inputs, stops listeners child-first).
- Flat backstop sweep of every runner ever created — catches anything the tree can't reach (e.g. a child whose owning profile was garbage collected, since _PROFILE_REGISTRY holds only weak references).
Runner.stop() is idempotent, so a runner hit by both passes stops once.
MacroBuilder
Fluent builder — accumulates state, does nothing until .run().
do(name)
Run the saved macro name's action sequence at this point.
Python twin of the TOML do: verb. Stored as a reference, not a
copy: .run() reads the target's TOML fresh each time (editing the
target changes every macro that does it), and .save() writes
do:name so the reference survives in the file. Only the target's
actions run — its own scheduling (every/repeat/jitter) is ignored.
Circular references and profile targets are rejected at run time.
drag_to(x1, y1, x2, y2)
Sugar: move to start, hold left, smooth move to end, release.
expand(pattern, replace=None)
Trigger on typed text: whenever pattern is typed anywhere, the trigger
text is erased and replace is typed in its place.
Call repeatedly to build a bank — every entry shares one keyboard listener::
keydaemon.macro().expand("///a", "Hello").expand("///b", "cool dudes only")
With replace=None, typing the pattern runs this macro's action sequence instead of typing text (at most one such action pattern per macro).
Patterns must be typed without modifier keys mid-pattern (lowercase); any special key resets the match buffer.
hotkey(key, mode='toggle')
Arm this macro behind a hotkey instead of running immediately.
mode="toggle" (default): press the key to start the loop, press again to stop it. mode="once": each press fires the loop a single time. The program stays alive between presses; use exit_key to quit entirely.
kill_all()
Stop EVERY macro in the process when reached — the programmatic equivalent of the emergency kill key. Pair with conditions (e.g. a wait_for_color) to bail out of all automation when something looks wrong.
save(name, description='')
Write this macro to the keydaemon macros directory as <name>.toml,
overwriting any existing file of that name — the Python script is the
source of truth, so running it stamps its current behavior over the TOML.
The saved file is a first-class CLI macro: run it with
keydaemon run <name>, see it in keydaemon list, detach it,
stop it by name.
Raises ValueError if the macro uses Python-only features that TOML can't express (the file is left untouched in that case).
stop_self()
Stop this macro (and its runner's children) when reached.
times_per_second(n)
Set the loop rate as a frequency instead of a period.
times_per_second(20) is sugar for every(1 / 20). Clickers and
anti-AFK loops are easier to reason about in Hz than in seconds.
Actions
DoAction
dataclass
Reference to another saved macro's action sequence (builder .do()).
A reference, never executed: MacroBuilder.run() flattens it into the
target's actions via loader.resolve_do_actions() before any runner sees
it, and .save() writes it back as a do:<name> string (the TOML loader
flattens that form itself at load time).
InputController
Bases: Protocol
Structural protocol — tests inject a mock without subclassing.
KillAllAction
dataclass
Stop every macro in the process — same effect as the emergency kill key.
This is the sanctioned way for a macro to invoke the global kill (the hardware combo itself is reserved and unbindable; see keydaemon.guard). Exists so conditional macros can bail out of everything, e.g. "if the screen goes red, kill all automation".
SelfStopAction
dataclass
Stop the macro this action runs in (and any children of its runner).
Runners
ExpandRunner
Bases: Runner
Listens for typed text patterns — a bank of them — with one listener.
Two kinds of entry share the single keyboard listener and match buffer:
- expansions: {pattern: replacement} — typing a pattern erases it and types its replacement. Many can be armed at once ("///a", "///b", ...).
- one optional action pattern — typing it erases it and fires this macro's action sequence instead of text.
Matching uses a rolling buffer capped at the longest pattern: each typed character appends, the tail is kept, and a fire happens the moment the buffer ends with any armed pattern. Any non-character key resets the match.
HotkeyRunner
Bases: Runner
Arms a global hotkey that starts/stops a child action loop.
Unlike DaemonRunner (which runs immediately) or ExpandRunner (which fires on typed text), HotkeyRunner stays armed and reacts to a single hotkey:
mode="toggle" press starts the child loop; press again stops it.
The same key is both "start" and "stop" — a real toggle.
mode="once" each press fires the child loop once (ignored while it's
still running, so presses don't stack).
The runner itself lives until stop() (listener stays alive between presses), so exit_key / global kill / stop_all terminate it and any running child.
A FRESH DaemonRunner is built on every start because DaemonRunner.stop() latches its stop event and cannot be restarted.
Runner
Lifecycle base for every runner — the single mechanism that makes runaway threads impossible.
Three guarantees, none of which a subclass can accidentally opt out of:
- Auto-registration: init adds self to _ALL_RUNNERS. You cannot build a runner that escapes the global backstop sweep.
- Idempotent, cascading stop(): stops children first, then self, exactly once. Safe to call from the owner tree AND the flat sweep — they overlap harmlessly.
- Template-method teardown: subclasses implement _stop_self() (kill my thread/listener). They can't forget to stop children — the base does it.
Children are stopped before parents so a supervisor (e.g. HotkeyRunner) always tears its loop down before releasing its own listener.
add_child(child)
Register a sub-runner so stop() cascades to it (children-first).
make_runner(lm)
Build the right runner for a loaded macro. Single source of truth so the CLI, profile loader, and Python API never drift on how a macro maps to a runner.
lm is a keydaemon.loader.LoadedMacro (duck-typed to avoid an import cycle).
stop_all_runners()
Hard backstop: stop every live runner, regardless of who owns it.
stop() is idempotent, so calling this alongside an ordered profile teardown is safe — anything already stopped is a no-op, anything orphaned still dies.
Profile
Profile
Execution context — owns a set of runners, an exit key, and the global kill listener. Every macro runs inside a Profile (explicit or implicit).
stop_all()
Kill everything. Two independent passes so nothing can survive:
- Ordered teardown through the profile tree — stops runners the clean way (releases held inputs, stops listeners child-first).
- Flat backstop sweep of every runner ever created — catches anything the tree can't reach (e.g. a child whose owning profile was garbage collected, since _PROFILE_REGISTRY holds only weak references).
Runner.stop() is idempotent, so a runner hit by both passes stops once.
Kill-Key Guard
Load-time protection for the emergency kill key.
GLOBAL_KILL_KEY is the one input that must always work: it is the user's last resort when a macro floods the machine with synthetic events. Macros therefore may not bind it as a hotkey/exit key, and may not synthesize the combo by pressing its keys — either would shadow or hijack the escape hatch.
Macros that legitimately want kill semantics have sanctioned actions instead:
stop:self / .stop_self() (this macro and its children) and
stop:all / .kill_all() (every macro in the process).
Both the TOML loader and MacroBuilder.run() call ensure_kill_key_unreachable(), so there is no path into a running macro that skips this check.
KillKeyError
Bases: ValueError
A macro tried to bind or synthesize the emergency kill combo.
ensure_kill_key_unreachable(actions, *, hotkey=None, exit_key=None, name='macro')
Raise KillKeyError if a macro could bind or type GLOBAL_KILL_KEY.
Checks two attack surfaces: 1. Key bindings (hotkey / exit_key) that cover the whole kill combo. 2. The action sequence: walks Press/Release/Tap tracking which keys are held, and rejects the moment all kill-combo keys would be down at once.
Preset Export
Serialize a MacroBuilder to macro TOML — the reverse of loader._parse_action.
Built-in presets are Python (MacroBuilder code), but the CLI runs only TOML
files from the macros directory. Rather than teaching the CLI a second dispatch
path, a preset is installed: its builder is serialized to a normal TOML file
the user can read, edit, and re-run like any hand-written macro. Regenerating
the file (keydaemon new
Python can express things TOML can't (computed values, per-action jitter overrides, non-default tolerances). If a preset ever uses one of those, this module raises rather than silently dropping the detail — such a preset must stay Python-only.
builder_to_toml(b, name, description='')
Render a MacroBuilder as macro TOML that load_macro() reads back identically.
install_preset(preset_name, as_name=None, force=False)
Materialize a built-in preset as a TOML file in the macros directory.
as_name lets a preset be forked under a different macro name. Refuses to overwrite an existing file unless force=True (regeneration).
Scheduler
Blocking scheduler loop — intended to run inside a DaemonRunner thread.
Executes the action list, waits the interval (with jitter), then repeats. Sleeps in SCHEDULER_TICK increments so stop_requested() is checked frequently.
Screen Utilities
color_matches(x, y, hex_color, tolerance=10)
Return True if the pixel at (x, y) is within tolerance of hex_color.
get_pixel_color(x, y)
Return the RGB color of the pixel at screen coordinates (x, y).
get_pixel_hex(x, y)
Return the hex color string of the pixel at (x, y), e.g. '#3A7D44'.
hex_to_rgb(hex_color)
Convert a hex color string like '#3A7D44' or '3A7D44' to (r, g, b).