reflex is a Lua-driven input automation tool for Linux. You write small Lua scripts that register hotkeys, remaps, timers, clipboard actions, process helpers, window observers, and other automation rules. The companion daemon, reflexd, owns input handling while scripts are loaded and unloaded.
The repository provides shell scripts for building and installing the release binaries.
./scripts/install.shThis script:
- Builds release binaries for
reflexandreflexd. - Installs them to
/usr/local/bin. - Installs the systemd unit for
reflexd. - Enables and restarts the daemon.
./scripts/update.shThis script rebuilds the release binaries, replaces the installed files, reloads systemd, and restarts reflexd.
cargoinstallsystemctlsudo
The install scripts also expect the bundled systemd unit at crates/reflexd/reflexd.service.
- Start or install
reflexd. - Write a Lua script.
- Run it with
reflex run script.lua.
Example:
reflex.bind("ctrl+t", function()
reflex.key.type("Hello from reflex")
reflex.key.send("enter")
end)Run it:
reflex run my_script.luaIf you want to validate a script without connecting to the daemon, use:
reflex check my_script.luareflex run script.lua
reflex run -d script.lua
reflex list
reflex stop <id|script>
reflex status
reflex check script.lua
reflex keysrunloads a script intoreflexd.run -dstarts the script in the background.list,stop, andstatustalk to the daemon.checkperforms a dry run and validates key names and combos.keysprints available key names.
Scripts can use the global reflex object without imports.
reflex.bind("ctrl+shift+t", function()
reflex.key.type("Text from a hotkey")
end)You can also provide separate press and release handlers:
reflex.bind("ctrl+u", {
down = function()
print("pressed")
end,
up = function()
print("released")
end,
})reflex.hotkey("capslock", "ctrl")
reflex.hotkey("back", "forward")reflex.key.type("Hello, World!")
reflex.key.send("ctrl+c")
reflex.key.down("shift")
reflex.key.up("shift")
reflex.mouse.move(100, 200)
reflex.mouse.click("left")
reflex.mouse.scroll(-1)reflex.timer.after(1000, function()
reflex.notify("reflex", "Timer fired")
end)
reflex.clipboard.set("copied text")
local value = reflex.clipboard.get()
reflex.signal.connect("reflex::started", function()
print("script loaded")
end)for _, win in ipairs(reflex.window.list()) do
print(win:id(), win:title(), win:app_id())
end
local editor = reflex.window.find(function(win)
return win:app_id() == "org.gnome.TextEditor"
end)
reflex.signal.connect("window::opened", function(win)
print("opened", win:title())
end)Window observation works with EWMH window managers on X11 and with supported foreign-toplevel interfaces on Wayland:
| Session | Backend |
|---|---|
| X11 | EWMH _NET_CLIENT_LIST |
| KDE Plasma Wayland | org_kde_plasma_window_management |
| wlroots and other compatible compositors | ext_foreign_toplevel_list_v1, falling back to zwlr_foreign_toplevel_manager_v1 |
| GNOME Wayland | org.gnome.Shell.Introspect.GetWindows |
GNOME restricts its introspection API. Reflex does not weaken that restriction:
the session must already authorize GetWindows (on current upstream GNOME
Shell this requires unsafe mode). If GNOME returns AccessDenied, Reflex
reports the requirement instead of changing the session. Enabling unsafe mode
has broader privacy and security implications because it relaxes Shell
restrictions for local session clients.
When WAYLAND_DISPLAY is set, Reflex does not fall back to X11 because that
would expose only XWayland windows. For diagnostics and nested sessions, set
REFLEX_WINDOW_BACKEND to auto, wayland, x11, kde, gnome, ext, or
wlr.
Common namespaces include:
reflex.bindreflex.hotkeyreflex.notifyreflex.keyreflex.mousereflex.clipboardreflex.timerreflex.processreflex.windowreflex.strreflex.tablereflex.signal
For more complete reference material, see docs/lua_api.md.
- Key names are lowercase strings such as
ctrl,shift,alt,win,enter, andspace. - Combos are joined with
+, for examplectrl+shift+t. - Clipboard support is text-only.
reflex.processis handled by the local runner, notreflexd.reflex.windowis handled by the local desktop session, notreflexd.reflexdis required for input bindings and hotkeys to work.
Common project commands are defined in Justfile:
just lint
just test
just run script.lua
just start-daemon