summaryrefslogtreecommitdiff
path: root/usr.bin
diff options
context:
space:
mode:
authorNathan Binkert <nate@cvs.openbsd.org>2002-05-10 00:09:18 +0000
committerNathan Binkert <nate@cvs.openbsd.org>2002-05-10 00:09:18 +0000
commita82d07519e6786b844ed49d488f67672db78daa2 (patch)
tree9ed579bb5297f1a9a77613ce6171eb968d7bf508 /usr.bin
parent0dd51d96675add74c11132637e3499fceb185340 (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/Makefile10
-rw-r--r--usr.bin/usbhidaction/usbhidaction.1137
-rw-r--r--usr.bin/usbhidaction/usbhidaction.c439
-rw-r--r--usr.bin/usbhidctl/usbhid.c447
-rw-r--r--usr.bin/usbhidctl/usbhidctl.199
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.