diff --git a/Makefile.am b/Makefile.am index cf6beab..f8dd288 100644 --- a/Makefile.am +++ b/Makefile.am @@ -56,6 +56,7 @@ libkmscon_core_la_SOURCES = \ src/uterm_video.c \ src/uterm_video_drm.c \ src/uterm_monitor.c \ + src/uterm_input.c \ src/gl.h \ src/gl_math.c \ src/gl_shader.c \ diff --git a/src/uterm.h b/src/uterm.h index e65f2e7..75d61c4 100644 --- a/src/uterm.h +++ b/src/uterm.h @@ -38,6 +38,7 @@ #ifndef UTERM_UTERM_H #define UTERM_UTERM_H +#include #include #include #include "eloop.h" @@ -192,6 +193,57 @@ int uterm_video_wake_up(struct uterm_video *video); bool uterm_video_is_awake(struct uterm_video *video); void uterm_video_poll(struct uterm_video *video); +/* + * Input Devices + * This input object can combine multiple linux input devices into a single + * device and notifies the application about events. It has several different + * keyboard backends so the full XKB feature set is available. + */ + +struct uterm_input; + +enum uterm_input_modifier { + UTERM_SHIFT_MASK = (1 << 0), + UTERM_LOCK_MASK = (1 << 1), + UTERM_CONTROL_MASK = (1 << 2), + UTERM_MOD1_MASK = (1 << 3), + UTERM_MOD2_MASK = (1 << 4), + UTERM_MOD3_MASK = (1 << 5), + UTERM_MOD4_MASK = (1 << 6), + UTERM_MOD5_MASK = (1 << 7), +}; + +#define UTERM_INPUT_INVALID 0xffffffff + +struct uterm_input_event { + uint16_t keycode; /* linux keycode - KEY_* - linux/input.h */ + uint32_t keysym; /* X keysym - XK_* - X11/keysym.h */ + unsigned int mods; /* active modifiers - uterm_modifier mask */ + uint32_t unicode; /* ucs4 unicode value or UTERM_INPUT_INVALID */ +}; + +typedef void (*uterm_input_cb) (struct uterm_input *input, + struct uterm_input_event *ev, + void *data); + +int uterm_input_new(struct uterm_input **out, struct ev_eloop *eloop); +void uterm_input_ref(struct uterm_input *input); +void uterm_input_unref(struct uterm_input *input); + +void uterm_input_add_dev(struct uterm_input *input, const char *node); +void uterm_input_remove_dev(struct uterm_input *input, const char *node); + +int uterm_input_register_cb(struct uterm_input *input, + uterm_input_cb cb, + void *data); +void uterm_input_unregister_cb(struct uterm_input *input, + uterm_input_cb cb, + void *data); + +void uterm_input_sleep(struct uterm_input *input); +void uterm_input_wake_up(struct uterm_input *input); +bool uterm_input_is_asleep(struct uterm_input *input); + /* * System Monitor * This watches the system for new seats, graphics devices or other devices that diff --git a/src/uterm_input.c b/src/uterm_input.c new file mode 100644 index 0000000..0460577 --- /dev/null +++ b/src/uterm_input.c @@ -0,0 +1,456 @@ +/* + * uterm - Linux User-Space Terminal + * + * Copyright (c) 2011 Ran Benita + * Copyright (c) 2011-2012 David Herrmann + * + * 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. + */ + +/* + * Input Devices + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "conf.h" +#include "eloop.h" +#include "kbd.h" +#include "log.h" +#include "misc.h" +#include "uterm.h" +#include "uterm_internal.h" + +#define LOG_SUBSYSTEM "input" + +/* How many longs are needed to hold \n bits. */ +#define NLONGS(n) (((n) + LONG_BIT - 1) / LONG_BIT) + +enum device_feature { + FEATURE_HAS_KEYS = 0x01, + FEATURE_HAS_LEDS = 0x02, +}; + +struct uterm_input_dev { + struct kmscon_dlist list; + struct uterm_input *input; + + unsigned int features; + int rfd; + char *node; + struct ev_fd *fd; + struct kmscon_kbd *kbd; +}; + +struct uterm_input { + unsigned long ref; + struct ev_eloop *eloop; + bool awake; + + struct kmscon_hook *hook; + struct kmscon_kbd_desc *desc; + + struct kmscon_dlist devices; +}; + +static void input_free_dev(struct uterm_input_dev *dev); + +static void notify_key(struct uterm_input_dev *dev, + uint16_t type, + uint16_t code, + int32_t value) +{ + int ret; + struct uterm_input_event ev; + + if (type != EV_KEY) + return; + + /* TODO: fix cast to (void*) */ + ret = kmscon_kbd_process_key(dev->kbd, value, code, (void*)&ev); + if (ret) + return; + + kmscon_hook_call(dev->input->hook, dev->input, &ev); +} + +static void input_data_dev(struct ev_fd *fd, int mask, void *data) +{ + struct uterm_input_dev *dev = data; + struct input_event ev[16]; + ssize_t len, n; + int i; + + if (mask & (EV_HUP | EV_ERR)) { + log_debug("EOF on %s", dev->node); + input_free_dev(dev); + return; + } + + len = sizeof(ev); + while (len == sizeof(ev)) { + len = read(dev->rfd, &ev, sizeof(ev)); + if (len < 0) { + if (errno == EWOULDBLOCK) + break; + log_warn("reading from %s failed (%d): %m", + dev->node, errno); + input_free_dev(dev); + } else if (len == 0) { + log_debug("EOF on %s", dev->node); + input_free_dev(dev); + } else if (len % sizeof(*ev)) { + log_warn("invalid input_event on %s", dev->node); + } else { + n = len / sizeof(*ev); + for (i = 0; i < n; i++) + notify_key(dev, ev[i].type, ev[i].code, + ev[i].value); + } + } +} + +static int input_wake_up_dev(struct uterm_input_dev *dev) +{ + int ret; + unsigned long ledbits[NLONGS(LED_CNT)] = { 0 }; + + if (dev->rfd >= 0) + return 0; + + dev->rfd = open(dev->node, O_CLOEXEC | O_NONBLOCK | O_RDONLY); + if (dev->rfd < 0) { + log_warn("cannot open device %s (%d): %m", dev->node, errno); + return -EFAULT; + } + + if (dev->features & FEATURE_HAS_KEYS) { + if (dev->features & FEATURE_HAS_LEDS) { + ret = ioctl(dev->rfd, EVIOCGLED(sizeof(ledbits)), + &ledbits); + if (ret == -1) + log_warn("cannot read LED state of %s (%d): %m", + errno, dev->node); + } + + /* rediscover the keyboard state if sth changed during sleep */ + kmscon_kbd_reset(dev->kbd, ledbits); + + ret = ev_eloop_new_fd(dev->input->eloop, &dev->fd, + dev->rfd, EV_READABLE, + input_data_dev, dev); + if (ret) { + close(dev->rfd); + dev->rfd = -1; + return ret; + } + } + + return 0; +} + +static void input_sleep_dev(struct uterm_input_dev *dev) +{ + if (dev->rfd < 0) + return; + + ev_eloop_rm_fd(dev->fd); + dev->fd = NULL; + close(dev->rfd); + dev->rfd = -1; +} + +static void input_new_dev(struct uterm_input *input, + const char *node, + unsigned int features) +{ + struct uterm_input_dev *dev; + int ret; + + dev = malloc(sizeof(*dev)); + if (!dev) + return; + memset(dev, 0, sizeof(*dev)); + dev->input = input; + dev->rfd = -1; + dev->features = features; + + dev->node = strdup(node); + if (!dev->node) + goto err_free; + + ret = kmscon_kbd_new(&dev->kbd, input->desc); + if (ret) + goto err_node; + + if (input->awake) { + ret = input_wake_up_dev(dev); + if (ret) + goto err_kbd; + } + + log_debug("new device %s", node); + kmscon_dlist_link(&input->devices, &dev->list); + return; + +err_kbd: + kmscon_kbd_unref(dev->kbd); +err_node: + free(dev->node); +err_free: + free(dev); +} + +static void input_free_dev(struct uterm_input_dev *dev) +{ + log_debug("free device %s", dev->node); + input_sleep_dev(dev); + kmscon_dlist_unlink(&dev->list); + kmscon_kbd_unref(dev->kbd); + free(dev->node); + free(dev); +} + +int uterm_input_new(struct uterm_input **out, + struct ev_eloop *eloop) +{ + struct uterm_input *input; + int ret; + + if (!out || !eloop) + return -EINVAL; + + input = malloc(sizeof(*input)); + if (!input) + return -ENOMEM; + memset(input, 0, sizeof(*input)); + input->ref = 1; + input->eloop = eloop; + + ret = kmscon_hook_new(&input->hook); + if (ret) + goto err_free; + + ret = kmscon_kbd_desc_new(&input->desc, + conf_global.xkb_layout, + conf_global.xkb_variant, + conf_global.xkb_options); + if (ret) + goto err_hook; + + log_debug("new object %p", input); + ev_eloop_ref(input->eloop); + *out = input; + return 0; + +err_hook: + kmscon_hook_free(input->hook); +err_free: + free(input); + return ret; +} + +void uterm_input_ref(struct uterm_input *input) +{ + if (!input || !input->ref) + + ++input->ref; +} + +void uterm_input_unref(struct uterm_input *input) +{ + struct uterm_input_dev *dev; + + if (!input || !input->ref || --input->ref) + return; + + log_debug("free object %p", input); + + while (input->devices.next != &input->devices) { + dev = kmscon_dlist_entry(input->devices.next, + struct uterm_input_dev, + list); + input_free_dev(dev); + } + + kmscon_kbd_desc_unref(input->desc); + kmscon_hook_free(input->hook); + ev_eloop_unref(input->eloop); + free(input); +} + +/* + * 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, ret; + 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 | O_RDONLY); + if (fd < 0) + return 0; + + /* Which types of input events the device supports. */ + ret = ioctl(fd, EVIOCGBIT(0, sizeof(evbits)), evbits); + if (ret == -1) + goto err_ioctl; + + /* Device supports keys/buttons. */ + if (input_bit_is_set(evbits, EV_KEY)) { + ret = ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybits)), keybits); + if (ret == -1) + 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 (input_bit_is_set(keybits, i)) { + features |= FEATURE_HAS_KEYS; + break; + } + } + } + + if (input_bit_is_set(evbits, EV_LED)) + features |= FEATURE_HAS_LEDS; + + close(fd); + return features; + +err_ioctl: + log_warn("cannot probe features of device %s (%d): %m", node, errno); + close(fd); + return 0; +} + +void uterm_input_add_dev(struct uterm_input *input, const char *node) +{ + unsigned int features; + + if (!input || !node) + return; + + features = probe_device_features(node); + if (!(features & FEATURE_HAS_KEYS)) { + log_debug("ignoring non-useful device %s", node); + return; + } + + input_new_dev(input, node, features); +} + +void uterm_input_remove_dev(struct uterm_input *input, const char *node) +{ + struct kmscon_dlist *iter; + struct uterm_input_dev *dev; + + if (!input || !node) + return; + + kmscon_dlist_for_each(iter, &input->devices) { + dev = kmscon_dlist_entry(iter, + struct uterm_input_dev, + list); + if (!strcmp(dev->node, node)) { + input_free_dev(dev); + break; + } + } +} + +int uterm_input_register_cb(struct uterm_input *input, + uterm_input_cb cb, + void *data) +{ + if (!input || !cb) + return -EINVAL; + + return kmscon_hook_add_cast(input->hook, cb, data); +} + +void uterm_input_unregister_cb(struct uterm_input *input, + uterm_input_cb cb, + void *data) +{ + if (!input || !cb) + return; + + kmscon_hook_rm_cast(input->hook, cb, data); +} + +void uterm_input_sleep(struct uterm_input *input) +{ + struct kmscon_dlist *iter; + struct uterm_input_dev *dev; + + if (!input || !input->awake) + return; + + input->awake = false; + + kmscon_dlist_for_each(iter, &input->devices) { + dev = kmscon_dlist_entry(iter, + struct uterm_input_dev, + list); + input_sleep_dev(dev); + } +} + +void uterm_input_wake_up(struct uterm_input *input) +{ + struct kmscon_dlist *iter, *tmp; + struct uterm_input_dev *dev; + int ret; + + if (!input || input->awake) + return; + + input->awake = true; + + kmscon_dlist_for_each_safe(iter, tmp, &input->devices) { + dev = kmscon_dlist_entry(iter, + struct uterm_input_dev, + list); + ret = input_wake_up_dev(dev); + if (ret) + input_free_dev(dev); + } +} + +bool uterm_input_is_awake(struct uterm_input *input) +{ + if (!input) + return false; + + return input->awake; +} diff --git a/src/uterm_internal.h b/src/uterm_internal.h index 77024b6..b96ef32 100644 --- a/src/uterm_internal.h +++ b/src/uterm_internal.h @@ -30,6 +30,7 @@ #include #include +#include #include #include #include "eloop.h" @@ -289,4 +290,9 @@ static inline int video_do_use(struct uterm_video *video) return VIDEO_CALL(video->ops->use, 0, video); } +static inline bool input_bit_is_set(const unsigned long *array, int bit) +{ + return !!(array[bit / LONG_BIT] & (1LL << (bit % LONG_BIT))); +} + #endif /* UTERM_INTERNAL_H */