1113288Smdodd/* $NetBSD: usbhidaction.c,v 1.8 2002/06/11 06:06:21 itojun Exp $ */ 2113288Smdodd/* $FreeBSD$ */ 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 * 20113288Smdodd * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 21113288Smdodd * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22113288Smdodd * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23113288Smdodd * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 24113288Smdodd * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25113288Smdodd * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26113288Smdodd * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27113288Smdodd * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28113288Smdodd * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29113288Smdodd * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30113288Smdodd * POSSIBILITY OF SUCH DAMAGE. 31113288Smdodd */ 32113288Smdodd 33113288Smdodd#include <stdio.h> 34113288Smdodd#include <stdlib.h> 35113288Smdodd#include <string.h> 36113288Smdodd#include <ctype.h> 37113288Smdodd#include <err.h> 38113288Smdodd#include <fcntl.h> 39113288Smdodd#include <limits.h> 40113288Smdodd#include <unistd.h> 41113288Smdodd#include <sys/types.h> 42188945Sthompsa#include <dev/usb/usbhid.h> 43113288Smdodd#include <usbhid.h> 44113288Smdodd#include <syslog.h> 45113288Smdodd#include <signal.h> 46200462Sdelphij#include <errno.h> 47113288Smdodd#include <sys/stat.h> 48113288Smdodd 49113288Smdoddstatic int verbose = 0; 50113288Smdoddstatic int isdemon = 0; 51113288Smdoddstatic int reparse = 1; 52126774Sdwmalonestatic const char * pidfile = "/var/run/usbaction.pid"; 53113288Smdodd 54113288Smdoddstruct command { 55113288Smdodd struct command *next; 56113288Smdodd int line; 57113288Smdodd 58113288Smdodd struct hid_item item; 59113288Smdodd int value; 60113309Smdodd int lastseen; 61113309Smdodd int lastused; 62113309Smdodd int debounce; 63113288Smdodd char anyvalue; 64113288Smdodd char *name; 65113288Smdodd char *action; 66113288Smdodd}; 67113288Smdoddstruct command *commands; 68113288Smdodd 69113288Smdodd#define SIZE 4000 70113288Smdodd 71113288Smdoddvoid usage(void); 72113288Smdoddstruct command *parse_conf(const char *, report_desc_t, int, int); 73113288Smdoddvoid docmd(struct command *, int, const char *, int, char **); 74113288Smdoddvoid freecommands(struct command *); 75113288Smdodd 76113288Smdoddstatic void 77126774Sdwmalonesighup(int sig __unused) 78113288Smdodd{ 79113288Smdodd reparse = 1; 80113288Smdodd} 81113288Smdodd 82113288Smdoddint 83113288Smdoddmain(int argc, char **argv) 84113288Smdodd{ 85113288Smdodd const char *conf = NULL; 86113288Smdodd const char *dev = NULL; 87171101Simp const char *table = NULL; 88126774Sdwmalone int fd, fp, ch, n, val, i; 89126774Sdwmalone size_t sz, sz1; 90113309Smdodd int demon, ignore, dieearly; 91113288Smdodd report_desc_t repd; 92113288Smdodd char buf[100]; 93113288Smdodd char devnamebuf[PATH_MAX]; 94113288Smdodd struct command *cmd; 95224511Smav int reportid = -1; 96113288Smdodd 97113288Smdodd demon = 1; 98113288Smdodd ignore = 0; 99113309Smdodd dieearly = 0; 100224511Smav while ((ch = getopt(argc, argv, "c:def:ip:r:t:v")) != -1) { 101113288Smdodd switch(ch) { 102113288Smdodd case 'c': 103113288Smdodd conf = optarg; 104113288Smdodd break; 105113288Smdodd case 'd': 106113288Smdodd demon ^= 1; 107113288Smdodd break; 108113309Smdodd case 'e': 109113309Smdodd dieearly = 1; 110113309Smdodd break; 111113288Smdodd case 'i': 112113288Smdodd ignore++; 113113288Smdodd break; 114113288Smdodd case 'f': 115113288Smdodd dev = optarg; 116113288Smdodd break; 117113288Smdodd case 'p': 118113288Smdodd pidfile = optarg; 119113288Smdodd break; 120224511Smav case 'r': 121224511Smav reportid = atoi(optarg); 122224511Smav break; 123171101Simp case 't': 124171101Simp table = optarg; 125171101Simp break; 126113288Smdodd case 'v': 127113288Smdodd demon = 0; 128113288Smdodd verbose++; 129113288Smdodd break; 130113288Smdodd case '?': 131113288Smdodd default: 132113288Smdodd usage(); 133113288Smdodd } 134113288Smdodd } 135113288Smdodd argc -= optind; 136113288Smdodd argv += optind; 137113288Smdodd 138113288Smdodd if (conf == NULL || dev == NULL) 139113288Smdodd usage(); 140113288Smdodd 141171101Simp hid_init(table); 142113288Smdodd 143113288Smdodd if (dev[0] != '/') { 144113288Smdodd snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s", 145113288Smdodd isdigit(dev[0]) ? "uhid" : "", dev); 146113288Smdodd dev = devnamebuf; 147113288Smdodd } 148113288Smdodd 149113288Smdodd fd = open(dev, O_RDWR); 150113288Smdodd if (fd < 0) 151113288Smdodd err(1, "%s", dev); 152113288Smdodd repd = hid_get_report_desc(fd); 153113288Smdodd if (repd == NULL) 154113288Smdodd err(1, "hid_get_report_desc() failed"); 155113288Smdodd 156113288Smdodd commands = parse_conf(conf, repd, reportid, ignore); 157113288Smdodd 158224511Smav sz = (size_t)hid_report_size(repd, hid_input, -1); 159113288Smdodd 160113288Smdodd if (verbose) 161126774Sdwmalone printf("report size %zu\n", sz); 162113288Smdodd if (sz > sizeof buf) 163113288Smdodd errx(1, "report too large"); 164113288Smdodd 165113288Smdodd (void)signal(SIGHUP, sighup); 166113288Smdodd 167113288Smdodd if (demon) { 168113288Smdodd fp = open(pidfile, O_WRONLY|O_CREAT, S_IRUSR|S_IRGRP|S_IROTH); 169113288Smdodd if (fp >= 0) { 170212048Skevlo sz1 = snprintf(buf, sizeof buf, "%ld\n", 171212048Skevlo (long)getpid()); 172126774Sdwmalone if (sz1 > sizeof buf) 173126774Sdwmalone sz1 = sizeof buf; 174126774Sdwmalone write(fp, buf, sz1); 175113288Smdodd close(fp); 176113288Smdodd } else 177113288Smdodd err(1, "%s", pidfile); 178113309Smdodd if (daemon(0, 0) < 0) 179113309Smdodd err(1, "daemon()"); 180113288Smdodd isdemon = 1; 181113288Smdodd } 182113288Smdodd 183113288Smdodd for(;;) { 184113288Smdodd n = read(fd, buf, sz); 185113288Smdodd if (verbose > 2) { 186113288Smdodd printf("read %d bytes:", n); 187113288Smdodd for (i = 0; i < n; i++) 188113288Smdodd printf(" %02x", buf[i]); 189113288Smdodd printf("\n"); 190113288Smdodd } 191113288Smdodd if (n < 0) { 192113288Smdodd if (verbose) 193113288Smdodd err(1, "read"); 194113288Smdodd else 195113288Smdodd exit(1); 196113288Smdodd } 197113288Smdodd#if 0 198113288Smdodd if (n != sz) { 199113288Smdodd err(2, "read size"); 200113288Smdodd } 201113288Smdodd#endif 202113288Smdodd for (cmd = commands; cmd; cmd = cmd->next) { 203224511Smav if (cmd->item.report_ID != 0 && 204224511Smav buf[0] != cmd->item.report_ID) 205224511Smav continue; 206224511Smav if (cmd->item.flags & HIO_VARIABLE) 207224511Smav val = hid_get_data(buf, &cmd->item); 208224511Smav else { 209224511Smav uint32_t pos = cmd->item.pos; 210224511Smav for (i = 0; i < cmd->item.report_count; i++) { 211224511Smav val = hid_get_data(buf, &cmd->item); 212224511Smav if (val == cmd->value) 213224511Smav break; 214224511Smav cmd->item.pos += cmd->item.report_size; 215224511Smav } 216224511Smav cmd->item.pos = pos; 217224511Smav val = (i < cmd->item.report_count) ? 218224511Smav cmd->value : -1; 219224511Smav } 220113309Smdodd if (cmd->value != val && cmd->anyvalue == 0) 221113309Smdodd goto next; 222113309Smdodd if ((cmd->debounce == 0) || 223113328Smdodd ((cmd->debounce == 1) && ((cmd->lastseen == -1) || 224113309Smdodd (cmd->lastseen != val)))) { 225113288Smdodd docmd(cmd, val, dev, argc, argv); 226113309Smdodd goto next; 227113309Smdodd } 228113309Smdodd if ((cmd->debounce > 1) && 229113309Smdodd ((cmd->lastused == -1) || 230113309Smdodd (abs(cmd->lastused - val) >= cmd->debounce))) { 231113309Smdodd docmd(cmd, val, dev, argc, argv); 232113309Smdodd cmd->lastused = val; 233113309Smdodd goto next; 234113309Smdodd } 235113309Smdoddnext: 236113309Smdodd cmd->lastseen = val; 237113288Smdodd } 238113309Smdodd 239113309Smdodd if (dieearly) 240113309Smdodd exit(0); 241113309Smdodd 242113288Smdodd if (reparse) { 243113288Smdodd struct command *cmds = 244113288Smdodd parse_conf(conf, repd, reportid, ignore); 245113288Smdodd if (cmds) { 246113288Smdodd freecommands(commands); 247113288Smdodd commands = cmds; 248113288Smdodd } 249113288Smdodd reparse = 0; 250113288Smdodd } 251113288Smdodd } 252113288Smdodd 253113288Smdodd exit(0); 254113288Smdodd} 255113288Smdodd 256113288Smdoddvoid 257113288Smdoddusage(void) 258113288Smdodd{ 259113288Smdodd 260113309Smdodd fprintf(stderr, "Usage: %s [-deiv] -c config_file -f hid_dev " 261171101Simp "[-p pidfile] [-t tablefile]\n", getprogname()); 262113288Smdodd exit(1); 263113288Smdodd} 264113288Smdodd 265113288Smdoddstatic int 266113288Smdoddpeek(FILE *f) 267113288Smdodd{ 268113288Smdodd int c; 269113288Smdodd 270113288Smdodd c = getc(f); 271113288Smdodd if (c != EOF) 272113288Smdodd ungetc(c, f); 273113288Smdodd return c; 274113288Smdodd} 275113288Smdodd 276113288Smdoddstruct command * 277113288Smdoddparse_conf(const char *conf, report_desc_t repd, int reportid, int ignore) 278113288Smdodd{ 279113288Smdodd FILE *f; 280113288Smdodd char *p; 281113288Smdodd int line; 282113309Smdodd char buf[SIZE], name[SIZE], value[SIZE], debounce[SIZE], action[SIZE]; 283229387Smav char usbuf[SIZE], coll[SIZE], *tmp; 284113288Smdodd struct command *cmd, *cmds; 285113288Smdodd struct hid_data *d; 286113288Smdodd struct hid_item h; 287229387Smav int inst, cinst, u, lo, hi, range, t; 288113288Smdodd 289113288Smdodd f = fopen(conf, "r"); 290113288Smdodd if (f == NULL) 291113288Smdodd err(1, "%s", conf); 292113288Smdodd 293113288Smdodd cmds = NULL; 294113288Smdodd for (line = 1; ; line++) { 295113288Smdodd if (fgets(buf, sizeof buf, f) == NULL) 296113288Smdodd break; 297113288Smdodd if (buf[0] == '#' || buf[0] == '\n') 298113288Smdodd continue; 299113288Smdodd p = strchr(buf, '\n'); 300113288Smdodd while (p && isspace(peek(f))) { 301113288Smdodd if (fgets(p, sizeof buf - strlen(buf), f) == NULL) 302113288Smdodd break; 303113288Smdodd p = strchr(buf, '\n'); 304113288Smdodd } 305113288Smdodd if (p) 306113288Smdodd *p = 0; 307113309Smdodd if (sscanf(buf, "%s %s %s %[^\n]", 308113309Smdodd name, value, debounce, action) != 4) { 309113288Smdodd if (isdemon) { 310113288Smdodd syslog(LOG_WARNING, "config file `%s', line %d" 311113288Smdodd ", syntax error: %s", conf, line, buf); 312113288Smdodd freecommands(cmds); 313113288Smdodd return (NULL); 314113288Smdodd } else { 315113288Smdodd errx(1, "config file `%s', line %d," 316113288Smdodd ", syntax error: %s", conf, line, buf); 317113288Smdodd } 318113288Smdodd } 319229387Smav tmp = strchr(name, '#'); 320229387Smav if (tmp != NULL) { 321229387Smav *tmp = 0; 322229387Smav inst = atoi(tmp + 1); 323229387Smav } else 324229387Smav inst = 0; 325113288Smdodd 326113288Smdodd cmd = malloc(sizeof *cmd); 327113288Smdodd if (cmd == NULL) 328113288Smdodd err(1, "malloc failed"); 329113288Smdodd cmd->next = cmds; 330113288Smdodd cmds = cmd; 331113288Smdodd cmd->line = line; 332113288Smdodd 333113288Smdodd if (strcmp(value, "*") == 0) { 334113288Smdodd cmd->anyvalue = 1; 335113288Smdodd } else { 336113288Smdodd cmd->anyvalue = 0; 337113288Smdodd if (sscanf(value, "%d", &cmd->value) != 1) { 338113288Smdodd if (isdemon) { 339113288Smdodd syslog(LOG_WARNING, 340113288Smdodd "config file `%s', line %d, " 341113309Smdodd "bad value: %s (should be * or a number)\n", 342113288Smdodd conf, line, value); 343113288Smdodd freecommands(cmds); 344113288Smdodd return (NULL); 345113288Smdodd } else { 346113288Smdodd errx(1, "config file `%s', line %d, " 347113309Smdodd "bad value: %s (should be * or a number)\n", 348113288Smdodd conf, line, value); 349113288Smdodd } 350113288Smdodd } 351113288Smdodd } 352113288Smdodd 353113309Smdodd if (sscanf(debounce, "%d", &cmd->debounce) != 1) { 354113309Smdodd if (isdemon) { 355113309Smdodd syslog(LOG_WARNING, 356113309Smdodd "config file `%s', line %d, " 357113309Smdodd "bad value: %s (should be a number >= 0)\n", 358113309Smdodd conf, line, debounce); 359113309Smdodd freecommands(cmds); 360113309Smdodd return (NULL); 361113309Smdodd } else { 362113309Smdodd errx(1, "config file `%s', line %d, " 363113309Smdodd "bad value: %s (should be a number >= 0)\n", 364113309Smdodd conf, line, debounce); 365113309Smdodd } 366113309Smdodd } 367113309Smdodd 368113288Smdodd coll[0] = 0; 369229387Smav cinst = 0; 370113288Smdodd for (d = hid_start_parse(repd, 1 << hid_input, reportid); 371113288Smdodd hid_get_item(d, &h); ) { 372113288Smdodd if (verbose > 2) 373113288Smdodd printf("kind=%d usage=%x\n", h.kind, h.usage); 374113288Smdodd if (h.flags & HIO_CONST) 375113288Smdodd continue; 376113288Smdodd switch (h.kind) { 377113288Smdodd case hid_input: 378113288Smdodd if (h.usage_minimum != 0 || 379113288Smdodd h.usage_maximum != 0) { 380113288Smdodd lo = h.usage_minimum; 381113288Smdodd hi = h.usage_maximum; 382113288Smdodd range = 1; 383113288Smdodd } else { 384113288Smdodd lo = h.usage; 385113288Smdodd hi = h.usage; 386113288Smdodd range = 0; 387113288Smdodd } 388113288Smdodd for (u = lo; u <= hi; u++) { 389113288Smdodd if (coll[0]) { 390126774Sdwmalone snprintf(usbuf, sizeof usbuf, 391113288Smdodd "%s.%s:%s", coll+1, 392229387Smav hid_usage_page(HID_PAGE(u)), 393113288Smdodd hid_usage_in_page(u)); 394229387Smav } else { 395229387Smav snprintf(usbuf, sizeof usbuf, 396229387Smav "%s:%s", 397229387Smav hid_usage_page(HID_PAGE(u)), 398229387Smav hid_usage_in_page(u)); 399113288Smdodd } 400229387Smav if (verbose > 2) 401229387Smav printf("usage %s\n", usbuf); 402229387Smav t = strlen(usbuf) - strlen(name); 403229387Smav if (t > 0) { 404229387Smav if (strcmp(usbuf + t, name)) 405229387Smav continue; 406229387Smav if (usbuf[t - 1] != '.') 407229387Smav continue; 408229387Smav } else if (strcmp(usbuf, name)) 409229387Smav continue; 410229387Smav if (inst == cinst++) 411229387Smav goto foundhid; 412113288Smdodd } 413113288Smdodd break; 414113288Smdodd case hid_collection: 415113288Smdodd snprintf(coll + strlen(coll), 416113288Smdodd sizeof coll - strlen(coll), ".%s:%s", 417113288Smdodd hid_usage_page(HID_PAGE(h.usage)), 418113288Smdodd hid_usage_in_page(h.usage)); 419113288Smdodd break; 420113288Smdodd case hid_endcollection: 421113288Smdodd if (coll[0]) 422113288Smdodd *strrchr(coll, '.') = 0; 423113288Smdodd break; 424113288Smdodd default: 425113288Smdodd break; 426113288Smdodd } 427113288Smdodd } 428113288Smdodd if (ignore) { 429113288Smdodd if (verbose) 430113288Smdodd warnx("ignore item '%s'", name); 431113288Smdodd continue; 432113288Smdodd } 433113288Smdodd if (isdemon) { 434113288Smdodd syslog(LOG_WARNING, "config file `%s', line %d, HID " 435113288Smdodd "item not found: `%s'\n", conf, line, name); 436113288Smdodd freecommands(cmds); 437113288Smdodd return (NULL); 438113288Smdodd } else { 439113288Smdodd errx(1, "config file `%s', line %d, HID item " 440113288Smdodd "not found: `%s'\n", conf, line, name); 441113288Smdodd } 442113288Smdodd 443113288Smdodd foundhid: 444113288Smdodd hid_end_parse(d); 445113328Smdodd cmd->lastseen = -1; 446113328Smdodd cmd->lastused = -1; 447113288Smdodd cmd->item = h; 448113288Smdodd cmd->name = strdup(name); 449113288Smdodd cmd->action = strdup(action); 450113288Smdodd if (range) { 451113288Smdodd if (cmd->value == 1) 452113288Smdodd cmd->value = u - lo; 453113288Smdodd else 454113288Smdodd cmd->value = -1; 455113288Smdodd } 456113288Smdodd 457113288Smdodd if (verbose) 458113288Smdodd printf("PARSE:%d %s, %d, '%s'\n", cmd->line, name, 459113288Smdodd cmd->value, cmd->action); 460113288Smdodd } 461113288Smdodd fclose(f); 462113288Smdodd return (cmds); 463113288Smdodd} 464113288Smdodd 465113288Smdoddvoid 466113288Smdodddocmd(struct command *cmd, int value, const char *hid, int argc, char **argv) 467113288Smdodd{ 468113288Smdodd char cmdbuf[SIZE], *p, *q; 469113288Smdodd size_t len; 470113288Smdodd int n, r; 471113288Smdodd 472113288Smdodd for (p = cmd->action, q = cmdbuf; *p && q < &cmdbuf[SIZE-1]; ) { 473113288Smdodd if (*p == '$') { 474113288Smdodd p++; 475113288Smdodd len = &cmdbuf[SIZE-1] - q; 476113288Smdodd if (isdigit(*p)) { 477113288Smdodd n = strtol(p, &p, 10) - 1; 478113288Smdodd if (n >= 0 && n < argc) { 479113288Smdodd strncpy(q, argv[n], len); 480113288Smdodd q += strlen(q); 481113288Smdodd } 482113288Smdodd } else if (*p == 'V') { 483113288Smdodd p++; 484113288Smdodd snprintf(q, len, "%d", value); 485113288Smdodd q += strlen(q); 486113288Smdodd } else if (*p == 'N') { 487113288Smdodd p++; 488113288Smdodd strncpy(q, cmd->name, len); 489113288Smdodd q += strlen(q); 490113288Smdodd } else if (*p == 'H') { 491113288Smdodd p++; 492113288Smdodd strncpy(q, hid, len); 493113288Smdodd q += strlen(q); 494113288Smdodd } else if (*p) { 495113288Smdodd *q++ = *p++; 496113288Smdodd } 497113288Smdodd } else { 498113288Smdodd *q++ = *p++; 499113288Smdodd } 500113288Smdodd } 501113288Smdodd *q = 0; 502113288Smdodd 503113288Smdodd if (verbose) 504113288Smdodd printf("system '%s'\n", cmdbuf); 505113288Smdodd r = system(cmdbuf); 506113288Smdodd if (verbose > 1 && r) 507113288Smdodd printf("return code = 0x%x\n", r); 508113288Smdodd} 509113288Smdodd 510113288Smdoddvoid 511113288Smdoddfreecommands(struct command *cmd) 512113288Smdodd{ 513113288Smdodd struct command *next; 514113288Smdodd 515113288Smdodd while (cmd) { 516113288Smdodd next = cmd->next; 517113288Smdodd free(cmd); 518113288Smdodd cmd = next; 519113288Smdodd } 520113288Smdodd} 521