Host-side pointer transform (accel / snap)
Acceleration, deceleration and angle snapping are not firmware features of any supported mouse. No HID command sets them; rivalcfg exposes no such setting for these devices; and probing finds no acknowledged command. SteelSeries Engine offers them on Windows, but applies them host-side in its always-running process - they stop working when Engine is closed and are never written to the mouse's onboard memory, unlike CPI, polling rate and button mapping.
alloyctl does the same on Linux, and because it works below the display server it behaves identically on X11 and every Wayland compositor.
How it works
Per-device daemon (alloyctl --accel-daemon VID:PID) sits in the input
pipeline, beneath libinput:
Resolve the mouse's motion evdev node by scanning
/sys/class/inputfor the node whosedevice/idmatches the VID:PID and whosecapabilities/reladvertisesREL_XandREL_Y. This is the boot pointer interface, not the vendor hidraw interface used for configuration.EVIOCGRABit - an exclusive grab, so the raw device no longer reaches the compositor.Create a
/dev/uinputvirtual pointer that mirrors the real device'sEV_KEY/EV_REL/EV_ABS/EV_MSCcapabilities, so buttons, wheel and everything else pass through untouched.For each
SYN_REPORTbatch, transform the accumulatedREL_X/REL_Y(angle snapping, then a speed-dependent gain, with sub-count remainder carry) and re-emit through the virtual pointer.
The transform math (src/accel_transform.c) is pure, integer-only and unit
tested; the device I/O and lifecycle live in src/accel.c.
The kernel releases EVIOCGRAB automatically when the file descriptor closes
or the process dies, so a crash never leaves the physical mouse unusable - the
real device simply reappears to the compositor.
Cooperate, never overwrite
Opening the TUI changes nothing about how the mouse behaves: alloyctl only probes whether a daemon is already running and reflects that. It never grabs the device, spawns a daemon or applies a transform until the user explicitly enables the engine, and disabling it drops the grab so motion returns to raw. Values initialise from the real running state and the persisted config.
Lifecycle and permissions
Persistence: enabling the engine writes a per-device XDG autostart entry (
~/.config/autostart/alloyctl-accel-<vid>-<pid>.desktop) that restarts the daemon at login, so the choice survives a reboot - without a systemd dependency. One daemon per device, guarded by a pidfile in$XDG_RUNTIME_DIR.Live edits: the TUI writes the config the daemon watches and sends
SIGHUP; the daemon re-reads it.Permissions (rootless): the daemon needs
/dev/uinput(root-only by default) and the mouse's/dev/inputmotion node (root:inputby default - not seat-accessible out of the box).make install(from source) andsudo ./install.sh(from a release tarball) both ship70-alloyctl-uinput.rulesgranting both to the active seat (uaccess):/dev/uinputdirectly and the evdev nodes of SteelSeries (VID1038) devices. The rule and installer ride inside the release tarball, so a binary download needs no source clone. On non-logind systems,input-group membership is the fallback (takes effect at the next login).
Interaction with the compositor
Compositor still applies its own pointer acceleration to the virtual device -
exactly as Windows re-accelerates SteelSeries' transformed counts. For
a pure curve, set the compositor/libinput acceleration profile to flat for the
alloyctl virtual pointer device (it carries a distinct, targetable name).