input: add features mechanism
authorRan Benita <ran234@gmail.com>
Sun, 4 Mar 2012 16:43:19 +0000 (18:43 +0200)
committerDavid Herrmann <dh.herrmann@googlemail.com>
Tue, 20 Mar 2012 18:00:45 +0000 (19:00 +0100)
This commit discards the simplistic udev check for the ID_KEYBOARD
property in favor of a more direct "feature" probing. This is done for
the following reasons:

- We will need to use input devices which are not necessarily keyboards,
  for example the PC speaker to sound the bell.

- To differentiate between keyboard with certain capabilities, such as
  LEDs (indicators). We can then perform actions on devices according to
  their feature bits.

- We check directly for what we need, i.e. the ability to send/receive
  certain evdev events, without relying on logic in udev. This also
  reduces our dependency on udev if we ever want to make it optional.

Signed-off-by: Ran Benita <ran234@gmail.com>
Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
src/input.c
src/input.h

index 5f6f18f..beba2b2 100644 (file)
 #include "kbd.h"
 #include "log.h"
 
+/* How many longs are needed to hold \n bits. */
+#define NLONGS(n) (((n) + LONG_BIT - 1) / LONG_BIT)
+
 enum input_state {
        INPUT_ASLEEP,
        INPUT_AWAKE,
 };
 
+/* See probe_device_features(). */
+enum device_feature {
+       FEATURE_HAS_KEYS = 0x01,
+};
+
 struct kmscon_input_device {
        size_t ref;
        struct kmscon_input_device *next;
        struct kmscon_input *input;
 
+       unsigned int features;
+
        int rfd;
        char *devnode;
        struct kmscon_fd *fd;
@@ -157,15 +167,18 @@ int kmscon_input_device_wake_up(struct kmscon_input_device *device)
                return -errno;
        }
 
-       /* this rediscovers the keyboard state if sth changed during sleep */
-       kmscon_kbd_reset(device->kbd, device->rfd);
+       if (device->features & FEATURE_HAS_KEYS) {
+               /* rediscover the keyboard state if sth changed during sleep */
+               kmscon_kbd_reset(device->kbd, device->rfd);
 
-       ret = kmscon_eloop_new_fd(device->input->eloop, &device->fd,
-               device->rfd, KMSCON_READABLE, device_data_arrived, device);
-       if (ret) {
-               close(device->rfd);
-               device->rfd = -1;
-               return ret;
+               ret = kmscon_eloop_new_fd(device->input->eloop, &device->fd,
+                                               device->rfd, KMSCON_READABLE,
+                                               device_data_arrived, device);
+               if (ret) {
+                       close(device->rfd);
+                       device->rfd = -1;
+                       return ret;
+               }
        }
 
        return 0;
@@ -179,14 +192,17 @@ void kmscon_input_device_sleep(struct kmscon_input_device *device)
        if (device->rfd < 0)
                return;
 
-       kmscon_eloop_rm_fd(device->fd);
+       if (device->features & FEATURE_HAS_KEYS)
+               kmscon_eloop_rm_fd(device->fd);
+
        device->fd = NULL;
        close(device->rfd);
        device->rfd = -1;
 }
 
 static int kmscon_input_device_new(struct kmscon_input_device **out,
-                       struct kmscon_input *input, const char *devnode)
+                       struct kmscon_input *input, const char *devnode,
+                                               unsigned int features)
 {
        int ret;
        struct kmscon_input_device *device;
@@ -217,6 +233,7 @@ static int kmscon_input_device_new(struct kmscon_input_device **out,
        }
 
        device->input = input;
+       device->features = features;
        device->rfd = -1;
 
        *out = device;
@@ -343,34 +360,80 @@ void kmscon_input_unref(struct kmscon_input *input)
        log_debug("input: destroying input object\n");
 }
 
