[PATCH evtest 2/2] Add one-shot query functionality

Daniel Drake dsd at laptop.org
Sat Jul 16 15:07:14 PDT 2011


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 <dsd at laptop.org>
---
 evtest.c   |  203 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 evtest.txt |   33 ++++++++--
 2 files changed, 226 insertions(+), 10 deletions(-)

diff --git a/evtest.c b/evtest.c
index eb04a51..9a7ac7f 100644
--- a/evtest.c
+++ b/evtest.c
@@ -49,6 +49,8 @@
 #include <stdlib.h>
 #include <dirent.h>
 #include <errno.h>
+#include <getopt.h>
+#include <ctype.h>
 
 #define BITS_PER_LONG (sizeof(long) * 8)
 #define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
@@ -69,6 +71,42 @@
 
 #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[] = {
+	{ "key", EV_KEY, KEY_MAX, EVIOCGKEY(KEY_MAX) },
+	{ "led", EV_LED, LED_MAX, EVIOCGLED(LED_MAX) },
+	{ "snd", EV_SND, SND_MAX, EVIOCGSND(SND_MAX) },
+	{ "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(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 (strcasecmp(mode->name, name) == 0)
+			return mode;
+	}
+	return NULL;
+}
+
 static const char * const events[EV_MAX + 1] = {
 	[0 ... EV_MAX] = NULL,
 	NAME_ELEMENT(EV_SYN),			NAME_ELEMENT(EV_KEY),
@@ -480,6 +518,41 @@ static const char * const * const names[EV_MAX + 1] = {
 };
 
 /**
+ * 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 && strcasecmp(name, kstr) == 0)
+				return i;
+		}
+
+		return -1;
+	}
+}
+
+/**
  * Filter for the AutoDevProbe scandir on /dev/input.
  *
  * @param dir The current directory entry provided by scandir.
@@ -546,8 +619,18 @@ static char* scan_devices(void)
  */
 static void 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 <type> <value>\n",
+		program_invocation_short_name);
+
+	printf("\n");
+	printf("<type> is one of: key, sw, led, snd\n");
+	printf("<value> 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");
 }
 
 /**
@@ -743,14 +826,124 @@ 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 char 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");
+		usage();
+		return EXIT_FAILURE;
+	}
+
+	query_mode = find_query_mode(event_type);
+	if (!query_mode) {
+		fprintf(stderr, "Unrecognsied event type: %s\n", event_type);
+		usage();
+		return EXIT_FAILURE;
+	}
+
+	keycode = get_keycode(query_mode, keyname);
+	if (keycode < 0) {
+		fprintf(stderr, "Unrecognised key name: %s\n", keyname);
+		usage();
+		return EXIT_FAILURE;
+	} else if (keycode > query_mode->max) {
+		fprintf(stderr, "Key %d 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;
+
+	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:
+			usage();
+			break;
+		}
+	}
 
-	if (argc >= 2)
-		device = argv[1];
+	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");
+		usage();
+		return EXIT_FAILURE;
+	}
 
-	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..4cb1a18 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 <type> <value>
 
 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: *key*, *sw*, *snd*, *led*
+
+*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)
-- 
1.7.6



More information about the xorg-devel mailing list