265 lines
11 KiB
Markdown
265 lines
11 KiB
Markdown
# Labfy RGB
|
||
|
||
A small native **C17 Linux command-line controller** for the RGB keyboard on the
|
||
**ASUS Vivobook S16 M5606UA**, using its **ITE5570 I2C HID controller (`0B05:5570`)**
|
||
and the standard **HID LampArray** protocol.
|
||
|
||
Labfy RGB talks to Linux `hidraw` through `open`, `close` and HID Feature ioctls.
|
||
It is a userspace application, not a kernel module. Static colors need no daemon.
|
||
It works independently of Sway, Wayland, X11 and QuickShell.
|
||
|
||
- Dynamically discovers the matching device; never assumes `/dev/hidraw1`.
|
||
- Checks the bus, VID/PID and complete known HID report descriptor before control.
|
||
- Reads live lamp capabilities before converting RGB and intensity values.
|
||
- Supports static colors, intensity, off, and explicit firmware control.
|
||
- Serializes cooperating instances and attempts firmware recovery on update errors.
|
||
- Keeps command history separate from hardware observations.
|
||
|
||
## Tested hardware and compatibility
|
||
|
||
| Item | Verified on the tested machine |
|
||
|---|---|
|
||
| Laptop | ASUS Vivobook S16 M5606UA |
|
||
| Controller | ITE5570, I2C HID `0B05:5570` |
|
||
| Kernel HID driver | `hid-generic` |
|
||
| Operating system | Arch Linux, x86-64 |
|
||
| Logical lamps | One, ID 0 |
|
||
| Programmability | Programmable RGB |
|
||
| Maximum R/G/B/intensity values | 255 / 255 / 255 / 255 |
|
||
| Minimum update interval | 3000 microseconds |
|
||
| Physical tests | Static RGB colors, including the eight examples below, and return to ASUS firmware control |
|
||
| Unprivileged access | Confirmed with the targeted udev rule and an active local session |
|
||
|
||
Firmware control returned the tested keyboard to white. Other firmware versions
|
||
may choose a different effect or color.
|
||
|
||
Other Vivobook models, other VID/PID combinations, USB devices and descriptor
|
||
variants are **not validated or currently accepted**. Even another `0B05:5570`
|
||
may have a different descriptor. The strict profile is deliberate: a familiar
|
||
product name or report length is not enough to safely identify field semantics.
|
||
The protocol may exist on other hardware, but that is not a compatibility claim.
|
||
|
||
## Build
|
||
|
||
Requirements: Linux headers, a C17 compiler, Meson and Ninja. No Python or HID
|
||
library is required for the controller implementation.
|
||
|
||
On Arch Linux, the usual build tools are provided by `base-devel`, `meson` and
|
||
`ninja`. The optional permission helper also needs Bash, systemd's `udevadm`,
|
||
coreutils and `getfacl` from `acl`.
|
||
|
||
```sh
|
||
git clone https://git.labfytools.com/fy59/labfy-rgb.git
|
||
cd labfy-rgb
|
||
meson setup build --prefix="$HOME/.local"
|
||
meson compile -C build
|
||
meson test -C build --print-errorlogs
|
||
```
|
||
|
||
GitHub mirror: <https://github.com/labfytools/labfy-rgb>.
|
||
|
||
Meson selects C17 and enables `-Wall -Wextra -Wpedantic -Werror` with GCC/Clang.
|
||
|
||
## Install
|
||
|
||
Install the executable for your user:
|
||
|
||
```sh
|
||
meson install -C build
|
||
```
|
||
|
||
With the prefix above, the executable is `$HOME/.local/bin/labfy-rgb`. Make sure
|
||
that directory is in your session's `PATH`. A system installation can instead
|
||
use a suitable prefix and an administrator-managed install step.
|
||
|
||
Installing the executable does **not** install udev rules, configure an autostart,
|
||
create a systemd service or modify desktop configuration.
|
||
|
||
## Device permissions
|
||
|
||
The device may initially be root-only. Do not use `chmod 666`, run your desktop
|
||
shell as root or grant access to all HID devices.
|
||
|
||
The opt-in rule in `packaging/70-labfy-rgb.rules` matches only:
|
||
|
||
```udev
|
||
SUBSYSTEM=="hidraw", KERNEL=="hidraw*", SUBSYSTEMS=="hid", KERNELS=="0018:0B05:5570.*", TAG+="uaccess"
|
||
```
|
||
|
||
On a systemd-logind system, `uaccess` grants access to the active local seat user.
|
||
A remote/headless session is not automatically entitled to access. This grants
|
||
access to the matching HID node as a whole, not just its lighting reports.
|
||
|
||
First inspect the read-only installation plan:
|
||
|
||
```sh
|
||
bash packaging/apply-udev-rule.sh --check
|
||
```
|
||
|
||
Then, only if you want to install and activate that rule:
|
||
|
||
```sh
|
||
sudo -- bash packaging/apply-udev-rule.sh --apply
|
||
```
|
||
|
||
The helper validates the narrow rule, dynamically identifies exactly one matching
|
||
controller, refuses to overwrite a different rule, installs it as root-owned
|
||
`0644`, reloads udev rules and triggers a `change` event **only for that hidraw
|
||
node**. It displays the resulting ACL. It sends no HID reports and changes no
|
||
kernel modules. Reloading rules reparses the system's rule set; other rules for
|
||
the targeted event may also run.
|
||
|
||
Verify as your normal user:
|
||
|
||
```sh
|
||
labfy-rgb status
|
||
```
|
||
|
||
For diagnostics, inspect the discovered node with `getfacl`. A successful rule
|
||
installation alone does not prove that an ACL was granted. Logind seat membership
|
||
and the resulting ACL must be checked. Do not broaden permissions as a fallback.
|
||
Automatic reapplication after reboot or device re-enumeration is expected from
|
||
udev matching, but was not physically tested by rebooting or reconnecting the
|
||
internal keyboard during development.
|
||
|
||
## Commands
|
||
|
||
```sh
|
||
labfy-rgb status
|
||
labfy-rgb status --json
|
||
labfy-rgb probe
|
||
labfy-rgb set '#8000FF'
|
||
labfy-rgb set '#8000FF' 60
|
||
labfy-rgb brightness 30
|
||
labfy-rgb off
|
||
labfy-rgb auto
|
||
```
|
||
|
||
| Command | Behavior |
|
||
|---|---|
|
||
| `status [--json]` | Read-only HID identity, descriptor and array attributes; local command history is labeled separately |
|
||
| `probe` | Select lamp 0 with SET `0x42`, then read attributes with GET `0x43`; this **is an HID write**, although it does not request a color or mode change |
|
||
| `set '#RRGGBB' [P]` | Set a uniform RGB color at intensity P, default **100%**; retain host control after success |
|
||
| `brightness P` | Reapply the last locally recorded color at absolute intensity 0–100%; refuse if that color is unknown |
|
||
| `off` | Send RGBI zero under host control; preserve a previously known color for a later brightness command |
|
||
| `auto` | Explicitly restore autonomous firmware control; do not promise the previous color |
|
||
| `--dry-run COMMAND [ARG...]` | Validate arguments and print the plan without opening HID or reading state |
|
||
|
||
Color strings must be exactly `#RRGGBB`; quote them in a shell. Intensities are
|
||
decimal integers from 0 through 100. Wire levels are rounded to the live channel
|
||
maxima. The HID intensity is distinct from the WMI `asus::kbd_backlight` setting;
|
||
Labfy RGB does not adjust that setting or assume a synchronized relationship.
|
||
|
||
### Saturated color examples
|
||
|
||
| Color | Command |
|
||
|---|---|
|
||
| Violet | `labfy-rgb set '#8000FF'` |
|
||
| Blue | `labfy-rgb set '#0040FF'` |
|
||
| Cyan | `labfy-rgb set '#00FFFF'` |
|
||
| Green | `labfy-rgb set '#00FF40'` |
|
||
| Pink | `labfy-rgb set '#FF0080'` |
|
||
| Red | `labfy-rgb set '#FF0000'` |
|
||
| Orange | `labfy-rgb set '#FF6000'` |
|
||
| Yellow | `labfy-rgb set '#FFFF00'` |
|
||
|
||
## Firmware recovery and limitations
|
||
|
||
A color operation verifies live capabilities, disables autonomous mode, waits
|
||
the device's minimum interval and sends one range update for lamp 0. On success,
|
||
it leaves host control enabled so that the color persists.
|
||
|
||
If a host-control/update operation fails, Labfy RGB attempts **one** firmware
|
||
restoration and reports the original error. It also handles ambiguous/short
|
||
ioctl returns conservatively, without blindly retrying SET reports. `auto` does
|
||
not require a successful capability read or a known color cache.
|
||
|
||
SIGKILL, default SIGINT/SIGTERM handling, power loss or disconnection can interrupt
|
||
recovery. The keyboard can remain in host control. When access is available, use:
|
||
|
||
```sh
|
||
labfy-rgb auto
|
||
```
|
||
|
||
An ioctl acknowledgement is not an optical measurement or a guarantee of lasting
|
||
visual state. Suspend/resume, other tools and firmware may alter the keyboard.
|
||
Do not run another lighting controller concurrently: advisory locking only
|
||
serializes cooperating Labfy RGB instances. Suspend/resume restoration and the
|
||
interaction with WMI brightness have not been comprehensively characterized.
|
||
|
||
## Runtime state and JSON
|
||
|
||
Normal user state lives at `$XDG_RUNTIME_DIR/labfy-rgb/state.bin`, with a `0700`
|
||
directory and `0600` file. Without XDG_RUNTIME_DIR, the fallback is `/run/user/UID`.
|
||
Paths, ownership and permissions are checked; symlinks are rejected at the opened
|
||
boundaries. Root uses a separate namespace and does not import the old root cache.
|
||
State is versioned, bounded and atomically published, with no native-struct
|
||
serialization. A replaced device node invalidates its cached identity.
|
||
|
||
The cache records the **last acknowledged command**, not current physical color.
|
||
`status --json` schema 1 exposes `commanded_mode`, nullable `last_color` and
|
||
`last_percent`, while `observed_mode` remains `unknown`. Missing/corrupt state does
|
||
not become an invented color. A successful firmware restoration can still return
|
||
a cache error if local state could not be invalidated or saved.
|
||
|
||
| Exit code | Meaning |
|
||
|---|---|
|
||
| 0 | Success or valid dry-run |
|
||
| 2 | Invalid arguments |
|
||
| 3 | Device absent or identity mismatch |
|
||
| 4 | Permission denied |
|
||
| 5 | Unsupported capabilities, descriptor/protocol mismatch or ambiguous discovery |
|
||
| 6 | I/O failure, including a failed operation whose recovery succeeded |
|
||
| 7 | Last color unknown/unreadable for brightness; no SET sent |
|
||
| 8 | Original operation and its single recovery both failed; device state uncertain |
|
||
| 9 | Runtime state/cache error; read the diagnostic, because firmware may already have been restored |
|
||
| 10 | Another cooperating instance holds the device lock |
|
||
|
||
## Tests
|
||
|
||
Seven Meson suites use memory transports, syscall doubles and temporary files.
|
||
They do not send reports to a real keyboard. Tests cover color parsing, RGBI
|
||
conversion, exact report bytes, discovery, identity rejection, cache validation,
|
||
process concurrency, errors, exit codes and single-attempt recovery.
|
||
|
||
```sh
|
||
CC=clang meson setup build-clang
|
||
meson test -C build-clang --print-errorlogs
|
||
|
||
CC=gcc meson setup build-sanitize -Db_sanitize=address,undefined -Db_lundef=false
|
||
meson test -C build-sanitize --print-errorlogs
|
||
|
||
CC=clang meson setup build-clang-sanitize -Db_sanitize=address,undefined -Db_lundef=false
|
||
meson test -C build-clang-sanitize --print-errorlogs
|
||
|
||
udevadm verify packaging/70-labfy-rgb.rules
|
||
bash -n packaging/apply-udev-rule.sh
|
||
git diff --check
|
||
```
|
||
|
||
Development validation used GCC 16.2.1 and Clang 23.1.4 on Arch Linux. Other Linux
|
||
distributions/toolchain versions are not claimed as hardware-tested platforms.
|
||
GNU-compatible linker wrapping is used for Linux transport tests.
|
||
|
||
## Optional QuickShell integration
|
||
|
||
QuickShell is **not required** for the CLI. An existing Control Center can call
|
||
it through native `Process` objects using argument arrays, never shell command
|
||
strings, sudo or pkexec. Serialize writes, handle exit/error states, and refresh
|
||
with `status --json` after completion. Keep the distinction between command
|
||
history and physical measurements visible.
|
||
|
||
The tested local integration used QuickShell 0.3.1 on SwayFX 0.6 with saturated
|
||
presets and a firmware button. It is not automatically installed by this project;
|
||
no private desktop configuration is bundled. See [docs/quickshell.md](docs/quickshell.md).
|
||
Startup restoration must remain opt-in; this CLI installs no autostart or service.
|
||
|
||
## Protocol and acknowledgements
|
||
|
||
See [docs/protocol.md](docs/protocol.md) for the accepted report layout and
|
||
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for reference implementations and
|
||
license notices. No third-party implementation source is vendored.
|
||
|
||
## License
|
||
|
||
MIT, copyright (c) 2026 fy59. See [LICENSE](LICENSE).
|