diff --git a/usr.bin/Makefile b/usr.bin/Makefile index 8a4b5ce9703a..ea43e5505663 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -186,6 +186,7 @@ SUBDIR= alias \ uniq \ units \ unvis \ + usbhidaction \ usbhidctl \ users \ uudecode \ diff --git a/usr.bin/usbhidaction/Makefile b/usr.bin/usbhidaction/Makefile new file mode 100644 index 000000000000..8f5a6b58c2a3 --- /dev/null +++ b/usr.bin/usbhidaction/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ +# $NetBSD: Makefile,v 1.4 2002/02/02 16:54:26 veego Exp $ + +PROG= usbhidaction +SRCS= usbhidaction.c + +LDADD+= -lusbhid +DPADD+= ${LIBUSBHID} + +.include diff --git a/usr.bin/usbhidaction/usbhidaction.1 b/usr.bin/usbhidaction/usbhidaction.1 new file mode 100644 index 000000000000..b0b491e3d32a --- /dev/null +++ b/usr.bin/usbhidaction/usbhidaction.1 @@ -0,0 +1,154 @@ +.\" $FreeBSD$ +.\" $NetBSD: usbhidaction.1,v 1.8 2003/02/25 10:35:59 wiz Exp $ +.\" +.\" Copyright (c) 2000 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Lennart Augustsson (lennart@augustsson.net). +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the NetBSD +.\" Foundation, Inc. and its contributors. +.\" 4. Neither the name of The NetBSD Foundation nor the names of its +.\" contributors may be used to endorse or promote products derived +.\" from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd April 9, 2003 +.Dt USBHIDACTION 1 +.Os +.Sh NAME +.Nm usbhidaction +.Nd perform actions according to USB HID controls +.Sh SYNOPSIS +.Nm +.Fl c Ar config-file +.Op Fl d +.Op Fl i +.Fl f Ar device +.Op Fl p Ar pidfile +.Op Fl v +.Ar arg ... +.Sh DESCRIPTION +.Nm +can be used to execute commands when certain values appear on HID controls. +The normal operation for this program is to read the configuration file +and then become a daemon and execute commands as the HID items specify. +If a read from the HID device fails the program dies; this will make it +die when the USB device is unplugged. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl c Ar config-file +Specify a path name for the config file. +.It Fl d +Toggle the daemon flag. +.It Fl i +Ignore HID items in the config file that does not exist in the device. +.It Fl f Ar device +Specify a path name for the device to operate on. +If +.Ar device +is numeric, it is taken to be the USB HID device number. +If it is a relative +path, it is taken to be the name of the device under +.Pa /dev . +An absolute path is taken to be the literal device pathname. +.It Fl p Ar pidfile +Specify an alternate file in which to store the process ID. +.It Fl v +Be verbose, and do not become a daemon. +.El +.Pp +The config file will be re-read if the process gets a HUP signal. +.Sh CONFIGURATION +The configuration file has a very simple format. +Each line describes an +action; if a line begins with a whitespace it is considered a continuation +of the previous line. +Lines beginning with `#' are considered as comments. +.Pp +Each line has three parts: a name of a USB HID item, a value for that item, +and an action. +There must be whitespace between the parts. +.Pp +The item names are similar to those used by +.Xr usbhidctl 1 , +but each part must be prefixed by its page name. +.Pp +The value is simply a numeric value. +When the item reports this value +the action will be performed. +If the value is `*' it will match any value. +.Pp +The action is a normal command that is executed with +.Xr system 3 . +Before it is executed some substitution will occur: +`$n' will be replaced by the nth argument on the +command line, `$V' will be replaced by the numeric value +of the HID item, `$N' will be replaced by the name +of the control, and `$H' will be replaced by the name +of the HID device. +.Sh FILES +.Bl -tag -indent +.It Pa /usr/share/misc/usb_hid_usages +The HID usage table. +.It Pa /var/run/usbaction.pid +The default location of the pid file. +.El +.Sh EXAMPLES +The following configuration file can be used to control a pair +of Philips USB speakers with the HID controls on the speakers. +.Bd -literal -offset indent +# Configuration for various Philips USB speakers +Consumer:Consumer_Control.Consumer:Volume_Up 1 + mixerctl -f $1 -n -w fea8-i7-master++ +Consumer:Consumer_Control.Consumer:Volume_Down 1 + mixerctl -f $1 -n -w fea8-i7-master-- +Consumer:Consumer_Control.Consumer:Mute 1 + mixerctl -f $1 -n -w fea8-i7-mute++ +Consumer:Consumer_Control.Consumer:Channel_Top.Microsoft:Base_Up 1 + mixerctl -f $1 -n -w fea8-i7-bass++ +Consumer:Consumer_Control.Consumer:Channel_Top.Microsoft:Base_Down 1 + mixerctl -f $1 -n -w fea8-i7-bass-- +.Ed +.Pp +A sample invocation using this configuration would be +.Bd -literal -offset indent +usbhidaction -f /dev/uhid1 -c conf /dev/mixer1 +.Ed +.Sh SEE ALSO +.Xr usbhidctl 1 , +.Xr usbhid 3 , +.Xr uhid 4 , +.Xr usb 4 +.Sh HISTORY +The +.Nm +command first appeared in +.Nx 1.6 . +The +.Nm +command appeard in +.Fx 5.1 . diff --git a/usr.bin/usbhidaction/usbhidaction.c b/usr.bin/usbhidaction/usbhidaction.c new file mode 100644 index 000000000000..22868b138a21 --- /dev/null +++ b/usr.bin/usbhidaction/usbhidaction.c @@ -0,0 +1,449 @@ +/* $NetBSD: usbhidaction.c,v 1.8 2002/06/11 06:06:21 itojun Exp $ */ +/* $FreeBSD$ */ + +/* + * Copyright (c) 2000, 2002 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int verbose = 0; +static int isdemon = 0; +static int reparse = 1; +static char * pidfile = "/var/run/usbaction.pid"; + +struct command { + struct command *next; + int line; + + struct hid_item item; + int value; + char anyvalue; + char *name; + char *action; +}; +struct command *commands; + +#define SIZE 4000 + +void usage(void); +struct command *parse_conf(const char *, report_desc_t, int, int); +void docmd(struct command *, int, const char *, int, char **); +void freecommands(struct command *); + +static void +sighup(int sig) +{ + reparse = 1; +} + +int +main(int argc, char **argv) +{ + const char *conf = NULL; + const char *dev = NULL; + int fd, fp, ch, sz, n, val, i; + int demon, ignore; + report_desc_t repd; + char buf[100]; + char devnamebuf[PATH_MAX]; + struct command *cmd; + int reportid; + + demon = 1; + ignore = 0; + while ((ch = getopt(argc, argv, "c:df:ip:v")) != -1) { + switch(ch) { + case 'c': + conf = optarg; + break; + case 'd': + demon ^= 1; + break; + case 'i': + ignore++; + break; + case 'f': + dev = optarg; + break; + case 'p': + pidfile = optarg; + break; + case 'v': + demon = 0; + verbose++; + break; + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (conf == NULL || dev == NULL) + usage(); + + hid_init(NULL); + + if (dev[0] != '/') { + snprintf(devnamebuf, sizeof(devnamebuf), "/dev/%s%s", + isdigit(dev[0]) ? "uhid" : "", dev); + dev = devnamebuf; + } + + fd = open(dev, O_RDWR); + if (fd < 0) + err(1, "%s", dev); + if (ioctl(fd, USB_GET_REPORT_ID, &reportid) < 0) + reportid = -1; + repd = hid_get_report_desc(fd); + if (repd == NULL) + err(1, "hid_get_report_desc() failed"); + + commands = parse_conf(conf, repd, reportid, ignore); + + sz = hid_report_size(repd, hid_input, reportid); + + if (verbose) + printf("report size %d\n", sz); + if (sz > sizeof buf) + errx(1, "report too large"); + + (void)signal(SIGHUP, sighup); + + if (demon) { + if (daemon(0, 0) < 0) + err(1, "daemon()"); + fp = open(pidfile, O_WRONLY|O_CREAT, S_IRUSR|S_IRGRP|S_IROTH); + if (fp >= 0) { + sz=snprintf(buf,100, "%d\n", getpid()); + write(fp, buf, sz); + close(fp); + } else + err(1, "%s", pidfile); + isdemon = 1; + } + + for(;;) { + n = read(fd, buf, sz); + if (verbose > 2) { + printf("read %d bytes:", n); + for (i = 0; i < n; i++) + printf(" %02x", buf[i]); + printf("\n"); + } + if (n < 0) { + if (verbose) + err(1, "read"); + else + exit(1); + } +#if 0 + if (n != sz) { + err(2, "read size"); + } +#endif + for (cmd = commands; cmd; cmd = cmd->next) { + val = hid_get_data(buf, &cmd->item); + if (cmd->value == val || cmd->anyvalue) + docmd(cmd, val, dev, argc, argv); + } + if (reparse) { + struct command *cmds = + parse_conf(conf, repd, reportid, ignore); + if (cmds) { + freecommands(commands); + commands = cmds; + } + reparse = 0; + } + } + + exit(0); +} + +void +usage(void) +{ + + fprintf(stderr, "Usage: %s -c config_file [-d] -f hid_dev " + "[-i] [-p pidfile] [-v]\n", getprogname()); + exit(1); +} + +static int +peek(FILE *f) +{ + int c; + + c = getc(f); + if (c != EOF) + ungetc(c, f); + return c; +} + +struct command * +parse_conf(const char *conf, report_desc_t repd, int reportid, int ignore) +{ + FILE *f; + char *p; + int line; + char buf[SIZE], name[SIZE], value[SIZE], action[SIZE]; + char usage[SIZE], coll[SIZE]; + struct command *cmd, *cmds; + struct hid_data *d; + struct hid_item h; + int u, lo, hi, range; + + + f = fopen(conf, "r"); + if (f == NULL) + err(1, "%s", conf); + + cmds = NULL; + for (line = 1; ; line++) { + if (fgets(buf, sizeof buf, f) == NULL) + break; + if (buf[0] == '#' || buf[0] == '\n') + continue; + p = strchr(buf, '\n'); + while (p && isspace(peek(f))) { + if (fgets(p, sizeof buf - strlen(buf), f) == NULL) + break; + p = strchr(buf, '\n'); + } + if (p) + *p = 0; + if (sscanf(buf, "%s %s %[^\n]", name, value, action) != 3) { + if (isdemon) { + syslog(LOG_WARNING, "config file `%s', line %d" + ", syntax error: %s", conf, line, buf); + freecommands(cmds); + return (NULL); + } else { + errx(1, "config file `%s', line %d," + ", syntax error: %s", conf, line, buf); + } + } + + cmd = malloc(sizeof *cmd); + if (cmd == NULL) + err(1, "malloc failed"); + cmd->next = cmds; + cmds = cmd; + cmd->line = line; + + if (strcmp(value, "*") == 0) { + cmd->anyvalue = 1; + } else { + cmd->anyvalue = 0; + if (sscanf(value, "%d", &cmd->value) != 1) { + if (isdemon) { + syslog(LOG_WARNING, + "config file `%s', line %d, " + "bad value: %s\n", + conf, line, value); + freecommands(cmds); + return (NULL); + } else { + errx(1, "config file `%s', line %d, " + "bad value: %s\n", + conf, line, value); + } + } + } + + coll[0] = 0; + for (d = hid_start_parse(repd, 1 << hid_input, reportid); + hid_get_item(d, &h); ) { + if (verbose > 2) + printf("kind=%d usage=%x\n", h.kind, h.usage); + if (h.flags & HIO_CONST) + continue; + switch (h.kind) { + case hid_input: + if (h.usage_minimum != 0 || + h.usage_maximum != 0) { + lo = h.usage_minimum; + hi = h.usage_maximum; + range = 1; + } else { + lo = h.usage; + hi = h.usage; + range = 0; + } + for (u = lo; u <= hi; u++) { + snprintf(usage, sizeof usage, "%s:%s", + hid_usage_page(HID_PAGE(u)), + hid_usage_in_page(u)); + if (verbose > 2) + printf("usage %s\n", usage); + if (!strcasecmp(usage, name)) + goto foundhid; + if (coll[0]) { + snprintf(usage, sizeof usage, + "%s.%s:%s", coll+1, + hid_usage_page(HID_PAGE(u)), + hid_usage_in_page(u)); + if (verbose > 2) + printf("usage %s\n", + usage); + if (!strcasecmp(usage, name)) + goto foundhid; + } + } + break; + case hid_collection: + snprintf(coll + strlen(coll), + sizeof coll - strlen(coll), ".%s:%s", + hid_usage_page(HID_PAGE(h.usage)), + hid_usage_in_page(h.usage)); + break; + case hid_endcollection: + if (coll[0]) + *strrchr(coll, '.') = 0; + break; + default: + break; + } + } + if (ignore) { + if (verbose) + warnx("ignore item '%s'", name); + continue; + } + if (isdemon) { + syslog(LOG_WARNING, "config file `%s', line %d, HID " + "item not found: `%s'\n", conf, line, name); + freecommands(cmds); + return (NULL); + } else { + errx(1, "config file `%s', line %d, HID item " + "not found: `%s'\n", conf, line, name); + } + + foundhid: + hid_end_parse(d); + cmd->item = h; + cmd->name = strdup(name); + cmd->action = strdup(action); + if (range) { + if (cmd->value == 1) + cmd->value = u - lo; + else + cmd->value = -1; + } + + if (verbose) + printf("PARSE:%d %s, %d, '%s'\n", cmd->line, name, + cmd->value, cmd->action); + } + fclose(f); + return (cmds); +} + +void +docmd(struct command *cmd, int value, const char *hid, int argc, char **argv) +{ + char cmdbuf[SIZE], *p, *q; + size_t len; + int n, r; + + for (p = cmd->action, q = cmdbuf; *p && q < &cmdbuf[SIZE-1]; ) { + if (*p == '$') { + p++; + len = &cmdbuf[SIZE-1] - q; + if (isdigit(*p)) { + n = strtol(p, &p, 10) - 1; + if (n >= 0 && n < argc) { + strncpy(q, argv[n], len); + q += strlen(q); + } + } else if (*p == 'V') { + p++; + snprintf(q, len, "%d", value); + q += strlen(q); + } else if (*p == 'N') { + p++; + strncpy(q, cmd->name, len); + q += strlen(q); + } else if (*p == 'H') { + p++; + strncpy(q, hid, len); + q += strlen(q); + } else if (*p) { + *q++ = *p++; + } + } else { + *q++ = *p++; + } + } + *q = 0; + + if (verbose) + printf("system '%s'\n", cmdbuf); + r = system(cmdbuf); + if (verbose > 1 && r) + printf("return code = 0x%x\n", r); +} + +void +freecommands(struct command *cmd) +{ + struct command *next; + + while (cmd) { + next = cmd->next; + free(cmd); + cmd = next; + } +}