/* uinput_tap.c — remote touch/key injection for R3 Pro II. * * Modes: * uinput_tap daemon create a virtual goodix-style touch * device + a virtual key device, then * serve commands from the fifo * uinput_tap tap enqueue a tap command * uinput_tap key enqueue a key press+release * uinput_tap seq taps with 300ms gaps * * The virtual devices are created BEFORE hiby_player starts so its * hgl_ts input scanner opens them. * * Static MIPS32r2 musl build: * ~/Project/R3ProII/.tools/zig-mipsel-cc -Os -static uinput_tap.c -o uinput_tap */ #include #include #include #include #include #include #include #include #include #include #define DEV "/dev/uinput" /* ---- input codes (avoid header dependency issues) ---- */ #ifndef EV_SYN #define EV_SYN 0 #define EV_KEY 1 #define EV_REL 2 #define EV_ABS 3 #endif #ifndef ABS_MT_SLOT #define ABS_MT_SLOT 0x2f #define ABS_MT_TOUCH_MAJOR 0x30 #define ABS_MT_POSITION_X 0x35 #define ABS_MT_POSITION_Y 0x36 #define ABS_MT_TRACKING_ID 0x39 #endif #ifndef BTN_TOUCH #define BTN_TOUCH 0x14a #endif #ifndef SYN_REPORT #define SYN_REPORT 0 #endif #ifndef KEY_VOLUMEUP #define KEY_VOLUMEUP 115 #define KEY_VOLUMEDOWN 114 #define KEY_POWER 116 #define KEY_PLAY 207 #define KEY_NEXT 163 #define KEY_PREVIOUS 165 #endif static int ufd = -1; static void emit(int type, int code, int val) { struct input_event ev; memset(&ev, 0, sizeof ev); ev.type = type; ev.code = code; ev.value = val; if (write(ufd, &ev, sizeof ev) != sizeof ev) perror("write event"); } static void syn(void) { emit(EV_SYN, SYN_REPORT, 0); } /* full tap: MT protocol B + legacy ABS_X/ABS_Y + BTN_TOUCH */ static void tap(int x, int y) { /* MT slot */ emit(EV_ABS, ABS_MT_SLOT, 0); emit(EV_ABS, ABS_MT_TRACKING_ID, 7); emit(EV_ABS, ABS_MT_POSITION_X, x); emit(EV_ABS, ABS_MT_POSITION_Y, y); emit(EV_ABS, ABS_MT_TOUCH_MAJOR, 6); /* legacy */ emit(EV_ABS, ABS_X, x); emit(EV_ABS, ABS_Y, y); emit(EV_KEY, BTN_TOUCH, 1); syn(); usleep(60000); emit(EV_ABS, ABS_MT_TRACKING_ID, -1); emit(EV_KEY, BTN_TOUCH, 0); syn(); } static void keytap(int code) { emit(EV_KEY, code, 1); syn(); usleep(80000); emit(EV_KEY, code, 0); syn(); } /* Use the legacy write()-based uinput API (struct uinput_user_dev) which is * supported by all kernels including the Ingenic 4.4.94 SDK (UI_DEV_SETUP * ioctl layout differs across versions -> avoid it). */ /* Ingenic kernel 4.4.94 changed UINPUT_IOCTL_BASE from 'U'(0x55) to 'f'(0x66) */ #define UINPUT_BASE 0x66 #define UI_DEV_CREATE ((0x66u << 8) | 1u) #define UI_DEV_DESTROY ((0x66u << 8) | 2u) #define UI_SET_EVBIT (0x40000000u | (4u << 16) | (0x66u << 8) | 100u) #define UI_SET_KEYBIT (0x40000000u | (4u << 16) | (0x66u << 8) | 101u) #define UI_SET_RELBIT (0x40000000u | (4u << 16) | (0x66u << 8) | 102u) #define UI_SET_ABSBIT (0x40000000u | (4u << 16) | (0x66u << 8) | 103u) struct uinput_user_dev_v44 { char name[80]; struct input_id id; int ff_effects_max; int absmax[64]; int absmin[64]; int absfuzz[64]; int absflat[64]; }; static void set_abs(struct uinput_user_dev_v44 *u, int code, int min, int max) { u->absmin[code] = min; u->absmax[code] = max; } static int dev_create(const char *name, unsigned short vendor, unsigned short product, unsigned short version) { struct uinput_user_dev_v44 u; memset(&u, 0, sizeof u); strncpy(u.name, name, 79); u.id.bustype = BUS_VIRTUAL; u.id.vendor = vendor; u.id.product = product; u.id.version = version; if (write(ufd, &u, sizeof u) != (ssize_t)sizeof u) { perror("write uinput"); return -1; } if (ioctl(ufd, UI_DEV_CREATE) < 0) { perror("UI_DEV_CREATE"); return -1; } return 0; } static int create_touch(const char *name) { ioctl(ufd, UI_SET_EVBIT, EV_KEY); ioctl(ufd, UI_SET_EVBIT, EV_ABS); ioctl(ufd, UI_SET_EVBIT, EV_SYN); ioctl(ufd, UI_SET_KEYBIT, BTN_TOUCH); ioctl(ufd, UI_SET_ABSBIT, ABS_X); ioctl(ufd, UI_SET_ABSBIT, ABS_Y); ioctl(ufd, UI_SET_ABSBIT, ABS_MT_SLOT); ioctl(ufd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR); ioctl(ufd, UI_SET_ABSBIT, ABS_MT_POSITION_X); ioctl(ufd, UI_SET_ABSBIT, ABS_MT_POSITION_Y); ioctl(ufd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID); struct uinput_user_dev_v44 u; memset(&u, 0, sizeof u); strncpy(u.name, name, 79); u.id.bustype = BUS_VIRTUAL; u.id.vendor = 0xdead; u.id.product = 0xbeef; u.id.version = 1; set_abs(&u, ABS_X, 0, 1023); set_abs(&u, ABS_Y, 0, 1023); set_abs(&u, ABS_MT_SLOT, 0, 9); set_abs(&u, ABS_MT_TOUCH_MAJOR, 0, 15); set_abs(&u, ABS_MT_POSITION_X, 0, 1023); set_abs(&u, ABS_MT_POSITION_Y, 0, 1023); set_abs(&u, ABS_MT_TRACKING_ID, 0, 65535); if (write(ufd, &u, sizeof u) != (ssize_t)sizeof u) { perror("write uinput"); return -1; } if (ioctl(ufd, UI_DEV_CREATE) < 0) { perror("UI_DEV_CREATE"); return -1; } return 0; } static int create_keys(const char *name) { ioctl(ufd, UI_SET_EVBIT, EV_KEY); ioctl(ufd, UI_SET_EVBIT, EV_SYN); ioctl(ufd, UI_SET_KEYBIT, KEY_POWER); ioctl(ufd, UI_SET_KEYBIT, KEY_VOLUMEUP); ioctl(ufd, UI_SET_KEYBIT, KEY_VOLUMEDOWN); ioctl(ufd, UI_SET_KEYBIT, KEY_PLAY); ioctl(ufd, UI_SET_KEYBIT, KEY_NEXT); ioctl(ufd, UI_SET_KEYBIT, KEY_PREVIOUS); struct uinput_user_dev_v44 u; memset(&u, 0, sizeof u); strncpy(u.name, name, 79); u.id.bustype = BUS_VIRTUAL; u.id.vendor = 1; u.id.product = 1; u.id.version = 0x100; if (write(ufd, &u, sizeof u) != (ssize_t)sizeof u) { perror("write uinput"); return -1; } if (ioctl(ufd, UI_DEV_CREATE) < 0) { perror("UI_DEV_CREATE"); return -1; } return 0; } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "usage: %s daemon|tap|key|seq ...\n", argv[0]); return 1; } if (!strcmp(argv[1], "scan")) { setvbuf(stdout, NULL, _IONBF, 0); ufd = open(DEV, O_WRONLY | O_NONBLOCK); if (ufd < 0) { perror("open uinput"); return 1; } struct uinput_user_dev_v44 u; memset(&u, 0, sizeof u); strncpy(u.name, "scan-test", 79); u.id.bustype = BUS_VIRTUAL; u.id.vendor = 5; u.id.product = 5; u.id.version = 1; write(ufd, &u, sizeof u); /* scan all 16-bit cmd cores (type<<8|nr), report non-EINVAL/ENOTTY */ int found = 0; for (int t = 0; t < 256 && found < 40; t++) { for (int n = 0; n < 256 && found < 40; n++) { unsigned int cmd = (t << 8) | n; /* no dir/size bits */ errno = 0; int rc = ioctl(ufd, cmd, 0); if (rc == 0 || (errno != EINVAL && errno != ENOTTY)) { printf("cmd=0x%04x (t=%02x n=%02x) rc=%d errno=%d\n", cmd, t, n, rc, errno); found++; } } } printf("done found=%d\n", found); /* deep scan nr 0x7d/0x7e with valid pointer + size bits */ int val = 0; for (int nr = 0x7d; nr <= 0x7e; nr++) { for (int sz = 0; sz < 128; sz++) { unsigned int cmd = 0x40000000u | ((unsigned int)sz << 16) | (0x66u << 8) | (unsigned int)nr; errno = 0; int rc = ioctl(ufd, cmd, &val); if (rc == 0 || (errno != EINVAL && errno != ENOTTY)) { printf("deep cmd=0x%08x nr=%02x sz=%d rc=%d errno=%d\n", cmd, nr, sz, rc, errno); } } } /* also try _IOR (0x80000000) */ for (int nr = 0x7d; nr <= 0x7e; nr++) { for (int sz = 0; sz < 128; sz++) { unsigned int cmd = 0x80000000u | ((unsigned int)sz << 16) | (0x66u << 8) | (unsigned int)nr; errno = 0; int rc = ioctl(ufd, cmd, &val); if (rc == 0 || (errno != EINVAL && errno != ENOTTY)) { printf("read cmd=0x%08x nr=%02x sz=%d rc=%d errno=%d\n", cmd, nr, sz, rc, errno); } } } sleep(1); return 0; } if (!strcmp(argv[1], "verbose2")) { setvbuf(stdout, NULL, _IONBF, 0); ufd = open(DEV, O_WRONLY | O_NONBLOCK); if (ufd < 0) { perror("open uinput"); return 1; } printf("fd=%d\n", ufd); int r; r = ioctl(ufd, UI_SET_EVBIT, EV_KEY); printf("SET_EVBIT KEY rc=%d errno=%d\n", r, errno); r = ioctl(ufd, UI_SET_EVBIT, EV_ABS); printf("SET_EVBIT ABS rc=%d errno=%d\n", r, errno); r = ioctl(ufd, UI_SET_EVBIT, EV_SYN); printf("SET_EVBIT SYN rc=%d errno=%d\n", r, errno); r = ioctl(ufd, UI_SET_KEYBIT, BTN_TOUCH); printf("SET_KEYBIT TOUCH rc=%d errno=%d\n", r, errno); r = ioctl(ufd, UI_SET_ABSBIT, ABS_X); printf("SET_ABSBIT X rc=%d errno=%d\n", r, errno); struct uinput_user_dev_v44 u; memset(&u, 0, sizeof u); strncpy(u.name, "v2-test", 79); u.id.bustype = BUS_VIRTUAL; u.id.vendor = 6; u.id.product = 6; u.id.version = 1; set_abs(&u, ABS_X, 0, 1023); ssize_t wr = write(ufd, &u, sizeof u); printf("write=%zd errno=%d\n", wr, errno); r = ioctl(ufd, UI_DEV_CREATE); printf("DEV_CREATE rc=%d errno=%d\n", r, errno); printf("sleeping 10s...\n"); sleep(10); return 0; } if (!strcmp(argv[1], "inject")) { /* 直接 write /dev/input/event1 (goodix-ts): evdev 支持 write 注入。 * 事件序列与 daemon 的 tap() 完全一致 (MT 协议 B + legacy ABS_X/Y + * BTN_TOUCH),与真实 goodix 驱动一致,hgl_ts 解析最可靠。 */ setvbuf(stdout, NULL, _IONBF, 0); const char *devpath = argc > 4 ? argv[4] : "/dev/input/event1"; int fd = open(devpath, O_WRONLY); if (fd < 0) { perror("open evdev"); return 1; } struct input_event ev; int x = atoi(argv[2]), y = atoi(argv[3]); ssize_t wr; #define W(type_, code_, val_) do { \ memset(&ev, 0, sizeof ev); \ ev.type = (type_); ev.code = (code_); ev.value = (val_); \ wr = write(fd, &ev, sizeof ev); \ printf("wr %s rc=%zd errno=%d\n", #code_, wr, errno); \ } while (0) W(EV_ABS, ABS_MT_SLOT, 0); W(EV_ABS, ABS_MT_TRACKING_ID, 7); W(EV_ABS, ABS_MT_POSITION_X, x); W(EV_ABS, ABS_MT_POSITION_Y, y); W(EV_ABS, ABS_MT_TOUCH_MAJOR, 6); W(EV_ABS, ABS_X, x); W(EV_ABS, ABS_Y, y); W(EV_KEY, BTN_TOUCH, 1); W(EV_SYN, SYN_REPORT, 0); usleep(80000); W(EV_ABS, ABS_MT_TRACKING_ID, -1); W(EV_KEY, BTN_TOUCH, 0); W(EV_SYN, SYN_REPORT, 0); printf("injected tap %d,%d to %s\n", x, y, devpath); close(fd); return 0; } if (!strcmp(argv[1], "alt")) { setvbuf(stdout, NULL, _IONBF, 0); ufd = open(DEV, O_WRONLY | O_NONBLOCK); if (ufd < 0) { perror("open uinput"); return 1; } struct uinput_user_dev_v44 u; memset(&u, 0, sizeof u); strncpy(u.name, "alt-test", 79); u.id.bustype = BUS_VIRTUAL; u.id.vendor = 3; u.id.product = 3; u.id.version = 1; /* 厂商命令 nr=125/126 (base 0x66): 试带各种结构 */ int r; r = ioctl(ufd, 0x667d, &u); printf("ioctl 0x667d(user_dev) rc=%d errno=%d\n", r, errno); r = ioctl(ufd, 0x667e, &u); printf("ioctl 0x667e(user_dev) rc=%d errno=%d\n", r, errno); r = ioctl(ufd, 0x6601, 0); printf("ioctl 0x6601(create) rc=%d errno=%d\n", r, errno); if (r == 0) { printf("created? sleeping\n"); sleep(4); } return 0; } if (!strcmp(argv[1], "brute")) { ufd = open(DEV, O_WRONLY | O_NONBLOCK); if (ufd < 0) { perror("open uinput"); return 1; } struct uinput_user_dev_v44 u; memset(&u, 0, sizeof u); strncpy(u.name, "brute-test", 79); u.id.bustype = BUS_VIRTUAL; u.id.vendor = 7; u.id.product = 7; u.id.version = 1; ssize_t wr = write(ufd, &u, sizeof u); printf("write=%zd errno=%d\n", wr, errno); /* brute UI_DEV_CREATE = _IO(base,1) = (base<<8)|1 */ for (int b = 0; b < 256; b++) { unsigned long req = (unsigned long)((b << 8) | 1); /* brute keeps generic */ errno = 0; int rc = ioctl(ufd, req, 0); if (rc == 0) { printf("FOUND base=0x%02x (%c) rc=0\n", b, b>=32&&b<127?b:'?'); break; } if (errno != EINVAL && errno != ENOTTY) printf("base=0x%02x rc=%d errno=%d\n", b, rc, errno); } sleep(2); return 0; } if (!strcmp(argv[1], "verbose")) { ufd = open(DEV, O_WRONLY | O_NONBLOCK); if (ufd < 0) { perror("open uinput"); return 1; } printf("fd=%d\n", ufd); int r; r = ioctl(ufd, UI_SET_EVBIT, EV_KEY); printf("SET_EVBIT KEY rc=%d errno=%d\n", r, errno); r = ioctl(ufd, UI_SET_EVBIT, EV_SYN); printf("SET_EVBIT SYN rc=%d errno=%d\n", r, errno); r = ioctl(ufd, UI_SET_KEYBIT, KEY_POWER); printf("SET_KEYBIT PWR rc=%d errno=%d\n", r, errno); struct uinput_user_dev_v44 u; memset(&u, 0, sizeof u); strncpy(u.name, "verbose-test", 79); u.id.bustype = BUS_VIRTUAL; u.id.vendor = 8; u.id.product = 8; u.id.version = 1; ssize_t wr = write(ufd, &u, sizeof u); printf("write rc=%zd errno=%d (size %zu)\n", wr, errno, sizeof u); r = ioctl(ufd, UI_DEV_CREATE); printf("DEV_CREATE rc=%d errno=%d\n", r, errno); if (r == 0) { printf("OK sleeping\n"); sleep(3); } return 0; } if (!strcmp(argv[1], "minimal")) { ufd = open(DEV, O_WRONLY | O_NONBLOCK); if (ufd < 0) { perror("open uinput"); return 1; } ioctl(ufd, UI_SET_EVBIT, EV_KEY); ioctl(ufd, UI_SET_EVBIT, EV_SYN); ioctl(ufd, UI_SET_KEYBIT, KEY_POWER); struct uinput_user_dev_v44 u; memset(&u, 0, sizeof u); strncpy(u.name, "minimal-test", 79); u.id.bustype = BUS_VIRTUAL; u.id.vendor = 9; u.id.product = 9; u.id.version = 1; ssize_t wr = write(ufd, &u, sizeof u); printf("write=%zd (expect %zu)\n", wr, sizeof u); int rc = ioctl(ufd, UI_DEV_CREATE); printf("create rc=%d errno=%d\n", rc, errno); if (rc == 0) { printf("device created, sleeping 5s\n"); sleep(5); } return 0; } if (!strcmp(argv[1], "daemon")) { if (argc < 3) { fprintf(stderr, "daemon \n"); return 1; } ufd = open(DEV, O_WRONLY | O_NONBLOCK); if (ufd < 0) { perror("open uinput"); return 1; } if (create_touch("goodix-ts-virt") < 0) return 1; printf("touch device created\n"); if (create_keys("md-gpio-keys-virt") < 0) return 1; printf("key device created\n"); fflush(stdout); /* serve fifo */ unlink(argv[2]); if (mkfifo(argv[2], 0600) < 0) { perror("mkfifo"); return 1; } for (;;) { int f = open(argv[2], O_RDONLY); if (f < 0) { perror("open fifo"); sleep(1); continue; } char line[128]; int n; while ((n = read(f, line, sizeof line - 1)) > 0) { line[n] = 0; int x, y, code; if (sscanf(line, "tap %d %d", &x, &y) == 2) { printf("tap %d %d\n", x, y); fflush(stdout); tap(x, y); } else if (sscanf(line, "key %d", &code) == 1) { printf("key %d\n", code); fflush(stdout); keytap(code); } } close(f); } } else if (!strcmp(argv[1], "tap")) { int x = atoi(argv[3]), y = atoi(argv[4]); int f = open(argv[2], O_WRONLY); if (f < 0) { perror("fifo"); return 1; } char buf[64]; int n = snprintf(buf, sizeof buf, "tap %d %d\n", x, y); write(f, buf, n); close(f); return 0; } else if (!strcmp(argv[1], "key")) { int code = atoi(argv[3]); int f = open(argv[2], O_WRONLY); if (f < 0) { perror("fifo"); return 1; } char buf[64]; int n = snprintf(buf, sizeof buf, "key %d\n", code); write(f, buf, n); close(f); return 0; } else if (!strcmp(argv[1], "seq")) { /* seq x1,y1;x2,y2;... */ int f = open(argv[2], O_WRONLY); if (f < 0) { perror("fifo"); return 1; } char *p = argv[3]; char buf[64]; while (*p) { int x, y; if (sscanf(p, "%d,%d", &x, &y) == 2) { int n = snprintf(buf, sizeof buf, "tap %d %d\n", x, y); write(f, buf, n); usleep(350000); } while (*p && *p != ';') p++; if (*p == ';') p++; } close(f); return 0; } return 0; }