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

Peter Hutterer peter.hutterer at who-t.net
Thu Jul 14 23:15:45 PDT 2011


On Thu, Jul 14, 2011 at 05:53:17PM +0100, Daniel Drake wrote:
> 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 monitor-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>

Merged patch 1/2, thanks. 

For this one, a few comments:
evtest.c: In function ‘get_keycode’:
evtest.c:541:2: warning: enumeration value ‘MODE_GRAB’ not handled in switch
[-Wswitch]
evtest.c: In function ‘do_query’:
evtest.c:856:2: warning: enumeration value ‘MODE_GRAB’ not handled in switch
[-Wswitch]


> ---
>  evtest.c   |  267 ++++++++++++++++++++++++++++++++++++++++++++++++++++--------
>  evtest.txt |   31 ++++++-
>  2 files changed, 257 insertions(+), 41 deletions(-)
> 
> diff --git a/evtest.c b/evtest.c
> index 5cc8505..39e403f 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)
> @@ -56,6 +58,7 @@
>  #define BIT(x)  (1UL<<OFF(x))
>  #define LONG(x) ((x)/BITS_PER_LONG)
>  #define test_bit(bit, array)	((array[LONG(bit)] >> OFF(bit)) & 1)
> +#define ARRAY_SIZE(i) (sizeof(i) / sizeof(*i))
>  
>  #define DEV_INPUT_EVENT "/dev/input"
>  #define EVENT_DEV_NAME "event"
> @@ -69,6 +72,14 @@
>  
>  #define NAME_ELEMENT(element) [element] = #element
>  
> +enum evtest_mode {
> +	MODE_GRAB,

probaby better to name it MODE_DEFAULT, or MODE_PRINT (see my comments on
do_grab)

> +	MODE_QUERY_SW,
> +	MODE_QUERY_KEY,
> +	MODE_QUERY_LED,
> +	MODE_QUERY_SND,
> +};
> +
>  static const char * const events[EV_MAX + 1] = {
>  	[0 ... EV_MAX] = NULL,
>  	NAME_ELEMENT(EV_SYN),			NAME_ELEMENT(EV_KEY),
> @@ -480,6 +491,68 @@ static const char * const * const names[EV_MAX + 1] = {
>  };
>  
>  /**
> + * Search for the code of a specific key/snd/led/sw bit, based on its textual
> + * representation (e.g. SW_DOCK).
> + *
> + * @param type The EV_ event type being queried
> + * @param kstr The string to parse and convert
> + *
> + * @return The requested code's numerical value, or negative on error.
> + */
> +static int key_name_to_code(int type, int max, const char *kstr)
> +{
> +	const char * const *keynames = names[type];
> +	int i;
> +
> +	for (i = 0; i < max; i++) {
> +		const char *name = keynames[i];
> +		if (!name)
> +			continue;
> +		if (strcasecmp(name, kstr) == 0)
> +			return i;

please instead use:
  if (name && strcasecmp() == 0)
  ...

> +	}
> +
> +	return -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_CODE") 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(enum evtest_mode 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;
> +	}
> +
> +	switch (mode) {
> +	case MODE_QUERY_SW:
> +		return key_name_to_code(EV_SW, SW_CNT, kstr);
> +	case MODE_QUERY_KEY:
> +		return key_name_to_code(EV_KEY, KEY_CNT, kstr);
> +	case MODE_QUERY_SND:
> +		return key_name_to_code(EV_SND, SND_CNT, kstr);
> +	case MODE_QUERY_LED:
> +		return key_name_to_code(EV_LED, LED_CNT, kstr);
> +	}
> +
> +	return -1;
> +}
> +
> +/**
>   * Filter for the AutoDevProbe scandir on /dev/input.
>   *
>   * @param dir The current directory entry provided by scandir.
> @@ -544,38 +617,26 @@ static char* scan_devices(void)
>  /**
>   * Print usage information.
>   */
> -static void usage(void)
> +static void __attribute__((noreturn)) usage(void)
>  {
> -	printf("Usage: evtest /dev/input/eventX\n");
> -	printf("Where X = input device number\n");
> -}
> -
> -/**
> - * Parse the commandline arguments.
> - *
> - * @return The filename of the device file to open, or NULL in case of
> - * error. This string is allocated and must be freed by the caller.
> - */
> -static char* parse_args(int argc, char **argv)
> -{
> -	char *filename;
> -
> -	if (argc < 2) {
> -		fprintf(stderr, "No device specified, trying to scan all of %s/%s*\n",
> -			DEV_INPUT_EVENT, EVENT_DEV_NAME);
> -
> -		if (getuid() != 0)
> -			fprintf(stderr, "Not running as root, no devices may be available.\n");
> -
> -		filename = scan_devices();
> -		if (!filename) {
> -			usage();
> -			return NULL;
> -		}
> -	} else
> -		filename = strdup(argv[argc - 1]);
> -
> -	return filename;
> +	printf("USAGE:\n");
> +	printf(" Grab mode:\n");
> +	printf("   %s /dev/input/eventX\n", program_invocation_short_name);

heh. thanks, hadn't heard of program_invocation_short_name yet.

> +	printf("\n");
> +	printf(" Query mode: (check exit code)\n");
> +	printf("   %s --query-sw <value> /dev/input/eventX\n",


I'd prefer the equiv of "evtest --query EV_SW SW_DOCK rather than
having separate --query-sw, --query-key, etc.

> +		program_invocation_short_name);
> +	printf("   %s --query-key <value> /dev/input/eventX\n",
> +		program_invocation_short_name);
> +	printf("   %s --query-led <value> /dev/input/eventX\n",
> +		program_invocation_short_name);
> +	printf("   %s --query-snd <value> /dev/input/eventX\n",
> +		program_invocation_short_name);
> +
> +	printf("\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");
> +	_exit(EXIT_FAILURE);

why not just return 1 and change the caller to "return usage()"?
does _exit() give us any real benefits here?

>  }
>  
>  /**
> @@ -708,17 +769,42 @@ static int test_grab(int fd)
>  	return rc;
>  }
>  
> -int main (int argc, char **argv)
> +static const struct option long_options[] = {
> +	{ "query-sw", required_argument, NULL, MODE_QUERY_SW },
> +	{ "query-snd", required_argument, NULL, MODE_QUERY_SND },
> +	{ "query-key", required_argument, NULL, MODE_QUERY_KEY },
> +	{ "query-led", required_argument, NULL, MODE_QUERY_LED },
> +	{ 0, },
> +};
> +
> +/**
> + * Enter grab mode. The requested event device will be grabbed and any
> + * captured events will be decoded and printed on the console.
> + *
> + * @param device The device to grab, or NULL if the user should be prompted.
> + * @return 0 on success, non-zero on error.
> + */
> +static int do_grab(const char *device)