+/*
+ * See if the device has anything useful to offer.
+ * We go over the desired features and return a mask of enum device_feature's.
+ * */
+static unsigned int probe_device_features(const char *node)
+{
+       int i, fd;
+       unsigned int features = 0;
+       unsigned long evbits[NLONGS(EV_CNT)] = { 0 };
+       unsigned long keybits[NLONGS(KEY_CNT)] = { 0 };
+
+       fd = open(node, O_NONBLOCK | O_CLOEXEC);
+       if (fd < 0)
+               return 0;
+
+       /* Which types of input events the device supports. */
+       errno = 0;
+       ioctl(fd, EVIOCGBIT(0, sizeof(evbits)), evbits);
+       if (errno)
+               goto err_ioctl;
+
+       /* Device supports keys/buttons. */
+       if (kmscon_evdev_bit_is_set(evbits, EV_KEY)) {
+               errno = 0;
+               ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybits)), keybits);
+               if (errno)
+                       goto err_ioctl;
+
+               /*
+                * If the device support any of the normal keyboard keys, we
+                * take it. Even if the keys are not ordinary they can be
+                * mapped to anything by the keyboard backend.
+                */
+               for (i = KEY_RESERVED; i <= KEY_MIN_INTERESTING; i++) {
+                       if (kmscon_evdev_bit_is_set(keybits, i)) {
+                               features |= FEATURE_HAS_KEYS;
+                               break;
+                       }
+               }
+       }
+
+       close(fd);
+       return features;
+
+err_ioctl:
+       if (errno != ENOTTY)
+               log_warn("input: cannot probe features of device (%s): %m\n",
+                                                                       node);
+       close(fd);
+       return 0;
+}
+
 static void add_device(struct kmscon_input *input,
                                        struct udev_device *udev_device)
 {
        int ret;
        struct kmscon_input_device *device;
-       const char *value, *node;
+       const char *node;
+       unsigned int features;
 
        if (!input || !udev_device)
                return;
 
-       /*
-        * TODO: Here should go a proper filtering of input devices we're
-        * interested in. Currently, we add all kinds of devices. A simple
-        * blacklist should be the easiest way.
-        */
-
        node = udev_device_get_devnode(udev_device);
        if (!node)
                return;
 
-       value = udev_device_get_property_value(udev_device,
-                                                       "ID_INPUT_KEYBOARD");
-       if (!value || strcmp(value, "1")) {
-               log_debug("input: ignoring non-keyboard device %s\n", node);
+       features = probe_device_features(node);
+       if (!(features & FEATURE_HAS_KEYS)) {
+               log_debug("input: ignoring non-useful device %s\n", node);
                return;
        }
 
-       ret = kmscon_input_device_new(&device, input, node);
+       ret = kmscon_input_device_new(&device, input, node, features);
        if (ret) {
                log_warn("input: cannot create input device for %s\n",
                                                                        node);
@@ -389,7 +452,7 @@ static void add_device(struct kmscon_input *input,
 
        device->next = input->devices;
        input->devices = device;
-       log_debug("input: added device %s\n", node);
+       log_debug("input: added device %s (features: %#x)\n", node, features);
 }
 
 static void remove_device(struct kmscon_input *input, const char *node)
index 9b70e31..d8029ad 100644 (file)
@@ -49,6 +49,7 @@
 #define KMSCON_INPUT_H
 
 #include <inttypes.h>
+#include <limits.h>
 #include <stdbool.h>
 #include "eloop.h"
 
@@ -89,4 +90,10 @@ void kmscon_input_sleep(struct kmscon_input *input);
 void kmscon_input_wake_up(struct kmscon_input *input);
 bool kmscon_input_is_asleep(struct kmscon_input *input);
 
+/* Querying the results of evdev ioctl's. Also used by kbd backends. */
+static inline bool kmscon_evdev_bit_is_set(const unsigned long *array, int bit)
+{
+       return !!(array[bit / LONG_BIT] & (1LL << (bit % LONG_BIT)));
+}
+
 #endif /* KMSCON_INPUT_H */