shl: move log.[ch] to shl_log.[ch]
[platform/upstream/kmscon.git] / src / uterm_input_uxkb.c
index dc7489e..3053dfc 100644 (file)
 #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"
 
 int uxkb_desc_init(struct uterm_input *input,
+                  const char *model,
                   const char *layout,
                   const char *variant,
                   const char *options)
@@ -47,7 +49,7 @@ int uxkb_desc_init(struct uterm_input *input,
        int ret;
        struct xkb_rule_names rmlvo = {
                .rules = "evdev",
-               .model = "",
+               .model = model,
                .layout = layout,
                .variant = variant,
                .options = options,
@@ -61,10 +63,11 @@ int uxkb_desc_init(struct uterm_input *input,
 
        input->keymap = xkb_keymap_new_from_names(input->ctx, &rmlvo, 0);
        if (!input->keymap) {
-               log_warn("failed to create keymap (%s, %s, %s), "
+               log_warn("failed to create keymap (%s, %s, %s, %s), "
                         "reverting to default system keymap",
-                        layout, variant, options);
+                        model, layout, variant, options);
 
+               rmlvo.model = "";
                rmlvo.layout = "";
                rmlvo.variant = "";
                rmlvo.options = "";
@@ -78,8 +81,8 @@ int uxkb_desc_init(struct uterm_input *input,
                }
        }
 
-       log_debug("new keyboard description (%s, %s, %s)",
-                 layout, variant, options);
+       log_debug("new keyboard description (%s, %s, %s, %s)",
+                 model, layout, variant, options);
        return 0;
 
 err_ctx:
@@ -137,6 +140,37 @@ enum {
        KEY_REPEATED = 2,
 };
 
+static void uxkb_dev_update_keyboard_leds(struct uterm_input_dev *dev)
+{
+       static const struct {
+               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 (!(dev->capabilities & UTERM_DEVICE_HAS_LEDS))
+               return;
+
+       memset(events, 0, sizeof(events));
+
+       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 inline int uxkb_dev_resize_event(struct uterm_input_dev *dev, size_t s)
 {
        uint32_t *tmp;
@@ -269,6 +303,7 @@ int uxkb_dev_process(struct uterm_input_dev *dev,
        xkb_keycode_t keycode;
        const xkb_keysym_t *keysyms;
        int num_keysyms, ret;
+       enum xkb_state_component changed;
 
        if (key_state == KEY_REPEATED)
                return -ENOKEY;
@@ -278,10 +313,14 @@ int uxkb_dev_process(struct uterm_input_dev *dev,
 
        num_keysyms = xkb_state_key_get_syms(state, keycode, &keysyms);
 
+       changed = 0;
        if (key_state == KEY_PRESSED)
-               xkb_state_update_key(state, keycode, XKB_KEY_DOWN);
+               changed = xkb_state_update_key(state, keycode, XKB_KEY_DOWN);
        else if (key_state == KEY_RELEASED)
-               xkb_state_update_key(state, keycode, XKB_KEY_UP);
+               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;
@@ -302,48 +341,58 @@ int uxkb_dev_process(struct uterm_input_dev *dev,
        return 0;
 }
 
-/*
- * 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.
- */
-void uxkb_dev_reset(struct uterm_input_dev *dev, const unsigned long *ledbits)
+void uxkb_dev_sleep(struct uterm_input_dev *dev)
 {
-       unsigned int i;
-       struct xkb_state *state;
-       static const struct {
-               int led;
-               const char *name;
-       } led_names[] = {
-               { LED_NUML, XKB_LED_NAME_NUM },
-               { LED_CAPSL, XKB_LED_NAME_CAPS },
-               { LED_SCROLLL, XKB_LED_NAME_SCROLL },
-       };
+       /*
+        * 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);
+}
 
-       /* 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(dev->input->keymap);
-       if (!state) {
-               log_warning("cannot recreate xkb-state");
+void uxkb_dev_wake_up(struct uterm_input_dev *dev)
+{
+       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;
        }
-       xkb_state_unref(dev->state);
-       dev->state = state;
 
-       for (i = 0; i < sizeof(led_names) / sizeof(*led_names); i++) {
-               if (!input_bit_is_set(ledbits, led_names[i].led))
+       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)));
+
+               if (old_bit == cur_bit)
                        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);
-                */
+               xkb_state_update_key(dev->state, code + EVDEV_KEYCODE_OFFSET,
+                                    cur_bit ? XKB_KEY_DOWN : XKB_KEY_UP);
        }
+
+       uxkb_dev_update_keyboard_leds(dev);
 }