diff options
author | Nathan Binkert <nate@cvs.openbsd.org> | 2002-05-10 00:09:18 +0000 |
---|---|---|
committer | Nathan Binkert <nate@cvs.openbsd.org> | 2002-05-10 00:09:18 +0000 |
commit | a82d07519e6786b844ed49d488f67672db78daa2 (patch) | |
tree | 9ed579bb5297f1a9a77613ce6171eb968d7bf508 /usr.bin | |
parent | 0dd51d96675add74c11132637e3499fceb185340 (diff) |
Update usb userland stuff to reflect hid changes in the kernel.
This adds the new program usbhidaction which can be used to assign actions
to events that occur on a uhid device. For example, you can now make the
volume buttons on some newer keyboards actually do something.
Diffstat (limited to 'usr.bin')
-rw-r--r-- | usr.bin/usbhidaction/Makefile | 10 | ||||
-rw-r--r-- | usr.bin/usbhidaction/usbhidaction.1 | 137 | ||||
-rw-r--r-- | usr.bin/usbhidaction/usbhidaction.c | 439 | ||||
-rw-r--r-- | usr.bin/usbhidctl/usbhid.c | 447 | ||||
-rw-r--r-- | usr.bin/usbhidctl/usbhidctl.1 | 99 |
5 files changed, 979 insertions, 153 deletions
diff --git a/usr.bin/usbhidaction/Makefile b/usr.bin/usbhidaction/Makefile new file mode 100644 index 00000000000..1ce2fe8b7ad --- /dev/null +++ b/usr.bin/usbhidaction/Makefile @@ -0,0 +1,10 @@ +# $OpenBSD: Makefile,v 1.1 2002/05/10 00:09:17 nate Exp $ +# $NetBSD: Makefile,v 1.4 2002/02/02 16:54:26 veego Exp $ + +PROG= usbhidaction +SRCS= usbhidaction.c + +LDADD+= -lusbhid -lutil +DPADD+= ${LIBUSBHID} ${LIBUTIL} + +.include <bsd.prog.mk> diff --git a/usr.bin/usbhidaction/usbhidaction.1 b/usr.bin/usbhidaction/usbhidaction.1 new file mode 100644 index 00000000000..9f075b80b13 --- /dev/null +++ b/usr.bin/usbhidaction/usbhidaction.1 @@ -0,0 +1,137 @@ +.\" $OpenBSD: usbhidaction.1,v 1.1 2002/05/10 00:09:17 nate Exp $ +.\" $NetBSD: usbhidaction.1,v 1.6 2002/01/18 14:38:59 augustss 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 December 29, 2000 +.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 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 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 +.Pa /usr/share/misc/usb_hid_usages +The HID usage table. +.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 +.Ox 3.2 . diff --git a/usr.bin/usbhidaction/usbhidaction.c b/usr.bin/usbhidaction/usbhidaction.c new file mode 100644 index 00000000000..7b218f5e49f --- /dev/null +++ b/usr.bin/usbhidaction/usbhidaction.c @@ -0,0 +1,439 @@ +/* $OpenBSD: usbhidaction.c,v 1.1 2002/05/10 00:09:17 nate Exp $ */ +/* $NetBSD: usbhidaction.c,v 1.7 2002/01/18 14:38:59 augustss Exp $ */ + +/* + * 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 <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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <err.h> +#include <fcntl.h> +#include <limits.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> +#include <usbhid.h> +#include <util.h> +#include <syslog.h> +#include <signal.h> + +int verbose = 0; +int isdemon = 0; +int reparse = 1; + +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, 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:iv")) != -1) { + switch(ch) { + case 'c': + conf = optarg; + break; + case 'd': + demon ^= 1; + break; + case 'i': + ignore++; + break; + case 'f': + dev = 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\n"); + + 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()"); + pidfile(NULL); + 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) +{ + extern char *__progname; + + fprintf(stderr, "Usage: %s -c config_file [-d] -f hid_dev " + "[-i] [-v]\n", __progname); + 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'\n", 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; + } +} diff --git a/usr.bin/usbhidctl/usbhid.c b/usr.bin/usbhidctl/usbhid.c index 1751eb8d1ea..83dd7f7abf7 100644 --- a/usr.bin/usbhidctl/usbhid.c +++ b/usr.bin/usbhidctl/usbhid.c @@ -1,8 +1,8 @@ -/* $OpenBSD: usbhid.c,v 1.3 2002/05/02 20:12:07 nate Exp $ */ -/* $NetBSD: usbhid.c,v 1.17 2001/03/28 03:17:42 simonb Exp $ */ +/* $OpenBSD: usbhid.c,v 1.4 2002/05/10 00:09:17 nate Exp $ */ +/* $NetBSD: usbhid.c,v 1.22 2002/02/20 20:30:42 christos Exp $ */ /* - * Copyright (c) 2000 The NetBSD Foundation, Inc. + * Copyright (c) 2001 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation @@ -53,14 +53,18 @@ #include <unistd.h> #include <usbhid.h> +/* + * Zero if not in a verbose mode. Greater levels of verbosity + * are indicated by values larger than one. + */ +unsigned int verbose; + /* Parser tokens */ #define DELIM_USAGE '.' #define DELIM_PAGE ':' #define DELIM_SET '=' -/* Zero if not in a verbose mode. Greater levels of verbosity are - indicated by values larger than one. */ -static unsigned int verbose; +static int reportid; struct Susbvar { /* Variable name, not NUL terminated */ @@ -77,6 +81,7 @@ struct Susbvar { #define MATCH_SHOWPAGENAME (1 << 5) #define MATCH_SHOWNUMERIC (1 << 6) #define MATCH_WRITABLE (1 << 7) +#define MATCH_SHOWVALUES (1 << 8) unsigned int mflags; /* Workspace for hidmatch() */ @@ -108,11 +113,183 @@ static struct { #define REPORT_MAXVAL 2 }; +/* + * Extract 16-bit unsigned usage ID from a numeric string. Returns -1 + * if string failed to parse correctly. + */ +static int +strtousage(const char *nptr, size_t nlen) +{ + char *endptr; + long result; + char numstr[16]; + + if (nlen >= sizeof(numstr) || !isdigit((unsigned char)*nptr)) + return -1; + + /* + * We use strtol() here, but unfortunately strtol() requires a + * NUL terminated string - which we don't have - at least not + * officially. + */ + memcpy(numstr, nptr, nlen); + numstr[nlen] = '\0'; + + result = strtol(numstr, &endptr, 0); + + if (result < 0 || result > 0xffff || endptr != &numstr[nlen]) + return -1; + + return result; +} + +struct usagedata { + char const *page_name; + char const *usage_name; + size_t page_len; + size_t usage_len; + int isfinal; + u_int32_t usage_id; +}; + +/* + * Test a rule against the current usage data. Returns -1 on no + * match, 0 on partial match and 1 on complete match. + */ +static int +hidtestrule(struct Susbvar *var, struct usagedata *cache) +{ + char const *varname; + ssize_t matchindex, pagesplit; + size_t strind, varlen; + int numusage; + u_int32_t usage_id; + + matchindex = var->matchindex; + varname = var->variable; + varlen = var->varlen; + + usage_id = cache->usage_id; + + /* + * Parse the current variable name, locating the end of the + * current 'usage', and possibly where the usage page name + * ends. + */ + pagesplit = -1; + for (strind = matchindex; strind < varlen; strind++) { + if (varname[strind] == DELIM_USAGE) + break; + if (varname[strind] == DELIM_PAGE) + pagesplit = strind; + } + + if (cache->isfinal && strind != varlen) + /* + * Variable name is too long (hit delimiter instead of + * end-of-variable). + */ + return -1; + + if (pagesplit >= 0) { + /* + * Page name was specified, determine whether it was + * symbolic or numeric. + */ + char const *strstart; + int numpage; + + strstart = &varname[matchindex]; + + numpage = strtousage(strstart, pagesplit - matchindex); + + if (numpage >= 0) { + /* Valid numeric */ + + if (numpage != HID_PAGE(usage_id)) + /* Numeric didn't match page ID */ + return -1; + } else { + /* Not a valid numeric */ + + /* + * Load and cache the page name if and only if + * it hasn't already been loaded (it's a + * fairly expensive operation). + */ + if (cache->page_name == NULL) { + cache->page_name = hid_usage_page(HID_PAGE(usage_id)); + cache->page_len = strlen(cache->page_name); + } + + /* + * Compare specified page name to actual page + * name. + */ + if (cache->page_len != + (size_t)(pagesplit - matchindex) || + memcmp(cache->page_name, + &varname[matchindex], + cache->page_len) != 0) + /* Mismatch, page name wrong */ + return -1; + } + + /* Page matches, discard page name */ + matchindex = pagesplit + 1; + } + + numusage = strtousage(&varname[matchindex], strind - matchindex); + + if (numusage >= 0) { + /* Valid numeric */ + + if (numusage != HID_USAGE(usage_id)) + /* Numeric didn't match usage ID */ + return -1; + } else { + /* Not a valid numeric */ + + /* Load and cache the usage name */ + if (cache->usage_name == NULL) { + cache->usage_name = hid_usage_in_page(usage_id); + cache->usage_len = strlen(cache->usage_name); + } + + /* + * Compare specified usage name to actual usage name + */ + if (cache->usage_len != (size_t)(strind - matchindex) || + memcmp(cache->usage_name, &varname[matchindex], + cache->usage_len) != 0) + /* Mismatch, usage name wrong */ + return -1; + } + + if (cache->isfinal) + /* Match */ + return 1; + + /* + * Partial match: Move index past this usage string + + * delimiter + */ + var->matchindex = strind + 1; + + return 0; +} + +/* + * hidmatch() determines whether the item specified in 'item', and + * nested within a heirarchy of collections specified in 'collist' + * matches any of the rules in the list 'varlist'. Returns the + * matching rule on success, or NULL on no match. + */ static struct Susbvar* hidmatch(u_int32_t const *collist, size_t collen, struct hid_item *item, struct Susbvar *varlist, size_t vlsize) { - size_t vlind, colind, vlactive; + size_t colind, vlactive, vlind; int iscollection; /* @@ -161,96 +338,52 @@ hidmatch(u_int32_t const *collist, size_t collen, struct hid_item *item, } } + /* + * Loop through each usage in the collection list, including + * the 'item' itself on the final iteration. For each usage, + * test which variables named in the rule list are still + * applicable - if any. + */ for (colind = 0; vlactive > 0 && colind <= collen; colind++) { - char const *usage_name, *page_name; - size_t usage_len, page_len; - int final; - u_int32_t usage_id; - - final = (colind == collen); + struct usagedata cache; - if (final) - usage_id = item->usage; + cache.isfinal = (colind == collen); + if (cache.isfinal) + cache.usage_id = item->usage; else - usage_id = collist[colind]; + cache.usage_id = collist[colind]; - usage_name = hid_usage_in_page(usage_id); - usage_len = strlen(usage_name); - - page_name = NULL; + cache.usage_name = NULL; + cache.page_name = NULL; + /* + * Loop through each rule, testing whether the rule is + * still applicable or not. For each rule, + * 'matchindex' retains the current match state as an + * index into the variable name string, or -1 if this + * rule has been proven not to match. + */ for (vlind = 0; vlind < vlsize; vlind++) { - ssize_t matchindex, pagesplit; - size_t varlen, strind; - char const *varname; struct Susbvar *var; + int matchres; var = &varlist[vlind]; - matchindex = var->matchindex; - varname = var->variable; - varlen = var->varlen; - - if (matchindex < 0) + if (var->matchindex < 0) /* Mismatch at a previous level */ continue; - pagesplit = -1; - for (strind = matchindex; strind < varlen; strind++) { - if (varname[strind] == DELIM_USAGE) - break; - if (varname[strind] == DELIM_PAGE) - pagesplit = strind; - } + matchres = hidtestrule(var, &cache); - if (final && strind != varlen) { - /* - * Variable name is too long (hit - * delimiter instead of - * end-of-variable) - */ - var->matchindex = -1; - vlactive--; - continue; - } - - if (pagesplit >= 0) { - if (page_name == NULL) { - page_name = hid_usage_page(HID_PAGE(usage_id)); - page_len = strlen(page_name); - } - if (page_len != - (size_t)(pagesplit - matchindex) || - memcmp(page_name, &varname[matchindex], - page_len) != 0) { - /* Mismatch, page name wrong */ - var->matchindex = -1; - vlactive--; - continue; - } - - /* Page matches, discard page name */ - matchindex = pagesplit + 1; - } - - if (usage_len != strind - matchindex || - memcmp(usage_name, &varname[matchindex], - usage_len) != 0) { - /* Mismatch, usage name wrong */ + if (matchres < 0) { + /* Bad match */ var->matchindex = -1; vlactive--; continue; - } - - if (final) - /* Match */ + } else if (matchres > 0) { + /* Complete match */ return var; - - /* - * Partial match: Move index past this usage - * string + delimiter - */ - var->matchindex = matchindex + usage_len + 1; + } } } @@ -262,8 +395,7 @@ allocreport(struct Sreport *report, report_desc_t rd, int repindex) { int reptsize; - reptsize = hid_report_size(rd, reptoparam[repindex].hid_kind, - &report->report_id); + reptsize = hid_report_size(rd, reptoparam[repindex].hid_kind, reportid); if (reptsize < 0) errx(1, "Negative report size"); report->size = reptsize; @@ -302,7 +434,8 @@ getreport(struct Sreport *report, int hidfd, report_desc_t rd, int repindex) report->buffer->ucr_report = reptoparam[repindex].uhid_report; if (ioctl(hidfd, USB_GET_REPORT, report->buffer) < 0) - err(1, "USB_GET_REPORT"); + err(1, "USB_GET_REPORT (probably not supported by " + "device)"); } } @@ -335,19 +468,30 @@ varop_display(struct hid_item *item, struct Susbvar *var, u_int32_t const *collist, size_t collen, u_char *buf) { size_t colitem; - - for (colitem = 0; colitem < collen; colitem++) { + int val, i; + + for (i = 0; i < item->report_count; i++) { + for (colitem = 0; colitem < collen; colitem++) { + if (var->mflags & MATCH_SHOWPAGENAME) + printf("%s:", + hid_usage_page(HID_PAGE(collist[colitem]))); + printf("%s.", hid_usage_in_page(collist[colitem])); + } if (var->mflags & MATCH_SHOWPAGENAME) - printf("%s:", - hid_usage_page(HID_PAGE(collist[colitem]))); - printf("%s.", hid_usage_in_page(collist[colitem])); + printf("%s:", hid_usage_page(HID_PAGE(item->usage))); + val = hid_get_data(buf, item); + item->pos += item->report_size; + if (item->usage_minimum != 0 || item->usage_maximum != 0) { + val += item->usage_minimum; + printf("%s=1", hid_usage_in_page(val)); + } else { + printf("%s=%d%s", hid_usage_in_page(item->usage), + val, item->flags & HIO_CONST ? " (const)" : ""); + } + if (item->report_count > 1) + printf(" [%d]", i); + printf("\n"); } - - if (var->mflags & MATCH_SHOWPAGENAME) - printf("%s:", hid_usage_page(HID_PAGE(item->usage))); - printf("%s=%d%s\n", hid_usage_in_page(item->usage), - hid_get_data(buf, item), - (item->flags & HIO_CONST) ? " (const)" : ""); return 0; } @@ -362,12 +506,8 @@ varop_modify(struct hid_item *item, struct Susbvar *var, hid_set_data(buf, item, dataval); - if (verbose >= 1) - /* - * Allow displaying of set value in verbose mode. - * This isn't particularly useful though, so don't - * bother documenting it. - */ + if (var->mflags & MATCH_SHOWVALUES) + /* Display set value */ varop_display(item, var, collist, collen, buf); return 1; @@ -376,14 +516,28 @@ varop_modify(struct hid_item *item, struct Susbvar *var, static void reportitem(char const *label, struct hid_item const *item, unsigned int mflags) { - printf("%s size=%d count=%d page=%s usage=%s%s", label, + int isconst = item->flags & HIO_CONST, + isvar = item->flags & HIO_VARIABLE; + printf("%s size=%d count=%d%s%s page=%s", label, item->report_size, item->report_count, - hid_usage_page(HID_PAGE(item->usage)), - hid_usage_in_page(item->usage), - item->flags & HIO_CONST ? " Const" : ""); - if (mflags & MATCH_SHOWNUMERIC) - printf(" (%u/0x%x)", - HID_PAGE(item->usage), HID_USAGE(item->usage)); + isconst ? " Const" : "", + !isvar && !isconst ? " Array" : "", + hid_usage_page(HID_PAGE(item->usage))); + if (item->usage_minimum != 0 || item->usage_maximum != 0) { + printf(" usage=%s..%s", hid_usage_in_page(item->usage_minimum), + hid_usage_in_page(item->usage_maximum)); + if (mflags & MATCH_SHOWNUMERIC) + printf(" (%u:0x%x..%u:0x%x)", + HID_PAGE(item->usage_minimum), + HID_USAGE(item->usage_minimum), + HID_PAGE(item->usage_maximum), + HID_USAGE(item->usage_maximum)); + } else { + printf(" usage=%s", hid_usage_in_page(item->usage)); + if (mflags & MATCH_SHOWNUMERIC) + printf(" (%u:0x%x)", + HID_PAGE(item->usage), HID_USAGE(item->usage)); + } printf(", logical range %d..%d", item->logical_minimum, item->logical_maximum); if (item->physical_minimum != item->physical_maximum) @@ -402,9 +556,14 @@ varop_report(struct hid_item *item, struct Susbvar *var, { switch (item->kind) { case hid_collection: - printf("Collection page=%s usage=%s\n", + printf("Collection page=%s usage=%s", hid_usage_page(HID_PAGE(item->usage)), hid_usage_in_page(item->usage)); + if (var->mflags & MATCH_SHOWNUMERIC) + printf(" (%u:0x%x)\n", + HID_PAGE(item->usage), HID_USAGE(item->usage)); + else + printf("\n"); break; case hid_endcollection: printf("End collection\n"); @@ -426,13 +585,12 @@ varop_report(struct hid_item *item, struct Susbvar *var, static void devloop(int hidfd, report_desc_t rd, struct Susbvar *varlist, size_t vlsize) { + u_char *dbuf; struct hid_data *hdata; + size_t collind, dlen; struct hid_item hitem; u_int32_t colls[128]; struct Sreport inreport; - size_t dlen; - u_char *dbuf; - size_t collind; allocreport(&inreport, rd, REPORT_INPUT); @@ -446,12 +604,14 @@ devloop(int hidfd, report_desc_t rd, struct Susbvar *varlist, size_t vlsize) ssize_t readlen; readlen = read(hidfd, dbuf, dlen); - if (readlen < 0 || dlen != (size_t)readlen) - err(1, "bad read %ld != %ld", - (long)readlen, (long)dlen); + if (readlen < 0) + err(1, "Device read error"); + if (dlen != (size_t)readlen) + errx(1, "Unexpected response length: %lu != %lu", + (unsigned long)readlen, (unsigned long)dlen); collind = 0; - hdata = hid_start_parse(rd, 1 << hid_input); + hdata = hid_start_parse(rd, 1 << hid_input, reportid); if (hdata == NULL) errx(1, "Failed to start parser"); @@ -476,6 +636,9 @@ devloop(int hidfd, report_desc_t rd, struct Susbvar *varlist, size_t vlsize) errx(1, "Unexpected non-input item returned"); } + if (reportid != -1 && hitem.report_ID != reportid) + continue; + matchvar = hidmatch(colls, collind, &hitem, varlist, vlsize); @@ -495,10 +658,9 @@ devshow(int hidfd, report_desc_t rd, struct Susbvar *varlist, size_t vlsize, int kindset) { struct hid_data *hdata; + size_t collind, repind, vlind; struct hid_item hitem; u_int32_t colls[128]; - size_t collind, repind, vlind; - struct Sreport reports[REPORT_MAXVAL + 1]; @@ -509,9 +671,7 @@ devshow(int hidfd, report_desc_t rd, struct Susbvar *varlist, size_t vlsize, } collind = 0; - hdata = hid_start_parse(rd, kindset | - (1 << hid_collection) | - (1 << hid_endcollection)); + hdata = hid_start_parse(rd, kindset, reportid); if (hdata == NULL) errx(1, "Failed to start parser"); @@ -519,6 +679,9 @@ devshow(int hidfd, report_desc_t rd, struct Susbvar *varlist, size_t vlsize, struct Susbvar *matchvar; int repindex; + if (verbose > 3) + printf("item: kind=%d repid=%d usage=0x%x\n", + hitem.kind, hitem.report_ID, hitem.usage); repindex = -1; switch (hitem.kind) { case hid_collection: @@ -542,6 +705,9 @@ devshow(int hidfd, report_desc_t rd, struct Susbvar *varlist, size_t vlsize, break; } + if (reportid != -1 && hitem.report_ID != reportid) + continue; + matchvar = hidmatch(colls, collind, &hitem, varlist, vlsize); if (matchvar != NULL) { @@ -609,13 +775,13 @@ usage(void) int main(int argc, char **argv) { - int hidfd; - report_desc_t repdesc; - char devnamebuf[PATH_MAX]; char const *dev; - int ch, wflag, aflag, nflag, rflag, lflag; - size_t varnum; char const *table; + size_t varnum; + int aflag, lflag, nflag, rflag, wflag; + int ch, hidfd; + report_desc_t repdesc; + char devnamebuf[PATH_MAX]; struct Susbvar variables[128]; wflag = aflag = nflag = verbose = rflag = lflag = 0; @@ -698,6 +864,14 @@ main(int argc, char **argv) if (!wflag) errx(2, "Must specify -w to set variables"); svar->mflags |= MATCH_WRITABLE; + if (verbose >= 1) + /* + * Allow displaying of set value in + * verbose mode. This isn't + * particularly useful though, so + * don't bother documenting it. + */ + svar->mflags |= MATCH_SHOWVALUES; svar->varlen = valuesep - name; svar->value = valuesep + 1; svar->opfunc = varop_modify; @@ -770,6 +944,10 @@ main(int argc, char **argv) if (hidfd < 0) err(1, "%s", dev); + if (ioctl(hidfd, USB_GET_REPORT_ID, &reportid) < 0) + reportid = -1; + if (verbose > 1) + printf("report ID=%d\n", reportid); repdesc = hid_get_report_desc(hidfd); if (repdesc == 0) errx(1, "USB_GET_REPORT_DESC"); @@ -788,31 +966,18 @@ main(int argc, char **argv) 1 << hid_output | 1 << hid_feature); -#if 0 - { - size_t repindex; - for (repindex = 0; - repindex < (sizeof(reptoparam) / sizeof(*reptoparam)); - repindex++) - devshow(hidfd, repdesc, variables, varnum, - 1 << reptoparam[repindex].hid_kind); - } -#endif - if (rflag) { /* Report mode trailer */ size_t repindex; for (repindex = 0; repindex < (sizeof(reptoparam) / sizeof(*reptoparam)); repindex++) { - int report_id, size; + int size; size = hid_report_size(repdesc, reptoparam[repindex].hid_kind, - &report_id); - size -= report_id != 0; - printf("Total %7s size %s%d bytes\n", - reptoparam[repindex].name, - report_id && size ? "1+" : "", size); + reportid); + printf("Total %7s size %d bytes\n", + reptoparam[repindex].name, size); } } diff --git a/usr.bin/usbhidctl/usbhidctl.1 b/usr.bin/usbhidctl/usbhidctl.1 index b7b2ec48efa..87f92e51a1a 100644 --- a/usr.bin/usbhidctl/usbhidctl.1 +++ b/usr.bin/usbhidctl/usbhidctl.1 @@ -1,7 +1,7 @@ -.\" $OpenBSD: usbhidctl.1,v 1.3 2001/12/30 07:24:07 pvalchev Exp $ -.\" $NetBSD: usbhidctl.1,v 1.10 2000/09/24 02:27:12 augustss Exp $ +.\" $OpenBSD: usbhidctl.1,v 1.4 2002/05/10 00:09:17 nate Exp $ +.\" $NetBSD: usbhidctl.1,v 1.14 2001/12/28 17:49:32 augustss Exp $ .\" -.\" Copyright (c) 2000 The NetBSD Foundation, Inc. +.\" Copyright (c) 2001 The NetBSD Foundation, Inc. .\" All rights reserved. .\" .\" This code is derived from software contributed to The NetBSD Foundation @@ -70,8 +70,8 @@ .Op Ar item=value ... .Sh DESCRIPTION .Nm -can be used to dump or modify the state of a USB HID (Human Interface Device). -If a list of items is present on the command line, then +can be used to output or modify the state of a USB HID (Human Interface +Device). If a list of items is present on the command line, then .Nm prints the current value of those items for the specified device. If the .Fl w @@ -83,6 +83,7 @@ The options are as follows: .Bl -tag -width Ds .It Fl a Show all items and their current values. +This option fails if the device does not support the GET_REPORT command. .It Fl f Ar device Specify a path name for the device to operate on. If .Ar device @@ -94,9 +95,10 @@ An absolute path is taken to be the literal device pathname. Loop and dump the device data every time it changes. Only 'input' items are displayed in this mode. .It Fl n -Suppress printing of the item name when querying specific item values. +Suppress printing of the item name when querying specific items. Only output +the current value. .It Fl r -Dump the report descriptor. +Dump the USB HID report descriptor. .It Fl t Ar table Specify a path name for the HID usage table file. .It Fl v @@ -108,18 +110,91 @@ option. .Sh FILES .Pa /usr/share/misc/usb_hid_usages The default HID usage table. +.Sh SYNTAX +.Nm +parses the names of items specified on the command line against the human +interface items reported by the USB device. Each human interface item is +mapped from its native form to a human readable name, using the HID usage +table file. Command line items are compared with the generated item names, +and the USB HID device is operated on when a match is found. +.Pp +Each human interface item is named by the +.Qq page +it appears in, the +.Qq usage +within that page, and the list of +.Qq collections +containing the item. Each collection in turn is also identified by page, and +the usage within that page. +.Pp +On the +.Nm +command line the page name is separated from the usage name with the character +.Cm So : Sc . +The collections are separated by the character +.Cm So . Sc . +.Pp +As an alternative notation in items on the command line, the native numeric +value for the page name or usage can be used instead of the full human +readable page name or usage name. Numeric values can be specified in decimal, +octal or hexadecimal. +.Sh EXAMPLES +On a standard USB mouse the item +.Dl Generic_Desktop:Mouse.Generic_Desktop:Pointer.Button:Button_2 +reflects the current status of button 2. The +.Qq button 2 +item is encapsulated within two collections, the +.Qq Mouse +collection in the +.Qq Generic Desktop +page, and the +.Qq Pointer +collection in the +.Qq Generic Desktop +page. The item itself is the usage +.Qq Button_2 +in the +.Qq Button +page. +.Pp +An item can generally be named by omitting one or more of the page names. For +example the +.Qq button 2 +item would usually just be referred to on the command line as: +.Dl usbhidctl -f /dev/mouse Mouse.Pointer.Button_2 +.Pp +Items can also be named by referring to parts of the item name with the +numeric representation of the native HID usage identifiers. This is most +useful when items are missing from the HID usage table. The page identifier +for the +.Qq Generic Desktop +page is 1, and the usage identifier for the usage +.Qq Button_2 +is 2, so the following can be used to refer to the +.Qq button 2 +item: +.Dl usbhidctl -f /dev/mouse 1:Mouse.1:Pointer.Button:2 +.Pp +Devices with human interface outputs can be manipulated with the +.Fl w +option. For example, some USB mice have a Light Emitting Diode under software +control as usage 2 under page 0xffff, in the +.Qq Mouse +collection. The following can be used to switch this LED off: +.Dl usbhidctl -f /dev/mouse -w Mouse.0xffff:2=0 .Sh SEE ALSO +.Xr usbhidaction 1 , .Xr usbhid 3 , .Xr uhid 4 , .Xr usb 4 -.Sh AUTHOR -David Sainty <David.Sainty@dtsp.co.nz> .Sh HISTORY The .Nm command first appeared in .Ox 3.0 . +.Sh AUTHORS +.An David Sainty Aq David.Sainty@dtsp.co.nz .Sh BUGS -Some USB HID devices report multiple items with exactly the same description. -The current naming scheme does not provide the means to specify which of the -identically named items you are referring to. +Some USB HID devices report multiple items with exactly the same usage +identifiers. The current naming scheme does not provide the means to specify +which of a set of identically named items you are referring to. |