diff options
author | Ran Benita <ran234@gmail.com> | 2011-12-13 14:00:44 +0200 |
---|---|---|
committer | David Herrmann <dh.herrmann@googlemail.com> | 2011-12-30 18:07:23 +0100 |
commit | 33afda84213fcb730e9d2b369a90936f596f6c37 (patch) | |
tree | d1d72a9ebf4a493c62b4c71c97bcac40bb6bcc5f /src/input.c | |
parent | f60356d429ca9931e940387d1aef8df34bee7c54 (diff) |
input: add infrastructure for an input subsystem
This does all the boring stuff for an input subsystem:
- Input device discovery using udev
- Input device hotplug (add/remove)
- Reading the evdev data from the devices
- Publishing input events to a callback through the eloop
- Suspending and resuming the input capture
Currently the delivered input events are basically just evdev's
"struct input_event" (type, code, value). More work is required to
have it pass out useful data.
Signed-off-by: Ran Benita <ran234@gmail.com>
Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
Diffstat (limited to 'src/input.c')
-rw-r--r-- | src/input.c | 611 |
1 files changed, 611 insertions, 0 deletions
diff --git a/src/input.c b/src/input.c new file mode 100644 index 0000000..5890c97 --- /dev/null +++ b/src/input.c @@ -0,0 +1,611 @@ +/* + * kmscon - udev input hotplug and evdev handling + * + * Copyright (c) 2011 Ran Benita <ran234@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The main object kmscon_input discovers and monitors input devices, and + * adds/removes them accordingly from the devices linked list. + * + * The udev monitor keeps running even while the object is in INPUT_ASLEEP. + * We do this because we'll either lose track of the devices, or otherwise + * have to re-scan the devices at every wakeup. + * + * The kmscon_input_device objects hold the file descriptors for their device + * nodes. All events go through the input-object callback; there is currently + * no "routing" or any differentiation between them. When the input is put to + * sleep, all fd's are closed. When woken up, they are opened. There should be + * not spurious events delivered. The initial state depends on the + * kmscon_input's state. + */ + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +#include <libudev.h> +#include <linux/input.h> + +#include "log.h" +#include "input.h" + +enum input_state { + INPUT_ASLEEP, + INPUT_AWAKE, +}; + +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 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; + + int rfd; + char *devnode; + struct kmscon_fd *fd; +}; + +/* 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 ret; + struct kmscon_input *input; + + if (!out) + return -EINVAL; + + input = malloc(sizeof(*input)); + if (!input) + return -ENOMEM; + + memset(input, 0, sizeof(*input)); + input->ref = 1; + input->state = INPUT_ASLEEP; + input->cb = cb; + + ret = init_input(input); + if (ret) { + free(input); + return ret; + } + + *out = input; + return 0; +} + +void kmscon_input_ref(struct kmscon_input *input) +{ + if (!input) + return; + + ++input->ref; +} + +static int init_input(struct kmscon_input *input) +{ + int ret; + + input->udev = udev_new(); + if (!input->udev) + return -EFAULT; + + input->monitor = udev_monitor_new_from_netlink(input->udev, "udev"); + if (!input->monitor) { + ret = -EFAULT; + goto err_udev; + } + + ret = udev_monitor_filter_add_match_subsystem_devtype(input->monitor, + "input", NULL); + if (ret) + goto err_monitor; + + /* + * 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; + + return 0; + +err_monitor: + udev_monitor_unref(input->monitor); +err_udev: + udev_unref(input->udev); + return ret; +} + +/* + * 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) +{ + 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; + + /* XXX: What if a device is added NOW? */ + + ret = add_initial_devices(input); + if (ret) + goto err_fd; + + return 0; + +err_fd: + kmscon_eloop_rm_fd(input->monitor_fd); + input->monitor_fd = NULL; +err_loop: + kmscon_eloop_unref(loop); + input->loop = NULL; + return ret; +} + +static int add_initial_devices(struct kmscon_input *input) +{ + int ret; + + struct udev_enumerate *e; + struct udev_list_entry *first; + struct udev_list_entry *item; + struct udev_device *udev_device; + const char *syspath; + + e = udev_enumerate_new(input->udev); + if (!e) + return -EFAULT; + + ret = udev_enumerate_add_match_subsystem(e, "input"); + if (ret) + goto err_enum; + + ret = udev_enumerate_scan_devices(e); + if (ret) + goto err_enum; + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + syspath = udev_list_entry_get_name(item); + + udev_device = udev_device_new_from_syspath(input->udev, syspath); + if (!udev_device) + continue; + + add_device(input, udev_device); + + udev_device_unref(udev_device); + } + +err_enum: + udev_enumerate_unref(e); + return ret; +} + +static void device_changed(struct kmscon_fd *fd, int mask, void *data) +{ + 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); + +err_device: + udev_device_unref(udev_device); +} + +static void device_added(struct kmscon_input *input, + struct udev_device *udev_device) +{ + add_device(input, udev_device); +} + +static void device_removed(struct kmscon_input *input, + struct udev_device *udev_device) +{ + remove_device(input, udev_device); +} + +static int add_device(struct kmscon_input *input, struct udev_device *udev_device) +{ + int ret; + struct kmscon_input_device *device; + const char *value, *devnode; + + if (!input || !udev_device) + return -EINVAL; + + /* + * TODO: Here should go a proper filtering of input devices we're + * interested in. Maybe also seats, etc? + */ + 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; + + ret = kmscon_input_device_new(&device, input, devnode); + if (ret) + return ret; + + 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; + } + } + + 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; +} + +static int remove_device(struct kmscon_input *input, struct udev_device *udev_device) +{ + 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; + + iter = input->devices; + prev = NULL; + while (iter) { + if (!strcmp(iter->devnode, devnode)) { + 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); + break; + } + + prev = iter; + iter = iter->next; + } + + return 0; +} + +void kmscon_input_unref(struct kmscon_input *input) +{ + struct kmscon_input_device *iter, *next; + + if (!input || !input->ref) + return; + + if (--input->ref) + 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) + return; + + kmscon_eloop_rm_fd(input->monitor_fd); + input->monitor_fd = NULL; + kmscon_eloop_unref(input->loop); + input->loop = NULL; +} + +void kmscon_input_sleep(struct kmscon_input *input) +{ + struct kmscon_input_device *iter; + + if (!input) + return; + + for (iter = input->devices; iter; iter = iter->next) + kmscon_input_device_sleep(iter); + + input->state = INPUT_ASLEEP; +} + +void kmscon_input_wake_up(struct kmscon_input *input) +{ + struct kmscon_input_device *iter; + + if (!input) + 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); + + input->state = INPUT_AWAKE; +} + +bool kmscon_input_is_asleep(struct kmscon_input *input) +{ + if (!input) + return false; + + return input->state == INPUT_ASLEEP; +} + +int kmscon_input_device_new(struct kmscon_input_device **out, + struct kmscon_input *input, + const char *devnode) +{ + struct kmscon_input_device *device; + + if (!out || !input) + return -EINVAL; + + device = malloc(sizeof(*device)); + if (!device) + return -ENOMEM; + + memset(device, 0, sizeof(*device)); + device->ref = 1; + device->input = input; + device->rfd = -1; + + device->devnode = strdup(devnode); + if (!device->devnode) { + free(device); + return -ENOMEM; + } + + *out = device; + return 0; +} + +void kmscon_input_device_ref(struct kmscon_input_device *device) +{ + if (!device) + return; + + ++device->ref; +} + +void kmscon_input_device_unref(struct kmscon_input_device *device) +{ + if (!device || !device->ref) + return; + + if (--device->ref) + return; + + kmscon_input_device_close(device); + free(device->devnode); + free(device); +} + +int kmscon_input_device_open(struct kmscon_input_device *device) +{ + 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; +} + +void kmscon_input_device_close(struct kmscon_input_device *device) +{ + if (!device) + return; + + if (device->rfd < 0) + return; + + kmscon_input_device_disconnect_eloop(device); + close(device->rfd); + device->rfd = -1; +} + +int kmscon_input_device_connect_eloop(struct kmscon_input_device *device) +{ + 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; +} + +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]; + + 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; + + /* this shouldn't happen */ + if (device->input->state != INPUT_AWAKE) + continue; + + n = len/sizeof(*ev); + for (i=0; i < n; i++) + device->input->cb(ev[i].type, ev[i].code, ev[i].value); + } +} + +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); +} + +int kmscon_input_device_wake_up(struct kmscon_input_device *device) +{ + 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; + } + + return 0; +} |