input, xkb: port to new kbd object
authorRan Benita <ran234@gmail.com>
Tue, 17 Jan 2012 21:57:11 +0000 (23:57 +0200)
committerDavid Herrmann <dh.herrmann@googlemail.com>
Mon, 23 Jan 2012 12:49:41 +0000 (13:49 +0100)
This commit ports the XKB handling to the new kbd interface, and makes
the input subsystem use it without any direct knowledge of XKB.

Some code is moved around but there are no functional changes.

Signed-off-by: Ran Benita <ran234@gmail.com>
Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
Makefile.am
src/input.c
src/input.h
src/input_xkb.h [deleted file]
src/kbd_xkb.c [moved from src/input_xkb.c with 87% similarity]

index bf240dc..32a3c0e 100644 (file)
@@ -47,10 +47,10 @@ libkmscon_core_la_SOURCES = \
        src/eloop.c src/eloop.h \
        src/vt.c src/vt.h \
        src/input.c src/input.h \
-       src/input_xkb.c src/input_xkb.h \
-       external/imKStoUCS.c external\imKStoUCS.h \
        src/vte.c src/vte.h \
-       src/terminal.c src/terminal.h
+       src/terminal.c src/terminal.h \
+       src/kbd_xkb.c src/kbd.h \
+       external/imKStoUCS.c external\imKStoUCS.h
 
 if USE_PANGO
 libkmscon_core_la_SOURCES += \
index 9d239ee..3ec63c3 100644 (file)
@@ -50,7 +50,7 @@
 
 #include "eloop.h"
 #include "input.h"
