chiark / gitweb /
keymap: More robust state machine
[elogind.git] / extras / keymap / keymap.c
index b708f38e7b838259a991dc9d78de720dbb2a28df..ed6b69d5f141d4b59c9bae21d473b1feb3864fca 100644 (file)
@@ -25,6 +25,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <stdint.h>
 #include <ctype.h>
 #include <unistd.h>
 #include <errno.h>
@@ -32,6 +33,7 @@
 #include <fcntl.h>
 #include <getopt.h>
 #include <sys/ioctl.h>
+#include <linux/limits.h>
 #include <linux/input.h>
 
 const struct key* lookup_key (const char *str, unsigned int len);
@@ -153,6 +155,34 @@ fail:
        return r;
 }
 
+static void set_key(int fd, const char* scancode_str, const char* keyname)
+{
+       unsigned scancode;
+       char *endptr;
+       char t[105] = "KEY_UNKNOWN";
+       const struct key *k;
+
+       scancode = (unsigned) strtol(scancode_str, &endptr, 0);
+       if (*endptr != '\0') {
+               fprintf(stderr, "ERROR: Invalid scancode\n");
+               exit(1);
+       }
+
+       snprintf(t, sizeof(t), "KEY_%s", keyname);
+
+       if (!(k = lookup_key(t, strlen(t)))) {
+               fprintf(stderr, "ERROR: Unknown key name '%s'\n", keyname);
+               exit(1);
+       }
+
+       if (evdev_set_keycode(fd, scancode, k->id) < 0)
+               fprintf(stderr, "setting scancode 0x%2X to key code %i failed\n", 
+                       scancode, k->id);
+       else
+               printf("setting scancode 0x%2X to key code %i\n", 
+                       scancode, k->id);
+}
+
 static int merge_table(int fd, const char *filename) {
        int r = 0;
        int line = 0;
@@ -219,7 +249,7 @@ static const char* default_keymap_path(const char* path)
 {
        static char result[PATH_MAX];
 
-       /* If keymap file is given without a path, assume udev diretory; must end with '/' * */
+       /* If keymap file is given without a path, assume udev directory; must end with '/' * */
        if (!strchr(path, '/')) {
                snprintf(result, sizeof(result), "%s%s", LIBEXECDIR "/keymaps/", path);
                return result;
@@ -227,51 +257,125 @@ static const char* default_keymap_path(const char* path)
        return path;
 }
 
-static void print_key(struct input_event *event)
+/* read one event; return 1 if valid */
+static int read_event(int fd, struct input_event* ev)
 {
-       static int cur_scancode = 0;
+       int ret;
+       ret = read(fd, ev, sizeof(struct input_event));
 
-       /* save scan code for next EV_KEY event */
-       if (event->type == EV_MSC && event->code == MSC_SCAN)
-           cur_scancode = event->value;
+       if (ret < 0) {
+               perror("read");
+               return 0;
+       }
+       if (ret != sizeof(struct input_event)) {
+               fprintf(stderr, "did not get enough data for event struct, aborting\n");
+               return 0;
+       }
 
-       /* key press */
-       if (event->type == EV_KEY && event->value)
-           printf("scan code: 0x%02X   key code: %s\n", cur_scancode,
-                   format_keyname(key_names[event->code]));
+       return 1;
+}
+
+static void print_key(uint32_t scancode, uint16_t keycode, int has_scan, int has_key)
+{
+       const char *keyname;
+
+       /* ignore key release events */
+       if (has_key == 1)
+               return;
+
+       if (has_key == 0 && has_scan != 0) {
+               fprintf(stderr, "got scan code event 0x%02X without a key code event\n",
+                       scancode);
+               return;
+       }
+
+       if (has_scan != 0)
+               printf("scan code: 0x%02X   ", scancode);
+       else
+               printf("(no scan code received)  ");
+
+       keyname = key_names[keycode];
+       if (keyname != NULL)
+               printf("key code: %s\n", format_keyname(keyname));
+       else
+               printf("key code: %03X\n", keycode);
 }
 
 static void interactive(int fd)
 {
        struct input_event ev;
-       int run = 1;
+       uint32_t last_scan = 0;
+       uint16_t last_key = 0;
+       int has_scan; /* boolean */
+       int has_key; /* 0: none, 1: release, 2: press */
 
        /* grab input device */
        ioctl(fd, EVIOCGRAB, 1);
-
        puts("Press ESC to finish");
-       while (run) {
-               switch (read(fd, &ev, sizeof(ev))) {
-               case -1:
-                       perror("read");
-                       run = 0;
-                       break;
-               case 0:
-                       run = 0;
-                       break;
-               default:
-                       print_key(&ev);
-                       /* stop on Escape key release */
-                       if (ev.type == EV_KEY && ev.code == KEY_ESC && ev.value == 0)
-                               run = 0;
-                       break;
+
+       has_scan = has_key = 0;
+       while (read_event(fd, &ev)) {
+               /* Drivers usually send the scan code first, then the key code,
+                * then a SYN. Some drivers (like thinkpad_acpi) send the key
+                * code first, and some drivers might not send SYN events, so
+                * keep a robust state machine which can deal with any of those
+                */
+
+               if (ev.type == EV_MSC && ev.code == MSC_SCAN) {
+                       if (has_scan) {
+                               fputs("driver did not send SYN event in between key events; previous event:\n",
+                                     stderr);
+                               print_key(last_scan, last_key, has_scan, has_key);
+                               has_key = 0;
+                       }
+
+                       last_scan = ev.value;
+                       has_scan = 1;
+                       /*printf("--- got scan %u; has scan %i key %i\n", last_scan, has_scan, has_key); */
+               }
+               else if (ev.type == EV_KEY) {
+                       if (has_key) {
+                               fputs("driver did not send SYN event in between key events; previous event:\n",
+                                     stderr);
+                               print_key(last_scan, last_key, has_scan, has_key);
+                               has_scan = 0;
+                       }
+
+                       last_key = ev.code;
+                       has_key = 1 + ev.value;
+                       /*printf("--- got key %hu; has scan %i key %i\n", last_key, has_scan, has_key);*/
+
+                       /* Stop on ESC */
+                       if (ev.code == KEY_ESC && ev.value == 0)
+                               break;
+               }
+               else if (ev.type == EV_SYN) {
+                       /*printf("--- got SYN; has scan %i key %i\n", has_scan, has_key);*/
+                       print_key(last_scan, last_key, has_scan, has_key);
+
+                       has_scan = has_key = 0;
                }
+
        }
 
        /* release input device */
        ioctl(fd, EVIOCGRAB, 0);
 }
 
+static void help(int error)
+{
+       const char* h = "Usage: keymap <event device> [<map file>]\n"
+                       "       keymap <event device> scancode keyname [...]\n"
+                       "       keymap -i <event device>\n";
+       if (error) {
+               fputs(h, stderr);
+               exit(2);
+       } else {
+               fputs(h, stdout);
+               exit(0);
+       }
+}
+
 int main(int argc, char **argv)
 {
        static const struct option options[] = {
@@ -281,6 +385,7 @@ int main(int argc, char **argv)
        };
        int fd = -1;
        int opt_interactive = 0;
+       int i;
 
        while (1) {
                int option;
@@ -291,8 +396,7 @@ int main(int argc, char **argv)
 
                switch (option) {
                case 'h':
-                       printf("Usage: keymap <event device> [<map file>]\n\n");
-                       return 0;
+                       help(0);
 
                case 'i':
                        opt_interactive = 1;
@@ -302,21 +406,35 @@ int main(int argc, char **argv)
                }
        }
 
-       if (argc < optind+1 || argc > optind+2) {
-               fprintf(stderr, "Usage: keymap <event device> [<map file>]\n\n");
-               return 2;
-       }
+       if (argc < optind+1)
+               help (1);
 
        if ((fd = evdev_open(argv[optind])) < 0)
                return 3;
 
-       if (argc == optind+2)
-               merge_table(fd, default_keymap_path(argv[optind+1]));
-       else {
+       /* one argument (device): dump or interactive */
+       if (argc == optind+1) {
                if (opt_interactive)
                        interactive(fd);
                else
                        dump_table(fd);
+               return 0;
        }
-       return 0;
+
+       /* two arguments (device, mapfile): set map file */
+       if (argc == optind+2) {
+               merge_table(fd, default_keymap_path(argv[optind+1]));
+               return 0;
+       }
+
+       /* more arguments (device, scancode/keyname pairs): set keys directly */
+       if ((argc - optind - 1) % 2 == 0) {
+               for (i = optind+1; i < argc; i += 2)
+                       set_key(fd, argv[i], argv[i+1]);        
+               return 0;
+       }
+
+       /* invalid number of arguments */
+       help(1);
+       return 1; /* not reached */
 }