usbhidaction.c revision 188945
163670Snsayer/*      $NetBSD: usbhidaction.c,v 1.8 2002/06/11 06:06:21 itojun Exp $ */
263670Snsayer/*	$FreeBSD: head/usr.bin/usbhidaction/usbhidaction.c 188945 2009-02-23 18:36:54Z thompsa $ */
363670Snsayer
463670Snsayer/*
563670Snsayer * Copyright (c) 2000, 2002 The NetBSD Foundation, Inc.
663670Snsayer * All rights reserved.
763670Snsayer *
863670Snsayer * This code is derived from software contributed to The NetBSD Foundation
963670Snsayer * by Lennart Augustsson <lennart@augustsson.net>.
1063670Snsayer *
1163670Snsayer * Redistribution and use in source and binary forms, with or without
1263670Snsayer * modification, are permitted provided that the following conditions
1363670Snsayer * are met:
1463670Snsayer * 1. Redistributions of source code must retain the above copyright
1563670Snsayer *    notice, this list of conditions and the following disclaimer.
1663670Snsayer * 2. Redistributions in binary form must reproduce the above copyright
1763670Snsayer *    notice, this list of conditions and the following disclaimer in the
1863670Snsayer *    documentation and/or other materials provided with the distribution.
1963670Snsayer * 3. All advertising materials mentioning features or use of this software
2063670Snsayer *    must display the following acknowledgement:
2163670Snsayer *        This product includes software developed by the NetBSD
2263670Snsayer *        Foundation, Inc. and its contributors.
2363670Snsayer * 4. Neither the name of The NetBSD Foundation nor the names of its
2463670Snsayer *    contributors may be used to endorse or promote products derived
2563670Snsayer *    from this software without specific prior written permission.
2663670Snsayer *
2763670Snsayer * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2863670Snsayer * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2963670Snsayer * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
3063670Snsayer * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
3163670Snsayer * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
3263670Snsayer * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
3363670Snsayer * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
3463670Snsayer * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
3563803Snsayer * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
3663670Snsayer * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3763670Snsayer * POSSIBILITY OF SUCH DAMAGE.
3863670Snsayer */
3963670Snsayer
4063670Snsayer#include <stdio.h>
4163670Snsayer#include <stdlib.h>
4263670Snsayer#include <string.h>
4363670Snsayer#include <ctype.h>
4463670Snsayer#include <err.h>
4563670Snsayer#include <fcntl.h>
4663670Snsayer#include <limits.h>
4763670Snsayer#include <unistd.h>
4863670Snsayer#include <sys/types.h>
4963670Snsayer#include <dev/usb/usbhid.h>
5063670Snsayer#include <usbhid.h>
5163670Snsayer#include <syslog.h>
5263670Snsayer#include <signal.h>
5363670Snsayer#include <errno.h>
5463670Snsayer#include <sys/stat.h>
5563670Snsayer
5663670Snsayerstatic int	verbose = 0;
5783043Sbrooksstatic int	isdemon = 0;
5883043Sbrooksstatic int	reparse = 1;
5983043Sbrooksstatic const char *	pidfile = "/var/run/usbaction.pid";
6063670Snsayer
6163670Snsayerstruct command {
6263670Snsayer	struct command *next;
6363670Snsayer	int line;
6463670Snsayer
6563670Snsayer	struct hid_item item;
6663670Snsayer	int value;
6763670Snsayer	int lastseen;
6863670Snsayer	int lastused;
6963670Snsayer	int debounce;
7063670Snsayer	char anyvalue;
7163670Snsayer	char *name;
7263670Snsayer	char *action;
7363670Snsayer};
7463670Snsayerstruct command *commands;
7563670Snsayer
7663670Snsayer#define SIZE 4000
7763670Snsayer
7863670Snsayervoid usage(void);
7983043Sbrooksstruct command *parse_conf(const char *, report_desc_t, int, int);
8083043Sbrooksvoid docmd(struct command *, int, const char *, int, char **);
8183043Sbrooksvoid freecommands(struct command *);
8263670Snsayer
8363670Snsayerstatic void
8493084Sbdesighup(int sig __unused)
8563670Snsayer{
8663670Snsayer	reparse = 1;
8793084Sbde}
8893084Sbde
8963670Snsayerint
9063670Snsayermain(int argc, char **argv)
9193084Sbde{
9293084Sbde	const char *conf = NULL;
9393084Sbde	const char *dev = NULL;
9463670Snsayer	const char *table = NULL;
9563670Snsayer	int fd, fp, ch, n, val, i;
9663670Snsayer	size_t sz, sz1;
9763670Snsayer	int demon, ignore, dieearly;
9863670Snsayer	report_desc_t repd;
9963670Snsayer	char buf[100];
10063670Snsayer	char devnamebuf[PATH_MAX];
10163670Snsayer	struct command *cmd;
10263670Snsayer	int reportid;
10363670Snsayer
10463670Snsayer	demon = 1;
10563670Snsayer	ignore = 0;
10663670Snsayer	dieearly = 0;
10763670Snsayer	while ((ch = getopt(argc, argv, "c:def:ip:t:v")) != -1) {
10863670Snsayer		switch(ch) {
10963670Snsayer		case 'c':
11063670Snsayer			conf = optarg;
11163670Snsayer			break;
11263670Snsayer		case 'd':
11363670Snsayer			demon ^= 1;
11463670Snsayer			break;
11563670Snsayer		case 'e':
11663670Snsayer			dieearly = 1;
11763670Snsayer			break;
11863670Snsayer		case 'i':
11983043Sbrooks			ignore++;
12083043Sbrooks			break;
12183043Sbrooks		case 'f':
12283043Sbrooks			dev = optarg;
12383043Sbrooks			break;
12483043Sbrooks		case 'p':
12563670Snsayer			pidfile = optarg;
12663670Snsayer			break;
12763670Snsayer		case 't':
12863670Snsayer			table = optarg;
12963670Snsayer			break;
13063670Snsayer		case 'v':
13163670Snsayer			demon = 0;
13263670Snsayer			verbose++;
13363670Snsayer			break;
13463670Snsayer		case '?':
13563670Snsayer		default:
13663670Snsayer			usage();
13763670Snsayer		}
13863670Snsayer	}
13963670Snsayer	argc -= optind;
14063670Snsayer	argv += optind;
14163670Snsayer
14283043Sbrooks	if (conf == NULL || dev == NULL)
14383043Sbrooks		usage();
14483043Sbrooks
14583043Sbrooks	hid_init(table);
14663670Snsayer
14763670Snsayer	if (dev[0] != '/') {
14863670Snsayer		snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s",
14983043Sbrooks			 isdigit(dev[0]) ? "uhid" : "", dev);
15083043Sbrooks		dev = devnamebuf;
15183043Sbrooks	}
15283043Sbrooks
15383043Sbrooks	fd = open(dev, O_RDWR);
15463670Snsayer	if (fd < 0)
15583043Sbrooks		err(1, "%s", dev);
15683043Sbrooks	reportid = hid_get_report_id(fd);
15783043Sbrooks	repd = hid_get_report_desc(fd);
15883043Sbrooks	if (repd == NULL)
15983043Sbrooks		err(1, "hid_get_report_desc() failed");
16083043Sbrooks
16183043Sbrooks	commands = parse_conf(conf, repd, reportid, ignore);
16283043Sbrooks
16383043Sbrooks	sz = (size_t)hid_report_size(repd, hid_input, reportid);
16483043Sbrooks
16583043Sbrooks	if (verbose)
16683043Sbrooks		printf("report size %zu\n", sz);
16783043Sbrooks	if (sz > sizeof buf)
16883043Sbrooks		errx(1, "report too large");
16983043Sbrooks
17083043Sbrooks	(void)signal(SIGHUP, sighup);
17183043Sbrooks
17283043Sbrooks	if (demon) {
17383043Sbrooks		fp = open(pidfile, O_WRONLY|O_CREAT, S_IRUSR|S_IRGRP|S_IROTH);
17483043Sbrooks		if (fp >= 0) {
17571602Sphk			sz1 = snprintf(buf, sizeof buf, "%d\n", getpid());
17683043Sbrooks			if (sz1 > sizeof buf)
17783043Sbrooks				sz1 = sizeof buf;
17883043Sbrooks			write(fp, buf, sz1);
17983043Sbrooks			close(fp);
18063670Snsayer		} else
18183043Sbrooks			err(1, "%s", pidfile);
18283043Sbrooks		if (daemon(0, 0) < 0)
18383043Sbrooks			err(1, "daemon()");
18483043Sbrooks		isdemon = 1;
18583043Sbrooks	}
18683043Sbrooks
18783043Sbrooks	for(;;) {
18871602Sphk		n = read(fd, buf, sz);
18983043Sbrooks		if (verbose > 2) {
19083043Sbrooks			printf("read %d bytes:", n);
19183043Sbrooks			for (i = 0; i < n; i++)
19283043Sbrooks				printf(" %02x", buf[i]);
19383043Sbrooks			printf("\n");
19483043Sbrooks		}
19583043Sbrooks		if (n < 0) {
19663670Snsayer			if (verbose)
19783043Sbrooks				err(1, "read");
19883043Sbrooks			else
19983043Sbrooks				exit(1);
20083043Sbrooks		}
20183043Sbrooks#if 0
20271602Sphk		if (n != sz) {
20363670Snsayer			err(2, "read size");
20483043Sbrooks		}
20583043Sbrooks#endif
20683043Sbrooks		for (cmd = commands; cmd; cmd = cmd->next) {
20783043Sbrooks			val = hid_get_data(buf, &cmd->item);
20871602Sphk			if (cmd->value != val && cmd->anyvalue == 0)
20983043Sbrooks				goto next;
21083043Sbrooks			if ((cmd->debounce == 0) ||
21183043Sbrooks			    ((cmd->debounce == 1) && ((cmd->lastseen == -1) ||
21283043Sbrooks					       (cmd->lastseen != val)))) {
21383043Sbrooks				docmd(cmd, val, dev, argc, argv);
21483043Sbrooks				goto next;
21583043Sbrooks			}
21683043Sbrooks			if ((cmd->debounce > 1) &&
21783043Sbrooks			    ((cmd->lastused == -1) ||
21883043Sbrooks			     (abs(cmd->lastused - val) >= cmd->debounce))) {
21983043Sbrooks				docmd(cmd, val, dev, argc, argv);
22083043Sbrooks				cmd->lastused = val;
22183043Sbrooks				goto next;
22263670Snsayer			}
22383043Sbrooksnext:
22463670Snsayer			cmd->lastseen = val;
22563670Snsayer		}
22693752Sluigi
22783043Sbrooks		if (dieearly)
22863670Snsayer			exit(0);
22983043Sbrooks
23083043Sbrooks		if (reparse) {
23163670Snsayer			struct command *cmds =
23283043Sbrooks			    parse_conf(conf, repd, reportid, ignore);
23383043Sbrooks			if (cmds) {
23463670Snsayer				freecommands(commands);
23583043Sbrooks				commands = cmds;
23663670Snsayer			}
23763670Snsayer			reparse = 0;
23863670Snsayer		}
23963670Snsayer	}
24063670Snsayer
24163670Snsayer	exit(0);
24263670Snsayer}
24363670Snsayer
24463670Snsayervoid
24563670Snsayerusage(void)
24671602Sphk{
24771602Sphk
24871602Sphk	fprintf(stderr, "Usage: %s [-deiv] -c config_file -f hid_dev "
24971602Sphk		"[-p pidfile] [-t tablefile]\n", getprogname());
25071602Sphk	exit(1);
25171602Sphk}
25271602Sphk
25371602Sphkstatic int
25471602Sphkpeek(FILE *f)
25571602Sphk{
25671602Sphk	int c;
25783043Sbrooks
25883043Sbrooks	c = getc(f);
25983043Sbrooks	if (c != EOF)
26071602Sphk		ungetc(c, f);
26171602Sphk	return c;
26271602Sphk}
26371602Sphk
26483043Sbrooksstruct command *
26583043Sbrooksparse_conf(const char *conf, report_desc_t repd, int reportid, int ignore)
26683043Sbrooks{
26783043Sbrooks	FILE *f;
26883043Sbrooks	char *p;
26983043Sbrooks	int line;
27083043Sbrooks	char buf[SIZE], name[SIZE], value[SIZE], debounce[SIZE], action[SIZE];
27183043Sbrooks	char usbuf[SIZE], coll[SIZE];
27283043Sbrooks	struct command *cmd, *cmds;
27383043Sbrooks	struct hid_data *d;
27483043Sbrooks	struct hid_item h;
27583043Sbrooks	int u, lo, hi, range;
27683043Sbrooks
27783043Sbrooks
27871602Sphk	f = fopen(conf, "r");
27983043Sbrooks	if (f == NULL)
28083043Sbrooks		err(1, "%s", conf);
28183043Sbrooks
28271602Sphk	cmds = NULL;
28383043Sbrooks	for (line = 1; ; line++) {
28483043Sbrooks		if (fgets(buf, sizeof buf, f) == NULL)
28583043Sbrooks			break;
28683043Sbrooks		if (buf[0] == '#' || buf[0] == '\n')
28783043Sbrooks			continue;
28883043Sbrooks		p = strchr(buf, '\n');
28983043Sbrooks		while (p && isspace(peek(f))) {
29083043Sbrooks			if (fgets(p, sizeof buf - strlen(buf), f) == NULL)
29183043Sbrooks				break;
29283043Sbrooks			p = strchr(buf, '\n');
29383043Sbrooks		}
29483043Sbrooks		if (p)
29583043Sbrooks			*p = 0;
29683043Sbrooks		if (sscanf(buf, "%s %s %s %[^\n]",
29783043Sbrooks			   name, value, debounce, action) != 4) {
29883043Sbrooks			if (isdemon) {
29983043Sbrooks				syslog(LOG_WARNING, "config file `%s', line %d"
30083043Sbrooks				       ", syntax error: %s", conf, line, buf);
30183043Sbrooks				freecommands(cmds);
30283043Sbrooks				return (NULL);
30383043Sbrooks			} else {
30471602Sphk				errx(1, "config file `%s', line %d,"
30571602Sphk				     ", syntax error: %s", conf, line, buf);
30683043Sbrooks			}
30783043Sbrooks		}
30871602Sphk
30971602Sphk		cmd = malloc(sizeof *cmd);
31083043Sbrooks		if (cmd == NULL)
31183043Sbrooks			err(1, "malloc failed");
31283043Sbrooks		cmd->next = cmds;
31383043Sbrooks		cmds = cmd;
31483043Sbrooks		cmd->line = line;
31583043Sbrooks
31683043Sbrooks		if (strcmp(value, "*") == 0) {
31771602Sphk			cmd->anyvalue = 1;
31871602Sphk		} else {
31971602Sphk			cmd->anyvalue = 0;
32071602Sphk			if (sscanf(value, "%d", &cmd->value) != 1) {
32163670Snsayer				if (isdemon) {
32263670Snsayer					syslog(LOG_WARNING,
32363670Snsayer					       "config file `%s', line %d, "
32463670Snsayer					       "bad value: %s (should be * or a number)\n",
32563670Snsayer					       conf, line, value);
32663670Snsayer					freecommands(cmds);
32763670Snsayer					return (NULL);
32863670Snsayer				} else {
32963670Snsayer					errx(1, "config file `%s', line %d, "
33063670Snsayer					     "bad value: %s (should be * or a number)\n",
33163670Snsayer					     conf, line, value);
33263803Snsayer				}
33363670Snsayer			}
33463670Snsayer		}
33563670Snsayer
33669781Sdwmalone		if (sscanf(debounce, "%d", &cmd->debounce) != 1) {
33783043Sbrooks			if (isdemon) {
33863670Snsayer				syslog(LOG_WARNING,
33983043Sbrooks				       "config file `%s', line %d, "
34083043Sbrooks				       "bad value: %s (should be a number >= 0)\n",
34163670Snsayer				       conf, line, debounce);
34263670Snsayer				freecommands(cmds);
34363670Snsayer				return (NULL);
34463803Snsayer			} else {
34583043Sbrooks				errx(1, "config file `%s', line %d, "
34663670Snsayer				     "bad value: %s (should be a number >= 0)\n",
34763670Snsayer				     conf, line, debounce);
34883043Sbrooks			}
34983043Sbrooks		}
35083043Sbrooks
35183043Sbrooks		coll[0] = 0;
35263670Snsayer		for (d = hid_start_parse(repd, 1 << hid_input, reportid);
35363670Snsayer		     hid_get_item(d, &h); ) {
35463670Snsayer			if (verbose > 2)
35563670Snsayer				printf("kind=%d usage=%x\n", h.kind, h.usage);
35663670Snsayer			if (h.flags & HIO_CONST)
35763670Snsayer				continue;
35863670Snsayer			switch (h.kind) {
35963670Snsayer			case hid_input:
36063670Snsayer				if (h.usage_minimum != 0 ||
36163670Snsayer				    h.usage_maximum != 0) {
36263670Snsayer					lo = h.usage_minimum;
36363670Snsayer					hi = h.usage_maximum;
36463670Snsayer					range = 1;
36563670Snsayer				} else {
36663670Snsayer					lo = h.usage;
36763670Snsayer					hi = h.usage;
36863670Snsayer					range = 0;
36963670Snsayer				}
37063670Snsayer				for (u = lo; u <= hi; u++) {
37163670Snsayer					snprintf(usbuf, sizeof usbuf,  "%s:%s",
37263670Snsayer						 hid_usage_page(HID_PAGE(u)),
37383043Sbrooks						 hid_usage_in_page(u));
37483043Sbrooks					if (verbose > 2)
37563803Snsayer						printf("usage %s\n", usbuf);
37683043Sbrooks					if (!strcasecmp(usbuf, name))
37763803Snsayer						goto foundhid;
37863670Snsayer					if (coll[0]) {
37963803Snsayer						snprintf(usbuf, sizeof usbuf,
38063803Snsayer						  "%s.%s:%s", coll+1,
38183043Sbrooks						  hid_usage_page(HID_PAGE(u)),
38283043Sbrooks						  hid_usage_in_page(u));
38363670Snsayer						if (verbose > 2)
38463670Snsayer							printf("usage %s\n",
38563670Snsayer							       usbuf);
38663670Snsayer						if (!strcasecmp(usbuf, name))
38763670Snsayer							goto foundhid;
38863670Snsayer					}
38963670Snsayer				}
39063670Snsayer				break;
39163670Snsayer			case hid_collection:
39283366Sjulian				snprintf(coll + strlen(coll),
39363670Snsayer				    sizeof coll - strlen(coll),  ".%s:%s",
39463670Snsayer				    hid_usage_page(HID_PAGE(h.usage)),
39563670Snsayer				    hid_usage_in_page(h.usage));
39683366Sjulian				break;
39763670Snsayer			case hid_endcollection:
39863670Snsayer				if (coll[0])
39983043Sbrooks					*strrchr(coll, '.') = 0;
40083043Sbrooks				break;
40163670Snsayer			default:
40293593Sjhb				break;
40363670Snsayer			}
40463670Snsayer		}
40583043Sbrooks		if (ignore) {
40683043Sbrooks			if (verbose)
40783043Sbrooks				warnx("ignore item '%s'", name);
40883043Sbrooks			continue;
40983043Sbrooks		}
41083043Sbrooks		if (isdemon) {
41183043Sbrooks			syslog(LOG_WARNING, "config file `%s', line %d, HID "
41283043Sbrooks			       "item not found: `%s'\n", conf, line, name);
41383043Sbrooks			freecommands(cmds);
41483043Sbrooks			return (NULL);
41583043Sbrooks		} else {
41683043Sbrooks			errx(1, "config file `%s', line %d, HID item "
41783043Sbrooks			     "not found: `%s'\n", conf, line, name);
41883043Sbrooks		}
41963670Snsayer
42063670Snsayer	foundhid:
42163670Snsayer		hid_end_parse(d);
42263670Snsayer		cmd->lastseen = -1;
42363670Snsayer		cmd->lastused = -1;
42463670Snsayer		cmd->item = h;
42583043Sbrooks		cmd->name = strdup(name);
42683043Sbrooks		cmd->action = strdup(action);
42763670Snsayer		if (range) {
42863861Snsayer			if (cmd->value == 1)
42963861Snsayer				cmd->value = u - lo;
43083043Sbrooks			else
43183366Sjulian				cmd->value = -1;
43263670Snsayer		}
43363670Snsayer
43483043Sbrooks		if (verbose)
43583043Sbrooks			printf("PARSE:%d %s, %d, '%s'\n", cmd->line, name,
43663670Snsayer			       cmd->value, cmd->action);
43763670Snsayer	}
43863670Snsayer	fclose(f);
43963670Snsayer	return (cmds);
44063670Snsayer}
44163670Snsayer
44263670Snsayervoid
44363670Snsayerdocmd(struct command *cmd, int value, const char *hid, int argc, char **argv)
44463670Snsayer{
44563670Snsayer	char cmdbuf[SIZE], *p, *q;
44663670Snsayer	size_t len;
44783366Sjulian	int n, r;
44863670Snsayer
44963670Snsayer	for (p = cmd->action, q = cmdbuf; *p && q < &cmdbuf[SIZE-1]; ) {
45063670Snsayer		if (*p == '$') {
45183366Sjulian			p++;
45263670Snsayer			len = &cmdbuf[SIZE-1] - q;
45383043Sbrooks			if (isdigit(*p)) {
45463670Snsayer				n = strtol(p, &p, 10) - 1;
45563670Snsayer				if (n >= 0 && n < argc) {
45663670Snsayer					strncpy(q, argv[n], len);
45787914Sjlemon					q += strlen(q);
45883043Sbrooks				}
45983043Sbrooks			} else if (*p == 'V') {
46063670Snsayer				p++;
46183043Sbrooks				snprintf(q, len, "%d", value);
46263670Snsayer				q += strlen(q);
46363803Snsayer			} else if (*p == 'N') {
46463803Snsayer				p++;
46563803Snsayer				strncpy(q, cmd->name, len);
46663803Snsayer				q += strlen(q);
46763803Snsayer			} else if (*p == 'H') {
46863803Snsayer				p++;
46963670Snsayer				strncpy(q, hid, len);
47063670Snsayer				q += strlen(q);
47163670Snsayer			} else if (*p) {
47263670Snsayer				*q++ = *p++;
47363670Snsayer			}
47463670Snsayer		} else {
47563803Snsayer			*q++ = *p++;
47663670Snsayer		}
47763670Snsayer	}
47863670Snsayer	*q = 0;
47963670Snsayer
48063670Snsayer	if (verbose)
48163670Snsayer		printf("system '%s'\n", cmdbuf);
48263670Snsayer	r = system(cmdbuf);
48363670Snsayer	if (verbose > 1 && r)
48463670Snsayer		printf("return code = 0x%x\n", r);
48563670Snsayer}
48663670Snsayer
48763670Snsayervoid
48863670Snsayerfreecommands(struct command *cmd)
48963670Snsayer{
49063670Snsayer	struct command *next;
49163670Snsayer
49263670Snsayer	while (cmd) {
49363670Snsayer		next = cmd->next;
49463670Snsayer		free(cmd);
49563670Snsayer		cmd = next;
49663670Snsayer	}
49763670Snsayer}
49863670Snsayer