-#include "input_xkb.h"
+#include "kbd.h"
 #include "log.h"
 
 enum input_state {
@@ -67,7 +67,7 @@ struct kmscon_input_device {
        char *devnode;
        struct kmscon_fd *fd;
 
-       struct xkb_state xkb_state;
+       struct kmscon_kbd *kbd;
 };
 
 struct kmscon_input {
@@ -83,7 +83,7 @@ struct kmscon_input {
        struct udev_monitor *monitor;
        struct kmscon_fd *monitor_fd;
 
-       struct xkb_desc *xkb_desc;
+       struct kmscon_kbd_desc *desc;
 };
 
 static void remove_device(struct kmscon_input *input, const char *node);
@@ -91,18 +91,20 @@ static void remove_device(struct kmscon_input *input, const char *node);
 static void notify_key(struct kmscon_input_device *device,
                                uint16_t type, uint16_t code, int32_t value)
 {
+       int ret;
        struct kmscon_input_event ev;
-       bool has_event;
        struct kmscon_input *input;
 
        if (type != EV_KEY)
                return;
 
        input = device->input;
-       has_event = kmscon_xkb_process_evdev_key(input->xkb_desc,
-                                       &device->xkb_state, value, code, &ev);
+       ret = kmscon_kbd_process_key(device->kbd, value, code, &ev);
+
+       if (ret && ret != -ENOKEY)
+               return;
 
-       if (has_event)
+       if (ret != -ENOKEY && input->cb)
                input->cb(input, &ev, input->data);
 }
 
@@ -155,9 +157,8 @@ int kmscon_input_device_wake_up(struct kmscon_input_device *device)
                return -errno;
        }
 
-       /* this rediscovers the xkb state if sth changed during sleep */
-       kmscon_xkb_reset_state(device->input->xkb_desc, &device->xkb_state,
-                                                               device->rfd);
+       /* this rediscovers the keyboard state if sth changed during sleep */
+       kmscon_kbd_reset(device->kbd, device->rfd);
 
        ret = kmscon_eloop_new_fd(device->input->eloop, &device->fd,
                device->rfd, KMSCON_READABLE, device_data_arrived, device);
@@ -187,6 +188,7 @@ void kmscon_input_device_sleep(struct kmscon_input_device *device)
 static int kmscon_input_device_new(struct kmscon_input_device **out,
                        struct kmscon_input *input, const char *devnode)
 {
+       int ret;
        struct kmscon_input_device *device;
 
        if (!out || !input)
@@ -207,6 +209,13 @@ static int kmscon_input_device_new(struct kmscon_input_device **out,
                return -ENOMEM;
        }
 
+       ret = kmscon_kbd_new(&device->kbd, input->desc);
+       if (ret) {
+               free(device->devnode);
+               free(device);
+               return ret;
+       }
+
        device->input = input;
        device->rfd = -1;
 
@@ -214,13 +223,13 @@ static int kmscon_input_device_new(struct kmscon_input_device **out,
        return 0;
 }
 
-static void kmscon_input_device_ref(struct kmscon_input_device *device)
-{
-       if (!device)
-               return;
+/* static void kmscon_input_device_ref(struct kmscon_input_device *device) */
+/* { */
+/*     if (!device) */
+/*             return; */
 
-       ++device->ref;
-}
+/*     ++device->ref; */
+/* } */
 
 static void kmscon_input_device_unref(struct kmscon_input_device *device)
 {
@@ -231,6 +240,7 @@ static void kmscon_input_device_unref(struct kmscon_input_device *device)
                return;
 
        kmscon_input_device_sleep(device);
+       kmscon_kbd_unref(device->kbd);
        log_debug("input: destroying input device %s\n", device->devnode);
        free(device->devnode);
        free(device);
@@ -260,7 +270,7 @@ int kmscon_input_new(struct kmscon_input **out)
        variant = getenv("KMSCON_XKB_VARIANT") ?: "";
        options = getenv("KMSCON_XKB_OPTIONS") ?: "";
 
-       ret = kmscon_xkb_new_desc(layout, variant, options, &input->xkb_desc);
+       ret = kmscon_kbd_desc_new(&input->desc, layout, variant, options);
        if (ret) {
                log_warning("input: cannot create xkb description\n");
                goto err_free;
@@ -303,7 +313,7 @@ err_monitor:
 err_udev:
        udev_unref(input->udev);
 err_xkb:
-       kmscon_xkb_free_desc(input->xkb_desc);
+       kmscon_kbd_desc_unref(input->desc);
 err_free:
        free(input);
        return ret;
@@ -328,7 +338,7 @@ void kmscon_input_unref(struct kmscon_input *input)
        kmscon_input_disconnect_eloop(input);
        udev_monitor_unref(input->monitor);
        udev_unref(input->udev);
-       kmscon_xkb_free_desc(input->xkb_desc);
+       kmscon_kbd_desc_unref(input->desc);
        free(input);
        log_debug("input: destroying input object\n");
 }
index 579716f..9b70e31 100644 (file)
@@ -77,17 +77,6 @@ struct kmscon_input_event {
 typedef void (*kmscon_input_cb) (struct kmscon_input *input,
                                struct kmscon_input_event *ev, void *data);
 
-/*
- * These are the values sent by the kernel in the /value/ field of the
- * /input_event/ struct.
- * See Documentation/input/event-codes.txt in the kernel tree.
- */
-enum kmscon_key_state {
-       KMSCON_KEY_RELEASED = 0,
-       KMSCON_KEY_PRESSED = 1,
-       KMSCON_KEY_REPEATED = 2,
-};
-
 int kmscon_input_new(struct kmscon_input **out);
 void kmscon_input_ref(struct kmscon_input *input);
 void kmscon_input_unref(struct kmscon_input *input);
diff --git a/src/input_xkb.h b/src/input_xkb.h
deleted file mode 100644 (file)
index b5b4816..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef KMSCON_INPUT_XKB_H
-#define KMSCON_INPUT_XKB_H
-
-#include <inttypes.h>
-#include <X11/extensions/XKBcommon.h>
-#include "input.h"
-
-int kmscon_xkb_new_desc(const char *layout, const char *variant,
-                                               const char *options,
-                                               struct xkb_desc **out);
-void kmscon_xkb_free_desc(struct xkb_desc *desc);
-
-void kmscon_xkb_reset_state(struct xkb_desc *desc,
-                                               struct xkb_state *state,
-                                               int evdev_fd);
-
-bool kmscon_xkb_process_evdev_key(struct xkb_desc *desc,
-                                               struct xkb_state *state,
-                                               enum kmscon_key_state key_state,
-                                               uint16_t code,
-                                               struct kmscon_input_event *out);
-
-#endif /* KMSCON_INPUT_XKB_H */
similarity index 87%
rename from src/input_xkb.c
rename to src/kbd_xkb.c
index e21c824..b46574c 100644 (file)
 #include <string.h>
 #include <sys/ioctl.h>
 #include <linux/input.h>
+#include <X11/extensions/XKBcommon.h>
 
-#include "input_xkb.h"
+#include "kbd.h"
 #include "log.h"
 #include "imKStoUCS.h"
 
-/* internal procedures prototypes */
-static void init_compat(struct xkb_desc *desc);
-static void init_key_types(struct xkb_desc *desc);
-static void init_actions(struct xkb_desc *desc);
-static void init_indicators(struct xkb_desc *desc);
-static void init_autorepeat(struct xkb_desc *desc);
-static int init_compat_for_keycode(struct xkb_desc *desc, KeyCode keycode);
-static int init_compat_for_keysym(struct xkb_desc *desc, KeyCode keycode,
-                                       uint8_t group, uint16_t level);
-static int allocate_key_acts(struct xkb_desc *desc, uint8_t keycode);
-static struct xkb_sym_interpret *find_sym_interpret(struct xkb_desc *desc,
-                       uint32_t sym, uint16_t level, uint8_t key_modmap);
-static bool are_modifiers_matching(uint8_t mods, unsigned char match,
-                                                       uint8_t to_mods);
-static uint8_t virtual_and_real_to_mask(struct xkb_desc *desc, uint16_t vmods,
-                                                       uint8_t real_mods);
-static uint8_t virtual_to_real_mods(struct xkb_desc *desc, uint16_t vmods);
-static void init_action(struct xkb_desc *desc, union xkb_action *action);
+struct kmscon_kbd_desc {
+       unsigned long ref;
 
-static bool process_action(struct xkb_desc *desc, struct xkb_state *state,
-                       KeyCode keycode, enum kmscon_key_state key_state,
-                                               union xkb_action *action);
-static bool process_mod_action(struct xkb_desc *desc, struct xkb_state *state,
-                       KeyCode keycode, enum kmscon_key_state key_state,
-                                       struct xkb_mod_action *action);
-static bool process_group_action(struct xkb_desc *desc, struct xkb_state *state,
-                       KeyCode keycode, enum kmscon_key_state key_state,
-                                       struct xkb_group_action *action);
+       struct xkb_desc *desc;
+};
 
-static bool should_key_repeat(struct xkb_desc *desc, KeyCode keycode);
-static uint8_t wrap_group_keycode(struct xkb_desc *desc, KeyCode keycode,
-                                                               int16_t group);
-static uint8_t wrap_group_control(struct xkb_desc *desc, int16_t group);
-static void update_effective_mods(struct xkb_desc *desc,
-                                               struct xkb_state *state);
-static void update_effective_group(struct xkb_desc *desc,
-                                               struct xkb_state *state);
-static struct xkb_indicator_map *find_indicator_map(struct xkb_desc *desc,
-                                               const char *indicator_name);
+struct kmscon_kbd {
+       unsigned long ref;
+       struct kmscon_kbd_desc *desc;
 
-/*
- * Create a ready-to-use xkb description object. This is used in most places
- * having to do with XKB.
- */
-int kmscon_xkb_new_desc(const char *layout, const char *variant,
-                                                       const char *options,
-                                                       struct xkb_desc **out)
+       struct xkb_state state;
+};
+
+int kmscon_kbd_new(struct kmscon_kbd **out, struct kmscon_kbd_desc *desc)
 {
-       struct xkb_desc *desc;
+       struct kmscon_kbd *kbd;
 
-       struct xkb_rule_names rmlvo = {
-               .rules = "evdev",
-               .model = "evdev",
-               .layout = layout,
-               .variant = variant,
-               .options = options,
-       };
+       kbd = malloc(sizeof(*kbd));
+       if (!kbd)
+               return -ENOMEM;
 
-       desc = xkb_compile_keymap_from_rules(&rmlvo);
-       if (!desc)
-               return -EFAULT;
+       memset(kbd, 0, sizeof(*kbd));
 
-       /* The order of these is important! */
-       init_compat(desc);
-       init_key_types(desc);
-       init_actions(desc);
-       init_indicators(desc);
-       init_autorepeat(desc);
+       kbd->desc = desc;
+       kmscon_kbd_desc_ref(desc);
 
-       *out = desc;
+       *out = kbd;
        return 0;
 }
 
-void kmscon_xkb_free_desc(struct xkb_desc *desc)
+void kmscon_kbd_ref(struct kmscon_kbd *kbd)
 {
-       if (!desc)
+       if (!kbd)
                return;
 
-       /*
-        * XXX: Seems this doesn't really free everything, valgrind shows some
-        * big leaks from libxkbcommon. Hopefully we use just one up until we
-        * exit.
-        */
-       xkb_free_keymap(desc);
+       ++kbd->ref;
 }
 
-/*
- * This mostly fills out the keycode-action mapping and puts the virtual
- * modifier mappings in the right place.
- */
-static void init_compat(struct xkb_desc *desc)
+void kmscon_kbd_unref(struct kmscon_kbd *kbd)
 {
-       /* If we use KeyCode it overflows. */
-       unsigned int keycode;
+       if (!kbd || !kbd->ref)
+               return;
 
-       for (keycode = desc->min_key_code; keycode <= desc->max_key_code; keycode++)
-               init_compat_for_keycode(desc, keycode);
+       if (--kbd->ref)
+               return;
+
+       kmscon_kbd_desc_unref(kbd->desc);
+       free(kbd);
 }
 
-static int init_compat_for_keycode(struct xkb_desc *desc, KeyCode keycode)
+static uint8_t virtual_to_real_mods(struct xkb_desc *desc, uint16_t vmods)
 {
-       int ret;
-       int i, bit;
-
-       uint8_t group;
-       uint16_t level;
-       int num_groups;
-       int num_levels;
-
-       /*
-        * It's possible that someone had set some actions for the keycode
-        * through the symbols file, and so we shouldn't override with the
-        * compat. This is very uncommon though, only used by the breaks_caps
-        * option here.
-        */
-       if (XkbKeyHasActions(desc, keycode))
-               return 0;
+       int i;
+       uint32_t bit;
+       uint8_t mods;
 
-       num_groups = XkbKeyNumGroups(desc, keycode);
+       mods = 0x00;
 
-       /*
-        * We need to track the sym level in order to support LevelOneOnly,
-        * which is used in some symbol interpretations.
-        */
+       for (i=0, bit=0x01; i < XkbNumVirtualMods; i++, bit<<=1)
+               if (vmods & bit)
+                       mods |= desc->server->vmods[i];
 
-       for (group=0, i=0; group < num_groups; group++) {
-               num_levels = XkbKeyGroupWidth(desc, keycode, group);
+       return mods;
+}
 
-               for (level=0; level < num_levels; level++) {
-                       ret = init_compat_for_keysym(desc, keycode,
-                                                               group, level);
-                       if (ret)
-                               return ret;
-               }
-       }
+static uint8_t virtual_and_real_to_mask(struct xkb_desc *desc,
+                                       uint16_t vmods, uint8_t real_mods)
+{
+       uint8_t mods = 0x00;
 
-       /*
-        * Translate the virtual modifiers bound to this key to the real
-        * modifiers bound to this key.
-        * See [Lib] 17.4 for vmodmap and friends.
-        */
-       for (i=0, bit=0x01; i < XkbNumVirtualMods; i++, bit<<=1)
-               if (bit&desc->server->vmodmap[keycode])
-                       desc->server->vmods[i] |= desc->map->modmap[keycode];
+       mods |= real_mods;
+       mods |= virtual_to_real_mods(desc, vmods);
 
-       return 0;
+       return mods;
 }
 
-static int init_compat_for_keysym(struct xkb_desc *desc, KeyCode keycode,
-                                               uint8_t group, uint16_t level)
+/*
+ * Helper function for the wrap_group_* functions.
+ * See [Lib] 11.7.1 for the rules.
+ */
+static uint8_t wrap_group(int16_t group, int num_groups, uint8_t group_info)
 {
-       int ret;
-       uint8_t key_modmap;
-       uint32_t sym;
-       struct xkb_sym_interpret *si;
-       union xkb_action *action;
-
-       key_modmap = desc->map->modmap[keycode];
-       sym = XkbKeySymEntry(desc, keycode, level, group);
-       si = find_sym_interpret(desc, sym, level, key_modmap);
+       /* No need for wrapping. */
+       if (XkbIsLegalGroup(group) && group < num_groups)
+               return group;
 
-       if (!si)
-               return 0;
+       switch (XkbOutOfRangeGroupAction(group_info)) {
+       case XkbWrapIntoRange:
+               /*
+                * C99 says a negative dividend in a modulo operation
+                * will always give a negative result.
+                */
+               if (group < 0)
+                       return num_groups + (group % num_groups);
+               else
+                       return group % num_groups;
 
-       /* Set the key action mapping. */
-       if (si->act.type != XkbSA_NoAction) {
-               ret = allocate_key_acts(desc, keycode);
-               if (ret)
-                       return ret;
+       case XkbClampIntoRange:
+               /* This one seems to be unused. */
+               return num_groups - 1;
 
-               action = XkbKeyActionEntry(desc, keycode, level, group);
-               *action = (union xkb_action)si->act;
+       case XkbRedirectIntoRange:
+               /* This one seems to be unused. */
+               group = XkbOutOfRangeGroupNumber(group_info);
+               /* If it's _still_ out of range, use the first group. */
+               if (group >= num_groups)
+                       return 0;
        }
 
-       /* Set the key virtual modifier mapping. */
-       if (si->virtual_mod != XkbNoModifier)
-               desc->server->vmodmap[keycode] |= 0x01 << si->virtual_mod;
-
        return 0;
 }
 
 /*
- * Allocate slots for a keycode in the key-action mapping array. xkbcommon
- * doesn't do this by itself for actions from compat (that is almost all of
- * them).
- * See [xserver] XKBMAlloc.c:XkbResizeKeyActions() for the equivalent.
+ * Wrap an arbitrary group into a legal effective global group according to
+ * the GroupsWrap control.
+ * (Group actions mostly act on the group number in a relative manner [e.g.
+ * +1, -1]. So if we have N groups, the effective group is N-1, and we get a
+ * SetGroup +1, this tells us what to do.)
  */
-static int allocate_key_acts(struct xkb_desc *desc, uint8_t keycode)
+static uint8_t wrap_group_control(struct xkb_desc *desc, int16_t group)
 {
-       unsigned short index;
-       union xkb_action *acts;
-       struct xkb_server_map *server;
-       int sym_count;
-       int new_needed;
-       unsigned short new_num_acts;
-       unsigned short new_size_acts;
-
-       server = desc->server;
-       sym_count = XkbKeyNumSyms(desc, keycode);
-
-       /*
-        * num_acts is the occupied slots, size_acts is the current total
-        * capacity.
-        */
-
-       if (XkbKeyHasActions(desc, keycode)) {
-               /* An array is already allocated for this key. */
+       int num_groups;
+       uint8_t group_info;
 
-               /* index = server->key_acts[keycode]; */
-       } else if (server->num_acts + sym_count <= server->size_acts) {
-               /* There's enough left over space; use it. */
+       num_groups = desc->ctrls->num_groups;
+       group_info = desc->ctrls->groups_wrap;
 
-               index = server->num_acts;
-               server->key_acts[keycode] = index;
-               server->num_acts += sym_count;
-       } else {
-               /* Need to allocate new space. */
+       return wrap_group(group, num_groups, group_info);
+}
 
-               index = server->num_acts;
-               new_num_acts = server->num_acts + sym_count;
-               new_needed = sym_count - (server->size_acts - new_num_acts);
-               /* Add some extra to avoid repeated reallocs. */
-               new_size_acts = server->size_acts + new_needed + 8;
+/*
+ * Wrap the effective global group to a legal group for the keycode, according
+ * to the rule specified for the key.
+ * (Some keycodes may have more groups than others, and so the effective
+ * group may not make sense for a certain keycode).
+ */
+static uint8_t wrap_group_keycode(struct xkb_desc *desc, KeyCode keycode,
+                                                               int16_t group)
+{
+       int num_groups;
+       uint8_t group_info;
 
-               acts = realloc(server->acts,
-                               sizeof(union xkb_action) * new_size_acts);
-               if (!acts)
-                       return -ENOMEM;
+       num_groups = XkbKeyNumGroups(desc, keycode);
+       group_info = XkbKeyGroupInfo(desc, keycode);
 
-               /* XkbSA_NoAction is 0x00 so we're good. */ 
-               memset(acts+index, 0, sym_count);
-               server->key_acts[keycode] = index;
-               server->num_acts = new_num_acts;
-               server->size_acts = new_size_acts;
-               server->acts = acts;
-       }
+       return wrap_group(group, num_groups, group_info);
+}
 
-       return 0;
+/*
+ * Need to update the effective mods after any changes to the base, latched or
+ * locked mods.
+ */
+static void update_effective_mods(struct xkb_desc *desc,
+                                               struct xkb_state *state)
+{
+       state->mods = state->base_mods | state->latched_mods |
+                                                       state->locked_mods;
 }
 
 /*
- * Look for the most specific symbol interpretation for the keysym.
- * See [xserver] XKBMisc.c:_XkbFindMatchingInterp() for the equivalent.
+ * Need to update the effective group after any changes to the base, latched or
+ * locked group.
  */
-static struct xkb_sym_interpret *find_sym_interpret(struct xkb_desc *desc,
-                       uint32_t sym, uint16_t level, uint8_t key_modmap)
+static void update_effective_group(struct xkb_desc *desc,
+                                               struct xkb_state *state)
 {
-       int i;
-       struct xkb_sym_interpret *si;
-       struct xkb_sym_interpret *all_syms_si;
+       int16_t group;
 
-       all_syms_si = NULL;
+       /* Update the effective group. */
+       group = state->base_group + state->locked_group + state->latched_group;
+       state->group = wrap_group_control(desc, group);
+}
+
+/*
+ * Updates the group state.
+ * See [Lib] Table 17.4 for logic.
+ */
+static bool process_group_action(struct xkb_desc *desc, struct xkb_state *state,
+                       KeyCode keycode, enum kmscon_key_state key_state,
+                                       struct xkb_group_action *action)
+{
+       int16_t group = action->group;
+       uint8_t flags = action->flags;
 
        /*
-        * If we find a matching interpret specific to our symbol, we return
-        * it immediatly.
-        * If we didn't find any, we return the first matching all-catching
-        * interpret.
+        * action->group is signed and may be negative if GroupAbsolute
+        * is not set. A group itself cannot be negative and is unsigend.
+        * Therefore we extend these to int16 to avoid underflow and
+        * signedness issues. Be careful!
         */
+       int16_t base_group = state->base_group;
+       int16_t latched_group = state->latched_group;
+       int16_t locked_group = state->locked_group;
 
-       for (i=0; i < desc->compat->num_si; i++) {
-               si = &desc->compat->sym_interpret[i];
-
-               if (si->sym != sym && si->sym != 0)
-                       continue;
-
-               /*
-                * If the interpret specified UseModMapMods=level1, the sym
-                * must be in the first level of its group.
-                * Note: [xserver] and [Lib] do different things here, and it
-                * doesn't seem to matter much. So it's commented for now.
-                */
-               /* if (si->match&XkbSI_LevelOneOnly && level != 0) */
-               /*      continue; */
+       /*
+        * FIXME: Some actions here should be conditioned "and no keys are
+        * physically depressed when this key is released".
+        */
 
-               if (!are_modifiers_matching(si->mods, si->match, key_modmap))
-                       continue;
+       switch (action->type) {
+       case XkbSA_SetGroup:
+               if (key_state == KMSCON_KEY_PRESSED) {
+                       if (flags & XkbSA_GroupAbsolute)
+                               base_group = group;
+                       else
+                               base_group += group;
+               } else if (key_state == KMSCON_KEY_RELEASED) {
+                       if (flags & XkbSA_ClearLocks)
+                               locked_group = 0;
+               }
 
-               if (si->sym != 0)
-                       return si;
-               else if (all_syms_si == NULL)
-                       all_syms_si = si;
-       }
+               break;
+       case XkbSA_LatchGroup:
+               if (key_state == KMSCON_KEY_PRESSED) {
+                       if (flags & XkbSA_GroupAbsolute)
+                               base_group = group;
+                       else
+                               base_group += group;
+               } else if (key_state == KMSCON_KEY_RELEASED) {
+                       if ((flags & XkbSA_LatchToLock) && latched_group) {
+                               locked_group += group;
+                               latched_group -= group;
+                       } else {
+                               latched_group += group;
+                       }
+               }
 
-       return all_syms_si;
-}
+               break;
+       case XkbSA_LockGroup:
+               if (key_state == KMSCON_KEY_PRESSED) {
+                       if (flags & XkbSA_GroupAbsolute)
+                               locked_group = group;
+                       else
+                               locked_group += group;
+               }
 
-/*
- * Check a sym interpret match condition.
- * See [Lib] Table 18.1 for the logic.
- */
-static bool are_modifiers_matching(uint8_t mods, unsigned char match,
-                                                       uint8_t to_mods)
-{
-       switch (match & XkbSI_OpMask) {
-       case XkbSI_NoneOf:
-               return (mods & to_mods) == 0;
-       case XkbSI_AnyOfOrNone:
-               return true;
-       case XkbSI_AnyOf:
-               return (mods & to_mods) != 0;
-       case XkbSI_AllOf:
-               return (mods & to_mods) == mods;
-       case XkbSI_Exactly:
-               return mods == to_mods;
+               break;
        }
 
-       return false;
+       /* Bring what was changed back into range. */
+       state->base_group = wrap_group_control(desc, base_group);
+       state->locked_group = wrap_group_control(desc, locked_group);
+       state->latched_group = wrap_group_control(desc, latched_group);
+       update_effective_group(desc, state);
+       return true;
 }
 
 /*
- * After we figured out the virtual mods from the compat component, we update
- * the effective modifiers in the key_types component accordingly, because we
- * use it extensively to find the correct shift level.
- */
-static void init_key_types(struct xkb_desc *desc)
+ * Updates the modifiers state.
+ * See [Lib] Table 17.1 for logic.
+ * */
+static bool process_mod_action(struct xkb_desc *desc, struct xkb_state *state,
+                       KeyCode keycode, enum kmscon_key_state key_state,
+                                               struct xkb_mod_action *action)
 {
-       int i, j;
-       struct xkb_key_type *type;
-       struct xkb_kt_map_entry *entry;
-       struct xkb_mods *mods;
-
-       for (i=0; i < desc->map->num_types; i++) {
-               type = &desc->map->types[i];
-               mods = &type->mods;
+       uint8_t mods;
+       uint8_t saved_mods;
+       uint8_t flags = action->flags;
 
-               mods->mask = virtual_and_real_to_mask(desc, mods->vmods,
-                                                       mods->real_mods);
+       if (flags & XkbSA_UseModMapMods)
+               mods = desc->map->modmap[keycode];
+       else
+               mods = action->mask;
 
-               for (j=0; j < type->map_count; j++) {
-                       entry = &type->map[j];
-                       mods = &entry->mods;
+       /*
+        * FIXME: Some actions here should be conditioned "and no keys are
+        * physically depressed when this key is released".
+        */
 
-                       mods->mask = virtual_and_real_to_mask(desc,
-                                               mods->vmods, mods->real_mods);
+       switch (action->type) {
+       case XkbSA_SetMods:
+               if (key_state == KMSCON_KEY_PRESSED) {
+                       state->base_mods |= mods;
+               } else if (key_state == KMSCON_KEY_RELEASED) {
+                       state->base_mods &= ~mods;
+                       if (flags & XkbSA_ClearLocks)
+                               state->locked_mods &= ~mods;
+               }
 
-                       /*
-                        * If the entry's vmods are bound to something, it
-                        * should be active.
-                        */
-                       if (virtual_to_real_mods(desc, mods->vmods))
-                               entry->active = true;
+               break;
+       case XkbSA_LatchMods:
+               if (key_state == KMSCON_KEY_PRESSED) {
+                       state->base_mods |= mods;
+               } else if (key_state == KMSCON_KEY_RELEASED) {
+                       if (flags & XkbSA_ClearLocks) {
+                               saved_mods = state->locked_mods;
+                               state->locked_mods &= ~mods;
+                               mods &= ~(mods & saved_mods);
+                       }
+                       if (flags & XkbSA_LatchToLock) {
+                               saved_mods = mods;
+                               mods = (mods & state->latched_mods);
+                               state->locked_mods |= mods;
+                               state->latched_mods &= ~mods;
+                               mods = saved_mods & (~mods);
+                       }
+                       state->latched_mods |= mods;
                }
-       }
-}
 
-/*
- * Update the effective modifer mask of the various action objects after we
- * initialized the virtual modifiers from compat. The only actions we change
- * here are the mod_action types.
- */
-static void init_actions(struct xkb_desc *desc)
-{
-       int i;
-       union xkb_action *action;
-       struct xkb_sym_interpret *si;
+               break;
+       case XkbSA_LockMods:
+               /* We fake a little here and toggle both on and off on keypress. */
+               if (key_state == KMSCON_KEY_PRESSED) {
+                       state->base_mods |= mods;
+                       state->locked_mods ^= mods;
+               } else if (key_state == KMSCON_KEY_RELEASED) {
+                       state->base_mods &= ~mods;
+               }
 
-       for (i=0; i < desc->server->num_acts; i++) {
-               action = &desc->server->acts[i];
-               init_action(desc, action);
+               break;
        }
 
-       for (i=0; i < desc->compat->num_si; i++) {
-               si = &desc->compat->sym_interpret[i];
-               action = (union xkb_action *)&si->act;
-               init_action(desc, action);
-       }
+       update_effective_mods(desc, state);
+       return true;
 }
 
-static void init_action(struct xkb_desc *desc, union xkb_action *action)
+/*
+ * An action dispatcher. The return value indicates whether the keyboard state
+ * was changed.
+ */
+static bool process_action(struct xkb_desc *desc, struct xkb_state *state,
+                       KeyCode keycode, enum kmscon_key_state key_state,
+                                               union xkb_action *action)
 {
-       struct xkb_mod_action *mod_act;
+       if (!action)
+               return false;
 
        switch (action->type) {
+       case XkbSA_NoAction:
+               break;
        case XkbSA_SetMods:
        case XkbSA_LatchMods:
        case XkbSA_LockMods:
-               mod_act = &action->mods;
-
-               mod_act->mask = virtual_and_real_to_mask(desc, mod_act->vmods,
-                                                       mod_act->real_mods);
+               return process_mod_action(desc, state, keycode, key_state,
+                                                       &action->mods);
+               break;
+       case XkbSA_SetGroup:
+       case XkbSA_LatchGroup:
+       case XkbSA_LockGroup:
+               return process_group_action(desc, state, keycode, key_state,
+                                                       &action->group);
+               break;
+       default:
+               /*
+                * Don't handle other actions.
+                * Note: There may be useful stuff here, like TerminateServer
+                * or SwitchScreen.
+                */
                break;
        }
+
+       return false;
 }
 
 /*
- * Update to the effective modifier mask of the indicator objects. We use them
- * to dicover which modifiers to match with which leds.
+ * The shift level to use for the keycode (together with the group) is
+ * determined by the modifier state. There are various "types" of ways to use
+ * the modifiers to shift the keycode; this is determined by the key_type
+ * object mapped to the (keycode, group) pair.
  */
-static void init_indicators(struct xkb_desc *desc)
+static uint16_t find_shift_level(struct xkb_desc *desc, KeyCode keycode,
+                                               uint8_t mods, uint8_t group)
 {
        int i;
-       struct xkb_indicator_map *im;
-       struct xkb_mods *mods;
+       struct xkb_key_type *type;
+       struct xkb_kt_map_entry *entry;
+       uint8_t masked_mods;
 
-       for (i=0; i < XkbNumIndicators; i++) {
-               im = &desc->indicators->maps[i];
-               mods = &im->mods;
+       type = XkbKeyKeyType(desc, keycode, group);
 
-               mods->mask = virtual_and_real_to_mask(desc, mods->vmods,
-                                                               mods->real_mods);
-       }
-}
+       masked_mods = type->mods.mask & mods;
 
-/*
- * We don't do soft repeat currently, but we use the controls to filter out
- * which evdev repeats to send.
- */
-static void init_autorepeat(struct xkb_desc *desc)
-{
-       /*
-        * This is taken from <xserver>/include/site.h
-        * If a bit is off for a keycode, it should not repeat.
-        */
-       static const char DEFAULT_AUTOREPEATS[XkbPerKeyBitArraySize] = {
-               0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-               0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+       for (i=0; i < type->map_count; i++) {
+               entry = &type->map[i];
 
-       memcpy(desc->ctrls->per_key_repeat,
-                               DEFAULT_AUTOREPEATS, XkbPerKeyBitArraySize);
+               if (!entry->active)
+                       continue;
 
-       desc->ctrls->enabled_ctrls |= XkbRepeatKeysMask;
+               /*
+                * Must match exactly after we masked it with the key_type's
+                * mask.
+                */
+               if (entry->mods.mask == masked_mods)
+                       return entry->level;
+       }
+
+       /* The default is LevelOne. */
+       return 0;
 }
 
 /* Whether to send out a repeat event for the key. */
@@ -516,30 +481,88 @@ static bool should_key_repeat(struct xkb_desc *desc, KeyCode keycode)
        return true;
 }
 
-static uint8_t virtual_to_real_mods(struct xkb_desc *desc, uint16_t vmods)
+int kmscon_kbd_process_key(struct kmscon_kbd *kbd,
+                                       enum kmscon_key_state key_state,
+                                       uint16_t code,
+                                       struct kmscon_input_event *out)
 {
-       int i;
-       uint32_t bit;
-       uint8_t mods;
+       struct xkb_desc *desc;
+       struct xkb_state *state;
+       KeyCode keycode;
+       uint8_t group;
+       uint16_t shift_level;
+       uint32_t sym;
+       union xkb_action *action;
+       bool state_changed, event_filled;
 
-       mods = 0x00;
+       if (!kbd)
+               return -EINVAL;
 
-       for (i=0, bit=0x01; i < XkbNumVirtualMods; i++, bit<<=1)
-               if (vmods & bit)
-                       mods |= desc->server->vmods[i];
+       desc = kbd->desc->desc;
+       state = &kbd->state;
 
-       return mods;
+       keycode = code + desc->min_key_code;
+
+       /* Valid keycode. */
+       if (!XkbKeycodeInRange(desc, keycode))
+               return -ENOKEY;
+       /* Active keycode. */
+       if (XkbKeyNumSyms(desc, keycode) == 0)
+               return -ENOKEY;
+       /* Unwanted repeat. */
+       if (key_state == KMSCON_KEY_REPEATED &&
+                                       !should_key_repeat(desc, keycode))
+               return -ENOKEY;
+
+       group = wrap_group_keycode(desc, keycode, state->group);
+       shift_level = find_shift_level(desc, keycode, state->mods, group);
+       sym = XkbKeySymEntry(desc, keycode, shift_level, group);
+
+       state_changed = false;
+       if (key_state != KMSCON_KEY_REPEATED) {
+               action = XkbKeyActionEntry(desc, keycode, shift_level, group);
+               state_changed = process_action(desc, state, keycode,
+                                                       key_state, action);
+       }
+
+       event_filled = false;
+       if (key_state != KMSCON_KEY_RELEASED) {
+               out->keycode = code;
+               out->keysym = sym;
+               /* 1-to-1 match - this might change. */
+               out->mods = state->mods;
+               out->unicode = KeysymToUcs4(sym);
+
+               if (out->unicode == 0)
+                       out->unicode = KMSCON_INPUT_INVALID;
+
+               event_filled = true;
+       }
+
+       if (state_changed) {
+               /* Release latches. */
+               state->latched_mods = 0;
+               update_effective_mods(desc, state);
+               state->latched_group = 0;
+               update_effective_group(desc, state);
+       }
+
+       return event_filled ? 0 : -ENOKEY;
 }
 
-static uint8_t virtual_and_real_to_mask(struct xkb_desc *desc,
-                                       uint16_t vmods, uint8_t real_mods)
+static struct xkb_indicator_map *find_indicator_map(struct xkb_desc *desc,
+                                               const char *indicator_name)
 {
-       uint8_t mods = 0x00;
+       int i;
+       uint32_t atom;
 
-       mods |= real_mods;
-       mods |= virtual_to_real_mods(desc, vmods);
+       atom = xkb_intern_atom(indicator_name);
 
-       return mods;
+       for (i=0; i < XkbNumIndicators; i++)
+               if (desc->names->indicators[i] == atom)
+                       return &desc->indicators->maps[i];
+
+       return NULL;
 }
 
 /*
@@ -547,14 +570,21 @@ static uint8_t virtual_and_real_to_mask(struct xkb_desc *desc,
  * 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 kmscon_xkb_reset_state(struct xkb_desc *desc, struct xkb_state *state,
-                                                               int evdev_fd)
+void kmscon_kbd_reset(struct kmscon_kbd *kbd, int evdev_fd)
 {
        int i;
+       struct xkb_desc *desc;
+       struct xkb_state *state;
        /* One long should be enough (LED_MAX is currently 16). */
        unsigned long leds, bit;
        struct xkb_indicator_map *im;
 
+       if (!kbd)
+               return;
+
+       desc = kbd->desc->desc;
+       state = &kbd->state;
+
        state->group = 0;
        state->base_group = 0;
        state->latched_group = 0;
@@ -567,7 +597,7 @@ void kmscon_xkb_reset_state(struct xkb_desc *desc, struct xkb_state *state,
        errno = 0;
        ioctl(evdev_fd, EVIOCGLED(sizeof(leds)), &leds);
        if (errno) {
-               log_warning("input: couldn't discover modifiers state: %m\n");
+               log_warning("kbd-xkb: cannot discover modifiers state: %m\n");
                return;
        }
 
@@ -592,410 +622,431 @@ void kmscon_xkb_reset_state(struct xkb_desc *desc, struct xkb_state *state,
                        break;
                }
 
-               /* Only locked modifiers really matter here. */
-               if (im && im->which_mods == XkbIM_UseLocked)
-                       state->locked_mods |= im->mods.mask;
-       }
+               /* Only locked modifiers really matter here. */
+               if (im && im->which_mods == XkbIM_UseLocked)
+                       state->locked_mods |= im->mods.mask;
+       }
+
+       update_effective_mods(desc, state);
+       update_effective_group(desc, state);
+}
+
+/*
+ * We don't do soft repeat currently, but we use the controls to filter out
+ * which evdev repeats to send.
+ */
+static void init_autorepeat(struct xkb_desc *desc)
+{
+       /*
+        * This is taken from <xserver>/include/site.h
+        * If a bit is off for a keycode, it should not repeat.
+        */
+       static const char DEFAULT_AUTOREPEATS[XkbPerKeyBitArraySize] = {
+               0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+               0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+       memcpy(desc->ctrls->per_key_repeat,
+                               DEFAULT_AUTOREPEATS, XkbPerKeyBitArraySize);
+
+       desc->ctrls->enabled_ctrls |= XkbRepeatKeysMask;
+}
+
+/*
+ * Update to the effective modifier mask of the indicator objects. We use them
+ * to dicover which modifiers to match with which leds.
+ */
+static void init_indicators(struct xkb_desc *desc)
+{
+       int i;
+       struct xkb_indicator_map *im;
+       struct xkb_mods *mods;
+
+       for (i=0; i < XkbNumIndicators; i++) {
+               im = &desc->indicators->maps[i];
+               mods = &im->mods;
+
+               mods->mask = virtual_and_real_to_mask(desc, mods->vmods,
+                                                               mods->real_mods);
+       }
+}
+
+static void init_action(struct xkb_desc *desc, union xkb_action *action)
+{
+       struct xkb_mod_action *mod_act;
+
+       switch (action->type) {
+       case XkbSA_SetMods:
+       case XkbSA_LatchMods:
+       case XkbSA_LockMods:
+               mod_act = &action->mods;
+
+               mod_act->mask = virtual_and_real_to_mask(desc, mod_act->vmods,
+                                                       mod_act->real_mods);
+               break;
+       }
+}
+
+/*
+ * Update the effective modifer mask of the various action objects after we
+ * initialized the virtual modifiers from compat. The only actions we change
+ * here are the mod_action types.
+ */
+static void init_actions(struct xkb_desc *desc)
+{
+       int i;
+       union xkb_action *action;
+       struct xkb_sym_interpret *si;
+
+       for (i=0; i < desc->server->num_acts; i++) {
+               action = &desc->server->acts[i];
+               init_action(desc, action);
+       }
+
+       for (i=0; i < desc->compat->num_si; i++) {
+               si = &desc->compat->sym_interpret[i];
+               action = (union xkb_action *)&si->act;
+               init_action(desc, action);
+       }
+}
+
+/*
+ * After we figured out the virtual mods from the compat component, we update
+ * the effective modifiers in the key_types component accordingly, because we
+ * use it extensively to find the correct shift level.
+ */
+static void init_key_types(struct xkb_desc *desc)
+{
+       int i, j;
+       struct xkb_key_type *type;
+       struct xkb_kt_map_entry *entry;
+       struct xkb_mods *mods;
+
+       for (i=0; i < desc->map->num_types; i++) {
+               type = &desc->map->types[i];
+               mods = &type->mods;
+
+               mods->mask = virtual_and_real_to_mask(desc, mods->vmods,
+                                                       mods->real_mods);
+
+               for (j=0; j < type->map_count; j++) {
+                       entry = &type->map[j];
+                       mods = &entry->mods;
+
+                       mods->mask = virtual_and_real_to_mask(desc,
+                                               mods->vmods, mods->real_mods);
 
-       update_effective_mods(desc, state);
-       update_effective_group(desc, state);
+                       /*
+                        * If the entry's vmods are bound to something, it
+                        * should be active.
+                        */
+                       if (virtual_to_real_mods(desc, mods->vmods))
+                               entry->active = true;
+               }
+       }
 }
 
-static struct xkb_indicator_map *find_indicator_map(struct xkb_desc *desc,
-                                               const char *indicator_name)
+/*
+ * Check a sym interpret match condition.
+ * See [Lib] Table 18.1 for the logic.
+ */
+static bool are_modifiers_matching(uint8_t mods, unsigned char match,
+                                                       uint8_t to_mods)
 {
-       int i;
-       uint32_t atom;
-
-       atom = xkb_intern_atom(indicator_name);
-
-       for (i=0; i < XkbNumIndicators; i++)
-               if (desc->names->indicators[i] == atom)
-                       return &desc->indicators->maps[i];
+       switch (match & XkbSI_OpMask) {
+       case XkbSI_NoneOf:
+               return (mods & to_mods) == 0;
+       case XkbSI_AnyOfOrNone:
+               return true;
+       case XkbSI_AnyOf:
+               return (mods & to_mods) != 0;
+       case XkbSI_AllOf:
+               return (mods & to_mods) == mods;
+       case XkbSI_Exactly:
+               return mods == to_mods;
+       }
 
-       return NULL;
+       return false;
 }
 
 /*
- * The shift level to use for the keycode (together with the group) is
- * determined by the modifier state. There are various "types" of ways to use
- * the modifiers to shift the keycode; this is determined by the key_type
- * object mapped to the (keycode, group) pair.
+ * Look for the most specific symbol interpretation for the keysym.
+ * See [xserver] XKBMisc.c:_XkbFindMatchingInterp() for the equivalent.
  */
-static uint16_t find_shift_level(struct xkb_desc *desc, KeyCode keycode,
-                                               uint8_t mods, uint8_t group)
+static struct xkb_sym_interpret *find_sym_interpret(struct xkb_desc *desc,
+                       uint32_t sym, uint16_t level, uint8_t key_modmap)
 {
        int i;
-       struct xkb_key_type *type;
-       struct xkb_kt_map_entry *entry;
-       uint8_t masked_mods;
+       struct xkb_sym_interpret *si;
+       struct xkb_sym_interpret *all_syms_si;
 
-       type = XkbKeyKeyType(desc, keycode, group);
+       all_syms_si = NULL;
 
-       masked_mods = type->mods.mask & mods;
+       /*
+        * If we find a matching interpret specific to our symbol, we return
+        * it immediatly.
+        * If we didn't find any, we return the first matching all-catching
+        * interpret.
+        */
 
-       for (i=0; i < type->map_count; i++) {
-               entry = &type->map[i];
+       for (i=0; i < desc->compat->num_si; i++) {
+               si = &desc->compat->sym_interpret[i];
 
-               if (!entry->active)
+               if (si->sym != sym && si->sym != 0)
                        continue;
 
                /*
-                * Must match exactly after we masked it with the key_type's
-                * mask.
+                * If the interpret specified UseModMapMods=level1, the sym
+                * must be in the first level of its group.
+                * Note: [xserver] and [Lib] do different things here, and it
+                * doesn't seem to matter much. So it's commented for now.
                 */
-               if (entry->mods.mask == masked_mods)
-                       return entry->level;
+               /* if (si->match&XkbSI_LevelOneOnly && level != 0) */
+               /*      continue; */
+
+               if (!are_modifiers_matching(si->mods, si->match, key_modmap))
+                       continue;
+
+               if (si->sym != 0)
+                       return si;
+               else if (all_syms_si == NULL)
+                       all_syms_si = si;
        }
 
-       /* The default is LevelOne. */
-       return 0;
+       return all_syms_si;
 }
 
 /*
- * This is the entry point to the XKB processing.
- * We get an evdev scancode and the keyboard state, and should put out a
- * proper input event.
- * Some evdev input events shouldn't result in us sending an input event
- * (e.g. a key release). The return value indicated whether the input_event
- * was filled out or not.
+ * Allocate slots for a keycode in the key-action mapping array. xkbcommon
+ * doesn't do this by itself for actions from compat (that is almost all of
+ * them).
+ * See [xserver] XKBMAlloc.c:XkbResizeKeyActions() for the equivalent.
  */
-bool kmscon_xkb_process_evdev_key(struct xkb_desc *desc,
-                                               struct xkb_state *state,
-                                               enum kmscon_key_state key_state,
-                                               uint16_t code,
-                                               struct kmscon_input_event *out)
+static int allocate_key_acts(struct xkb_desc *desc, uint8_t keycode)
 {
-       KeyCode keycode;
-       uint8_t group;
-       uint16_t shift_level;
-       uint32_t sym;
-       union xkb_action *action;
-       bool state_changed, event_filled;
-
-       keycode = code + desc->min_key_code;
-
-       /* Valid keycode. */
-       if (!XkbKeycodeInRange(desc, keycode))
-               return false;
-       /* Active keycode. */
-       if (XkbKeyNumSyms(desc, keycode) == 0)
-               return false;
-       /* Unwanted repeat. */
-       if (key_state == KMSCON_KEY_REPEATED &&
-                                       !should_key_repeat(desc, keycode))
-               return false;
-
-       group = wrap_group_keycode(desc, keycode, state->group);
-       shift_level = find_shift_level(desc, keycode, state->mods, group);
-       sym = XkbKeySymEntry(desc, keycode, shift_level, group);
+       unsigned short index;
+       union xkb_action *acts;
+       struct xkb_server_map *server;
+       int sym_count;
+       int new_needed;
+       unsigned short new_num_acts;
+       unsigned short new_size_acts;
 
-       state_changed = false;
-       if (key_state != KMSCON_KEY_REPEATED) {
-               action = XkbKeyActionEntry(desc, keycode, shift_level, group);
-               state_changed = process_action(desc, state, keycode,
-                                                       key_state, action);
-       }
+       server = desc->server;
+       sym_count = XkbKeyNumSyms(desc, keycode);
 
-       event_filled = false;
-       if (key_state != KMSCON_KEY_RELEASED && !state_changed) {
-               out->keycode = code;
-               out->keysym = sym;
-               /* 1-to-1 match - this might change. */
-               out->mods = state->mods;
-               out->unicode = KeysymToUcs4(sym);
+       /*
+        * num_acts is the occupied slots, size_acts is the current total
+        * capacity.
+        */
 
-               if (out->unicode == 0)
-                       out->unicode = KMSCON_INPUT_INVALID;
+       if (XkbKeyHasActions(desc, keycode)) {
+               /* An array is already allocated for this key. */
 
-               event_filled = true;
-       }
+               /* index = server->key_acts[keycode]; */
+       } else if (server->num_acts + sym_count <= server->size_acts) {
+               /* There's enough left over space; use it. */
 
-       if (state_changed) {
-               /* Release latches. */
-               state->latched_mods = 0;
-               update_effective_mods(desc, state);
-               state->latched_group = 0;
-               update_effective_group(desc, state);
-       }
+               index = server->num_acts;
+               server->key_acts[keycode] = index;
+               server->num_acts += sym_count;
+       } else {
+               /* Need to allocate new space. */
 
-       return event_filled;
-}
+               index = server->num_acts;
+               new_num_acts = server->num_acts + sym_count;
+               new_needed = sym_count - (server->size_acts - new_num_acts);
+               /* Add some extra to avoid repeated reallocs. */
+               new_size_acts = server->size_acts + new_needed + 8;
 
-/*
- * An action dispatcher. The return value indicates whether the keyboard state
- * was changed.
- */
-static bool process_action(struct xkb_desc *desc, struct xkb_state *state,
-                       KeyCode keycode, enum kmscon_key_state key_state,
-                                               union xkb_action *action)
-{
-       if (!action)
-               return false;
+               acts = realloc(server->acts,
+                               sizeof(union xkb_action) * new_size_acts);
+               if (!acts)
+                       return -ENOMEM;
 
-       switch (action->type) {
-       case XkbSA_NoAction:
-               break;
-       case XkbSA_SetMods:
-       case XkbSA_LatchMods:
-       case XkbSA_LockMods:
-               return process_mod_action(desc, state, keycode, key_state,
-                                                       &action->mods);
-               break;
-       case XkbSA_SetGroup:
-       case XkbSA_LatchGroup:
-       case XkbSA_LockGroup:
-               return process_group_action(desc, state, keycode, key_state,
-                                                       &action->group);
-               break;
-       default:
-               /*
-                * Don't handle other actions.
-                * Note: There may be useful stuff here, like TerminateServer
-                * or SwitchScreen.
-                */
-               break;
+               /* XkbSA_NoAction is 0x00 so we're good. */
+               memset(acts+index, 0, sym_count);
+               server->key_acts[keycode] = index;
+               server->num_acts = new_num_acts;
+               server->size_acts = new_size_acts;
+               server->acts = acts;
        }
 
-       return false;
+       return 0;
 }
 
-/*
- * Updates the modifiers state.
- * See [Lib] Table 17.1 for logic.
- * */
-static bool process_mod_action(struct xkb_desc *desc, struct xkb_state *state,
-                       KeyCode keycode, enum kmscon_key_state key_state,
-                                               struct xkb_mod_action *action)
+static int init_compat_for_keysym(struct xkb_desc *desc, KeyCode keycode,
+                                               uint8_t group, uint16_t level)
 {
-       uint8_t mods;
-       uint8_t saved_mods;
-       uint8_t flags = action->flags;
-
-       if (flags & XkbSA_UseModMapMods)
-               mods = desc->map->modmap[keycode];
-       else
-               mods = action->mask;
-
-       /*
-        * FIXME: Some actions here should be conditioned "and no keys are
-        * physically depressed when this key is released".
-        */
-
-       switch (action->type) {
-       case XkbSA_SetMods:
-               if (key_state == KMSCON_KEY_PRESSED) {
-                       state->base_mods |= mods;
-               } else if (key_state == KMSCON_KEY_RELEASED) {
-                       state->base_mods &= ~mods;
-                       if (flags & XkbSA_ClearLocks)
-                               state->locked_mods &= ~mods;
-               }
+       int ret;
+       uint8_t key_modmap;
+       uint32_t sym;
+       struct xkb_sym_interpret *si;
+       union xkb_action *action;
 
-               break;
-       case XkbSA_LatchMods:
-               if (key_state == KMSCON_KEY_PRESSED) {
-                       state->base_mods |= mods;
-               } else if (key_state == KMSCON_KEY_RELEASED) {
-                       if (flags & XkbSA_ClearLocks) {
-                               saved_mods = state->locked_mods;
-                               state->locked_mods &= ~mods;
-                               mods &= ~(mods & saved_mods);
-                       }
-                       if (flags & XkbSA_LatchToLock) {
-                               saved_mods = mods;
-                               mods = (mods & state->latched_mods);
-                               state->locked_mods |= mods;
-                               state->latched_mods &= ~mods;
-                               mods = saved_mods & (~mods);
-                       }
-                       state->latched_mods |= mods;
-               }
+       key_modmap = desc->map->modmap[keycode];
+       sym = XkbKeySymEntry(desc, keycode, level, group);
+       si = find_sym_interpret(desc, sym, level, key_modmap);
 
-               break;
-       case XkbSA_LockMods:
-               /* We fake a little here and toggle both on and off on keypress. */
-               if (key_state == KMSCON_KEY_PRESSED) {
-                       state->base_mods |= mods;
-                       state->locked_mods ^= mods;
-               } else if (key_state == KMSCON_KEY_RELEASED) {
-                       state->base_mods &= ~mods;
-               }
+       if (!si)
+               return 0;
 
-               break;
+       /* Set the key action mapping. */
+       if (si->act.type != XkbSA_NoAction) {
+               ret = allocate_key_acts(desc, keycode);
+               if (ret)
+                       return ret;
+
+               action = XkbKeyActionEntry(desc, keycode, level, group);
+               *action = (union xkb_action)si->act;
        }
 
-       update_effective_mods(desc, state);
-       return true;
+       /* Set the key virtual modifier mapping. */
+       if (si->virtual_mod != XkbNoModifier)
+               desc->server->vmodmap[keycode] |= 0x01 << si->virtual_mod;
+
+       return 0;
 }
 
-/*
- * Updates the group state.
- * See [Lib] Table 17.4 for logic.
- */
-static bool process_group_action(struct xkb_desc *desc, struct xkb_state *state,
-                       KeyCode keycode, enum kmscon_key_state key_state,
-                                       struct xkb_group_action *action)
+static int init_compat_for_keycode(struct xkb_desc *desc, KeyCode keycode)
 {
-       int16_t group = action->group;
-       uint8_t flags = action->flags;
+       int ret;
+       int i, bit;
+
+       uint8_t group;
+       uint16_t level;
+       int num_groups;
+       int num_levels;
 
        /*
-        * action->group is signed and may be negative if GroupAbsolute
-        * is not set. A group itself cannot be negative and is unsigend.
-        * Therefore we extend these to int16 to avoid underflow and
-        * signedness issues. Be careful!
+        * It's possible that someone had set some actions for the keycode
+        * through the symbols file, and so we shouldn't override with the
+        * compat. This is very uncommon though, only used by the breaks_caps
+        * option here.
         */
-       int16_t base_group = state->base_group;
-       int16_t latched_group = state->latched_group;
-       int16_t locked_group = state->locked_group;
+       if (XkbKeyHasActions(desc, keycode))
+               return 0;
+
+       num_groups = XkbKeyNumGroups(desc, keycode);
 
        /*
-        * FIXME: Some actions here should be conditioned "and no keys are
-        * physically depressed when this key is released".
+        * We need to track the sym level in order to support LevelOneOnly,
+        * which is used in some symbol interpretations.
         */
 
-       switch (action->type) {
-       case XkbSA_SetGroup:
-               if (key_state == KMSCON_KEY_PRESSED) {
-                       if (flags & XkbSA_GroupAbsolute)
-                               base_group = group;
-                       else
-                               base_group += group;
-               } else if (key_state == KMSCON_KEY_RELEASED) {
-                       if (flags & XkbSA_ClearLocks)
-                               locked_group = 0;
-               }
+       for (group=0, i=0; group < num_groups; group++) {
+               num_levels = XkbKeyGroupWidth(desc, keycode, group);
 
-               break;
-       case XkbSA_LatchGroup:
-               if (key_state == KMSCON_KEY_PRESSED) {
-                       if (flags & XkbSA_GroupAbsolute)
-                               base_group = group;
-                       else
-                               base_group += group;
-               } else if (key_state == KMSCON_KEY_RELEASED) {
-                       if ((flags & XkbSA_LatchToLock) && latched_group) {
-                               locked_group += group;
-                               latched_group -= group;
-                       } else {
-                               latched_group += group;
-                       }
+               for (level=0; level < num_levels; level++) {
+                       ret = init_compat_for_keysym(desc, keycode,
+                                                               group, level);
+                       if (ret)
+                               return ret;
                }
+       }
 
-               break;
-       case XkbSA_LockGroup:
-               if (key_state == KMSCON_KEY_PRESSED) {
-                       if (flags & XkbSA_GroupAbsolute)
-                               locked_group = group;
-                       else
-                               locked_group += group;
-               }
+       /*
+        * Translate the virtual modifiers bound to this key to the real
+        * modifiers bound to this key.
+        * See [Lib] 17.4 for vmodmap and friends.
+        */
+       for (i=0, bit=0x01; i < XkbNumVirtualMods; i++, bit<<=1)
+               if (bit&desc->server->vmodmap[keycode])
+                       desc->server->vmods[i] |= desc->map->modmap[keycode];
 
-               break;
-       }
+       return 0;
+}
 
-       /* Bring what was changed back into range. */
-       state->base_group = wrap_group_control(desc, base_group);
-       state->locked_group = wrap_group_control(desc, locked_group);
-       state->latched_group = wrap_group_control(desc, latched_group);
-       update_effective_group(desc, state);
-       return true;
+/*
+ * This mostly fills out the keycode-action mapping and puts the virtual
+ * modifier mappings in the right place.
+ */
+static void init_compat(struct xkb_desc *desc)
+{
+       /* If we use KeyCode it overflows. */
+       unsigned int keycode;
+
+       for (keycode = desc->min_key_code; keycode <= desc->max_key_code; keycode++)
+               init_compat_for_keycode(desc, keycode);
 }
 
 /*
- * Helper function for the wrap_group_* functions.
- * See [Lib] 11.7.1 for the rules.
+ * Create a ready-to-use xkb description object. It is used in most places
+ * having to do with XKB.
  */
-static uint8_t wrap_group(int16_t group, int num_groups, uint8_t group_info)
+int kmscon_kbd_desc_new(struct kmscon_kbd_desc **out, const char *layout,
+                               const char *variant, const char *options)
 {
-       /* No need for wrapping. */
-       if (XkbIsLegalGroup(group) && group < num_groups)
-               return group;
+       struct kmscon_kbd_desc *desc;
 
-       switch (XkbOutOfRangeGroupAction(group_info)) {
-       case XkbWrapIntoRange:
-               /*
-                * C99 says a negative dividend in a modulo operation
-                * will always give a negative result.
-                */
-               if (group < 0)
-                       return num_groups + (group % num_groups);
-               else
-                       return group % num_groups;
+       if (!out)
+               return -EINVAL;
 
-       case XkbClampIntoRange:
-               /* This one seems to be unused. */
-               return num_groups - 1;
+       log_debug("kbd-xkb: new keyboard description (%s, %s, %s)\n",
+                                               layout, variant, options);
 
-       case XkbRedirectIntoRange:
-               /* This one seems to be unused. */
-               group = XkbOutOfRangeGroupNumber(group_info);
-               /* If it's _still_ out of range, use the first group. */
-               if (group >= num_groups)
-                       return 0;
+       desc = malloc(sizeof(*desc));
+       if (!desc)
+               return -ENOMEM;
+
+       memset(desc, 0, sizeof(*desc));
+       desc->ref = 1;
+
+       struct xkb_rule_names rmlvo = {
+               .rules = "evdev",
+               .model = "evdev",
+               .layout = layout,
+               .variant = variant,
+               .options = options,
+       };
+
+       desc->desc = xkb_compile_keymap_from_rules(&rmlvo);
+       if (!desc->desc) {
+               log_err("kbd-xkb: cannot compile keymap from rules\n");
+               free(desc);
+               return -EFAULT;
        }
 
+       /* The order of these is important! */
+       init_compat(desc->desc);
+       init_key_types(desc->desc);
+       init_actions(desc->desc);
+       init_indicators(desc->desc);
+       init_autorepeat(desc->desc);
+
+       *out = desc;
        return 0;
 }
 
-/*
- * Wrap an arbitrary group into a legal effective global group according to
- * the GroupsWrap control.
- * (Group actions mostly act on the group number in a relative manner [e.g.
- * +1, -1]. So if we have N groups, the effective group is N-1, and we get a
- * SetGroup +1, this tells us what to do.)
- */
-static uint8_t wrap_group_control(struct xkb_desc *desc, int16_t group)
+void kmscon_kbd_desc_ref(struct kmscon_kbd_desc *desc)
 {
-       int num_groups;
-       uint8_t group_info;
-
-       num_groups = desc->ctrls->num_groups;
-       group_info = desc->ctrls->groups_wrap;
+       if (!desc)
+               return;
 
-       return wrap_group(group, num_groups, group_info);
+       ++desc->ref;
 }
 
-/*
- * Wrap the effective global group to a legal group for the keycode, according
- * to the rule specified for the key.
- * (Some keycodes may have more groups than others, and so the effective
- * group may not make sense for a certain keycode).
- */
-static uint8_t wrap_group_keycode(struct xkb_desc *desc, KeyCode keycode,
-                                                               int16_t group)
+void kmscon_kbd_desc_unref(struct kmscon_kbd_desc *desc)
 {
-       int num_groups;
-       uint8_t group_info;
+       if (!desc || !desc->ref)
+               return;
 
-       num_groups = XkbKeyNumGroups(desc, keycode);
-       group_info = XkbKeyGroupInfo(desc, keycode);
+       if (--desc->ref)
+               return;
 
-       return wrap_group(group, num_groups, group_info);
-}
+       log_debug("kbd-xkb: destroying keyboard description\n");
 
-/*
- * Need to update the effective mods after any changes to the base, latched or
- * locked mods.
- */
-static void update_effective_mods(struct xkb_desc *desc,
-                                               struct xkb_state *state)
-{
-       state->mods = state->base_mods | state->latched_mods |
-                                                       state->locked_mods;
+       /*
+        * XXX: Seems this doesn't really free everything, valgrind shows some
+        * big leaks from libxkbcommon. Hopefully we use just one up until we
+        * exit.
+        */
+       xkb_free_keymap(desc->desc);
+       free(desc);
 }
 
-/*
- * Need to update the effective group after any changes to the base, latched or
- * locked group.
- */
-static void update_effective_group(struct xkb_desc *desc,
-                                               struct xkb_state *state)
+void kmscon_kbd_keysym_to_string(uint32_t keysym, char *str, size_t size)
 {
-       int16_t group;
-
-       /* Update the effective group. */
-       group = state->base_group + state->locked_group + state->latched_group;
-       state->group = wrap_group_control(desc, group);
+       xkb_keysym_to_string(keysym, str, size);
 }