labfy-rgb/README.md
2026-11-09 19:01:00 +01:00

265 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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).