usbhidaction.c revision 171101
1113288Smdodd/* $NetBSD: usbhidaction.c,v 1.8 2002/06/11 06:06:21 itojun Exp $ */ 2113288Smdodd/* $FreeBSD: head/usr.bin/usbhidaction/usbhidaction.c 171101 2007-06-30 03:58:01Z imp $ */ 3113288Smdodd 4113288Smdodd/* 5113288Smdodd * Copyright (c) 2000, 2002 The NetBSD Foundation, Inc. 6113288Smdodd * All rights reserved. 7113288Smdodd * 8113288Smdodd * This code is derived from software contributed to The NetBSD Foundation 9113288Smdodd * by Lennart Augustsson <lennart@augustsson.net>. 10113288Smdodd * 11113288Smdodd * Redistribution and use in source and binary forms, with or without 12113288Smdodd * modification, are permitted provided that the following conditions 13113288Smdodd * are met: 14113288Smdodd * 1. Redistributions of source code must retain the above copyright 15113288Smdodd * notice, this list of conditions and the following disclaimer. 16113288Smdodd * 2. Redistributions in binary form must reproduce the above copyright 17113288Smdodd * notice, this list of conditions and the following disclaimer in the 18113288Smdodd * documentation and/or other materials provided with the distribution. 19113288Smdodd * 3. All advertising materials mentioning features or use of this software 20113288Smdodd * must display the following acknowledgement: 21113288Smdodd * This product includes software developed by the NetBSD 22113288Smdodd * Foundation, Inc. and its contributors. 23113288Smdodd * 4. Neither the name of The NetBSD Foundation nor the names of its 24113288Smdodd * contributors may be used to endorse or promote products derived 25113288Smdodd * from this software without specific prior written permission. 26113288Smdodd * 27113288Smdodd * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 28113288Smdodd * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 29113288Smdodd * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 30113288Smdodd * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 31113288Smdodd * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 32113288Smdodd * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33113288Smdodd * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34113288Smdodd * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 35113288Smdodd * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 36113288Smdodd * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37113288Smdodd * POSSIBILITY OF SUCH DAMAGE. 38113288Smdodd */ 39113288Smdodd 40113288Smdodd#include <stdio.h> 41113288Smdodd#include <stdlib.h> 42113288Smdodd#include <string.h> 43113288Smdodd#include <ctype.h> 44113288Smdodd#include <err.h> 45113288Smdodd#include <fcntl.h> 46113288Smdodd#include <limits.h> 47113288Smdodd#include <unistd.h> 48113288Smdodd#include <sys/types.h> 49113288Smdodd#include <sys/ioctl.h> 50113288Smdodd#include <dev/usb/usb.h> 51113288Smdodd#include <dev/usb/usbhid.h> 52113288Smdodd#include <usbhid.h> 53113288Smdodd#include <syslog.h> 54113288Smdodd#include <signal.h> 55113288Smdodd#include <errno.h> 56113288Smdodd#include <sys/stat.h> 57113288Smdodd 58113288Smdoddstatic int verbose = 0; 59113288Smdoddstatic int isdemon = 0; 60113288Smdoddstatic int reparse = 1; 61126774Sdwmalonestatic const char * pidfile = "/var/run/usbaction.pid"; 62113288Smdodd 63113288Smdoddstruct command { 64113288Smdodd struct command *next; 65113288Smdodd int line; 66113288Smdodd 67113288Smdodd struct hid_item item; 68113288Smdodd int value; 69113309Smdodd int lastseen; 70113309Smdodd int lastused; 71113309Smdodd int debounce; 72113288Smdodd char anyvalue; 73113288Smdodd char *name; 74113288Smdodd char *action; 75113288Smdodd}; 76113288Smdoddstruct command *commands; 77113288Smdodd 78113288Smdodd#define SIZE 4000 79113288Smdodd 80113288Smdoddvoid usage(void); 81113288Smdoddstruct command *parse_conf(const char *, report_desc_t, int, int); 82113288Smdoddvoid docmd(struct command *, int, const char *, int, char **); 83113288Smdoddvoid freecommands(struct command *); 84113288Smdodd 85113288Smdoddstatic void 86126774Sdwmalonesighup(int sig __unused) 87113288Smdodd{ 88113288Smdodd reparse = 1; 89113288Smdodd} 90113288Smdodd 91113288Smdoddint 92113288Smdoddmain(int argc, char **argv) 93113288Smdodd{ 94113288Smdodd const char *conf = NULL; 95113288Smdodd const char *dev = NULL; 96171101Simp const char *table = NULL; 97126774Sdwmalone int fd, fp, ch, n, val, i; 98126774Sdwmalone size_t sz, sz1; 99113309Smdodd int demon, ignore, dieearly; 100113288Smdodd report_desc_t repd; 101113288Smdodd char buf[100]; 102113288Smdodd char devnamebuf[PATH_MAX]; 103113288Smdodd struct command *cmd; 104113288Smdodd int reportid; 105113288Smdodd 106113288Smdodd demon = 1; 107113288Smdodd ignore = 0; 108113309Smdodd dieearly = 0; 109171101Simp while ((ch = getopt(argc, argv, "c:def:ip:t:v")) != -1) { 110113288Smdodd switch(ch) { 111113288Smdodd case 'c': 112113288Smdodd conf = optarg; 113113288Smdodd break; 114113288Smdodd case 'd': 115113288Smdodd demon ^= 1; 116113288Smdodd break; 117113309Smdodd case 'e': 118113309Smdodd dieearly = 1; 119113309Smdodd break; 120113288Smdodd case 'i': 121113288Smdodd ignore++; 122113288Smdodd break; 123113288Smdodd case 'f': 124113288Smdodd dev = optarg; 125113288Smdodd break; 126113288Smdodd case 'p': 127113288Smdodd pidfile = optarg; 128113288Smdodd break; 129171101Simp case 't': 130171101Simp table = optarg; 131171101Simp break; 132113288Smdodd case 'v': 133113288Smdodd demon = 0; 134113288Smdodd verbose++; 135113288Smdodd break; 136113288Smdodd case '?': 137113288Smdodd default: 138113288Smdodd usage(); 139113288Smdodd } 140113288Smdodd } 141113288Smdodd argc -= optind; 142113288Smdodd argv += optind; 143113288Smdodd 144113288Smdodd if (conf == NULL || dev == NULL) 145113288Smdodd usage(); 146113288Smdodd 147171101Simp hid_init(table); 148113288Smdodd 149113288Smdodd if (dev[0] != '/') { 150113288Smdodd snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s", 151113288Smdodd isdigit(dev[0]) ? "uhid" : "", dev); 152113288Smdodd dev = devnamebuf; 153113288Smdodd } 154113288Smdodd 155113288Smdodd fd = open(dev, O_RDWR); 156113288Smdodd if (fd < 0) 157113288Smdodd err(1, "%s", dev); 158113288Smdodd if (ioctl(fd, USB_GET_REPORT_ID, &reportid) < 0) 159113288Smdodd reportid = -1; 160113288Smdodd repd = hid_get_report_desc(fd); 161113288Smdodd if (repd == NULL) 162113288Smdodd err(1, "hid_get_report_desc() failed"); 163113288Smdodd 164113288Smdodd commands = parse_conf(conf, repd, reportid, ignore); 165113288Smdodd 166126774Sdwmalone sz = (size_t)hid_report_size(repd, hid_input, reportid); 167113288Smdodd 168113288Smdodd if (verbose) 169126774Sdwmalone printf("report size %zu\n", sz); 170113288Smdodd if (sz > sizeof buf) 171113288Smdodd errx(1, "report too large"); 172113288Smdodd 173113288Smdodd (void)signal(SIGHUP, sighup); 174113288Smdodd 175113288Smdodd if (demon) { 176113288Smdodd fp = open(pidfile, O_WRONLY|O_CREAT, S_IRUSR|S_IRGRP|S_IROTH); 177113288Smdodd if (fp >= 0) { 178126774Sdwmalone sz1 = snprintf(buf, sizeof buf, "%d\n", getpid()); 179126774Sdwmalone if (sz1 > sizeof buf) 180126774Sdwmalone sz1 = sizeof buf; 181126774Sdwmalone write(fp, buf, sz1); 182113288Smdodd close(fp); 183113288Smdodd } else 184113288Smdodd err(1, "%s", pidfile); 185113309Smdodd if (daemon(0, 0) < 0) 186113309Smdodd err(1, "daemon()"); 187113288Smdodd isdemon = 1; 188113288Smdodd } 189113288Smdodd 190113288Smdodd for(;;) { 191113288Smdodd n = read(fd, buf, sz); 192113288Smdodd if (verbose > 2) { 193113288Smdodd printf("read %d bytes:", n); 194113288Smdodd for (i = 0; i < n; i++) 195113288Smdodd printf(" %02x", buf[i]); 196113288Smdodd printf("\n"); 197113288Smdodd } 198113288Smdodd if (n < 0) { 199113288Smdodd if (verbose) 200113288Smdodd err(1, "read"); 201113288Smdodd else 202113288Smdodd exit(1); 203113288Smdodd } 204113288Smdodd#if 0 205113288Smdodd if (n != sz) { 206113288Smdodd err(2, "read size"); 207113288Smdodd } 208113288Smdodd#endif 209113288Smdodd for (cmd = commands; cmd; cmd = cmd->next) { 210113288Smdodd val = hid_get_data(buf, &cmd->item); 211113309Smdodd if (cmd->value != val && cmd->anyvalue == 0) 212113309Smdodd goto next; 213113309Smdodd if ((cmd->debounce == 0) || 214113328Smdodd ((cmd->debounce == 1) && ((cmd->lastseen == -1) || 215113309Smdodd (cmd->lastseen != val)))) { 216113288Smdodd docmd(cmd, val, dev, argc, argv); 217113309Smdodd goto next; 218113309Smdodd } 219113309Smdodd if ((cmd->debounce > 1) && 220113309Smdodd ((cmd->lastused == -1) || 221113309Smdodd (abs(cmd->lastused - val) >= cmd->debounce))) { 222113309Smdodd docmd(cmd, val, dev, argc, argv); 223113309Smdodd cmd->lastused = val; 224113309Smdodd goto next; 225113309Smdodd } 226113309Smdoddnext: 227113309Smdodd cmd->lastseen = val; 228113288Smdodd } 229113309Smdodd 230113309Smdodd if (dieearly) 231113309Smdodd exit(0); 232113309Smdodd 233113288Smdodd if (reparse) { 234113288Smdodd struct command *cmds = 235113288Smdodd parse_conf(conf, repd, reportid, ignore); 236113288Smdodd if (cmds) { 237113288Smdodd freecommands(commands); 238113288Smdodd commands = cmds; 239113288Smdodd } 240113288Smdodd reparse = 0; 241113288Smdodd } 242113288Smdodd } 243113288Smdodd 244113288Smdodd exit(0); 245113288Smdodd} 246113288Smdodd 247113288Smdoddvoid 248113288Smdoddusage(void) 249113288Smdodd{ 250113288Smdodd 251113309Smdodd fprintf(stderr, "Usage: %s [-deiv] -c config_file -f hid_dev " 252171101Simp "[-p pidfile] [-t tablefile]\n", getprogname()); 253113288Smdodd exit(1); 254113288Smdodd} 255113288Smdodd 256113288Smdoddstatic int 257113288Smdoddpeek(FILE *f) 258113288Smdodd{ 259113288Smdodd int c; 260113288Smdodd 261113288Smdodd c = getc(f); 262113288Smdodd if (c != EOF) 263113288Smdodd ungetc(c, f); 264113288Smdodd return c; 265113288Smdodd} 266113288Smdodd 267113288Smdoddstruct command * 268113288Smdoddparse_conf(const char *conf, report_desc_t repd, int reportid, int ignore) 269113288Smdodd{ 270113288Smdodd FILE *f; 271113288Smdodd char *p; 272113288Smdodd int line; 273113309Smdodd char buf[SIZE], name[SIZE], value[SIZE], debounce[SIZE], action[SIZE]; 274126774Sdwmalone char usbuf[SIZE], coll[SIZE]; 275113288Smdodd struct command *cmd, *cmds; 276113288Smdodd struct hid_data *d; 277113288Smdodd struct hid_item h; 278113288Smdodd int u, lo, hi, range; 279113288Smdodd 280113288Smdodd 281113288Smdodd f = fopen(conf, "r"); 282113288Smdodd if (f == NULL) 283113288Smdodd err(1, "%s", conf); 284113288Smdodd 285113288Smdodd cmds = NULL; 286113288Smdodd for (line = 1; ; line++) { 287113288Smdodd if (fgets(buf, sizeof buf, f) == NULL) 288113288Smdodd break; 289113288Smdodd if (buf[0] == '#' || buf[0] == '\n') 290113288Smdodd continue; 291113288Smdodd p = strchr(buf, '\n'); 292113288Smdodd while (p && isspace(peek(f))) { 293113288Smdodd if (fgets(p, sizeof buf - strlen(buf), f) == NULL) 294113288Smdodd break; 295113288Smdodd p = strchr(buf, '\n'); 296113288Smdodd } 297113288Smdodd if (p) 298113288Smdodd *p = 0; 299113309Smdodd if (sscanf(buf, "%s %s %s %[^\n]", 300113309Smdodd name, value, debounce, action) != 4) { 301113288Smdodd if (isdemon) { 302113288Smdodd syslog(LOG_WARNING, "config file `%s', line %d" 303113288Smdodd ", syntax error: %s", conf, line, buf); 304113288Smdodd freecommands(cmds); 305113288Smdodd return (NULL); 306113288Smdodd } else { 307113288Smdodd errx(1, "config file `%s', line %d," 308113288Smdodd ", syntax error: %s", conf, line, buf); 309113288Smdodd } 310113288Smdodd } 311113288Smdodd 312113288Smdodd cmd = malloc(sizeof *cmd); 313113288Smdodd if (cmd == NULL) 314113288Smdodd err(1, "malloc failed"); 315113288Smdodd cmd->next = cmds; 316113288Smdodd cmds = cmd; 317113288Smdodd cmd->line = line; 318113288Smdodd 319113288Smdodd if (strcmp(value, "*") == 0) { 320113288Smdodd cmd->anyvalue = 1; 321113288Smdodd } else { 322113288Smdodd cmd->anyvalue = 0; 323113288Smdodd if (sscanf(value, "%d", &cmd->value) != 1) { 324113288Smdodd if (isdemon) { 325113288Smdodd syslog(LOG_WARNING, 326113288Smdodd "config file `%s', line %d, " 327113309Smdodd "bad value: %s (should be * or a number)\n", 328113288Smdodd conf, line, value); 329113288Smdodd freecommands(cmds); 330113288Smdodd return (NULL); 331113288Smdodd } else { 332113288Smdodd errx(1, "config file `%s', line %d, " 333113309Smdodd "bad value: %s (should be * or a number)\n", 334113288Smdodd conf, line, value); 335113288Smdodd } 336113288Smdodd } 337113288Smdodd } 338113288Smdodd 339113309Smdodd if (sscanf(debounce, "%d", &cmd->debounce) != 1) { 340113309Smdodd if (isdemon) { 341113309Smdodd syslog(LOG_WARNING, 342113309Smdodd "config file `%s', line %d, " 343113309Smdodd "bad value: %s (should be a number >= 0)\n", 344113309Smdodd conf, line, debounce); 345113309Smdodd freecommands(cmds); 346113309Smdodd return (NULL); 347113309Smdodd } else { 348113309Smdodd errx(1, "config file `%s', line %d, " 349113309Smdodd "bad value: %s (should be a number >= 0)\n", 350113309Smdodd conf, line, debounce); 351113309Smdodd } 352113309Smdodd } 353113309Smdodd 354113288Smdodd coll[0] = 0; 355113288Smdodd for (d = hid_start_parse(repd, 1 << hid_input, reportid); 356113288Smdodd hid_get_item(d, &h); ) { 357113288Smdodd if (verbose > 2) 358113288Smdodd printf("kind=%d usage=%x\n", h.kind, h.usage); 359113288Smdodd if (h.flags & HIO_CONST) 360113288Smdodd continue; 361113288Smdodd switch (h.kind) { 362113288Smdodd case hid_input: 363113288Smdodd if (h.usage_minimum != 0 || 364113288Smdodd h.usage_maximum != 0) { 365113288Smdodd lo = h.usage_minimum; 366113288Smdodd hi = h.usage_maximum; 367113288Smdodd range = 1; 368113288Smdodd } else { 369113288Smdodd lo = h.usage; 370113288Smdodd hi = h.usage; 371113288Smdodd range = 0; 372113288Smdodd } 373113288Smdodd for (u = lo; u <= hi; u++) { 374126774Sdwmalone snprintf(usbuf, sizeof usbuf, "%s:%s", 375113288Smdodd hid_usage_page(HID_PAGE(u)), 376113288Smdodd hid_usage_in_page(u)); 377113288Smdodd if (verbose > 2) 378126774Sdwmalone printf("usage %s\n", usbuf); 379126774Sdwmalone if (!strcasecmp(usbuf, name)) 380113288Smdodd goto foundhid; 381113288Smdodd if (coll[0]) { 382126774Sdwmalone snprintf(usbuf, sizeof usbuf, 383113288Smdodd "%s.%s:%s", coll+1, 384113288Smdodd hid_usage_page(HID_PAGE(u)), 385113288Smdodd hid_usage_in_page(u)); 386113288Smdodd if (verbose > 2) 387113288Smdodd printf("usage %s\n", 388126774Sdwmalone usbuf); 389126774Sdwmalone if (!strcasecmp(usbuf, name)) 390113288Smdodd goto foundhid; 391113288Smdodd } 392113288Smdodd } 393113288Smdodd break; 394113288Smdodd case hid_collection: 395113288Smdodd snprintf(coll + strlen(coll), 396113288Smdodd sizeof coll - strlen(coll), ".%s:%s", 397113288Smdodd hid_usage_page(HID_PAGE(h.usage)), 398113288Smdodd hid_usage_in_page(h.usage)); 399113288Smdodd break; 400113288Smdodd case hid_endcollection: 401113288Smdodd if (coll[0]) 402113288Smdodd *strrchr(coll, '.') = 0; 403113288Smdodd break; 404113288Smdodd default: 405113288Smdodd break; 406113288Smdodd } 407113288Smdodd } 408113288Smdodd if (ignore) { 409113288Smdodd if (verbose) 410113288Smdodd warnx("ignore item '%s'", name); 411113288Smdodd continue; 412113288Smdodd } 413113288Smdodd if (isdemon) { 414113288Smdodd syslog(LOG_WARNING, "config file `%s', line %d, HID " 415113288Smdodd "item not found: `%s'\n", conf, line, name); 416113288Smdodd freecommands(cmds); 417113288Smdodd return (NULL); 418113288Smdodd } else { 419113288Smdodd errx(1, "config file `%s', line %d, HID item " 420113288Smdodd "not found: `%s'\n", conf, line, name); 421113288Smdodd } 422113288Smdodd 423113288Smdodd foundhid: 424113288Smdodd hid_end_parse(d); 425113328Smdodd cmd->lastseen = -1; 426113328Smdodd cmd->lastused = -1; 427113288Smdodd cmd->item = h; 428113288Smdodd cmd->name = strdup(name); 429113288Smdodd cmd->action = strdup(action); 430113288Smdodd if (range) { 431113288Smdodd if (cmd->value == 1) 432113288Smdodd cmd->value = u - lo; 433113288Smdodd else 434113288Smdodd cmd->value = -1; 435113288Smdodd } 436113288Smdodd 437113288Smdodd if (verbose) 438113288Smdodd printf("PARSE:%d %s, %d, '%s'\n", cmd->line, name, 439113288Smdodd cmd->value, cmd->action); 440113288Smdodd } 441113288Smdodd fclose(f); 442113288Smdodd return (cmds); 443113288Smdodd} 444113288Smdodd 445113288Smdoddvoid 446113288Smdodddocmd(struct command *cmd, int value, const char *hid, int argc, char **argv) 447113288Smdodd{ 448113288Smdodd char cmdbuf[SIZE], *p, *q; 449113288Smdodd size_t len; 450113288Smdodd int n, r; 451113288Smdodd 452113288Smdodd for (p = cmd->action, q = cmdbuf; *p && q < &cmdbuf[SIZE-1]; ) { 453113288Smdodd if (*p == '$') { 454113288Smdodd p++; 455113288Smdodd len = &cmdbuf[SIZE-1] - q; 456113288Smdodd if (isdigit(*p)) { 457113288Smdodd n = strtol(p, &p, 10) - 1; 458113288Smdodd if (n >= 0 && n < argc) { 459113288Smdodd strncpy(q, argv[n], len); 460113288Smdodd q += strlen(q); 461113288Smdodd } 462113288Smdodd } else if (*p == 'V') { 463113288Smdodd p++; 464113288Smdodd snprintf(q, len, "%d", value); 465113288Smdodd q += strlen(q); 466113288Smdodd } else if (*p == 'N') { 467113288Smdodd p++; 468113288Smdodd strncpy(q, cmd->name, len); 469113288Smdodd q += strlen(q); 470113288Smdodd } else if (*p == 'H') { 471113288Smdodd p++; 472113288Smdodd strncpy(q, hid, len); 473113288Smdodd q += strlen(q); 474113288Smdodd } else if (*p) { 475113288Smdodd *q++ = *p++; 476113288Smdodd } 477113288Smdodd } else { 478113288Smdodd *q++ = *p++; 479113288Smdodd } 480113288Smdodd } 481113288Smdodd *q = 0; 482113288Smdodd 483113288Smdodd if (verbose) 484113288Smdodd printf("system '%s'\n", cmdbuf); 485113288Smdodd r = system(cmdbuf); 486113288Smdodd if (verbose > 1 && r) 487113288Smdodd printf("return code = 0x%x\n", r); 488113288Smdodd} 489113288Smdodd 490113288Smdoddvoid 491113288Smdoddfreecommands(struct command *cmd) 492113288Smdodd{ 493113288Smdodd struct command *next; 494113288Smdodd 495113288Smdodd while (cmd) { 496113288Smdodd next = cmd->next; 497113288Smdodd free(cmd); 498113288Smdodd cmd = next; 499113288Smdodd } 500113288Smdodd} 501