From 8a10dfe3b7bc53b319107de89434922a2c79ef94 Mon Sep 17 00:00:00 2001 From: Daniel Drake Date: Wed, 20 Jul 2011 18:25:03 +0100 Subject: Add one-shot query functionality Add functionality to query evdev state of a specific key, switch, button, LED or sound event. This is useful in programs such as powerd (http://wiki.laptop.org/go/Powerd) which need to query things like the state of the laptop lid switch from shell code. Original capture-mode functionality is left unchanged and is still activated by default. New usage modes are explained in the man page. Signed-off-by: Daniel Drake Reviewed-by: Peter Hutterer Signed-off-by: Peter Hutterer --- evtest.c | 248 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- evtest.txt | 33 ++++++-- 2 files changed, 266 insertions(+), 15 deletions(-) diff --git a/evtest.c b/evtest.c index eb04a51..022229e 100644 --- a/evtest.c +++ b/evtest.c @@ -49,6 +49,8 @@ #include #include #include +#include +#include #define BITS_PER_LONG (sizeof(long) * 8) #define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) @@ -69,6 +71,83 @@ #define NAME_ELEMENT(element) [element] = #element +enum evtest_mode { + MODE_CAPTURE, + MODE_QUERY, +}; + +static const struct query_mode { + const char *name; + int event_type; + int max; + int rq; +} query_modes[] = { + { "EV_KEY", EV_KEY, KEY_MAX, EVIOCGKEY(KEY_MAX) }, + { "EV_LED", EV_LED, LED_MAX, EVIOCGLED(LED_MAX) }, + { "EV_SND", EV_SND, SND_MAX, EVIOCGSND(SND_MAX) }, + { "EV_SW", EV_SW, SW_MAX, EVIOCGSW(SW_MAX) }, +}; + +/** + * Look up an entry in the query_modes table by its textual name. The search + * is case-insensitive. + * + * @param mode The name of the entry to be found. + * + * @return The requested query_mode, or NULL if it could not be found. + */ +static const struct query_mode *find_query_mode_by_name(const char *name) +{ + int i; + for (i = 0; i < sizeof(query_modes) / sizeof(*query_modes); i++) { + const struct query_mode *mode = &query_modes[i]; + if (strcmp(mode->name, name) == 0) + return mode; + } + return NULL; +} + +/** + * Look up an entry in the query_modes table by value. + * + * @param event_type The value of the entry to be found. + * + * @return The requested query_mode, or NULL if it could not be found. + */ +static const struct query_mode *find_query_mode_by_value(int event_type) +{ + int i; + for (i = 0; i < sizeof(query_modes) / sizeof(*query_modes); i++) { + const struct query_mode *mode = &query_modes[i]; + if (mode->event_type == event_type) + return mode; + } + return NULL; +} + +/** + * Find a query_mode based on a string identifier. The string can either + * be a numerical value (e.g. "5") or the name of the event type in question + * (e.g. "EV_SW"). + * + * @param query_mode The mode to search for + * + * @return The requested code's numerical value, or negative on error. + */ +static const struct query_mode *find_query_mode(const char *query_mode) +{ + if (isdigit(query_mode[0])) { + unsigned long val; + errno = 0; + val = strtoul(query_mode, NULL, 0); + if (errno) + return NULL; + return find_query_mode_by_value(val); + } else { + return find_query_mode_by_name(query_mode); + } +} + static const char * const events[EV_MAX + 1] = { [0 ... EV_MAX] = NULL, NAME_ELEMENT(EV_SYN), NAME_ELEMENT(EV_KEY), @@ -479,6 +558,41 @@ static const char * const * const names[EV_MAX + 1] = { [EV_FF] = force, [EV_FF_STATUS] = forcestatus, }; +/** + * Convert a string to a specific key/snd/led/sw code. The string can either + * be the name of the key in question (e.g. "SW_DOCK") or the numerical + * value, either as decimal (e.g. "5") or as hex (e.g. "0x5"). + * + * @param mode The mode being queried (key, snd, led, sw) + * @param kstr The string to parse and convert + * + * @return The requested code's numerical value, or negative on error. + */ +static int get_keycode(const struct query_mode *query_mode, const char *kstr) +{ + if (isdigit(kstr[0])) { + unsigned long val; + errno = 0; + val = strtoul(kstr, NULL, 0); + if (errno) { + fprintf(stderr, "Could not interpret value %s\n", kstr); + return -1; + } + return (int) val; + } else { + const char * const *keynames = names[query_mode->event_type]; + int i; + + for (i = 0; i < query_mode->max; i++) { + const char *name = keynames[i]; + if (name && strcmp(name, kstr) == 0) + return i; + } + + return -1; + } +} + /** * Filter for the AutoDevProbe scandir on /dev/input. * @@ -544,10 +658,22 @@ static char* scan_devices(void) /** * Print usage information. */ -static void usage(void) +static int usage(void) { - printf("Usage: evtest /dev/input/eventX\n"); - printf("Where X = input device number\n"); + printf("USAGE:\n"); + printf(" Grab mode:\n"); + printf(" %s /dev/input/eventX\n", program_invocation_short_name); + printf("\n"); + printf(" Query mode: (check exit code)\n"); + printf(" %s --query /dev/input/eventX \n", + program_invocation_short_name); + + printf("\n"); + printf(" is one of: EV_KEY, EV_SW, EV_LED, EV_SND\n"); + printf(" can either be a numerical value, or the textual name of the\n"); + printf("key/switch/LED/sound being queried (e.g. SW_DOCK).\n"); + + return EXIT_FAILURE; } /** @@ -700,10 +826,8 @@ static int do_capture(const char *device) fprintf(stderr, "Not running as root, no devices may be available.\n"); filename = scan_devices(); - if (!filename) { - usage(); - return EXIT_FAILURE; - } + if (!filename) + return usage(); } else filename = strdup(device); @@ -743,14 +867,118 @@ static int do_capture(const char *device) return print_events(fd); } +/** + * Perform a one-shot state query on a specific device. The query can be of + * any known mode, on any valid keycode. + * + * @param device Path to the evdev device node that should be queried. + * @param query_mode The event type that is being queried (e.g. key, switch) + * @param keycode The code of the key/switch/sound/LED to be queried + * @return 0 if the state bit is unset, 10 if the state bit is set, 1 on error. + */ +static int query_device(const char *device, const struct query_mode *query_mode, int keycode) +{ + int fd; + int r; + unsigned long state[NBITS(query_mode->max)]; + + fd = open(device, O_RDONLY); + if (fd < 0) { + perror("open"); + return EXIT_FAILURE; + } + memset(state, 0, sizeof(state)); + r = ioctl(fd, query_mode->rq, state); + close(fd); + + if (r == -1) { + perror("ioctl"); + return EXIT_FAILURE; + } + + if (test_bit(keycode, state)) + return 10; /* different from EXIT_FAILURE */ + else + return 0; +} + +/** + * Enter query mode. The requested event device will be queried for the state + * of a particular switch/key/sound/LED. + * + * @param device The device to query. + * @param mode The mode (event type) that is to be queried (snd, sw, key, led) + * @param keycode The key code to query the state of. + * @return 0 if the state bit is unset, 10 if the state bit is set. + */ +static int do_query(const char *device, const char *event_type, const char *keyname) +{ + const struct query_mode *query_mode; + int keycode; + + if (!device) { + fprintf(stderr, "Device argument is required for query.\n"); + return usage(); + } + + query_mode = find_query_mode(event_type); + if (!query_mode) { + fprintf(stderr, "Unrecognised event type: %s\n", event_type); + return usage(); + } + + keycode = get_keycode(query_mode, keyname); + if (keycode < 0) { + fprintf(stderr, "Unrecognised key name: %s\n", keyname); + return usage(); + } else if (keycode > query_mode->max) { + fprintf(stderr, "Key %d is out of bounds.\n", keycode); + return EXIT_FAILURE; + } + + return query_device(device, query_mode, keycode); +} + +static const struct option long_options[] = { + { "query", no_argument, NULL, MODE_QUERY }, + { 0, }, +}; + int main (int argc, char **argv) { const char *device = NULL; + const char *keyname; + const char *event_type; + enum evtest_mode mode = MODE_CAPTURE; - if (argc >= 2) - device = argv[1]; + while (1) { + int option_index = 0; + int c = getopt_long(argc, argv, "", long_options, &option_index); + if (c == -1) + break; + switch (c) { + case MODE_QUERY: + mode = c; + break; + default: + return usage(); + } + } + + if (optind < argc) + device = argv[optind++]; + + if (mode == MODE_CAPTURE) + return do_capture(device); + + if ((argc - optind) < 2) { + fprintf(stderr, "Query mode requires device, type and key parameters\n"); + return usage(); + } - return do_capture(device); + event_type = argv[optind++]; + keyname = argv[optind++]; + return do_query(device, event_type, keyname); } /* vim: set noexpandtab tabstop=8 shiftwidth=8: */ diff --git a/evtest.txt b/evtest.txt index 685a4de..5ae7092 100644 --- a/evtest.txt +++ b/evtest.txt @@ -4,17 +4,33 @@ EVTEST(1) NAME ---- - evtest - Input device event monitor + evtest - Input device event monitor and query tool SYNOPSIS -------- - evtest "/dev/input/eventX" + evtest /dev/input/eventX + + evtest --query /dev/input/eventX DESCRIPTION ----------- -evtest displays information on the input device specified on the command -line, including all the events supported by the device. It then monitors the -device and displays all the events layer events generated. +The first invocation type displayed above ("capture mode") causes evtest to +display information about the specified input device, including all the events +supported by the device. It then monitors the device and displays all the +events layer events generated. + +In the second invocation type ("query mode"), evtest performs a one-shot query +of the state of a specific key *value* of an event *type*. + +*type* is one of: *EV_KEY*, *EV_SW*, *EV_SND*, *EV_LED* (or the numerical value) + +*value* can be either a decimal representation (e.g. 44), hex +(e.g. 0x2c), or the constant name (e.g. KEY_Z) of the key/switch/sound/LED +being queried. + +If the state bit is set (key pressed, switch on, ...), evtest exits with +code 0. If the state bit is unset (key depressed, switch off, ...), evtest +exits with code 10. No other output is generated. evtest needs to be able to read from the device; in most cases this means it must be run as root. @@ -32,6 +48,13 @@ when debugging a synaptics device from within X. VT switching to a TTY or shutting down the X server terminates this grab and synaptics devices can be debugged. +EXIT CODE +--------- +evtest returns 1 on error. + +When used to query state, evtest returns 0 if the state bit is unset and +10 if the state bit is set. + SEE ALSO -------- inputattach(1) -- cgit v1.2.3