this isn't quite correct. if you check the code again, you'll see that all
we do here is to grab and immediately ungrab the device. Simple reason: I
got too many logs that lacked any events, so I naïvely thought that might
stop users from doing that. so "do_grab" is a bit of a misnomer.

In regards to the patch, I'd prefer it if you can split this out.
Re-indentations are notoriously bad to review. Having one patch that just
moves this code into a separate function (i.e. _just_ moving + whitespace
changes) and then your patch on top would make it much less confusing to see
what you added.

>  {
>  	int fd;
>  	char *filename;
>  
> -	filename = parse_args(argc, argv);
> +	if (device) {
> +		filename = strdup(device);
> +	} else {
> +		fprintf(stderr, "No device specified, trying to scan all of %s/%s*\n",
> +			DEV_INPUT_EVENT, EVENT_DEV_NAME);
>  
> -	if (!filename)
> -		return 1;
> +		if (getuid() != 0)
> +			fprintf(stderr, "Not running as root, no devices may be available.\n");
>  
> -	if ((fd = open(filename, O_RDONLY)) < 0) {
> +		filename = scan_devices();
> +		if (!filename)
> +			usage();
> +	}
> +
> +	fd = open(filename, O_RDONLY);
> +	if (fd < 0) {
>  		perror("evtest");
>  		if (errno == EACCES && getuid() != 0)
>  			fprintf(stderr, "You do not have access to %s. Try "
> @@ -751,4 +837,113 @@ int main (int argc, char **argv)
>  	return print_events(fd);
>  }
>  
> +/**
> + * 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, enum evtest_mode mode, int keycode)
> +{
> +	int rq;
> +	int max;
> +	int fd;
> +	int r;
> +
> +	switch (mode) {
> +	case MODE_QUERY_SW:
> +		max = SW_MAX;
> +		rq = EVIOCGSW(SW_MAX);
> +		break;
> +	case MODE_QUERY_KEY:
> +		max = KEY_MAX;
> +		rq = EVIOCGKEY(KEY_MAX);
> +		break;
> +	case MODE_QUERY_SND:
> +		max = SND_MAX;
> +		rq = EVIOCGSND(SND_MAX);
> +		break;
> +	case MODE_QUERY_LED:
> +		max = SND_MAX;
> +		rq = EVIOCGLED(LED_MAX);
> +		break;
> +	}
> +
> +	if (keycode > max) {
> +		fprintf(stderr, "Key 0x%x out of bounds.\n", keycode);
> +		_exit(EXIT_FAILURE);
> +	}
> +
> +	unsigned char state[NBITS(max)];

move this up to the beginning of the function please.

for this function as well, is the _exit() really beneficial over a simple
return EXIT_FAILURE?

> +
> +	fd = open(device, O_RDONLY);
> +	if (fd < 0) {
> +		perror("open");
> +		_exit(EXIT_FAILURE);
> +	}
> +
> +	memset(state, 0, sizeof(state));
> +	r = ioctl(fd, rq, state);
> +	close(fd);
> +
> +	if (r == -1) {
> +		perror("ioctl");
> +		_exit(EXIT_FAILURE);
> +	}
> +
> +	if (test_bit(keycode, state))
> +		return 10; /* different from EXIT_FAILURE */
> +	else
> +		return 0;
> +}
> +
> +int main (int argc, char **argv)
> +{
> +	const char *device = NULL;
> +	const char *keyname;
> +	enum evtest_mode mode = MODE_GRAB;
> +	int keycode;
> +
> +	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_SW:
> +		case MODE_QUERY_KEY:
> +		case MODE_QUERY_LED:
> +		case MODE_QUERY_SND:
> +			mode = c;
> +			keyname = optarg;
> +			keycode = get_keycode(mode, keyname);
> +			break;
> +		default:
> +			usage();
> +			break;
> +		}
> +	}
> +
> +	if (optind < argc)
> +		device = argv[optind++];
> +
> +	if (mode == MODE_GRAB)
> +		return do_grab(device);
> +
> +	if (keycode < 0) {
> +		fprintf(stderr, "Unrecognised key name: %s\n", keyname);
> +		usage();
> +	}
> +
> +	if (!device) {
> +		fprintf(stderr, "Device argument is required for query.\n");
> +		usage();
> +	}
> +
> +	return do_query(device, mode, keycode);
> +}
> +
>  /* vim: set noexpandtab tabstop=8 shiftwidth=8: */
> diff --git a/evtest.txt b/evtest.txt
> index 685a4de..f255edd 100644
> --- a/evtest.txt
> +++ b/evtest.txt
> @@ -4,17 +4,31 @@ 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-sw <value> /dev/input/eventX
> +     evtest --query-key <value> /dev/input/eventX
> +     evtest --query-snd <value> /dev/input/eventX
> +     evtest --query-led <value> /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 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 other 4 invocation types, evtest performs a one-shot query of the state
> +of a specific switch (sw), key, sound (snd) or LED, specified by *value*. 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. *value* can be either a
> +decimal representation (e.g. 44), hex (e.g. 0x2c), or the constant name (e.g.
> +KEY_Z).
>  
>  evtest needs to be able to read from the device; in most cases this means it
>  must be run as root.
> @@ -32,6 +46,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
> +11 if the state bit is set.

s/11/10/

Cheers,
  Peter

> +
>  SEE ALSO
>  --------
>  inputattach(1)
> -- 
> 1.7.6
> 


More information about the xorg-devel mailing list