Initial: kbd — ASUS TUF FA608 keyboard backlight CLI

Single C binary controlling ITE51368 (0B05:19B6) keyboard backlight.
- HID feature report [0x46,0x01] switches to EC mode
- Brightness control via sysfs asus::kbd_backlight
- RGB effects via sysfs kbd_rgb_mode (6 params: cmd mode R G B speed)
- systemd service for boot-time EC mode init
This commit is contained in:
lou
2026-06-27 15:36:58 +08:00
commit 947949bd13
5 changed files with 261 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
kbd
*.o
+18
View File
@@ -0,0 +1,18 @@
CFLAGS += -Wall -Wextra -O2 -Wno-unused-result $(shell pkg-config --cflags hidapi-hidraw)
LDFLAGS += $(shell pkg-config --libs hidapi-hidraw)
TARGET = kbd
SRC = src/main.c
.PHONY: all clean install
all: $(TARGET)
$(TARGET): $(SRC)
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
install: $(TARGET)
install -D -m 0755 $(TARGET) $(DESTDIR)/usr/local/bin/$(TARGET)
clean:
rm -f $(TARGET)
+59
View File
@@ -0,0 +1,59 @@
# kbd
ASUS TUF Gaming A16 FA608 keyboard backlight CLI.
Single C binary — no runtime dependencies beyond `libhidapi`.
## Why
The FA608 uses an **ITE51368 keyboard controller over I2C HID** (`0B05:19B6`).
On Linux this device defaults to *Windows Dynamic Lighting* mode after boot,
which ignores all WMI/sysfs backlight commands.
This tool sends the HID feature report `[0x46, 0x01]` to switch it to
*Embedded Controller* mode, then controls brightness and RGB effects
through the standard `asus::kbd_backlight` sysfs interface.
## Build
```bash
sudo apt install libhidapi-dev # Debian/Ubuntu
make
sudo make install # → /usr/local/bin/kbd
```
## Usage
```
kbd init # switch ITE51368 to EC mode (first boot)
kbd on|off|toggle # brightness
kbd 0-3 # set brightness level
kbd color ff6600 # static orange
kbd color ffffff # static white
kbd breathe 00ff00 2 # green breathing (speed 0-2)
kbd rainbow 1 # rainbow cycle (speed 0-2)
kbd mode 10 ff8800 2 # custom mode 10 (pulse)
```
Modes: 0=Static, 1=Breathe, 2=RainbowCycle, 3=RainbowWave, 4=Star,
5=Rain, 6=Highlight, 7=Laser, 8=Ripple, 10=Pulse, 11=Comet
## Auto-start
Install the systemd service to run `kbd init` at boot:
```bash
sudo cp kbd-init.service /etc/systemd/system/
sudo systemctl enable --now kbd-init.service
```
## Udev rule (avoid sudo)
```bash
# /etc/udev/rules.d/99-kbd.rules
KERNEL=="hidraw*", ATTRS{idVendor}=="0b05", ATTRS{idProduct}=="19b6", MODE="0666"
```
## License
MIT
+12
View File
@@ -0,0 +1,12 @@
[Unit]
Description=ASUS TUF A16 keyboard backlight EC mode init
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/kbd init
ExecStartPost=/usr/local/bin/kbd on
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
+170
View File
@@ -0,0 +1,170 @@
/*
* kbd — ASUS TUF FA608 keyboard backlight CLI
* ITE51368 (0B05:19B6) I2C HID control
* License: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <hidapi.h>
#define VID 0x0B05
#define PID 0x19B6
#define SYS_BRIGHT "/sys/class/leds/asus::kbd_backlight/brightness"
#define SYS_RGB "/sys/class/leds/asus::kbd_backlight/kbd_rgb_mode"
#define MAX_BRIGHT 3
/* ── helpers ─────────────────────────────────────────────── */
static void sys_write(const char *path, const char *fmt, ...) {
char buf[128];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
int fd = open(path, O_WRONLY);
if (fd < 0) { fprintf(stderr, "open %s: %s\n", path, strerror(errno)); return; }
write(fd, buf, strlen(buf));
close(fd);
}
static int sys_read_int(const char *path) {
int fd = open(path, O_RDONLY);
if (fd < 0) return -1;
char buf[16] = {0};
read(fd, buf, sizeof(buf)-1);
close(fd);
return atoi(buf);
}
/* ── HID init: send feature report to switch to EC mode ──── */
static int kbd_hid_init(void) {
if (hid_init()) { fprintf(stderr, "hid_init failed\n"); return -1; }
hid_device *dev = hid_open(VID, PID, NULL);
if (!dev) {
fprintf(stderr, "ITE51368 (0B05:19B6) not found\n");
hid_exit();
return -1;
}
unsigned char report[2] = {0x46, 0x01};
int ret = hid_send_feature_report(dev, report, sizeof(report));
hid_close(dev);
hid_exit();
if (ret < 0) {
fprintf(stderr, "feature report failed\n");
return -1;
}
return 0;
}
/* ── RGB helper ──────────────────────────────────────────── */
static void set_rgb(int cmd, int mode, int r, int g, int b, int speed) {
sys_write(SYS_RGB, "%d %d %d %d %d %d", cmd, mode, r, g, b, speed);
}
static int hex2rgb(const char *hex, int *r, int *g, int *b) {
unsigned int val;
if (strlen(hex) != 6 || sscanf(hex, "%x", &val) != 1) return -1;
*r = (val >> 16) & 0xFF;
*g = (val >> 8) & 0xFF;
*b = val & 0xFF;
return 0;
}
/* ── main ────────────────────────────────────────────────── */
static void usage(void) {
printf("kbd — ASUS TUF FA608 keyboard backlight\n\n"
" kbd on|off|toggle brightness\n"
" kbd 0-3 set brightness\n"
" kbd color RRGGBB static color\n"
" kbd breathe RRGGBB [spd] breathing effect (spd: 0-2)\n"
" kbd rainbow [spd] rainbow cycle\n"
" kbd mode N RRGGBB [spd] custom mode 0-12(except 9)\n"
" kbd init switch ITE51368 to EC mode\n");
}
int main(int argc, char **argv) {
if (argc < 2) {
int b = sys_read_int(SYS_BRIGHT);
printf("brightness: %d/%d\n", b >= 0 ? b : -1, MAX_BRIGHT);
return 0;
}
char *cmd = argv[1];
/* brightness */
if (!strcmp(cmd, "on")) { sys_write(SYS_BRIGHT, "%d", MAX_BRIGHT); printf("on\n"); return 0; }
if (!strcmp(cmd, "off")) { sys_write(SYS_BRIGHT, "0"); printf("off\n"); return 0; }
if (!strcmp(cmd, "toggle")) {
int b = sys_read_int(SYS_BRIGHT);
sys_write(SYS_BRIGHT, "%d", b > 0 ? 0 : MAX_BRIGHT);
printf("%s\n", b > 0 ? "off" : "on");
return 0;
}
/* numeric brightness */
char *end;
long val = strtol(cmd, &end, 10);
if (!*end && val >= 0 && val <= MAX_BRIGHT) {
sys_write(SYS_BRIGHT, "%ld", val);
printf("brightness: %ld/%d\n", val, MAX_BRIGHT);
return 0;
}
/* HID init */
if (!strcmp(cmd, "init")) { int r = kbd_hid_init(); printf("%s\n", r ? "FAIL" : "OK"); return r; }
/* color RRGGBB */
if (!strcmp(cmd, "color") && argc >= 3) {
int r, g, b;
if (hex2rgb(argv[2], &r, &g, &b)) { usage(); return 1; }
set_rgb(0, 0, r, g, b, 1);
sys_write(SYS_BRIGHT, "%d", MAX_BRIGHT);
printf("static #%s\n", argv[2]);
return 0;
}
/* breathe RRGGBB [speed] */
if (!strcmp(cmd, "breathe") && argc >= 3) {
int r, g, b, spd = 1;
if (hex2rgb(argv[2], &r, &g, &b)) { usage(); return 1; }
if (argc >= 4) spd = atoi(argv[3]);
set_rgb(0, 1, r, g, b, spd);
sys_write(SYS_BRIGHT, "%d", MAX_BRIGHT);
printf("breathe #%s speed=%d\n", argv[2], spd);
return 0;
}
/* rainbow [speed] */
if (!strcmp(cmd, "rainbow")) {
int spd = (argc >= 3) ? atoi(argv[2]) : 1;
set_rgb(0, 2, 0, 0, 0, spd);
sys_write(SYS_BRIGHT, "%d", MAX_BRIGHT);
printf("rainbow speed=%d\n", spd);
return 0;
}
/* mode N RRGGBB [speed] */
if (!strcmp(cmd, "mode") && argc >= 4) {
int m = atoi(argv[2]);
int r, g, b, spd = 1;
if (hex2rgb(argv[3], &r, &g, &b)) { usage(); return 1; }
if (argc >= 5) spd = atoi(argv[4]);
set_rgb(0, m, r, g, b, spd);
sys_write(SYS_BRIGHT, "%d", MAX_BRIGHT);
printf("mode=%d #%s speed=%d\n", m, argv[3], spd);
return 0;
}
usage();
return 1;
}