/*
* evdev-manip [<options> <device> ...]
+ *
* modes:
- * --dump default
- * per-device options:
- * --[no-]grab --nograb is default
+ * --dump-raw default
+ * --redact print redacted version
+ * output is <path|label> <value> <str> <str>...
+ * where <str>... identifies the button, axis, etc.
+ *
+ * global options:
+ * --stdin-monitor quit if stdin becomes readable
+ *
+ * per-device options (applies only to next device):
+ * --label LABEL use LABEL instead of path in redacted output
+ *
+ * per-device options (apply to all subsequent):
+ * --elide|show-unchanged --elide-unchanged is default
+ * --evdev subsequent devices are evdev
+ * --hiddev subsequent devices are hiddev
+ * hiddev <str> are application and field
+ * both in hex, page<<16 | usage
+ *
+ * per-evdev options (apply to all subsequent):
+ * --[no-]grab --nograb is default
* --expect-sysfs /sys/class/input/inputX/eventY/dev
* ^^^^^^^^^^^^^^^^^^^^
* this part is in /proc/bus/usb/devices
* and can thus be specified by caller
- *
- * --stdin-monitor quit if stdin becomes readable
+ * for evdev devices
*/
#include "common.h"
#include <oop-read.h>
#include <poll.h>
#include <sys/fcntl.h>
+#include <search.h>
#include <linux/input.h>
+#include <linux/hiddev.h>
typedef struct InputEventState InputEventState;
#include "input-codes.h" /* not really a header */
-typedef struct {
- char *path;
- int fd;
-} Device;
-
-typedef struct {
- void (*event)(Device *d, const struct input_event *ie);
+typedef struct Device Device;
+typedef struct KindInfo KindInfo;
+typedef struct ModeInfo ModeInfo;
+typedef const ModeInfo *Mode;
+typedef const KindInfo *Kind;
+
+struct ModeInfo {
+ void (*evdev_event)(Device *d, const struct input_event *ie);
+ void (*evdev_readable)(Device *d);
+ void (*evdev_synch)(Device *d, struct timeval tv);
+ void (*hiddev_event)(Device *d, const struct hiddev_usage_ref *ur);
+ int (*hiddev_xflags)(void);
+ void (*redacted)(Device *d, int nstrs, const char *strs[nstrs], int value);
void (*died)(Device *d, int revents, int readr, int readc, int e)
__attribute__((noreturn));
void (*mainloop)(void);
-} ModeInfo;
+};
-typedef const ModeInfo *Mode;
+struct KindInfo {
+ void (*prepare)(Device*);
+ void (*readable)(Device*);
+};
-static int ndevices;
-static Device *devices;
+typedef struct {
+ int elide;
+} DeviceFlags;
+typedef struct {
+ struct hiddev_field_info fi;
+ int *lastvalues;
+} HiddevField;
+
+struct Device {
+ char *path;
+ const char *label;
+ int fd;
+ const KindInfo *kind;
+ DeviceFlags flags;
+ union {
+ struct {
+ void *froot;
+ HiddevField *fbuf;
+ } hiddev;
+ } forkind;
+};
+
+/*---------- globals ----------*/
+
+/* command line options */
static Mode mode;
+static Kind kind;
static int grab, stdinmonitor;
-static const char *expect_sysfs;
+static const char *expect_sysfs, *label;
+static DeviceFlags dflags= { 1 };
+
+static int ndevices;
+static Device *devices;
+
+/*---------- generally useful ----------*/
static void pr_hex(unsigned long value) { printf("%#lx",value); }
printf("%ju.%06d", (uintmax_t)tv.tv_sec, (int)tv.tv_usec);
}
-static void dump_event(Device *d, const struct input_event *ie) {
+static void mread(Device *d, void *buf, size_t l) {
+ char *p;
+ int r, remain;
+
+ for (p=buf, remain=l;
+ remain;
+ p+=r, remain-=r) {
+ r= read(d->fd, p, remain);
+ if (r<=0) { mode->died(d, POLLIN, r, -1, errno); abort(); }
+ assert(r <= remain);
+ }
+}
+
+static void dump_vpv(unsigned vendor, unsigned product, unsigned version) {
+ printf(" vendor %#x product %#x version %#x",
+ vendor, product, version);
+}
+#define DUMP_VPV(t) (dump_vpv((t).vendor, (t).product, (t).version))
+
+/*---------- evdev kind ----------*/
+
+static void evdev_dump(Device *d, const struct input_event *ie) {
const InputEventTypeInfo *t;
- printf("event ");
+ printf("evdev ");
pr_time(ie->time);
printf(" ");
printf("\n");
}
-static void process_device(Device *d) {
+#define MAXTABSTRH 20
+static void tab_redact(const InputEventStringInfo *strings, int nstrings,
+ unsigned long value, char hexbuf[MAXTABSTRH],
+ const char *sb[2]) {
+ const InputEventStringInfo *string;
+ if (value <= nstrings &&
+ (string= &strings[value],
+ string->prefix)) {
+ sb[0]= string->prefix;
+ sb[1]= string->main;
+ } else {
+ snprintf(hexbuf,sizeof(hexbuf),"%lx",value);
+ sb[0]= "0x";
+ sb[1]= hexbuf;
+ }
+}
+
+static void evdev_redact(Device *d, const struct input_event *ie) {
+ const InputEventTypeInfo *t;
+ char sbh_type[MAXTABSTRH];
+ char sbh_code[MAXTABSTRH];
+ const char *strs[4];
+
+ tab_redact(iesis_ev, ien_ev, ie->type, sbh_type, &strs[0]);
+
+ if (ie->type >= IETIN) {
+ t= 0;
+ } else {
+ t= &ietis[ie->type];
+ if (!t->strings) t= 0;
+ }
+ tab_redact(t ? t->strings : 0,
+ t ? t->nstrings : 0,
+ ie->code, sbh_code, &strs[2]);
+
+ mode->redacted(d, 4,strs, ie->value);
+}
+
+static void evdev_readable(Device *d) {
struct input_event ie;
- int r, remain;
- char *p;
- printf("report-from device %s\n",d->path);
+ if (mode->evdev_readable) mode->evdev_readable(d);
for (;;) {
- for (p=(void*)&ie, remain=sizeof(ie);
- remain;
- p+=r, remain-=r) {
- r= read(d->fd, &ie, remain);
- if (r<=0) { mode->died(d, POLLIN, r, -1, errno); abort(); }
- assert(r <= remain);
- }
+ mread(d, &ie, sizeof(ie));
if (ie.type == EV_SYN) {
- printf("synch ");
- pr_time(ie.time);
- printf("\n");
+ if (mode->evdev_synch) mode->evdev_synch(d, ie.time);
break;
}
- mode->event(d, &ie);
+ mode->evdev_event(d, &ie);
+ }
+}
+
+static void evdev_readable_dump(Device *d) {
+ printf("report-from device %s\n",d->path);
+}
+static void evdev_synch_dump(Device *d, struct timeval tv) {
+ printf("synch ");
+ pr_time(tv);
+ printf("\n");
+}
+
+static void check_expect_sysfs(int fd, const char *path, const char *efn) {
+ char buf[50], *ep;
+ unsigned long maj, min;
+ struct stat stab;
+ FILE *sysfs;
+ int r;
+
+ r= fstat(fd, &stab); if (r) diee("%s: fstat failed", path);
+ if (!S_ISCHR(stab.st_mode)) die("%s: not a character device", path);
+
+ sysfs= fopen(efn,"r");
+ if (!sysfs) diee("%s: failed to open sysfs %s", path, efn);
+ if (!fgets(buf,sizeof(buf)-1,sysfs)) {
+ if (ferror(sysfs)) diee("%s: failed to read sysfs %s", path, efn);
+ assert(feof(sysfs)); die("%s: eof on sysfs %s", path, efn);
}
+ buf[sizeof(buf)-1]= 0;
+ errno=0; maj=strtoul(buf,&ep,0);
+ if (errno || *ep!=':') die("%s: bad major number or no colon in sysfs"
+ " dev file %s", path, efn);
+ errno=0; min=strtoul(ep+1,&ep,0);
+ if (errno || *ep!='\n') die("%s: bad minor number or no colon in sysfs"
+ " dev file %s", path, efn);
+
+ if (maj != major(stab.st_rdev) || min != minor(stab.st_rdev))
+ die("%s: is %lu:%lu, expected %lu:%lu", path,
+ (unsigned long)major(stab.st_rdev),
+ (unsigned long)minor(stab.st_rdev),
+ maj, min);
+
+ if (fclose(sysfs)) die("%s: failed to close sysfs %s", path, efn);
+}
+
+static void evdev_prepare(Device *d) {
+ int r;
+ struct input_id iid;
+
+ if (expect_sysfs) {
+ check_expect_sysfs(d->fd, d->path, expect_sysfs);
+ expect_sysfs= 0;
+ }
+
+ r= ioctl(d->fd, EVIOCGID, &iid);
+ if (r) diee("%s: failed to get id",d->path);
+
+ printf("device %s bustype ", d->path);
+ PR_TABLE_STR(bus, iid.bustype);
+ DUMP_VPV(iid);
+ putchar('\n');
mflushstdout();
+
+ if (grab) {
+ r= ioctl(d->fd, EVIOCGRAB, 1);
+ if (r) diee("%s: failed to grab",d->path);
+ }
+}
+
+static const KindInfo kind_evdev= { evdev_prepare, evdev_readable };
+
+/*---------- hiddev kind ----------*/
+
+static int hiddev_f_compar(const void *a_v, const void *b_v) {
+ const HiddevField *a=a_v, *b=b_v;
+ /* these are all unsigned 0..0xffff so the differences fit nicely */
+ return (int)a->fi.report_type - (int)b->fi.report_type ? :
+ (int)a->fi.report_id - (int)b->fi.report_id ? :
+ (int)a->fi.field_index - (int)b->fi.field_index;
+}
+
+static HiddevField *hiddev_get_f(Device *d,
+ const struct hiddev_usage_ref *ur) {
+ HiddevField *f;
+ void **fvp;
+ int r;
+
+ if (ur->field_index == HID_FIELD_INDEX_NONE)
+ return 0;
+
+ if (!d->forkind.hiddev.fbuf) {
+ d->forkind.hiddev.fbuf= mmalloc(sizeof(*d->forkind.hiddev.fbuf));
+ }
+
+ memset(&d->forkind.hiddev.fbuf->fi,0x55,sizeof(d->forkind.hiddev.fbuf->fi));
+ d->forkind.hiddev.fbuf->fi.report_type= ur->report_type;
+ d->forkind.hiddev.fbuf->fi.report_id= ur->report_id;
+ d->forkind.hiddev.fbuf->fi.field_index= ur->field_index;
+ fvp= tsearch(d->forkind.hiddev.fbuf,
+ &d->forkind.hiddev.froot,
+ hiddev_f_compar);
+ if (!fvp) diee("tsearch hiddev type/id/index");
+ f= *fvp;
+
+ if (f == d->forkind.hiddev.fbuf) {
+ d->forkind.hiddev.fbuf= 0;
+
+ r= ioctl(d->fd, HIDIOCGFIELDINFO, &f->fi);
+ if (r) diee("%s: ioctl HIDIOCGFIELDINFO %#x %#x %#x", d->path,
+ f->fi.report_type, f->fi.report_id, f->fi.field_index);
+
+ size_t sz= sizeof(*f->lastvalues) * f->fi.maxusage;
+ f->lastvalues= mmalloc(sz);
+ memset(f->lastvalues,0,sz);
+ }
+ assert(ur->usage_index < f->fi.maxusage);
+
+ return f;
+}
+
+static int hiddev_elide(Device *d, const struct hiddev_usage_ref *ur,
+ HiddevField *f) {
+ if (!f)
+ return 0;
+
+ if (!d->flags.elide)
+ return 0;
+
+ unsigned relevant_flags= f->fi.flags &
+ (HID_FIELD_RELATIVE|HID_FIELD_BUFFERED_BYTE);
+
+ if (relevant_flags == HID_FIELD_RELATIVE &&
+ !ur->value) {
+ return 1;
+ }
+ if (relevant_flags == 0) {
+ if (ur->value == f->lastvalues[ur->usage_index])
+ return 1;
+ f->lastvalues[ur->usage_index]= ur->value;
+ }
+ return 0;
+}
+
+static void hiddev_dump(Device *d, const struct hiddev_usage_ref *ur) {
+ HiddevField *f;
+
+ f= hiddev_get_f(d, ur);
+
+ if (hiddev_elide(d, ur, f))
+ return;
+
+ printf("hiddev type %04x id %04x", ur->report_type, ur->report_id);
+ if (ur->field_index == HID_FIELD_INDEX_NONE) {
+ printf(" field index NONE\n");
+ return;
+ }
+
+ printf(" field index %04x"
+ " usage index %04x code %04x value %08lx ",
+ ur->field_index,
+ ur->usage_index, ur->usage_code,
+ (unsigned long)ur->value);
+
+ printf(" maxusage %04x flags %04x"
+ " physical %04x %08lx..%08lx"
+ " logical %04x %08lx..%08lx"
+ " application %04x"
+ " unit %04x exponent %04x",
+ f->fi.maxusage, f->fi.flags,
+ f->fi.physical, (unsigned long)f->fi.physical_minimum,
+ (unsigned long)f->fi.physical_maximum,
+ f->fi.logical, (unsigned long)f->fi.logical_minimum,
+ (unsigned long)f->fi.logical_maximum,
+ f->fi.application,
+ f->fi.unit, f->fi.unit_exponent);
+
+ putchar('\n');
+}
+
+static void hiddev_redact(Device *d, const struct hiddev_usage_ref *ur) {
+ HiddevField *f;
+ char sb_app[9], sb_usage[9];
+ const char *strs[2];
+
+ if (ur->field_index == HID_FIELD_INDEX_NONE)
+ return;
+
+ f= hiddev_get_f(d, ur);
+ if (hiddev_elide(d, ur, f))
+ return;
+
+ assert(f->fi.application <= 0xffffffffUL);
+ assert(ur->usage_code <= 0xffffffffUL);
+ sprintf(sb_app, "%lx", (unsigned long)f->fi.application);
+ sprintf(sb_usage, "%lx", (unsigned long)ur->usage_code);
+
+ strs[0]= sb_app;
+ strs[1]= sb_usage;
+ mode->redacted(d, 2,strs, ur->value);
+}
+
+static void hiddev_readable(Device *d) {
+ struct hiddev_usage_ref ur;
+ mread(d, &ur, sizeof(ur));
+ mode->hiddev_event(d, &ur);
+}
+
+static void hiddev_prepare(Device *d) {
+ int r, flags;
+ struct hiddev_devinfo di;
+
+ flags= HIDDEV_FLAG_UREF;
+ if (mode->hiddev_xflags) flags |= mode->hiddev_xflags();
+ r= ioctl(d->fd, HIDIOCSFLAG, &flags);
+ if (r) diee("hiddev %s: ioctl HIDIOCSFLAG", d->path);
+
+ r= ioctl(d->fd, HIDIOCGDEVINFO, &di);
+ if (r) diee("hiddev %s: ioctl HIDIOCGDEVINFO", d->path);
+
+ printf("device %s bustype ", d->path);
+ PR_TABLE_STR(bus, di.bustype);
+ printf(" bus %d dev %d if %d", di.busnum, di.devnum, di.ifnum);
+ DUMP_VPV(di);
+ printf(" napplications %d\n", di.num_applications);
+
+ d->forkind.hiddev.froot= 0;
+ d->forkind.hiddev.fbuf= 0;
}
+static int hiddev_xflags_dump(void) { return HIDDEV_FLAG_REPORT; }
+
+static const KindInfo kind_hiddev= { hiddev_prepare, hiddev_readable };
+
+/*---------- mode dump ----------*/
+
static void dump_died(Device *d, int revents, int readr, int readc, int e)
__attribute__((noreturn));
static void dump_died(Device *d, int revents, int readr, int readc, int e) {
mode->died(&devices[i], polls[i].revents, r, dummy, errno);
abort();
}
- if (polls[i].revents)
- process_device(&devices[i]);
+ if (polls[i].revents) {
+ Device *d= &devices[i];
+ d->kind->readable(d);
+ mflushstdout();
+ }
}
}
}
-static const ModeInfo mode_dump= { dump_event, dump_died, mainloop };
-
-static void check_expect_sysfs(int fd, const char *path, const char *efn) {
- char buf[50], *ep;
- unsigned long maj, min;
- struct stat stab;
- FILE *sysfs;
- int r;
-
- r= fstat(fd, &stab); if (r) diee("%s: fstat failed", path);
- if (!S_ISCHR(stab.st_mode)) die("%s: not a character device", path);
-
- sysfs= fopen(efn,"r");
- if (!sysfs) diee("%s: failed to open sysfs %s", path, efn);
- if (!fgets(buf,sizeof(buf)-1,sysfs)) {
- if (ferror(sysfs)) diee("%s: failed to read sysfs %s", path, efn);
- assert(feof(sysfs)); die("%s: eof on sysfs %s", path, efn);
- }
- buf[sizeof(buf)-1]= 0;
- errno=0; maj=strtoul(buf,&ep,0);
- if (errno || *ep!=':') die("%s: bad major number or no colon in sysfs"
- " dev file %s", path, efn);
- errno=0; min=strtoul(ep+1,&ep,0);
- if (errno || *ep!='\n') die("%s: bad minor number or no colon in sysfs"
- " dev file %s", path, efn);
+static const ModeInfo mode_dump= {
+ evdev_dump, evdev_readable_dump, evdev_synch_dump,
+ hiddev_dump, hiddev_xflags_dump,
+ 0, dump_died, mainloop
+};
+
+/*---------- mode redact ----------*/
+
+static void redact_redacted(Device *d, int nstrs, const char *strs[nstrs],
+ int value) {
+ int i;
+
+ printf("%s %d", d->label, value);
+ for (i=0; i<nstrs; i++)
+ printf(" %s", strs[i]);
+ putchar('\n');
+}
- if (maj != major(stab.st_rdev) || min != minor(stab.st_rdev))
- die("%s: is %lu:%lu, expected %lu:%lu", path,
- (unsigned long)major(stab.st_rdev),
- (unsigned long)minor(stab.st_rdev),
- maj, min);
+static const ModeInfo mode_redact= {
+ evdev_redact, 0, 0,
+ hiddev_redact, 0,
+ redact_redacted, dump_died, mainloop
+};
- if (fclose(sysfs)) die("%s: failed to close sysfs %s", path, efn);
-}
+/*---------- main program ----------*/
static void getdevice(const char *path) {
- int r;
- struct input_id iid;
Device *d;
ndevices++;
devices= mrealloc(devices, sizeof(*devices)*ndevices);
d->path= mstrdup(path);
d->fd= open(path, O_RDONLY); if (d->fd<0) diee("%s: failed to open",path);
+ d->kind= kind;
+ d->flags= dflags;
- if (expect_sysfs) {
- check_expect_sysfs(d->fd, path, expect_sysfs);
- expect_sysfs= 0;
+ if (label) {
+ d->label= label;
+ label= 0;
+ } else {
+ d->label= d->path;
}
- r= ioctl(d->fd, EVIOCGID, &iid); if (r) diee("%s: failed to get id",path);
- printf("device %s bustype ", path);
- PR_TABLE_STR(bus, iid.bustype);
- printf(" vendor %#x product %#x version %#x\n",
- iid.vendor, iid.product, iid.version);
- mflushstdout();
-
- if (grab)
- r= ioctl(d->fd, EVIOCGRAB, 1); if (r) diee("%s: failed to grab",path);
+ kind->prepare(d);
}
int main(int argc, const char **argv) {
const char *arg;
mode= &mode_dump;
+ kind= &kind_evdev;
while ((arg= *++argv)) {
if (arg[0] != '-') {
else if (!strcmp(arg,"--expect-sysfs")) {
if (!(expect_sysfs= *++argv)) badusage("missing arg for --expect-sysfs");
}
- else if (!strcmp(arg,"--dump")) { mode= &mode_dump; }
+ else if (!strcmp(arg,"--label")) {
+ if (!(label= *++argv)) badusage("missing arg for --expect-sysfs");
+ }
+ else if (!strcmp(arg,"--dump-raw")) { mode= &mode_dump; }
+ else if (!strcmp(arg,"--redact")) { mode= &mode_redact; }
+ else if (!strcmp(arg,"--evdev")) { kind= &kind_evdev; }
+ else if (!strcmp(arg,"--hiddev")) { kind= &kind_hiddev; }
else if (!strcmp(arg,"--grab")) { grab= 1; }
else if (!strcmp(arg,"--no-grab")) { grab= 0; }
+ else if (!strcmp(arg,"--show-unchanged")) { dflags.elide= 0; }
+ else if (!strcmp(arg,"--elide-unchanged")) { dflags.elide= 1; }
else if (!strcmp(arg,"--stdin-monitor")) { stdinmonitor= 1; }
else badusage("unknown option");
}