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:
@@ -0,0 +1,2 @@
|
||||
kbd
|
||||
*.o
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user