266 lines
9.1 KiB
C
266 lines
9.1 KiB
C
/* evtool.c — tiny evdev probe tool for R3 Pro II (static MIPS32r2 musl).
|
|
*
|
|
* Subcommands:
|
|
* evtool codes <dev> dump EV bits (types + key codes)
|
|
* evtool dump <dev> [dev...] read + print events (no grab)
|
|
* evtool send <dev> <type> <code> <value> inject one event via write()
|
|
* evtool grab <dev> [dev...] EVIOCGRAB, then read + print
|
|
* evtool fwd <dev> EVIOCGRAB, read, write back every event
|
|
* (forward experiment) + print
|
|
* evtool keys <dev> EVIOCGKEY poll loop; print state changes
|
|
* evtool grabchk <dev> EVIOCGRAB result only (0 / errno)
|
|
*
|
|
* Event struct is fixed 16 bytes on MIPS32: timeval(8) + type(2) + code(2) + value(4).
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <time.h>
|
|
#include <sys/ioctl.h>
|
|
#include <poll.h>
|
|
#include <linux/input.h>
|
|
|
|
#ifndef EVIOCGRAB
|
|
#define EVIOCGRAB _IOW('E', 0x90, int)
|
|
#endif
|
|
#ifndef EVIOCGKEY
|
|
#define EVIOCGKEY(len) _IOC(_IOC_READ, 'E', 0x18, len)
|
|
#endif
|
|
|
|
#define EV_MAXC 0x1f
|
|
#define KEY_MAXC 0x2ff
|
|
#define KEY_WORDS ((KEY_MAXC + 1 + 31) / 32)
|
|
|
|
static unsigned long long now_ms(void)
|
|
{
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return (unsigned long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
|
}
|
|
|
|
static int open_dev(const char *dev, int wr)
|
|
{
|
|
int fd = open(dev, wr ? O_RDWR : O_RDONLY);
|
|
if (fd < 0) { fprintf(stderr, "open %s: %s\n", dev, strerror(errno)); return -1; }
|
|
return fd;
|
|
}
|
|
|
|
static void print_ev(const char *tag, const unsigned char *ev)
|
|
{
|
|
uint16_t type = *(uint16_t *)(ev + 8);
|
|
uint16_t code = *(uint16_t *)(ev + 10);
|
|
int32_t val = *(int32_t *)(ev + 12);
|
|
fprintf(stderr, "%s t=%llu type=%d code=%d val=%d\n",
|
|
tag, now_ms(), type, code, val);
|
|
}
|
|
|
|
/* ---------- codes ---------- */
|
|
static void cmd_codes(const char *dev)
|
|
{
|
|
int fd = open_dev(dev, 0);
|
|
if (fd < 0) return;
|
|
unsigned long evbits[EV_MAXC / 32 + 1];
|
|
memset(evbits, 0, sizeof evbits);
|
|
if (ioctl(fd, EVIOCGBIT(0, sizeof evbits), evbits) < 0) {
|
|
perror("EVIOCGBIT(0)"); return;
|
|
}
|
|
printf("EV bits:");
|
|
for (int i = 0; i <= EV_MAXC; i++)
|
|
if (evbits[i / 32] & (1UL << (i % 32))) printf(" %d", i);
|
|
printf("\n");
|
|
|
|
unsigned long kb[KEY_WORDS];
|
|
memset(kb, 0, sizeof kb);
|
|
if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof kb), kb) >= 0) {
|
|
printf("KEY codes:");
|
|
for (int i = 0; i <= KEY_MAXC; i++)
|
|
if (kb[i / 32] & (1UL << (i % 32))) printf(" %d", i);
|
|
printf("\n");
|
|
} else {
|
|
perror("EVIOCGBIT(EV_KEY)");
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
/* ---------- dump ---------- */
|
|
static void dump_dev(const char *dev, const char *tag)
|
|
{
|
|
int fd = open_dev(dev, 0);
|
|
if (fd < 0) return;
|
|
printf("opened %s as %s\n", dev, tag);
|
|
fflush(stdout);
|
|
struct pollfd pfd = { fd, POLLIN, 0 };
|
|
unsigned char ev[16];
|
|
for (;;) {
|
|
int r = poll(&pfd, 1, -1);
|
|
if (r < 0) { perror("poll"); break; }
|
|
if (pfd.revents & POLLIN) {
|
|
ssize_t n = read(fd, ev, sizeof ev);
|
|
if (n == 16) print_ev(tag, ev);
|
|
else if (n < 0) { perror("read"); break; }
|
|
}
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
static void cmd_dump(int argc, char **argv)
|
|
{
|
|
for (int i = 0; i < argc; i++) {
|
|
pid_t p = fork();
|
|
if (p == 0) { dump_dev(argv[i], argv[i]); _exit(0); }
|
|
}
|
|
for (;;) pause();
|
|
}
|
|
|
|
/* ---------- send ---------- */
|
|
static void cmd_send(const char *dev, int type, int code, int value)
|
|
{
|
|
int fd = open_dev(dev, 1);
|
|
if (fd < 0) return;
|
|
struct input_event ev;
|
|
memset(&ev, 0, sizeof ev);
|
|
ev.type = type; ev.code = code; ev.value = value;
|
|
ssize_t n = write(fd, &ev, sizeof ev);
|
|
printf("write(%s) -> %zd errno=%d (%s)\n", dev, n, errno, strerror(errno));
|
|
close(fd);
|
|
}
|
|
|
|
/* ---------- grab / fwd ---------- */
|
|
static int do_grab(int fd, int on)
|
|
{
|
|
int v = on ? 1 : 0;
|
|
int r = ioctl(fd, EVIOCGRAB, v);
|
|
printf("EVIOCGRAB(%d) -> %d errno=%d (%s)\n", on, r, errno, strerror(errno));
|
|
return r;
|
|
}
|
|
|
|
static void loop_forward(const char *dev, int fwd)
|
|
{
|
|
int fd = open_dev(dev, 1);
|
|
if (fd < 0) return;
|
|
if (do_grab(fd, 1) < 0) return;
|
|
printf("GRABBED %s (fwd=%d), reading...\n", dev, fwd);
|
|
fflush(stdout);
|
|
struct pollfd pfd = { fd, POLLIN, 0 };
|
|
unsigned char ev[16];
|
|
for (;;) {
|
|
int r = poll(&pfd, 1, -1);
|
|
if (r < 0) { perror("poll"); break; }
|
|
if (pfd.revents & POLLIN) {
|
|
ssize_t n = read(fd, ev, sizeof ev);
|
|
if (n != 16) { if (n < 0) perror("read"); continue; }
|
|
print_ev("G", ev);
|
|
if (fwd) {
|
|
ssize_t w = write(fd, ev, sizeof ev);
|
|
if (w != 16) fprintf(stderr, " write-back failed: %s\n", strerror(errno));
|
|
}
|
|
}
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
/* ---------- fwdt: grab + self-inject + forward ---------- */
|
|
/* Sequence: grab <dev>; after wait_ms inject press(code); after +300ms inject
|
|
* release(code); meanwhile forward every read event back to <dev>.
|
|
* Optional 5th arg: total duration ms (0 = run forever).
|
|
* A second reader on <dev> tells us whether write-back reaches non-grabbers. */
|
|
static void loop_fwdt(const char *dev, int code, int wait_ms, int dur_ms)
|
|
{
|
|
int fd = open_dev(dev, 1);
|
|
if (fd < 0) return;
|
|
if (do_grab(fd, 1) < 0) return;
|
|
printf("FWDT grabbed %s code=%d wait=%d dur=%d\n", dev, code, wait_ms, dur_ms);
|
|
fflush(stdout);
|
|
struct pollfd pfd = { fd, POLLIN, 0 };
|
|
unsigned char ev[16];
|
|
unsigned long long t0 = now_ms(), inject_done = 0;
|
|
int phase = 0; /* 0=idle, 1=pressed, 2=released */
|
|
for (;;) {
|
|
unsigned long long t = now_ms();
|
|
if (dur_ms > 0 && t - t0 >= (unsigned long long)dur_ms) break;
|
|
if (!inject_done && t - t0 >= (unsigned long long)wait_ms) {
|
|
struct input_event e; memset(&e, 0, sizeof e);
|
|
e.type = EV_KEY; e.code = code; e.value = 1;
|
|
if (write(fd, &e, sizeof e) != sizeof e) fprintf(stderr, "inject press fail\n");
|
|
printf("== injected PRESS %d\n", code);
|
|
phase = 1; inject_done = 1;
|
|
} else if (phase == 1 && t - t0 >= (unsigned long long)wait_ms + 300) {
|
|
struct input_event e; memset(&e, 0, sizeof e);
|
|
e.type = EV_KEY; e.code = code; e.value = 0;
|
|
if (write(fd, &e, sizeof e) != sizeof e) fprintf(stderr, "inject release fail\n");
|
|
printf("== injected RELEASE %d\n", code);
|
|
phase = 2;
|
|
}
|
|
int r = poll(&pfd, 1, 100);
|
|
if (r < 0) { perror("poll"); break; }
|
|
if (pfd.revents & POLLIN) {
|
|
ssize_t n = read(fd, ev, sizeof ev);
|
|
if (n == 16) {
|
|
print_ev("RD", ev);
|
|
ssize_t w = write(fd, ev, sizeof ev);
|
|
if (w != 16) fprintf(stderr, " WB fail: %s\n", strerror(errno));
|
|
}
|
|
}
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
/* ---------- keys (EVIOCGKEY poll) ---------- */
|
|
static void cmd_keys(const char *dev, int dur_ms)
|
|
{
|
|
int fd = open_dev(dev, 0);
|
|
if (fd < 0) return;
|
|
unsigned long prev[KEY_WORDS], cur[KEY_WORDS];
|
|
memset(prev, 0, sizeof prev);
|
|
printf("EVIOCGKEY poll on %s (dur=%dms)\n", dev, dur_ms);
|
|
fflush(stdout);
|
|
unsigned long long t0 = now_ms();
|
|
for (;;) {
|
|
if (dur_ms > 0 && now_ms() - t0 >= (unsigned long long)dur_ms) break;
|
|
memset(cur, 0, sizeof cur);
|
|
if (ioctl(fd, EVIOCGKEY(sizeof cur), cur) < 0) { perror("EVIOCGKEY"); break; }
|
|
for (int w = 0; w < KEY_WORDS; w++) {
|
|
if (cur[w] != prev[w]) {
|
|
for (int b = 0; b < 32; b++) {
|
|
int cb = (cur[w] >> b) & 1;
|
|
int pb = (prev[w] >> b) & 1;
|
|
if (cb != pb)
|
|
printf("key %d %s\n", w * 32 + b, cb ? "DOWN" : "UP");
|
|
}
|
|
prev[w] = cur[w];
|
|
}
|
|
}
|
|
fflush(stdout);
|
|
usleep(10000); /* 10 ms */
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
setvbuf(stderr, NULL, _IONBF, 0);
|
|
if (argc < 2) {
|
|
fprintf(stderr, "usage: evtool codes|dump|send|grab|fwd|keys|grabchk <args>\n");
|
|
return 1;
|
|
}
|
|
const char *cmd = argv[1];
|
|
if (!strcmp(cmd, "codes") && argc >= 3) { cmd_codes(argv[2]); return 0; }
|
|
if (!strcmp(cmd, "dump") && argc >= 3) { cmd_dump(argc - 2, argv + 2); return 0; }
|
|
if (!strcmp(cmd, "send") && argc >= 6) { cmd_send(argv[2], atoi(argv[3]), atoi(argv[4]), atoi(argv[5])); return 0; }
|
|
if (!strcmp(cmd, "grab") && argc >= 3) { loop_forward(argv[2], 0); return 0; }
|
|
if (!strcmp(cmd, "fwd") && argc >= 3) { loop_forward(argv[2], 1); return 0; }
|
|
if (!strcmp(cmd, "keys") && argc >= 3) { cmd_keys(argv[2], argc >= 4 ? atoi(argv[3]) : 0); return 0; }
|
|
if (!strcmp(cmd, "fwdt") && argc >= 4) { loop_fwdt(argv[2], atoi(argv[3]), argc >= 5 ? atoi(argv[4]) : 1000, argc >= 6 ? atoi(argv[5]) : 0); return 0; }
|
|
if (!strcmp(cmd, "grabchk") && argc >= 3) {
|
|
int fd = open_dev(argv[2], 1);
|
|
if (fd >= 0) { do_grab(fd, 1); do_grab(fd, 0); close(fd); }
|
|
return 0;
|
|
}
|
|
fprintf(stderr, "bad args\n");
|
|
return 1;
|
|
}
|