#include <errno.h>
#include <fcntl.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-
#include <libudev.h>
#include <linux/input.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "eloop.h"
#include "log.h"
#include "input.h"
INPUT_AWAKE,
};
+struct kmscon_input_device {
+ size_t ref;
+ struct kmscon_input_device *next;
+ struct kmscon_input *input;
+
+ int rfd;
+ char *devnode;
+ struct kmscon_fd *fd;
+};
+
struct kmscon_input {
size_t ref;
enum input_state state;
struct kmscon_input_device *devices;
- /*
- * We need to keep a reference to the eloop for sleeping, waking up and
- * hotplug. Avoiding this makes things harder.
- */
- struct kmscon_eloop *loop;
+ struct kmscon_eloop *eloop;
+ kmscon_input_cb cb;
+ void *data;
struct udev *udev;
struct udev_monitor *monitor;
struct kmscon_fd *monitor_fd;
-
- kmscon_input_cb cb;
};
-struct kmscon_input_device {
- size_t ref;
- struct kmscon_input *input;
- struct kmscon_input_device *next;
+static void remove_device(struct kmscon_input *input, const char *node);
- int rfd;
- char *devnode;
- struct kmscon_fd *fd;
-};
+static void device_data_arrived(struct kmscon_fd *fd, int mask, void *data)
+{
+ int i;
+ ssize_t len, n;
+ struct kmscon_input_device *device = data;
+ struct kmscon_input *input = device->input;
+ struct input_event ev[16];
+
+ len = sizeof(ev);
+ while (len == sizeof(ev)) {
+ len = read(device->rfd, &ev, sizeof(ev));
+ if (len < 0) {
+ if (errno == EWOULDBLOCK)
+ break;
+
+ log_warning("input: reading device %s failed %d\n",
+ device->devnode, errno);
+ remove_device(input, device->devnode);
+ } else if (len == 0) {
+ log_debug("input: EOF device %s\n", device->devnode);
+ remove_device(input, device->devnode);
+ } else if (len % sizeof(*ev)) {
+ log_warning("input: read invalid input_event\n");
+ } else {
+ n = len / sizeof(*ev);
+ for (i = 0; i < n; i++)
+ input->cb(input, ev[i].type, ev[i].code,
+ ev[i].value, input->data);
+ }
+ }
+}
-/* kmscon_input_device prototypes */
-int kmscon_input_device_new(struct kmscon_input_device **out,
- struct kmscon_input *input,
- const char *devnode);
-void kmscon_input_device_ref(struct kmscon_input_device *device);
-void kmscon_input_device_unref(struct kmscon_input_device *device);
-
-int kmscon_input_device_open(struct kmscon_input_device *device);
-void kmscon_input_device_close(struct kmscon_input_device *device);
-
-int kmscon_input_device_connect_eloop(struct kmscon_input_device *device);
-void kmscon_input_device_disconnect_eloop(struct kmscon_input_device *device);
-void kmscon_input_device_sleep(struct kmscon_input_device *device);
-int kmscon_input_device_wake_up(struct kmscon_input_device *device);
-
-/* internal procedures prototypes */
-static int init_input(struct kmscon_input *input);
-static void device_changed(struct kmscon_fd *fd, int mask, void *data);
-static int add_initial_devices(struct kmscon_input *input);
-static int add_device(struct kmscon_input *input, struct udev_device *udev_device);
-static void device_added(struct kmscon_input *input, struct udev_device *udev_device);
-static void device_removed(struct kmscon_input *input, struct udev_device *udev_device);
-static int remove_device(struct kmscon_input *input, struct udev_device *udev_device);
-static void device_data_arrived(struct kmscon_fd *fd, int mask, void *data);
-
-int kmscon_input_new(struct kmscon_input **out, kmscon_input_cb cb)
+int kmscon_input_device_wake_up(struct kmscon_input_device *device)
{
int ret;
- struct kmscon_input *input;
- if (!out)
+ if (!device || !device->input || !device->input->eloop)
return -EINVAL;
- input = malloc(sizeof(*input));
- if (!input)
- return -ENOMEM;
+ if (device->fd)
+ return 0;
- memset(input, 0, sizeof(*input));
- input->ref = 1;
- input->state = INPUT_ASLEEP;
- input->cb = cb;
+ device->rfd = open(device->devnode, O_CLOEXEC | O_NONBLOCK, O_RDONLY);
+ if (device->rfd < 0) {
+ log_warning("input: cannot open input device %s: %d\n",
+ device->devnode, errno);
+ return -errno;
+ }
- ret = init_input(input);
+ ret = kmscon_eloop_new_fd(device->input->eloop, &device->fd,
+ device->rfd, KMSCON_READABLE, device_data_arrived, device);
if (ret) {
- free(input);
+ close(device->rfd);
+ device->rfd = -1;
return ret;
}
- *out = input;
return 0;
}
-void kmscon_input_ref(struct kmscon_input *input)
+void kmscon_input_device_sleep(struct kmscon_input_device *device)
{
- if (!input)
+ if (!device)
return;
- ++input->ref;
+ if (device->rfd < 0)
+ return;
+
+ kmscon_eloop_rm_fd(device->fd);
+ device->fd = NULL;
+ close(device->rfd);
+ device->rfd = -1;
}
-static int init_input(struct kmscon_input *input)
+static int kmscon_input_device_new(struct kmscon_input_device **out,
+ struct kmscon_input *input, const char *devnode)
{
- int ret;
+ struct kmscon_input_device *device;
- input->udev = udev_new();
- if (!input->udev)
- return -EFAULT;
+ if (!out || !input)
+ return -EINVAL;
- input->monitor = udev_monitor_new_from_netlink(input->udev, "udev");
- if (!input->monitor) {
- ret = -EFAULT;
- goto err_udev;
- }
+ log_debug("input: new input device %s\n", devnode);
- ret = udev_monitor_filter_add_match_subsystem_devtype(input->monitor,
- "input", NULL);
- if (ret)
- goto err_monitor;
+ device = malloc(sizeof(*device));
+ if (!device)
+ return -ENOMEM;
- /*
- * There's no way to query the state of the monitor, so just start it
- * from here.
- */
- ret = udev_monitor_enable_receiving(input->monitor);
- if (ret)
- goto err_monitor;
+ memset(device, 0, sizeof(*device));
+ device->ref = 1;
- return 0;
+ device->devnode = strdup(devnode);
+ if (!device->devnode) {
+ free(device);
+ return -ENOMEM;
+ }
-err_monitor:
- udev_monitor_unref(input->monitor);
-err_udev:
- udev_unref(input->udev);
- return ret;
+ kmscon_input_ref(input);
+ device->input = input;
+ device->rfd = -1;
+
+ *out = device;
+ return 0;
}
-/*
- * This takes a ref of the loop, but it most likely not what should be
- * keeping it alive.
- */
-int kmscon_input_connect_eloop(struct kmscon_input *input, struct kmscon_eloop *loop)
+static void kmscon_input_device_ref(struct kmscon_input_device *device)
{
- int ret;
- int fd;
-
- if (!input || !loop || !input->monitor)
- return -EINVAL;
-
- if (input->loop)
- return -EALREADY;
-
- input->loop = loop;
- kmscon_eloop_ref(loop);
-
- fd = udev_monitor_get_fd(input->monitor);
-
- ret = kmscon_eloop_new_fd(loop, &input->monitor_fd, fd, KMSCON_READABLE,
- device_changed, input);
- if (ret)
- goto err_loop;
+ if (!device)
+ return;
- /* XXX: What if a device is added NOW? */
+ ++device->ref;
+}
- ret = add_initial_devices(input);
- if (ret)
- goto err_fd;
+static void kmscon_input_device_unref(struct kmscon_input_device *device)
+{
+ if (!device || !device->ref)
+ return;
- return 0;
+ if (--device->ref)
+ return;
-err_fd:
- kmscon_eloop_rm_fd(input->monitor_fd);
- input->monitor_fd = NULL;
-err_loop:
- kmscon_eloop_unref(loop);
- input->loop = NULL;
- return ret;
+ kmscon_input_device_sleep(device);
+ log_debug("input: destroying input device %s\n", device->devnode);
+ free(device->devnode);
+ free(device);
}
-static int add_initial_devices(struct kmscon_input *input)
+int kmscon_input_new(struct kmscon_input **out)
{
int ret;
+ struct kmscon_input *input;
- struct udev_enumerate *e;
- struct udev_list_entry *first;
- struct udev_list_entry *item;
- struct udev_device *udev_device;
- const char *syspath;
+ if (!out)
+ return -EINVAL;
- e = udev_enumerate_new(input->udev);
- if (!e)
- return -EFAULT;
+ input = malloc(sizeof(*input));
+ if (!input)
+ return -ENOMEM;
- ret = udev_enumerate_add_match_subsystem(e, "input");
- if (ret)
- goto err_enum;
+ log_debug("input: creating input object\n");
- ret = udev_enumerate_scan_devices(e);
- if (ret)
- goto err_enum;
+ memset(input, 0, sizeof(*input));
+ input->ref = 1;
+ input->state = INPUT_ASLEEP;
- first = udev_enumerate_get_list_entry(e);
- udev_list_entry_foreach(item, first) {
- syspath = udev_list_entry_get_name(item);
+ input->udev = udev_new();
+ if (!input->udev) {
+ log_warning("input: cannot create udev object\n");
+ ret = -EFAULT;
+ goto err_free;
+ }
- udev_device = udev_device_new_from_syspath(input->udev, syspath);
- if (!udev_device)
- continue;
+ input->monitor = udev_monitor_new_from_netlink(input->udev, "udev");
+ if (!input->monitor) {
+ log_warning("input: cannot create udev monitor\n");
+ ret = -EFAULT;
+ goto err_udev;
+ }
- add_device(input, udev_device);
+ ret = udev_monitor_filter_add_match_subsystem_devtype(input->monitor,
+ "input", NULL);
+ if (ret) {
+ log_warning("input: cannot add udev filter\n");
+ ret = -EFAULT;
+ goto err_monitor;
+ }
- udev_device_unref(udev_device);
+ ret = udev_monitor_enable_receiving(input->monitor);
+ if (ret) {
+ log_warning("input: cannot start udev monitor\n");
+ ret = -EFAULT;
+ goto err_monitor;
}
-err_enum:
- udev_enumerate_unref(e);
+ *out = input;
+ return 0;
+
+err_monitor:
+ udev_monitor_unref(input->monitor);
+err_udev:
+ udev_unref(input->udev);
+err_free:
+ free(input);
return ret;
}
-static void device_changed(struct kmscon_fd *fd, int mask, void *data)
+void kmscon_input_ref(struct kmscon_input *input)
{
- struct kmscon_input *input = data;
- struct udev_device *udev_device;
- const char *action;
-
- if (!input || !input->monitor)
- return;
-
- udev_device = udev_monitor_receive_device(input->monitor);
- if (!udev_device)
- goto err_device;
-
- action = udev_device_get_action(udev_device);
- if (!action)
- goto err_device;
-
- /*
- * XXX: need to do something with the others? (change, online,
- * offline)
- */
- if (!strcmp(action, "add"))
- device_added(input, udev_device);
- else if (!strcmp(action, "remove"))
- device_removed(input, udev_device);
+ if (!input)
+ return;
-err_device:
- udev_device_unref(udev_device);
+ ++input->ref;
}
-static void device_added(struct kmscon_input *input,
- struct udev_device *udev_device)
+void kmscon_input_unref(struct kmscon_input *input)
{
- add_device(input, udev_device);
-}
+ if (!input || !input->ref)
+ return;
-static void device_removed(struct kmscon_input *input,
- struct udev_device *udev_device)
-{
- remove_device(input, udev_device);
+ if (--input->ref)
+ return;
+
+ kmscon_input_disconnect_eloop(input);
+ udev_monitor_unref(input->monitor);
+ udev_unref(input->udev);
+ free(input);
+ log_debug("input: destroying input object\n");
}
-static int add_device(struct kmscon_input *input, struct udev_device *udev_device)
+static void add_device(struct kmscon_input *input,
+ struct udev_device *udev_device)
{
int ret;
struct kmscon_input_device *device;
- const char *value, *devnode;
+ const char *value, *node;
if (!input || !udev_device)
- return -EINVAL;
+ return;
/*
* TODO: Here should go a proper filtering of input devices we're
- * interested in. Maybe also seats, etc?
+ * interested in. Currently, we add all kinds of devices. A simple
+ * blacklist should be the easiest way.
*/
- value = udev_device_get_property_value(udev_device, "ID_INPUT_KEYBOARD");
- if (!value || strcmp(value, "1") != 0)
- return 0;
- devnode = udev_device_get_devnode(udev_device);
- if (!devnode)
- return -EFAULT;
+ node = udev_device_get_devnode(udev_device);
+ if (!node) {
+ log_warning("input: cannot get udev node for input device\n");
+ return;
+ }
- ret = kmscon_input_device_new(&device, input, devnode);
- if (ret)
- return ret;
+ 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);
+ return;
+ }
+
+ ret = kmscon_input_device_new(&device, input, node);
+ if (ret) {
+ log_warning("input: cannot create input device for %s\n",
+ node);
+ return;
+ }
if (input->state == INPUT_AWAKE) {
ret = kmscon_input_device_wake_up(device);
if (ret) {
- log_warning("input: cannot wake up new device %s: %s\n",
- devnode, strerror(-ret));
- goto err_device;
+ log_warning("input: cannot wake up new device %s\n",
+ node);
+ kmscon_input_device_unref(device);
+ return;
}
}
device->next = input->devices;
input->devices = device;
-
- log_debug("input: added device %s\n", devnode);
-
- return 0;
-
-err_device:
- kmscon_input_device_unref(device);
- return ret;
+ log_debug("input: added device %s\n", node);
}
-static int remove_device(struct kmscon_input *input, struct udev_device *udev_device)
+static void remove_device(struct kmscon_input *input, const char *node)
{
struct kmscon_input_device *iter, *prev;
- const char *devnode;
-
- if (!input || !udev_device)
- return -EINVAL;
-
- if (!input->devices)
- return 0;
- devnode = udev_device_get_devnode(udev_device);
- if (!devnode)
- return -EFAULT;
+ if (!input || !node || !input->devices)
+ return;
iter = input->devices;
prev = NULL;
+
while (iter) {
- if (!strcmp(iter->devnode, devnode)) {
+ if (!strcmp(iter->devnode, node)) {
if (prev == NULL)
input->devices = iter->next;
else
prev->next = iter->next;
+
kmscon_input_device_unref(iter);
- log_debug("input: removed device %s\n", devnode);
+ log_debug("input: removed device %s\n", node);
break;
}
prev = iter;
iter = iter->next;
}
-
- return 0;
}
-void kmscon_input_unref(struct kmscon_input *input)
+static void remove_device_udev(struct kmscon_input *input,
+ struct udev_device *udev_device)
{
- struct kmscon_input_device *iter, *next;
+ const char *node;
- if (!input || !input->ref)
- return;
-
- if (--input->ref)
+ if (!udev_device)
return;
- iter = input->devices;
- while (iter) {
- next = iter->next;
- kmscon_input_device_unref(iter);
- iter = next;
- }
-
- kmscon_input_disconnect_eloop(input);
- udev_monitor_unref(input->monitor);
- udev_unref(input->udev);
-
- free(input);
-}
-
-void kmscon_input_disconnect_eloop(struct kmscon_input *input)
-{
- if (!input || !input->loop)
+ node = udev_device_get_devnode(udev_device);
+ if (!node)
return;
- kmscon_eloop_rm_fd(input->monitor_fd);
- input->monitor_fd = NULL;
- kmscon_eloop_unref(input->loop);
- input->loop = NULL;
+ remove_device(input, node);
}
-void kmscon_input_sleep(struct kmscon_input *input)
+static void device_changed(struct kmscon_fd *fd, int mask, void *data)
{
- struct kmscon_input_device *iter;
+ struct kmscon_input *input = data;
+ struct udev_device *udev_device;
+ const char *action;
- if (!input)
+ udev_device = udev_monitor_receive_device(input->monitor);
+ if (!udev_device)
return;
- for (iter = input->devices; iter; iter = iter->next)
- kmscon_input_device_sleep(iter);
+ action = udev_device_get_action(udev_device);
+ if (!action) {
+ log_warning("input: cannot get action field of new device\n");
+ goto err_device;
+ }
- input->state = INPUT_ASLEEP;
+ if (!strcmp(action, "add"))
+ add_device(input, udev_device);
+ else if (!strcmp(action, "remove"))
+ remove_device_udev(input, udev_device);
+
+err_device:
+ udev_device_unref(udev_device);
}
-void kmscon_input_wake_up(struct kmscon_input *input)
+static void add_initial_devices(struct kmscon_input *input)
{
- struct kmscon_input_device *iter;
+ int ret;
+ struct udev_enumerate *e;
+ struct udev_list_entry *first;
+ struct udev_list_entry *item;
+ struct udev_device *udev_device;
+ const char *syspath;
- if (!input)
+ e = udev_enumerate_new(input->udev);
+ if (!e) {
+ log_warning("input: cannot create udev enumeration\n");
return;
+ }
- /*
- * XXX: should probably catch errors here and do something about
- * them.
- */
- for (iter = input->devices; iter; iter = iter->next)
- kmscon_input_device_wake_up(iter);
+ ret = udev_enumerate_add_match_subsystem(e, "input");
+ if (ret) {
+ log_warning("input: cannot add match to udev enumeration\n");
+ goto err_enum;
+ }
- input->state = INPUT_AWAKE;
-}
+ ret = udev_enumerate_scan_devices(e);
+ if (ret) {
+ log_warning("input: cannot scan udev enumeration\n");
+ goto err_enum;
+ }
-bool kmscon_input_is_asleep(struct kmscon_input *input)
-{
- if (!input)
- return false;
+ first = udev_enumerate_get_list_entry(e);
+ udev_list_entry_foreach(item, first) {
+ syspath = udev_list_entry_get_name(item);
+ if (!syspath)
+ continue;
- return input->state == INPUT_ASLEEP;
+ udev_device = udev_device_new_from_syspath(input->udev, syspath);
+ if (!udev_device) {
+ log_warning("input: cannot create device "
+ "from udev path\n");
+ continue;
+ }
+
+ add_device(input, udev_device);
+ udev_device_unref(udev_device);
+ }
+
+err_enum:
+ udev_enumerate_unref(e);
}
-int kmscon_input_device_new(struct kmscon_input_device **out,
- struct kmscon_input *input,
- const char *devnode)
+int kmscon_input_connect_eloop(struct kmscon_input *input,
+ struct kmscon_eloop *eloop, kmscon_input_cb cb, void *data)
{
- struct kmscon_input_device *device;
+ int ret;
+ int fd;
- if (!out || !input)
+ if (!input || !eloop || !cb)
return -EINVAL;
- device = malloc(sizeof(*device));
- if (!device)
- return -ENOMEM;
+ if (input->eloop)
+ return -EALREADY;
- memset(device, 0, sizeof(*device));
- device->ref = 1;
- device->input = input;
- device->rfd = -1;
+ fd = udev_monitor_get_fd(input->monitor);
+ ret = kmscon_eloop_new_fd(eloop, &input->monitor_fd, fd,
+ KMSCON_READABLE, device_changed, input);
+ if (ret)
+ return ret;
- device->devnode = strdup(devnode);
- if (!device->devnode) {
- free(device);
- return -ENOMEM;
- }
+ kmscon_eloop_ref(eloop);
+ input->eloop = eloop;
+ input->cb = cb;
+ input->data = data;
+
+ add_initial_devices(input);
- *out = device;
return 0;
}
-void kmscon_input_device_ref(struct kmscon_input_device *device)
+void kmscon_input_disconnect_eloop(struct kmscon_input *input)
{
- if (!device)
- return;
-
- ++device->ref;
-}
+ struct kmscon_input_device *tmp;
-void kmscon_input_device_unref(struct kmscon_input_device *device)
-{
- if (!device || !device->ref)
+ if (!input || !input->eloop)
return;
- if (--device->ref)
- return;
+ while (input->devices) {
+ tmp = input->devices;
+ input->devices = tmp->next;
+ kmscon_input_device_unref(tmp);
+ }
- kmscon_input_device_close(device);
- free(device->devnode);
- free(device);
+ kmscon_eloop_rm_fd(input->monitor_fd);
+ input->monitor_fd = NULL;
+ kmscon_eloop_unref(input->eloop);
+ input->eloop = NULL;
+ input->cb = NULL;
+ input->data = NULL;
}
-int kmscon_input_device_open(struct kmscon_input_device *device)
+void kmscon_input_sleep(struct kmscon_input *input)
{
- if (!device || !device->devnode)
- return -EINVAL;
-
- if (device->rfd >= 0)
- return -EALREADY;
-
- device->rfd = open(device->devnode, O_CLOEXEC, O_RDONLY);
- if (device->rfd < 0)
- return -errno;
-
- return 0;
-}
+ struct kmscon_input_device *iter;
-void kmscon_input_device_close(struct kmscon_input_device *device)
-{
- if (!device)
+ if (!input)
return;
- if (device->rfd < 0)
- return;
+ for (iter = input->devices; iter; iter = iter->next)
+ kmscon_input_device_sleep(iter);
- kmscon_input_device_disconnect_eloop(device);
- close(device->rfd);
- device->rfd = -1;
+ input->state = INPUT_ASLEEP;
}
-int kmscon_input_device_connect_eloop(struct kmscon_input_device *device)
+void kmscon_input_wake_up(struct kmscon_input *input)
{
+ struct kmscon_input_device *iter, *prev, *tmp;
int ret;
- if (!device || !device->input || !device->input->loop)
- return -EINVAL;
-
- if (device->fd)
- return -EALREADY;
-
- ret = kmscon_eloop_new_fd(device->input->loop, &device->fd,
- device->rfd, KMSCON_READABLE,
- device_data_arrived, device);
- if (ret)
- return ret;
-
- return 0;
-}
+ if (!input)
+ return;
-static void device_data_arrived(struct kmscon_fd *fd, int mask, void *data)
-{
- int i;
- ssize_t len, n;
- struct kmscon_input_device *device = data;
- /* 16 is what xf86-input-evdev uses (NUM_EVENTS) */
- struct input_event ev[16];
+ prev = NULL;
+ iter = input->devices;
- len = sizeof(ev);
- while (len == sizeof(ev)) {
- /* we're always supposed to get full events */
- len = read(device->rfd, &ev, sizeof(ev));
- if (len <= 0 || len%sizeof(*ev) != 0)
- break;
+ while (iter) {
+ ret = kmscon_input_device_wake_up(iter);
+ if (ret) {
+ if (!prev)
+ input->devices = iter->next;
+ else
+ prev->next = iter->next;
- /* this shouldn't happen */
- if (device->input->state != INPUT_AWAKE)
- continue;
+ tmp = iter;
+ iter = iter->next;
- n = len/sizeof(*ev);
- for (i=0; i < n; i++)
- device->input->cb(ev[i].type, ev[i].code, ev[i].value);
+ log_warning("input: device %s does not wake up, "
+ "removing device\n", tmp->devnode);
+ kmscon_input_device_unref(tmp);
+ } else {
+ prev = iter;
+ iter = iter->next;
+ }
}
-}
-
-void kmscon_input_device_disconnect_eloop(struct kmscon_input_device *device)
-{
- if (!device || !device->fd)
- return;
- kmscon_eloop_rm_fd(device->fd);
- device->fd = NULL;
-}
-
-void kmscon_input_device_sleep(struct kmscon_input_device *device)
-{
- kmscon_input_device_close(device);
+ input->state = INPUT_AWAKE;
}
-int kmscon_input_device_wake_up(struct kmscon_input_device *device)
+bool kmscon_input_is_asleep(struct kmscon_input *input)
{
- int ret;
-
- ret = kmscon_input_device_open(device);
- if (ret && ret != -EALREADY)
- return ret;
-
- ret = kmscon_input_device_connect_eloop(device);
- if (ret && ret != -EALREADY) {
- kmscon_input_device_close(device);
- return ret;
- }
+ if (!input)
+ return false;
- return 0;
+ return input->state == INPUT_ASLEEP;
}