engine internals

Daemon & systemd

wflow daemon is the long-lived background process that binds every chord declared in your library and dispatches the right workflow when one fires. It runs as a systemd user service in production and stays out of your way.

How backends get picked

The daemon probes three paths at startup, in priority order:

  1. GlobalShortcuts portal (org.freedesktop.portal.GlobalShortcuts) — used on KDE Plasma 6 and GNOME 46+. The first time you bind a chord, the desktop pops a consent dialog showing the shortcuts wflow is asking for; accept it once and the chords are live for the session.
  2. Hyprland IPC via $XDG_RUNTIME_DIR/hypr/$HIS/.socket.sock. The daemon issues keyword bind directives that point back at wflow run <id> --yes.
  3. Sway IPC over $SWAYSOCK using the i3 RUN_COMMAND protocol: bindsym MODS+KEY exec wflow run <id> --yes.

Whatever's available wins. You don't pick the backend; the daemon picks for you.

Manual run

# Foreground (logs to stderr):
wflow daemon

# With debug logging:
RUST_LOG=wflow=debug wflow daemon

Foreground is what you want during setup so you can see errors. Once you trust it, switch to the systemd user unit below.

systemd user service

AUR, Flatpak, and tarball builds drop a unit at /usr/lib/systemd/user/wflow-daemon.service:

[Unit]
Description=wflow trigger daemon (global hotkey dispatch)
After=graphical-session.target
PartOf=graphical-session.target

[Service]
Type=simple
ExecStart=wflow daemon
Restart=on-failure
RestartSec=2
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=graphical-session.target

Enable + start it for every session:

systemctl --user enable --now wflow-daemon

# Status:
systemctl --user status wflow-daemon

# Logs:
journalctl --user -u wflow-daemon -f

With WantedBy=graphical-session.target, the daemon starts when KDE / GNOME / Hyprland / Sway come up and stops on logout. Restart=on-failure handles crashes.

If you installed via cargo install --path . and your user-systemd PATH doesn't include ~/.cargo/bin, edit ExecStart to use an absolute path: ~/.cargo/bin/wflow daemon.

First-run auto-enable

The wflow GUI offers to enable the user unit on first launch. Accept once and you're set up for every login; decline and the choice persists, no nagging on subsequent launches. You can always toggle it later with systemctl --user enable --now wflow-daemon or disable.

Hot reload

Edit a workflow's KDL while the daemon's running. Add a new trigger, change a chord, drop one. The daemon watches ~/.config/wflow/workflows/ via inotify, diffs the chord set against what's currently bound, and unbinds-then-rebinds the deltas. No daemon restart, no hyprctl reload.

Caveat: the GlobalShortcuts portal binds shortcuts once per session by spec, so portal mode (Plasma 6, GNOME 46+) does need a systemctl --user restart wflow-daemon to pick up trigger changes. Compositor-IPC mode (Hyprland, Sway) is fully live-reloadable.

Single-instance lock

Two wflow daemon invocations can't both bind the same chords. The first writes a pidfile to $XDG_RUNTIME_DIR/wflow/daemon.pid. The second sees the pidfile, checks /proc/$pid to confirm the first is still alive, and exits with "already running (pid N)". Crashed daemons leave a stale pidfile that the next start treats as recoverable.

Workflow directory + state

  • Workflows: ~/.config/wflow/workflows/*.kdl. Drop new files here and the daemon picks them up automatically. The wflow://import handler also writes to this directory; see wflow:// protocol.
  • State: ~/.config/wflow/state.toml — onboarding flags, theme + palette, signed-in account, daemon-autostart decision.
  • Workflow metadata: ~/.config/wflow/workflows.toml — created / modified / last-run timestamps, canvas card positions, folder placement. Sidecar to the KDL files; the engine treats KDL as the source of truth.
  • Logs: journalctl --user -u wflow-daemon when running under systemd. Otherwise stderr of whatever shell you launched the daemon in.

Debug output

wflow uses tracing, configured via the standard RUST_LOG env var:

# Verbose:
RUST_LOG=wflow=debug wflow daemon

# Trace:
RUST_LOG=wflow=trace wflow daemon

# Module-scoped:
RUST_LOG=wflow::triggers=trace wflow daemon

For the systemd unit, drop a ~/.config/systemd/user/wflow-daemon.service.d/env.conf override with Environment=RUST_LOG=wflow=debug and systemctl --user daemon-reload && systemctl --user restart wflow-daemon.

Next

Troubleshooting →