commit 947949bd13728aa50cbb280e0c4fe0eddf7d64fe Author: lou Date: Sat Jun 27 15:36:58 2026 +0800 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3739f7c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +kbd +*.o diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..aea2a48 --- /dev/null +++ b/Makefile @@ -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) diff --git a/README.md b/README.md new file mode 100644 index 0000000..af82c53 --- /dev/null +++ b/README.md @@ -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 diff --git a/kbd-init.service b/kbd-init.service new file mode 100644 index 0000000..acdcd3a --- /dev/null +++ b/kbd-init.service @@ -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 diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..836732f --- /dev/null +++ b/src/main.c @@ -0,0 +1,170 @@ +/* + * kbd — ASUS TUF FA608 keyboard backlight CLI + * ITE51368 (0B05:19B6) I2C HID control + * License: MIT + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#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; +}