From dc41f2f4277f46b5d91b7a1c5cdfb7037a44cb62 Mon Sep 17 00:00:00 2001 From: augustss Date: Mon, 22 Oct 2001 22:03:49 +0000 Subject: [PATCH] Change usbhidctl to take numeric usage names. Add examples in the man page. From Dave Sainty . --- usr.bin/usbhidctl/usbhid.c | 332 ++++++++++++++++++++++++---------- usr.bin/usbhidctl/usbhidctl.1 | 94 +++++++++- 2 files changed, 319 insertions(+), 107 deletions(-) diff --git a/usr.bin/usbhidctl/usbhid.c b/usr.bin/usbhidctl/usbhid.c index 722e0440372a..132be2bf2855 100644 --- a/usr.bin/usbhidctl/usbhid.c +++ b/usr.bin/usbhidctl/usbhid.c @@ -1,7 +1,7 @@ -/* $NetBSD: usbhid.c,v 1.17 2001/03/28 03:17:42 simonb Exp $ */ +/* $NetBSD: usbhid.c,v 1.18 2001/10/22 22:03:49 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 @@ -57,10 +57,6 @@ #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; - struct Susbvar { /* Variable name, not NUL terminated */ char const *variable; @@ -76,6 +72,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() */ @@ -107,11 +104,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; /* @@ -160,96 +329,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; + struct usagedata cache; - final = (colind == collen); - - 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) - */ + if (matchres < 0) { + /* Bad match */ 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 */ - 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; + } } } @@ -361,12 +486,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; @@ -381,7 +502,7 @@ reportitem(char const *label, struct hid_item const *item, unsigned int mflags) hid_usage_in_page(item->usage), item->flags & HIO_CONST ? " Const" : ""); if (mflags & MATCH_SHOWNUMERIC) - printf(" (%u/0x%x)", + printf(" (%u:0x%x)", HID_PAGE(item->usage), HID_USAGE(item->usage)); printf(", logical range %d..%d", item->logical_minimum, item->logical_maximum); @@ -401,9 +522,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"); @@ -425,13 +551,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); @@ -494,10 +619,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]; @@ -608,15 +732,21 @@ usage(void) int main(int argc, char **argv) { - int hidfd; + char const *dev; + char const *table; + size_t varnum; + int aflag, lflag, nflag, rflag, wflag; + int ch, 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; struct Susbvar variables[128]; + /* + * Zero if not in a verbose mode. Greater levels of verbosity + * are indicated by values larger than one. + */ + unsigned int verbose; + wflag = aflag = nflag = verbose = rflag = lflag = 0; dev = NULL; table = NULL; @@ -697,6 +827,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; diff --git a/usr.bin/usbhidctl/usbhidctl.1 b/usr.bin/usbhidctl/usbhidctl.1 index 77a329f79247..8dd1267b6e9c 100644 --- a/usr.bin/usbhidctl/usbhidctl.1 +++ b/usr.bin/usbhidctl/usbhidctl.1 @@ -1,6 +1,6 @@ -.\" $NetBSD: usbhidctl.1,v 1.10 2000/09/24 02:27:12 augustss Exp $ +.\" $NetBSD: usbhidctl.1,v 1.11 2001/10/22 22:03:49 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 @@ -66,8 +66,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 @@ -90,9 +90,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 @@ -104,10 +105,83 @@ 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 usb 3 , .Xr uhid 4 , -.Xr usb 4 +.Xr usb 4 , +.Xr usbhidaction 1 .Sh AUTHOR David Sainty .Sh HISTORY @@ -116,6 +190,6 @@ The command first appeared in .Nx 1.4 . .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.