Daemon
The z13ctl daemon is a long-running background process that provides three things ordinary one-shot CLI invocations cannot:
- State persistence — saves your last-applied lighting, profile, battery,
fan curve, TDP, and undervolt settings to
~/.local/state/z13ctl/state.jsonand restores them automatically at every boot. - Sleep/resume recovery — watches for system resume events via D-Bus and reapplies lighting and volatile settings (fan curves, TDP, undervolt) that are lost during sleep.
- HID device ownership — holds the hidraw devices open continuously so that commands arrive instantly rather than waiting to reopen the device each time.
- Armoury Crate button events — captures
KEY_PROG3(the dedicated Armoury Crate button) and broadcasts agui-toggleevent to any connected subscribers (see API).
All CLI commands (apply, brightness, off, profile, batterylimit,
bootsound, paneloverdrive, fancurve, tdp, undervolt, status) automatically route through the
daemon socket when it is running. If the daemon is not running they fall back to direct hardware or
sysfs access transparently — there is no user-visible difference other than
persistence.
Systemd setup (recommended)
z13ctl ships two systemd user units that use socket activation:
z13ctl.socket— systemd creates and manages the Unix socket. The daemon is started on first use and does not run if nothing has connected.z13ctl.service—Type=notify,Restart=on-failure. The daemon sendssd_notify READYwhen it is listening.
The units target graphical-session.target, so they work in both desktop
environments (KDE, GNOME) and Steam Gaming Mode (gamescope session).
Install and enable:
install -Dm644 contrib/systemd/user/z13ctl.socket \
~/.config/systemd/user/z13ctl.socket
install -Dm644 contrib/systemd/user/z13ctl.service \
~/.config/systemd/user/z13ctl.service
systemctl --user daemon-reload
systemctl --user enable --now z13ctl.socket z13ctl.service
Or, if you built from source:
Managing the service
# Check status
systemctl --user status z13ctl.socket
systemctl --user status z13ctl.service
# View live logs
journalctl --user -u z13ctl -f
# Restart the daemon (e.g., after a config change)
systemctl --user restart z13ctl.service
Remove the user service
systemctl --user disable --now z13ctl.socket z13ctl.service
rm -f ~/.config/systemd/user/z13ctl.socket \
~/.config/systemd/user/z13ctl.service
systemctl --user daemon-reload
Running without systemd
Start the daemon directly for testing or on systems without systemd:
To disable the Armoury Crate button watcher (e.g., when another tool such as a Steam controller mapper needs exclusive access to the button device):
The daemon listens on a Unix socket at:
(falls back to /tmp/z13ctl/z13ctl.sock if XDG_RUNTIME_DIR is not set).
State file
The daemon persists state to:
The file is written atomically after every successful command. It stores:
lighting— mode, color, color2, speed, brightness, enabled flagdevices— per-device overrides (keyboard/lightbar can have independent state)profile— last-set performance profilebattery_limit— last-set charge limitfan_curve— custom curve points and mode (applied to both fans)tdp— PL1, PL2, and PL3 power limits in wattsundervolt— CPU Curve Optimizer offset and active flag (preserved across profile switches for recall;undervolt-getincludes the current profile so clients can distinguish active vs saved values)
On get-state requests the daemon also populates temperature (APU die
temperature in °C), fan_rpm (fan speed in RPM), and undervolt_available
(whether the ryzen_smu kernel module is present) from live sysfs reads.
These are not persisted — they are real-time sensor values.
On startup the daemon reads this file and restores all saved settings before
accepting any connections. If the last profile was custom, saved fan curves,
TDP values, and undervolt offsets are re-applied to the hardware.
Raw hidrawN paths are not persisted
Commands sent with --device /dev/hidraw2 (a raw path) are applied but
not saved — raw device numbers are transient and may change across reboots.
Use keyboard or lightbar by name for persistent per-zone settings.
Sleep/resume recovery
Several hardware settings are volatile — they are lost when the system enters sleep (suspend/hibernate) and must be reapplied on resume:
- Lighting — RGB lighting is turned off by the hardware on sleep
- Fan curves — custom PWM curves reset to firmware defaults on sleep
- TDP (PPT) — power limits revert to the firmware profile's defaults
- Undervolt (Curve Optimizer) — CO offsets reset to stock on every sleep cycle
The daemon monitors D-Bus for org.freedesktop.login1.Manager.PrepareForSleep
signals from systemd-logind. When the system resumes (PrepareForSleep(false)),
the daemon restores lighting (regardless of profile) and all custom-profile
volatile settings from its saved state. Fan curves, TDP, and undervolt are only
restored when the custom profile is active — stock profiles (quiet,
balanced, performance) let the firmware manage these settings.
This happens transparently with no user intervention. You can verify it worked by checking the daemon logs after a resume:
Armoury Crate button
The daemon watches the ASUS WMI hotkeys input device for KEY_PROG3 (key code
202) — the physical Armoury Crate button on the Z13. When pressed, it broadcasts
a gui-toggle event to all connected subscribers.
External tools can subscribe to this event via the API:
See the API page for details.
InputPlumber compatibility
On gaming distributions such as Bazzite and ChimeraOS, InputPlumber
ships a built-in device profile for the ROG Flow Z13 (50-rog_flow_z13.yaml)
that grabs "Asus WMI hotkeys" as a managed source device. This creates an
exclusive evdev conflict: z13ctl cannot open the device and will log:
Workaround: Create an override config that marks "Asus WMI hotkeys" as
ignore: true. This tells InputPlumber to leave that device unmanaged so z13ctl
can grab it exclusively, while preserving all other InputPlumber functionality
(controller emulation, touchpad, etc.).
First, check whether the built-in config exists on your system:
If found, create the override directory if needed, then save the override file:
Create /etc/inputplumber/devices.d/50-rog_flow_z13.yaml with ignore: true added
to the keyboard source device:
# /etc/inputplumber/devices.d/50-rog_flow_z13.yaml
version: 1
kind: CompositeDevice
name: ASUS ROG Flow Z13 (2025)
single_source: false
matches:
- dmi_data:
board_name: GZ302EA
sys_vendor: ASUSTeK COMPUTER INC.
source_devices:
- group: keyboard
ignore: true # allow z13ctl to grab this device exclusively
evdev:
name: Asus WMI hotkeys
handler: event*
options:
auto_manage: true
target_devices:
- xbox-elite
- touchpad
- keyboard
capability_map_id: flw1
Note
Always place overrides under /etc/inputplumber/devices.d/ — never edit files
under /usr/share/inputplumber/devices/ directly, as those are owned by the
package and will be overwritten on upgrades.
If your model's board_name differs from GZ302EA, verify it with:
After saving the file, restart InputPlumber:
Then restore device permissions that the previous InputPlumber instance may have changed (InputPlumber restricts device node access while managing a device, and may not fully restore permissions on shutdown):
Then confirm z13ctl can grab the button:
journalctl --user -u z13ctl.service -f
# Should show: watching Armoury Crate button path=/dev/input/eventN
Alternatively, disable the button watcher entirely and let InputPlumber handle the button exclusively: