#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
+#include <unistd.h>
#include <xkbcommon/xkbcommon.h>
-#include "log.h"
+#include "shl_hook.h"
+#include "shl_log.h"
#include "shl_misc.h"
-#include "uterm.h"
#include "uterm_input.h"
+#include "uterm_input_internal.h"
#define LOG_SUBSYSTEM "input_uxkb"
-static void uxkb_dev_ref(struct kbd_dev *kbd)
+int uxkb_desc_init(struct uterm_input *input,
+ const char *model,
+ const char *layout,
+ const char *variant,
+ const char *options)
{
- if (!kbd || !kbd->ref)
- return;
-
- ++kbd->ref;
-}
+ int ret;
+ struct xkb_rule_names rmlvo = {
+ .rules = "evdev",
+ .model = model,
+ .layout = layout,
+ .variant = variant,
+ .options = options,
+ };
-static void uxkb_dev_unref(struct kbd_dev *kbd)
-{
- if (!kbd || !kbd->ref || --kbd->ref)
- return;
+ input->ctx = xkb_context_new(0);
+ if (!input->ctx) {
+ log_error("cannot create XKB context");
+ return -ENOMEM;
+ }
- xkb_state_unref(kbd->uxkb.state);
- kbd_desc_unref(kbd->desc);
- free(kbd);
-}
+ input->keymap = xkb_keymap_new_from_names(input->ctx, &rmlvo, 0);
+ if (!input->keymap) {
+ log_warn("failed to create keymap (%s, %s, %s, %s), "
+ "reverting to default system keymap",
+ model, layout, variant, options);
-#define EVDEV_KEYCODE_OFFSET 8
-enum {
- KEY_RELEASED = 0,
- KEY_PRESSED = 1,
- KEY_REPEATED = 2,
-};
+ rmlvo.model = "";
+ rmlvo.layout = "";
+ rmlvo.variant = "";
+ rmlvo.options = "";
-static int uxkb_dev_process(struct kbd_dev *kbd,
- uint16_t key_state,
- uint16_t code,
- struct uterm_input_event *out)
-{
- struct xkb_state *state;
- struct xkb_keymap *keymap;
- xkb_keycode_t keycode;
- const xkb_keysym_t *keysyms;
- int num_keysyms;
+ input->keymap = xkb_keymap_new_from_names(input->ctx,
+ &rmlvo, 0);
+ if (!input->keymap) {
+ log_warn("failed to create XKB keymap");
+ ret = -EFAULT;
+ goto err_ctx;
+ }
+ }
- if (!kbd)
- return -EINVAL;
+ log_debug("new keyboard description (%s, %s, %s, %s)",
+ model, layout, variant, options);
+ return 0;
- state = kbd->uxkb.state;
- keymap = xkb_state_get_map(state);
- keycode = code + EVDEV_KEYCODE_OFFSET;
+err_ctx:
+ xkb_context_unref(input->ctx);
+ return ret;
+}
- num_keysyms = xkb_key_get_syms(state, keycode, &keysyms);
+void uxkb_desc_destroy(struct uterm_input *input)
+{
+ xkb_keymap_unref(input->keymap);
+ xkb_context_unref(input->ctx);
+}
- if (key_state == KEY_PRESSED)
- xkb_state_update_key(state, keycode, XKB_KEY_DOWN);
- else if (key_state == KEY_RELEASED)
- xkb_state_update_key(state, keycode, XKB_KEY_UP);
+static void timer_event(struct ev_timer *timer, uint64_t num, void *data)
+{
+ struct uterm_input_dev *dev = data;
- if (key_state == KEY_RELEASED)
- return -ENOKEY;
+ dev->repeat_event.handled = false;
+ shl_hook_call(dev->input->hook, dev->input, &dev->repeat_event);
+}
- if (key_state == KEY_REPEATED && !xkb_key_repeats(keymap, keycode))
- return -ENOKEY;
+int uxkb_dev_init(struct uterm_input_dev *dev)
+{
+ int ret;
- if (num_keysyms <= 0)
- return -ENOKEY;
+ ret = ev_eloop_new_timer(dev->input->eloop, &dev->repeat_timer, NULL,
+ timer_event, dev);
+ if (ret)
+ return ret;
- /*
- * TODO: xkbcommon actually supports multiple keysyms
- * per key press. Here we're just using the first one,
- * but we might want to support this feature.
- */
- out->keycode = code;
- out->keysym = keysyms[0];
- out->mods = shl_get_xkb_mods(state);
- out->unicode = xkb_keysym_to_utf32(out->keysym) ? : UTERM_INPUT_INVALID;
+ dev->state = xkb_state_new(dev->input->keymap);
+ if (!dev->state) {
+ log_error("cannot create XKB state");
+ ret = -ENOMEM;
+ goto err_timer;
+ }
return 0;
+
+err_timer:
+ ev_eloop_rm_timer(dev->repeat_timer);
+ return ret;
}
-/*
- * Call this when we regain control of the keyboard after losing it.
- * We don't reset the locked group, this should survive a VT switch, etc. The
- * locked modifiers are reset according to the keyboard LEDs.
- */
-static void uxkb_dev_reset(struct kbd_dev *kbd, const unsigned long *ledbits)
+void uxkb_dev_destroy(struct uterm_input_dev *dev)
+{
+ xkb_state_unref(dev->state);
+ ev_eloop_rm_timer(dev->repeat_timer);
+}
+
+#define EVDEV_KEYCODE_OFFSET 8
+enum {
+ KEY_RELEASED = 0,
+ KEY_PRESSED = 1,
+ KEY_REPEATED = 2,
+};
+
+static void uxkb_dev_update_keyboard_leds(struct uterm_input_dev *dev)
{
- unsigned int i;
- struct xkb_state *state;
static const struct {
- int led;
- const char *name;
- } led_names[] = {
+ int evdev_led;
+ const char *xkb_led;
+ } leds[] = {
{ LED_NUML, XKB_LED_NAME_NUM },
{ LED_CAPSL, XKB_LED_NAME_CAPS },
{ LED_SCROLLL, XKB_LED_NAME_SCROLL },
};
+ struct input_event events[sizeof(leds) / sizeof(*leds)];
+ int i, ret;
- if (!kbd)
- return;
-
- /* TODO: Urghs, while the input device was closed we might have missed
- * some events that affect internal state. As xkbcommon does not provide
- * a way to reset the internal state, we simply recreate the state. This
- * should have the same effect.
- * It also has a bug that if the CTRL-Release event is skipped, then
- * every further release will never perform a _real_ release. Kind of
- * buggy so we should fix it upstream. */
- state = xkb_state_new(kbd->desc->uxkb.keymap);
- if (!state) {
- log_warning("cannot recreate xkb-state");
+ if (!(dev->capabilities & UTERM_DEVICE_HAS_LEDS))
return;
- }
- xkb_state_unref(kbd->uxkb.state);
- kbd->uxkb.state = state;
- for (i = 0; i < sizeof(led_names) / sizeof(*led_names); i++) {
- if (!input_bit_is_set(ledbits, led_names[i].led))
- continue;
+ memset(events, 0, sizeof(events));
- /*
- * TODO: Add support in xkbcommon for setting the led state,
- * and updating the modifier state accordingly. E.g., something
- * like this:
- * xkb_state_led_name_set_active(state, led_names[i].led);
- */
+ for (i = 0; i < sizeof(leds) / sizeof(*leds); i++) {
+ events[i].type = EV_LED;
+ events[i].code = leds[i].evdev_led;
+ if (xkb_state_led_name_is_active(dev->state,
+ leds[i].xkb_led) > 0)
+ events[i].value = 1;
}
+
+ ret = write(dev->rfd, events, sizeof(events));
+ if (ret != sizeof(events))
+ log_warning("cannot update LED state (%d): %m", errno);
}
-static int uxkb_desc_init(struct kbd_desc **out,
- const char *layout,
- const char *variant,
- const char *options)
+static inline int uxkb_dev_resize_event(struct uterm_input_dev *dev, size_t s)
{
- int ret;
- struct kbd_desc *desc;
- struct xkb_rule_names rmlvo = {
- .rules = "evdev",
- .model = "evdev",
- .layout = layout,
- .variant = variant,
- .options = options,
- };
+ uint32_t *tmp;
+
+ if (s > dev->num_syms) {
+ tmp = realloc(dev->event.keysyms,
+ sizeof(uint32_t) * s);
+ if (!tmp) {
+ log_warning("cannot reallocate keysym buffer");
+ return -ENOKEY;
+ }
+ dev->event.keysyms = tmp;
- if (!out)
- return -EINVAL;
+ tmp = realloc(dev->event.codepoints,
+ sizeof(uint32_t) * s);
+ if (!tmp) {
+ log_warning("cannot reallocate codepoints buffer");
+ return -ENOKEY;
+ }
+ dev->event.codepoints = tmp;
- desc = malloc(sizeof(*desc));
- if (!desc)
- return -ENOMEM;
+ tmp = realloc(dev->repeat_event.keysyms,
+ sizeof(uint32_t) * s);
+ if (!tmp) {
+ log_warning("cannot reallocate keysym buffer");
+ return -ENOKEY;
+ }
+ dev->repeat_event.keysyms = tmp;
- memset(desc, 0, sizeof(*desc));
- desc->ref = 1;
- desc->ops = &uxkb_desc_ops;
+ tmp = realloc(dev->repeat_event.codepoints,
+ sizeof(uint32_t) * s);
+ if (!tmp) {
+ log_warning("cannot reallocate codepoints buffer");
+ return -ENOKEY;
+ }
+ dev->repeat_event.codepoints = tmp;
- desc->uxkb.ctx = xkb_context_new(0);
- if (!desc->uxkb.ctx) {
- ret = -ENOMEM;
- goto err_desc;
+ dev->num_syms = s;
}
- desc->uxkb.keymap = xkb_map_new_from_names(desc->uxkb.ctx, &rmlvo, 0);
- if (!desc->uxkb.keymap) {
- log_warn("failed to create keymap (%s, %s, %s), "
- "reverting to default US keymap",
- layout, variant, options);
-
- rmlvo.layout = "us";
- rmlvo.variant = "";
- rmlvo.options = "";
+ return 0;
+}
- desc->uxkb.keymap = xkb_map_new_from_names(desc->uxkb.ctx,
- &rmlvo, 0);
- if (!desc->uxkb.keymap) {
- log_warn("failed to create keymap");
- ret = -EFAULT;
- goto err_ctx;
- }
+static int uxkb_dev_fill_event(struct uterm_input_dev *dev,
+ struct uterm_input_event *ev,
+ xkb_keycode_t code,
+ int num_syms,
+ const xkb_keysym_t *syms)
+{
+ int ret, i;
+
+ ret = uxkb_dev_resize_event(dev, num_syms);
+ if (ret)
+ return ret;
+
+ ev->keycode = code;
+ ev->ascii = shl_get_ascii(dev->state, code, syms, num_syms);
+ ev->mods = shl_get_xkb_mods(dev->state);
+ ev->num_syms = num_syms;
+ memcpy(ev->keysyms, syms, sizeof(uint32_t) * num_syms);
+
+ for (i = 0; i < num_syms; ++i) {
+ ev->codepoints[i] = xkb_keysym_to_utf32(syms[i]);
+ if (!ev->codepoints[i])
+ ev->codepoints[i] = UTERM_INPUT_INVALID;
}
- log_debug("new keyboard description (%s, %s, %s)",
- layout, variant, options);
- *out = desc;
return 0;
-
-err_ctx:
- xkb_context_unref(desc->uxkb.ctx);
-err_desc:
- free(desc);
- return ret;
}
-static void uxkb_desc_ref(struct kbd_desc *desc)
+static void uxkb_dev_repeat(struct uterm_input_dev *dev, unsigned int state)
{
- if (!desc || !desc->ref)
+ struct xkb_keymap *keymap = xkb_state_get_keymap(dev->state);
+ unsigned int i;
+ int num_keysyms, ret;
+ const uint32_t *keysyms;
+ struct itimerspec spec;
+
+ if (dev->repeating && dev->repeat_event.keycode == dev->event.keycode) {
+ if (state == KEY_RELEASED) {
+ dev->repeating = false;
+ ev_timer_update(dev->repeat_timer, NULL);
+ }
return;
+ }
- ++desc->ref;
-}
+ if (state == KEY_PRESSED &&
+ xkb_keymap_key_repeats(keymap, dev->event.keycode)) {
+ dev->repeat_event.keycode = dev->event.keycode;
+ dev->repeat_event.ascii = dev->event.ascii;
+ dev->repeat_event.mods = dev->event.mods;
+ dev->repeat_event.num_syms = dev->event.num_syms;
+
+ for (i = 0; i < dev->event.num_syms; ++i) {
+ dev->repeat_event.keysyms[i] = dev->event.keysyms[i];
+ dev->repeat_event.codepoints[i] =
+ dev->event.codepoints[i];
+ }
+ } else if (dev->repeating &&
+ !xkb_keymap_key_repeats(keymap, dev->event.keycode)) {
+ num_keysyms = xkb_state_key_get_syms(dev->state,
+ dev->repeat_event.keycode,
+ &keysyms);
+ if (num_keysyms <= 0)
+ return;
+
+ ret = uxkb_dev_fill_event(dev, &dev->repeat_event,
+ dev->repeat_event.keycode,
+ num_keysyms, keysyms);
+ if (ret)
+ return;
-static void uxkb_desc_unref(struct kbd_desc *desc)
-{
- if (!desc || !desc->ref || --desc->ref)
return;
+ } else {
+ return;
+ }
- log_debug("destroying keyboard description");
- xkb_map_unref(desc->uxkb.keymap);
- xkb_context_unref(desc->uxkb.ctx);
- free(desc);
+ dev->repeating = true;
+ spec.it_interval.tv_sec = 0;
+ spec.it_interval.tv_nsec = dev->input->repeat_rate * 1000000;
+ spec.it_value.tv_sec = 0;
+ spec.it_value.tv_nsec = dev->input->repeat_delay * 1000000;
+ ev_timer_update(dev->repeat_timer, &spec);
}
-static int uxkb_desc_alloc(struct kbd_desc *desc, struct kbd_dev **out)
+int uxkb_dev_process(struct uterm_input_dev *dev,
+ uint16_t key_state, uint16_t code)
{
- struct kbd_dev *kbd;
+ struct xkb_state *state;
+ xkb_keycode_t keycode;
+ const xkb_keysym_t *keysyms;
+ int num_keysyms, ret;
+ enum xkb_state_component changed;
- kbd = malloc(sizeof(*kbd));
- if (!kbd)
- return -ENOMEM;
+ if (key_state == KEY_REPEATED)
+ return -ENOKEY;
- memset(kbd, 0, sizeof(*kbd));
- kbd->ref = 1;
- kbd->desc = desc;
- kbd->ops = &uxkb_dev_ops;
+ state = dev->state;
+ keycode = code + EVDEV_KEYCODE_OFFSET;
- kbd->uxkb.state = xkb_state_new(desc->uxkb.keymap);
- if (!kbd->uxkb.state) {
- free(kbd);
- return -ENOMEM;
- }
+ num_keysyms = xkb_state_key_get_syms(state, keycode, &keysyms);
+
+ changed = 0;
+ if (key_state == KEY_PRESSED)
+ changed = xkb_state_update_key(state, keycode, XKB_KEY_DOWN);
+ else if (key_state == KEY_RELEASED)
+ changed = xkb_state_update_key(state, keycode, XKB_KEY_UP);
+
+ if (changed & XKB_STATE_LEDS)
+ uxkb_dev_update_keyboard_leds(dev);
+
+ if (num_keysyms <= 0)
+ return -ENOKEY;
+
+ ret = uxkb_dev_fill_event(dev, &dev->event, keycode, num_keysyms,
+ keysyms);
+ if (ret)
+ return -ENOKEY;
+
+ uxkb_dev_repeat(dev, key_state);
+
+ if (key_state == KEY_RELEASED)
+ return -ENOKEY;
+
+ dev->event.handled = false;
+ shl_hook_call(dev->input->hook, dev->input, &dev->event);
- kbd_desc_ref(desc);
- *out = kbd;
return 0;
}
-static void uxkb_keysym_to_string(uint32_t keysym, char *str, size_t size)
+void uxkb_dev_sleep(struct uterm_input_dev *dev)
{
- xkb_keysym_get_name(keysym, str, size);
+ /*
+ * While the device is asleep, we don't receive key events. This
+ * means that when we wake up, the keyboard state may be different
+ * (e.g. some key is pressed but we don't know about it). This can
+ * cause various problems, like stuck modifiers: consider if we
+ * miss a release of the left Shift key. When the user presses it
+ * again, xkb_state_update_key() will think there is *another* left
+ * Shift key that was pressed. When the key is released, it's as if
+ * this "second" key was released, but the "first" is still left
+ * pressed.
+ * To handle this, when the device goes to sleep, we save our
+ * current knowledge of the keyboard's press/release state. On wake
+ * up, we compare the states before and after, and just feed
+ * xkb_state_update_key() the deltas.
+ */
+ memset(dev->key_state_bits, 0, sizeof(dev->key_state_bits));
+ errno = 0;
+ ioctl(dev->rfd, EVIOCGKEY(sizeof(dev->key_state_bits)),
+ dev->key_state_bits);
+ if (errno)
+ log_warn("failed to save keyboard state (%d): %m", errno);
}
-int uxkb_string_to_keysym(const char *n, uint32_t *out)
+void uxkb_dev_wake_up(struct uterm_input_dev *dev)
{
- uint32_t keysym;
+ uint32_t code;
+ char *old_bits, cur_bits[sizeof(dev->key_state_bits)];
+ char old_bit, cur_bit;
+
+ old_bits = dev->key_state_bits;
+
+ memset(cur_bits, 0, sizeof(cur_bits));
+ errno = 0;
+ ioctl(dev->rfd, EVIOCGKEY(sizeof(cur_bits)), cur_bits);
+ if (errno) {
+ log_warn("failed to get current keyboard state (%d): %m",
+ errno);
+ return;
+ }
- /* TODO: fix xkbcommon upstream to be case-insensitive if case-sensitive
- * match fails. */
- keysym = xkb_keysym_from_name(n);
- if (!keysym)
- return -EFAULT;
+ for (code = 0; code < KEY_CNT; code++) {
+ old_bit = (old_bits[code / 8] & (1 << (code % 8)));
+ cur_bit = (cur_bits[code / 8] & (1 << (code % 8)));
- *out = keysym;
- return 0;
-}
+ if (old_bit == cur_bit)
+ continue;
-const struct kbd_desc_ops uxkb_desc_ops = {
- .init = uxkb_desc_init,
- .ref = uxkb_desc_ref,
- .unref = uxkb_desc_unref,
- .alloc = uxkb_desc_alloc,
- .keysym_to_string = uxkb_keysym_to_string,
- .string_to_keysym = uxkb_string_to_keysym,
-};
+ xkb_state_update_key(dev->state, code + EVDEV_KEYCODE_OFFSET,
+ cur_bit ? XKB_KEY_DOWN : XKB_KEY_UP);
+ }
-const struct kbd_dev_ops uxkb_dev_ops = {
- .ref = uxkb_dev_ref,
- .unref = uxkb_dev_unref,
- .reset = uxkb_dev_reset,
- .process = uxkb_dev_process,
-};
+ uxkb_dev_update_keyboard_leds(dev);
+}