shl: move log.[ch] to shl_log.[ch]
[platform/upstream/kmscon.git] / src / uterm_input_uxkb.c
index 59a5b85..3053dfc 100644 (file)
 #include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 #include <xkbcommon/xkbcommon.h>
-#include "log.h"
-#include "uterm.h"
+#include "shl_hook.h"
+#include "shl_log.h"
+#include "shl_misc.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;
+       int ret;
+       struct xkb_rule_names rmlvo = {
+               .rules = "evdev",
+               .model = model,
+               .layout = layout,
+               .variant = variant,
+               .options = options,
+       };
 
-       ++kbd->ref;
-}
+       input->ctx = xkb_context_new(0);
+       if (!input->ctx) {
+               log_error("cannot create XKB context");
+               return -ENOMEM;
+       }
 
-static void uxkb_dev_unref(struct kbd_dev *kbd)
-{
-       if (!kbd || !kbd->ref || --kbd->ref)
-               return;
+       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);
 
-       xkb_state_unref(kbd->uxkb.state);
-       kbd_desc_unref(kbd->desc);
-       free(kbd);
-}
+               rmlvo.model = "";
+               rmlvo.layout = "";
+               rmlvo.variant = "";
+               rmlvo.options = "";
 
-#define EVDEV_KEYCODE_OFFSET 8
-enum {
-       KEY_RELEASED = 0,
-       KEY_PRESSED = 1,
-       KEY_REPEATED = 2,
-};
+               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;
+               }
+       }
 
-static unsigned int get_effective_modmask(struct xkb_state *state)
-{
-       unsigned int mods = 0;
-
-       if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_SHIFT,
-                                               XKB_STATE_EFFECTIVE))
-           mods |= UTERM_SHIFT_MASK;
-       if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CAPS,
-                                               XKB_STATE_EFFECTIVE))
-           mods |= UTERM_LOCK_MASK;
-       if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_CTRL,
-                                               XKB_STATE_EFFECTIVE))
-           mods |= UTERM_CONTROL_MASK;
-       if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_ALT,
-                                               XKB_STATE_EFFECTIVE))
-           mods |= UTERM_MOD1_MASK;
-       if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_LOGO,
-                                               XKB_STATE_EFFECTIVE))
-           mods |= UTERM_MOD4_MASK;
-
-       return mods;
+       log_debug("new keyboard description (%s, %s, %s, %s)",
+                 model, layout, variant, options);
+       return 0;
+
+err_ctx:
+       xkb_context_unref(input->ctx);
+       return ret;
 }
 
-static int uxkb_dev_process(struct kbd_dev *kbd,
-                           uint16_t key_state,
-                           uint16_t code,
-                           struct uterm_input_event *out)
+void uxkb_desc_destroy(struct uterm_input *input)
 {
-       struct xkb_state *state;
-       struct xkb_keymap *keymap;
-       xkb_keycode_t keycode;
-       const xkb_keysym_t *keysyms;
-       int num_keysyms;
-
-       if (!kbd)
-               return -EINVAL;
+       xkb_keymap_unref(input->keymap);
+       xkb_context_unref(input->ctx);
+}
 
-       state = kbd->uxkb.state;
-       keymap = xkb_state_get_map(state);
-       keycode = code + EVDEV_KEYCODE_OFFSET;
+static void timer_event(struct ev_timer *timer, uint64_t num, void *data)
+{
+       struct uterm_input_dev *dev = data;
 
-       num_keysyms = xkb_key_get_syms(state, keycode, &keysyms);
+       dev->repeat_event.handled = false;
+       shl_hook_call(dev->input->hook, dev->input, &dev->repeat_event);
+}
 
-       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);
+int uxkb_dev_init(struct uterm_input_dev *dev)
+{
+       int ret;
 
-       if (key_state == KEY_RELEASED)
-               return -ENOKEY;
+       ret = ev_eloop_new_timer(dev->input->eloop, &dev->repeat_timer, NULL,
+                                timer_event, dev);
+       if (ret)
+               return ret;
 
-       if (key_state == KEY_REPEATED && !xkb_key_repeats(keymap, keycode))
-               return -ENOKEY;
+       dev->state = xkb_state_new(dev->input->keymap);
+       if (!dev->state) {
+               log_error("cannot create XKB state");
+               ret = -ENOMEM;
+               goto err_timer;
+       }
 
-       if (num_keysyms <= 0)
-               return -ENOKEY;
+       return 0;
 
-       /*
-        * 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 = get_effective_modmask(state);
-       out->unicode = xkb_keysym_to_utf32(out->keysym) ? : UTERM_INPUT_INVALID;
+err_timer:
+       ev_eloop_rm_timer(dev->repeat_timer);
+       return ret;
+}
 
-       return 0;
+void uxkb_dev_destroy(struct uterm_input_dev *dev)
+{
+       xkb_state_unref(dev->state);
+       ev_eloop_rm_timer(dev->repeat_timer);
 }
 
-/*
- * 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)
+#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)
+       if (!(dev->capabilities & UTERM_DEVICE_HAS_LEDS))
                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");
-               return;
-       }
-       xkb_state_unref(kbd->uxkb.state);
-       kbd->uxkb.state = state;
+       memset(events, 0, sizeof(events));
 
-       for (i = 0; i < sizeof(led_names) / sizeof(*led_names); i++) {
-               if (!input_bit_is_set(ledbits, led_names[i].led))
-                       continue;
-
-               /*
-                * 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);
+}