chiark / gitweb /
realtime: movpos: debug output: exclude some more stuff from the default movpos output
[trains.git] / hostside / evdev-manip.c
1 /*
2  * evdev-manip [<options> <device> ...]
3  *
4  *  modes:
5  *     --dump-raw      default
6  *     --redact        print redacted version
7  *                      output is   <path|label> <value> <str> <str>...
8  *                      where <str>... identifies the button, axis, etc.
9  *
10  *  global options:
11  *     --stdin-monitor      quit if stdin becomes readable
12  *
13  *  redaction mode options:
14  *
15  *     --redaction <str>... --suppress  do not print these
16  *     --redaction <str>... --show      print these normally
17  *
18  *        Longest matching <str>... applies.  Later identical
19  *        <str>... paths override earlier ones.
20  *
21  *     --new-redactions     resets everything to default (which is --show)
22  *
23  *        Every device specified after --new-redactions gets the new
24  *        set of redactions, which includes even redactions specified
25  *        _after_ the device but _before_ the next --new-redactions.
26  *        It is not possible to selectively edit the redactions list;
27  *        devices which need different redaction lists need them
28  *        respecifying.
29  *
30  *  per-device options (applies only to next device):
31  *    --label LABEL         use LABEL instead of path in redacted output
32  *
33  *  per-device options (apply to all subsequent):
34  *     --evdev                     subsequent devices are evdev
35  *     --hiddev                    subsequent devices are hiddev
36  *
37  *  per-evdev options (apply to all subsequent):
38  *     --[no-]grab                 --nograb is default
39  *     --expect-sysfs /sys/class/input/inputX/eventY/dev
40  *                        ^^^^^^^^^^^^^^^^^^^^
41  *                          this part is in /proc/bus/usb/devices
42  *                           and can thus be specified by caller
43  *                          for evdev devices
44  *
45  *     evdev <str>... are type and code, each split up into
46  *     prefix and tail, eg EV_KEY KEY_EQUAL becomes EV KEY KEY EQUAL.
47  *     If the type or code is not found in our number-to-string
48  *     tables, the respective two <strs> are 0x and the value in hex.
49  *
50  *  per-hiddev options (apply to all subsequent):
51  *     --elide|show-unchanged      --elide-unchanged is default
52  *
53  *     hiddev <str>... are application and field.  In each case, page
54  *     and usage, both in hex, both at least two digits with 0x in
55  *     from of page only.  This matches what is shown in the USB HID
56  *     usage tables specification which can be found at
57  *     http://www.usb.org/developers/hidpage/
58  */
59
60 #include "common.h"
61
62 #include <oop-read.h>
63 #include <poll.h>
64 #include <sys/fcntl.h>
65 #include <search.h>
66
67 #include <linux/input.h>
68 #include <linux/hiddev.h>
69
70 typedef struct InputEventState InputEventState;
71 #include "input-codes.h" /* not really a header */
72
73 typedef struct Device Device;
74 typedef struct KindInfo KindInfo;
75 typedef struct ModeInfo ModeInfo;
76 typedef const ModeInfo *Mode;
77 typedef const KindInfo *Kind;
78
79 typedef enum { RA_Show, RA_IntermediateNode, RA_Suppress } RedactionAction;
80
81 typedef struct RedactionNode {
82   const char *str;
83   void *children; /* tsearch tree of RedactionNodes */
84   RedactionAction act;
85 } RedactionNode;
86
87 struct ModeInfo {
88   void (*evdev_event)(Device *d, const struct input_event *ie);
89   void (*evdev_readable)(Device *d);
90   void (*evdev_synch)(Device *d, struct timeval tv);
91   void (*hiddev_event)(Device *d, const struct hiddev_usage_ref *ur);
92   int (*hiddev_xflags)(void);
93   void (*opened)(Device *d);
94   void (*redacted)(Device *d, int nstrs, const char *strs[nstrs], int value);
95   void (*died)(Device *d, int revents, int readr, int readc, int e)
96        __attribute__((noreturn));
97   void (*mainloop)(void);
98 };
99
100 struct KindInfo {
101   void (*prepare)(Device*);
102   void (*readable)(Device*);
103 };
104
105 typedef struct {
106   int elide;
107   RedactionNode redactions; /* root; .str is 0 and irrelevant */
108 } DeviceOptions;
109
110 typedef struct {
111   struct hiddev_field_info fi;
112   int *lastvalues;
113 } HiddevField;
114
115 struct Device {
116   char *path;
117   const char *label;
118   int fd;
119   const KindInfo *kind;
120   DeviceOptions opts;
121   union {
122     struct {
123       void *froot;
124       HiddevField *fbuf;
125     } hiddev;
126   } forkind;
127 };
128
129 /*---------- globals ----------*/
130
131 /* command line options */
132 static Mode mode;
133 static Kind kind;
134 static int grab, stdinmonitor;
135 static const char *expect_sysfs, *label;
136 static DeviceOptions dopts;
137
138 static int ndevices;
139 static Device *devices;
140
141 /*---------- generally useful ----------*/
142
143 static void pr_hex(unsigned long value) { printf("%#lx",value); }
144
145 #define PR_TABLE_STR(tab, val) (pr_table_str(iesis_##tab, ien_##tab, (val)))
146 static void pr_table_str(const InputEventStringInfo *strings, int nstrings,
147                          unsigned long value) {
148   const InputEventStringInfo *string;
149   if (value > nstrings) { pr_hex(value); return; }
150   string= &strings[value];
151   if (!string->prefix) { pr_hex(value); return; }
152   printf("%s_%s", string->prefix, string->main);
153 }
154
155 static void pr_time(struct timeval tv) {
156   printf("%ju.%06d", (uintmax_t)tv.tv_sec, (int)tv.tv_usec);
157 }
158
159 static void mread(Device *d, void *buf, size_t l) {
160   char *p;
161   int r, remain;
162
163   for (p=buf, remain=l;
164        remain;
165        p+=r, remain-=r) {
166     r= read(d->fd, p, remain);
167     if (r<=0) { mode->died(d, POLLIN, r, -1, errno); abort(); }
168     assert(r <= remain);
169   }
170 }
171
172 static void dump_vpv(unsigned vendor, unsigned product, unsigned version) {
173   printf(" vendor %#x product %#x version %#x",
174          vendor, product, version);
175 }
176 #define DUMP_VPV(t) (dump_vpv((t).vendor, (t).product, (t).version))
177
178 /*---------- evdev kind ----------*/
179
180 static void evdev_dump(Device *d, const struct input_event *ie) {
181   const InputEventTypeInfo *t;
182
183   printf("evdev ");
184   pr_time(ie->time);
185
186   printf(" ");
187   PR_TABLE_STR(ev, ie->type);
188
189   printf(" ");
190   if (ie->type >= IETIN) {
191     t= 0;
192   } else {
193     t= &ietis[ie->type];
194     if (!t->strings) t= 0;
195   }
196   if (t) pr_table_str(t->strings, t->nstrings, ie->code);
197   else pr_hex(ie->code);
198
199   printf(" ");
200   switch (ie->type) {
201   case EV_ABS:
202   case EV_REL:
203     printf("%ld",(long)ie->value);
204     break;
205   default:
206     printf("%lx",(unsigned long)ie->value);
207     break;
208   }
209   printf("\n");
210 }
211
212 #define MAXTABSTRH 20
213 static void tab_redact(const InputEventStringInfo *strings, int nstrings,
214                        unsigned long value, char hexbuf[MAXTABSTRH],
215                        const char *sb[2]) {
216   const InputEventStringInfo *string;
217   if (value < nstrings &&
218       (string= &strings[value],
219        string->prefix)) {
220     sb[0]= string->prefix;
221     sb[1]= string->main;
222   } else {
223     snprintf(hexbuf,sizeof(hexbuf),"%lx",value);
224     sb[0]= "0x";
225     sb[1]= hexbuf;
226   }
227 }
228
229 static void evdev_redact(Device *d, const struct input_event *ie) {
230   const InputEventTypeInfo *t;
231   char sbh_type[MAXTABSTRH];
232   char sbh_code[MAXTABSTRH];
233   const char *strs[4];
234   
235   tab_redact(iesis_ev, ien_ev, ie->type, sbh_type, &strs[0]);
236
237   if (ie->type >= IETIN) {
238     t= 0;
239   } else {
240     t= &ietis[ie->type];
241     if (!t->strings) t= 0;
242   }
243   tab_redact(t ? t->strings : 0,
244              t ? t->nstrings : 0,
245              ie->code, sbh_code, &strs[2]);
246
247   mode->redacted(d, 4,strs, ie->value);
248 }
249
250 static void evdev_readable(Device *d) {
251   struct input_event ie;
252
253   if (mode->evdev_readable) mode->evdev_readable(d);
254
255   for (;;) {
256     mread(d, &ie, sizeof(ie));
257     if (ie.type == EV_SYN) {
258       if (mode->evdev_synch) mode->evdev_synch(d, ie.time);
259       break;
260     }
261
262     mode->evdev_event(d, &ie);
263   }
264 }
265
266 static void evdev_readable_dump(Device *d) {
267   printf("report-from device %s\n",d->path);
268 }
269 static void evdev_synch_dump(Device *d, struct timeval tv) {
270   printf("synch ");
271   pr_time(tv);
272   printf("\n");
273 }
274
275 static void check_expect_sysfs(int fd, const char *path, const char *efn) {
276   char buf[50], *ep;
277   unsigned long maj, min;
278   struct stat stab;
279   FILE *sysfs;
280   int r;
281     
282   r= fstat(fd, &stab);  if (r) diee("%s: fstat failed", path);
283   if (!S_ISCHR(stab.st_mode)) die("%s: not a character device", path);
284     
285   sysfs= fopen(efn,"r");
286   if (!sysfs) diee("%s: failed to open sysfs %s", path, efn);
287   if (!fgets(buf,sizeof(buf)-1,sysfs)) {
288     if (ferror(sysfs)) diee("%s: failed to read sysfs %s", path, efn);
289     assert(feof(sysfs)); die("%s: eof on sysfs %s", path, efn);
290   }
291   buf[sizeof(buf)-1]= 0;
292   errno=0; maj=strtoul(buf,&ep,0);
293   if (errno || *ep!=':') die("%s: bad major number or no colon in sysfs"
294                              " dev file %s", path, efn);
295   errno=0; min=strtoul(ep+1,&ep,0);
296   if (errno || *ep!='\n') die("%s: bad minor number or no colon in sysfs"
297                               " dev file %s", path, efn);
298
299   if (maj != major(stab.st_rdev) || min != minor(stab.st_rdev))
300     die("%s: is %lu:%lu, expected %lu:%lu", path,
301         (unsigned long)major(stab.st_rdev),
302         (unsigned long)minor(stab.st_rdev),
303         maj, min);
304
305   if (fclose(sysfs)) die("%s: failed to close sysfs %s", path, efn);
306 }
307
308 static void evdev_prepare(Device *d) {
309   int r;
310   struct input_id iid;
311   
312   if (expect_sysfs) {
313     check_expect_sysfs(d->fd, d->path, expect_sysfs);
314     expect_sysfs= 0;
315   }
316
317   r= ioctl(d->fd, EVIOCGID, &iid);
318   if (r) diee("%s: failed to get id",d->path);
319
320   mode->opened(d);
321   printf(" bustype ");
322   PR_TABLE_STR(bus, iid.bustype);
323   DUMP_VPV(iid);
324   putchar('\n');
325   mflushstdout();
326
327   if (grab) {
328     r= ioctl(d->fd, EVIOCGRAB, 1);
329     if (r) diee("%s: failed to grab",d->path);
330   }
331 }
332
333 static const KindInfo kind_evdev= { evdev_prepare, evdev_readable };
334
335 /*---------- hiddev kind ----------*/
336
337 static int hiddev_f_compar(const void *a_v, const void *b_v) {
338   const HiddevField *a=a_v, *b=b_v;
339   /* these are all unsigned 0..0xffff so the differences fit nicely */
340   return (int)a->fi.report_type - (int)b->fi.report_type ? :
341          (int)a->fi.report_id   - (int)b->fi.report_id   ? :
342          (int)a->fi.field_index - (int)b->fi.field_index;
343 }
344
345 static HiddevField *hiddev_get_f(Device *d,
346                                  const struct hiddev_usage_ref *ur) {
347   HiddevField *f;
348   void **fvp;
349   int r;
350   
351   if (ur->field_index == HID_FIELD_INDEX_NONE)
352     return 0;
353
354   if (!d->forkind.hiddev.fbuf) {
355     d->forkind.hiddev.fbuf= mmalloc(sizeof(*d->forkind.hiddev.fbuf));
356   }
357
358   memset(&d->forkind.hiddev.fbuf->fi,0x55,sizeof(d->forkind.hiddev.fbuf->fi));
359   d->forkind.hiddev.fbuf->fi.report_type= ur->report_type;
360   d->forkind.hiddev.fbuf->fi.report_id=   ur->report_id;
361   d->forkind.hiddev.fbuf->fi.field_index= ur->field_index;
362   fvp= tsearch(d->forkind.hiddev.fbuf,
363                &d->forkind.hiddev.froot,
364                hiddev_f_compar);
365   if (!fvp) diee("tsearch hiddev type/id/index");
366   f= *fvp;
367
368   if (f == d->forkind.hiddev.fbuf) {
369     d->forkind.hiddev.fbuf= 0;
370     
371     r= ioctl(d->fd, HIDIOCGFIELDINFO, &f->fi);
372     if (r) diee("%s: ioctl HIDIOCGFIELDINFO %#x %#x %#x", d->path,
373                 f->fi.report_type, f->fi.report_id, f->fi.field_index);
374
375     size_t sz= sizeof(*f->lastvalues) * f->fi.maxusage;
376     f->lastvalues= mmalloc(sz);
377     memset(f->lastvalues,0,sz);
378   }
379   assert(ur->usage_index < f->fi.maxusage);
380
381   return f;
382 }
383
384 static int hiddev_elide(Device *d, const struct hiddev_usage_ref *ur,
385                         HiddevField *f) {
386   if (!f)
387     return 0;
388   
389   if (!d->opts.elide)
390     return 0;
391
392   unsigned relevant_flags= f->fi.flags &
393     (HID_FIELD_RELATIVE|HID_FIELD_BUFFERED_BYTE);
394
395   if (relevant_flags == HID_FIELD_RELATIVE &&
396       !ur->value) {
397     return 1;
398   }
399   if (relevant_flags == 0) {
400     if (ur->value == f->lastvalues[ur->usage_index])
401       return 1;
402     f->lastvalues[ur->usage_index]= ur->value;
403   }
404   return 0;
405 }
406
407 static void hiddev_dump(Device *d, const struct hiddev_usage_ref *ur) {
408   HiddevField *f;
409
410   f= hiddev_get_f(d, ur);
411
412   if (hiddev_elide(d, ur, f))
413     return;
414
415   printf("hiddev type %04x id %04x", ur->report_type, ur->report_id);
416   if (ur->field_index == HID_FIELD_INDEX_NONE) {
417     printf(" field index NONE\n");
418     return;
419   }
420
421   printf(" field index %04x"
422          " usage index %04x code %04x value %08lx ",
423          ur->field_index,
424          ur->usage_index, ur->usage_code,
425          (unsigned long)ur->value);
426
427   printf(" maxusage %04x flags %04x"
428          " physical %04x %08lx..%08lx"
429          " logical %04x %08lx..%08lx"
430          " application %04x"
431          " unit %04x exponent %04x",
432          f->fi.maxusage, f->fi.flags,
433          f->fi.physical, (unsigned long)f->fi.physical_minimum,
434          (unsigned long)f->fi.physical_maximum,
435          f->fi.logical, (unsigned long)f->fi.logical_minimum,
436          (unsigned long)f->fi.logical_maximum,
437          f->fi.application,
438          f->fi.unit, f->fi.unit_exponent);
439
440   putchar('\n');
441 }
442
443 static void hiddev_redact_pageusage(unsigned long code,
444                                     char sb_buf[2][7],
445                                     const char *strs[2]) {
446   assert(code <= 0xffffffffUL);
447   sprintf(sb_buf[0], "%#04lx", code >> 16);
448   sprintf(sb_buf[1], "%02lx", code & 0xffff);
449   strs[0]= sb_buf[0];
450   strs[1]= sb_buf[1];
451 }
452
453 static void hiddev_redact(Device *d, const struct hiddev_usage_ref *ur) {
454   HiddevField *f;
455   char sb_app[2][7], sb_usage[2][7];
456   const char *strs[4];
457
458   if (ur->field_index == HID_FIELD_INDEX_NONE)
459     return;
460
461   f= hiddev_get_f(d, ur);
462   if (hiddev_elide(d, ur, f))
463     return;
464
465   hiddev_redact_pageusage(f->fi.application, sb_app,   &strs[0]);
466   hiddev_redact_pageusage(ur->usage_code,    sb_usage, &strs[2]);
467
468   mode->redacted(d, 4,strs, ur->value);
469 }
470
471 static void hiddev_readable(Device *d) {
472   struct hiddev_usage_ref ur;
473   mread(d, &ur, sizeof(ur));
474   mode->hiddev_event(d, &ur);
475 }
476
477 static void hiddev_prepare(Device *d) {
478   int r, flags;
479   struct hiddev_devinfo di;
480
481   flags= HIDDEV_FLAG_UREF;
482   if (mode->hiddev_xflags) flags |= mode->hiddev_xflags();
483   r= ioctl(d->fd, HIDIOCSFLAG, &flags);
484   if (r) diee("hiddev %s: ioctl HIDIOCSFLAG", d->path);
485
486   r= ioctl(d->fd, HIDIOCGDEVINFO, &di);
487   if (r) diee("hiddev %s: ioctl HIDIOCGDEVINFO", d->path);
488
489   mode->opened(d);
490   printf(" bustype ");
491   PR_TABLE_STR(bus, di.bustype);
492   printf(" bus %d dev %d if %d", di.busnum, di.devnum, di.ifnum);
493   DUMP_VPV(di);
494   printf(" napplications %d\n", di.num_applications);
495
496   d->forkind.hiddev.froot= 0;
497   d->forkind.hiddev.fbuf= 0;
498 }
499
500 static int hiddev_xflags_dump(void) { return HIDDEV_FLAG_REPORT; }
501
502 static const KindInfo kind_hiddev= { hiddev_prepare, hiddev_readable };
503
504 /*---------- mode dump ----------*/
505
506 static void dump_died(Device *d, int revents, int readr, int readc, int e)
507      __attribute__((noreturn));
508 static void dump_died(Device *d, int revents, int readr, int readc, int e) {
509   printf("device-terminated %s %#x ", d->path, revents);
510   if (readr<0) printf("err %s", strerror(e));
511   else if (readr==0) printf("eof");
512   else printf("%#x",readc);
513   printf("\n");
514   mflushstdout();
515   exit(0);
516 }
517
518 static void dump_opened(Device *d) {
519   printf("device %s", d->path);
520 }
521
522 static void mainloop(void) {
523   struct pollfd *polls;
524   int i, r, npolls;
525
526   npolls= ndevices + stdinmonitor;
527   polls= mmalloc(sizeof(*polls)*npolls);
528   for (i=0; i<ndevices; i++) {
529     polls[i].fd= devices[i].fd;
530     polls[i].events= POLLIN;
531   }
532   if (stdinmonitor) {
533     polls[ndevices].fd= 0;
534     polls[ndevices].events= POLLIN;
535   }
536
537   for (;;) {
538     for (i=0; i<npolls; i++)
539       polls[i].revents= 0;
540
541     r= poll(polls,npolls,-1);
542     if (r==-1) {
543       if (errno==EINTR) continue;
544       diee("poll failed");
545     }
546     assert(r>0);
547
548     if (stdinmonitor) {
549       if (polls[ndevices].revents) {
550         printf("quitting-stdin-polled %#x\n", polls[ndevices].revents);
551         exit(0);
552       }
553     }
554
555     for (i=0; i<ndevices; i++) {
556       if (polls[i].revents & ~POLLIN) {
557         unsigned char dummy;
558         r= oop_fd_nonblock(polls[i].fd, 1);
559         if (r) diee("nonblock %s during poll unepxected %#x",
560                     devices[i].path, polls[i].revents);
561         r= read(polls[i].fd, &dummy,1);
562         mode->died(&devices[i], polls[i].revents, r, dummy, errno);
563         abort();
564       }
565       if (polls[i].revents) {
566         Device *d= &devices[i];
567         d->kind->readable(d);
568         mflushstdout();
569       }
570     }
571   }
572 }
573
574 static const ModeInfo mode_dump= {
575   evdev_dump, evdev_readable_dump, evdev_synch_dump,
576   hiddev_dump, hiddev_xflags_dump,
577   dump_opened, 0, dump_died, mainloop
578 };
579
580 /*---------- mode redact ----------*/
581
582 static int redaction_node_compar(const void *a_v, const void *b_v) {
583   const RedactionNode *a=a_v, *b=b_v;
584   return strcmp(a->str, b->str);
585 }
586
587 static RedactionNode *redact_find_node(int nstrs, const char *strs[nstrs]) {
588   RedactionNode key, *lastfound, *search;
589   void **rn_vp;
590
591   lastfound= search= &dopts.redactions;
592   for (;;) {
593     if (!nstrs) return lastfound;
594     key.str= strs[0];
595     rn_vp= tfind(&key, &search->children, redaction_node_compar);
596     if (!rn_vp) return lastfound;
597     search= *rn_vp;
598     if (search->act != RA_IntermediateNode)
599       lastfound= search;
600     strs++;
601     nstrs--;
602   }
603 }
604
605 static void redact_redacted(Device *d, int nstrs, const char *strs[nstrs],
606                             int value) {
607   int i;
608   RedactionNode *rn;
609
610   rn= redact_find_node(nstrs, strs);
611   switch (rn->act) {
612   case RA_Show: break;
613   case RA_Suppress: return;
614   default: abort();
615   }
616
617   printf("%s %d", d->label, value);
618   for (i=0; i<nstrs; i++)
619     printf(" %s", strs[i]);
620   putchar('\n');
621 }
622
623 static void redact_opened(Device *d) {
624   printf("%s opened", d->label);
625 }
626
627 static const ModeInfo mode_redact= {
628   evdev_redact, 0, 0,
629   hiddev_redact, 0,
630   redact_opened, redact_redacted, dump_died, mainloop
631 };
632
633 static void redaction(const char ***argv) {
634   RedactionNode *path, *newnode;
635   void **searched;
636   const char *arg;
637
638   path= &dopts.redactions;
639   for (;;) {
640     arg= *++(*argv);
641     if (!arg) badusage("missing str or action for --redaction");
642     if (arg[0]=='-') break;
643     newnode= mmalloc(sizeof(*newnode));
644     newnode->str= arg;
645     searched= tsearch(newnode, &path->children, redaction_node_compar);
646     if (!searched) diee("allocate new redaction node");
647     if (*searched == newnode) {
648       newnode->children= 0;
649       newnode->act= RA_IntermediateNode;
650       path= newnode;
651     } else {
652       free(newnode);
653       path= *searched;
654     }
655   }
656   if (!strcmp(arg,"--suppress")) {
657     path->act= RA_Suppress;
658   } else if (!strcmp(arg,"--show")) {
659     path->act= RA_Show;
660   } else {
661     badusage("unknown or missing action for --redaction");
662   }
663 }
664
665 /*---------- main program ----------*/
666
667 static void getdevice(const char *path) {
668   Device *d;
669   ndevices++;
670   devices= mrealloc(devices, sizeof(*devices)*ndevices);
671   d= &devices[ndevices-1];
672
673   d->path= mstrdup(path);
674   d->fd= open(path, O_RDONLY);  if (d->fd<0) diee("%s: failed to open",path);
675   d->kind= kind;
676   d->opts= dopts;
677
678   if (label) {
679     d->label= label;
680     label= 0;
681   } else {
682     d->label= d->path;
683   }
684
685   kind->prepare(d);
686 }
687
688 int main(int argc, const char **argv) {
689   const char *arg;
690
691   mode= &mode_dump;
692   kind= &kind_evdev;
693   dopts.elide= 1;
694
695   while ((arg= *++argv)) {
696     if (arg[0] != '-') {
697       getdevice(arg);
698     }
699     else if (!strcmp(arg,"--expect-sysfs")) {
700       if (!(expect_sysfs= *++argv)) badusage("missing arg for --expect-sysfs");
701     }
702     else if (!strcmp(arg,"--label")) {
703       if (!(label= *++argv)) badusage("missing arg for --expect-sysfs");
704     }
705     else if (!strcmp(arg,"--redaction")) {
706       redaction(&argv);
707     }
708     else if (!strcmp(arg,"--new-redactions")) {
709       dopts.redactions.children= 0;
710       dopts.redactions.act= RA_Show;
711     }
712     else if (!strcmp(arg,"--dump-raw")) { mode= &mode_dump; }
713     else if (!strcmp(arg,"--redact")) { mode= &mode_redact; }
714     else if (!strcmp(arg,"--evdev")) { kind= &kind_evdev; }
715     else if (!strcmp(arg,"--hiddev")) { kind= &kind_hiddev; }
716     else if (!strcmp(arg,"--grab")) { grab= 1; }
717     else if (!strcmp(arg,"--no-grab")) { grab= 0; }
718     else if (!strcmp(arg,"--show-unchanged")) { dopts.elide= 0; }
719     else if (!strcmp(arg,"--elide-unchanged")) { dopts.elide= 1; }
720     else if (!strcmp(arg,"--stdin-monitor")) { stdinmonitor= 1; }
721     else badusage("unknown option");
722   }
723   mode->mainloop();
724   return 0;
725 }
726
727 const char *progname= "evdev-manip";
728 void die_hook(void) { }
729 void die_vprintf_hook(const char *fmt, va_list al) { }