[libxkbcommon,1/2,v2] x11: add XKB protocol keymap and state creation support

Submitted by Ran Benita on Jan. 15, 2014, 10:35 p.m.

Details

Message ID 1389825359-6406-1-git-send-email-ran234@gmail.com
State Superseded
Headers show

Not browsing as part of any series.

Commit Message

Ran Benita Jan. 15, 2014, 10:35 p.m.
These are function to create an xkb_keymap directly from XKB requests
to the X server. This opens up the possibility for X clients to use
xcb + xcb-xkb + xkbcommon as a proper replacement for Xlib + xkbfile for
keyboard support.

The X11 support must be enabled with --enable-x11 for now.
The functions are in xkbcommon/xkbcommon-x11.h. It depends on a recent
libxcb with xkb enabled. The functions are in a new libxkbcommon-x11.so,
with a new pkg-config file, etc. so that the packages may be split, and
libxkbcommon.so itself remains dependency-free.

Why not just use the RMLVO that the server puts in the _XKB_RULES_NAMES
property? This does not account for custom keymaps, on-the-fly keymap
modifications, remote clients, etc., so is not a proper solution in
practice. Also, some servers don't even set it. Now, the client just
needs to recreate the keymap in response to a change in the server's
keymap (as Xlib clients do with XRefreshKeyboardMapping() and friends).

Signed-off-by: Ran Benita <ran234@gmail.com>
---

v2:
* Renamed xkb_x11_keymap_new_for_device() -> xkb_x11_keymap_new_from_device(),
  to match xkb_keymap_new_from_file() and friends.
  Same for xkb_x11_keymap_new_for_state(), it's now 'from'.
* Fixed empty key name handling in get_key_names().

 Makefile.am                     |   27 +
 configure.ac                    |   12 +
 src/utils.h                     |   16 +
 src/x11/keymap.c                | 1146 +++++++++++++++++++++++++++++++++++++++
 src/x11/state.c                 |   71 +++
 src/x11/util.c                  |  210 +++++++
 src/x11/x11-priv.h              |   54 ++
 xkbcommon-x11-uninstalled.pc.in |   10 +
 xkbcommon-x11.pc.in             |   12 +
 xkbcommon/xkbcommon-x11.h       |  163 ++++++
 10 files changed, 1721 insertions(+)
 create mode 100644 src/x11/keymap.c
 create mode 100644 src/x11/state.c
 create mode 100644 src/x11/util.c
 create mode 100644 src/x11/x11-priv.h
 create mode 100644 xkbcommon-x11-uninstalled.pc.in
 create mode 100644 xkbcommon-x11.pc.in
 create mode 100644 xkbcommon/xkbcommon-x11.h

Patch hide | download patch | download mbox

diff --git a/Makefile.am b/Makefile.am
index 3168b55..5fc982b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -78,6 +78,33 @@  libxkbcommon_la_SOURCES = \
 	src/utils.c \
 	src/utils.h
 
+if ENABLE_X11
+pkgconfig_DATA += xkbcommon-x11.pc
+
+xkbcommon_x11includedir = $(xkbcommonincludedir)
+xkbcommon_x11include_HEADERS = \
+	xkbcommon/xkbcommon-x11.h
+
+lib_LTLIBRARIES += libxkbcommon-x11.la
+
+libxkbcommon_x11_la_CFLAGS = $(AM_CFLAGS) $(XCB_XKB_CFLAGS)
+libxkbcommon_x11_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/src/x11
+libxkbcommon_x11_la_LIBADD = libxkbcommon.la $(XCB_XKB_LIBS)
+
+libxkbcommon_x11_la_SOURCES = \
+	src/x11/keymap.c \
+	src/x11/state.c \
+	src/x11/util.c \
+	src/x11/x11-priv.h \
+	src/context.h \
+	src/context-priv.c \
+	src/keymap.h \
+	src/keymap-priv.c \
+	src/atom.h \
+	src/atom.c
+
+endif ENABLE_X11
+
 BUILT_SOURCES = \
 	src/xkbcomp/parser.c \
 	src/xkbcomp/parser.h
diff --git a/configure.ac b/configure.ac
index b8e242e..d09e915 100644
--- a/configure.ac
+++ b/configure.ac
@@ -143,10 +143,22 @@  if ! test "x$DEFAULT_XKB_OPTIONS" = x; then
                        [Default XKB options])
 fi
 
+AC_ARG_ENABLE([x11],
+    [AS_HELP_STRING([--enable-x11],
+        [Enable support for creating keymaps with the X11 protocol (default: no)])],
+    [with_x11=yes], [])
+if test "x$with_x11" = xyes; then
+    PKG_CHECK_MODULES([XCB_XKB], [xcb xcb-xkb >= 1.10], [],
+        [AC_MSG_ERROR([--enable-x11 was passed but xcb-xkb is missing])])
+fi
+AM_CONDITIONAL([ENABLE_X11], [test "x$with_x11" = xyes])
+
 AC_CONFIG_FILES([
     Makefile
     xkbcommon-uninstalled.pc
     xkbcommon.pc
+    xkbcommon-x11.pc
+    xkbcommon-x11-uninstalled.pc
     doc/Doxyfile
 ])
 AC_OUTPUT
diff --git a/src/utils.h b/src/utils.h
index 04fb9c5..81d1cc9 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -158,6 +158,22 @@  is_graph(char ch)
     return ch >= '!' && ch <= '~';
 }
 
+/*
+ * Return the bit position of the most significant bit.
+ * Note: this is 1-based! It's more useful this way, and returns 0 when
+ * mask is all 0s.
+ */
+static inline int
+msb_pos(uint32_t mask)
+{
+    int pos = 0;
+    while (mask) {
+        pos++;
+        mask >>= 1;
+    }
+    return pos;
+}
+
 bool
 map_file(FILE *file, const char **string_out, size_t *size_out);
 
diff --git a/src/x11/keymap.c b/src/x11/keymap.c
new file mode 100644
index 0000000..968f187
--- /dev/null
+++ b/src/x11/keymap.c
@@ -0,0 +1,1146 @@ 
+/*
+ * Copyright © 2013 Ran Benita
+ *
+ * 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 (including the next
+ * paragraph) 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.
+ */
+
+#include "x11-priv.h"
+
+/*
+ * References for the lonesome traveler:
+ * Xkb protocol specification:
+ *      http://www.x.org/releases/current/doc/kbproto/xkbproto.html
+ * The XCB xkb XML protocol file:
+ *      /user/share/xcb/xkb.xml
+ * The XCB xkb header file:
+ *      /usr/include/xcb/xkb.h
+ * The old kbproto header files:
+ *      /usr/include/X11/extensions/XKB{,proto,str}.h
+ * Xlib XKB source code:
+ *      <libX11>/src/xkb/XKBGetMap.c (and friends)
+ * X server XKB protocol handling:
+ *      <xserver>/xkb/xkb.c
+ * Man pages:
+ *      XkbGetMap(3), XkbGetCompatMap(3), etc.
+ */
+
+/* Constants from /usr/include/X11/extensions/XKB.h */
+/* XkbNumModifiers. */
+#define NUM_REAL_MODS 8
+/* XkbNumVirtualMods. */
+#define NUM_VMODS 16
+/* XkbNoModifier. */
+#define NO_MODIFIER 0xff
+/* XkbNumIndicators. */
+#define NUM_INDICATORS 32
+/* XkbAllIndicatorsMask. */
+#define ALL_INDICATORS_MASK 0xffffffff
+
+/* Some macros. Not very nice but it'd be worse without them. */
+
+/*
+ * We try not to trust the server too much and be paranoid. If we get
+ * something which we definitely shouldn't, we fail.
+ */
+#define STRINGIFY(expr) #expr
+#define FAIL_UNLESS(expr) do {                                          \
+    if (!(expr)) {                                                      \
+        log_err(keymap->ctx,                                            \
+                "x11: failed to get keymap from X server: unmet condition in %s(): %s\n", \
+                __func__, STRINGIFY(expr));                             \
+        goto fail;                                                      \
+    }                                                                   \
+} while (0)
+
+#define FAIL_IF_BAD_REPLY(reply, request_name) do {                     \
+    if (!reply) {                                                       \
+        log_err(keymap->ctx,                                            \
+                "x11: failed to get keymap from X server: %s request failed\n", \
+                (request_name));                                        \
+        goto fail;                                                      \
+    }                                                                   \
+} while (0)
+
+#define ALLOC_OR_FAIL(arr, nmemb) do {                                  \
+    if ((nmemb) > 0) {                                                  \
+        (arr) = calloc((nmemb), sizeof(*(arr)));                        \
+        if (!(arr))                                                     \
+            goto fail;                                                  \
+    }                                                                   \
+} while (0)
+
+
+static xkb_mod_mask_t
+translate_mods(uint8_t rmods, uint16_t vmods_low, uint16_t vmods_high)
+{
+    /* We represent mod masks in a single uint32_t value, with real mods
+     * first and vmods after (though we don't make these distinctions). */
+    return rmods | (vmods_low << 8) | (vmods_high << 16);
+}
+
+static enum xkb_action_controls
+translate_controls_mask(uint16_t wire)
+{
+    enum xkb_action_controls ret = 0;
+    if (wire & XCB_XKB_BOOL_CTRL_REPEAT_KEYS)
+        ret |= CONTROL_REPEAT;
+    if (wire & XCB_XKB_BOOL_CTRL_SLOW_KEYS)
+        ret |= CONTROL_SLOW;
+    if (wire & XCB_XKB_BOOL_CTRL_BOUNCE_KEYS)
+        ret |= CONTROL_DEBOUNCE;
+    if (wire & XCB_XKB_BOOL_CTRL_STICKY_KEYS)
+        ret |= CONTROL_STICKY;
+    if (wire & XCB_XKB_BOOL_CTRL_MOUSE_KEYS)
+        ret |= CONTROL_MOUSEKEYS;
+    if (wire & XCB_XKB_BOOL_CTRL_MOUSE_KEYS_ACCEL)
+        ret |= CONTROL_MOUSEKEYS_ACCEL;
+    if (wire & XCB_XKB_BOOL_CTRL_ACCESS_X_KEYS)
+        ret |= CONTROL_AX;
+    if (wire & XCB_XKB_BOOL_CTRL_ACCESS_X_TIMEOUT_MASK)
+        ret |= CONTROL_AX_TIMEOUT;
+    if (wire & XCB_XKB_BOOL_CTRL_ACCESS_X_FEEDBACK_MASK)
+        ret |= CONTROL_AX_FEEDBACK;
+    if (wire & XCB_XKB_BOOL_CTRL_AUDIBLE_BELL_MASK)
+        ret |= CONTROL_BELL;
+    if (wire & XCB_XKB_BOOL_CTRL_IGNORE_GROUP_LOCK_MASK)
+        ret |= CONTROL_IGNORE_GROUP_LOCK;
+    /* Some controls are not supported and don't appear here. */
+    return ret;
+}
+
+static void
+translate_action(union xkb_action *action, const xcb_xkb_action_t *wire)
+{
+    switch (wire->type) {
+    case XCB_XKB_SA_TYPE_SET_MODS:
+        action->type = ACTION_TYPE_MOD_SET;
+
+        action->mods.mods.mods = translate_mods(wire->setmods.realMods,
+                                                wire->setmods.vmodsLow,
+                                                wire->setmods.vmodsHigh);
+        action->mods.mods.mask = translate_mods(wire->setmods.mask, 0, 0);
+
+        if (wire->setmods.flags & XCB_XKB_SA_CLEAR_LOCKS)
+            action->mods.flags |= ACTION_LOCK_CLEAR;
+        if (wire->setmods.flags & XCB_XKB_SA_LATCH_TO_LOCK)
+            action->mods.flags |= ACTION_LATCH_TO_LOCK;
+        if (wire->setmods.flags & XCB_XKB_SA_USE_MOD_MAP_MODS)
+            action->mods.flags |= ACTION_MODS_LOOKUP_MODMAP;
+
+        break;
+    case XCB_XKB_SA_TYPE_LATCH_MODS:
+        action->type = ACTION_TYPE_MOD_LATCH;
+
+        action->mods.mods.mods = translate_mods(wire->latchmods.realMods,
+                                                wire->latchmods.vmodsLow,
+                                                wire->latchmods.vmodsHigh);
+        action->mods.mods.mask = translate_mods(wire->latchmods.mask, 0, 0);
+
+        if (wire->latchmods.flags & XCB_XKB_SA_CLEAR_LOCKS)
+            action->mods.flags |= ACTION_LOCK_CLEAR;
+        if (wire->latchmods.flags & XCB_XKB_SA_LATCH_TO_LOCK)
+            action->mods.flags |= ACTION_LATCH_TO_LOCK;
+        if (wire->latchmods.flags & XCB_XKB_SA_USE_MOD_MAP_MODS)
+            action->mods.flags |= ACTION_MODS_LOOKUP_MODMAP;
+
+        break;
+    case XCB_XKB_SA_TYPE_LOCK_MODS:
+        action->type = ACTION_TYPE_MOD_LOCK;
+
+        action->mods.mods.mods = translate_mods(wire->lockmods.realMods,
+                                                wire->lockmods.vmodsLow,
+                                                wire->lockmods.vmodsHigh);
+        action->mods.mods.mask = translate_mods(wire->lockmods.mask, 0, 0);
+
+        if (wire->lockmods.flags & XCB_XKB_SA_ISO_LOCK_FLAG_NO_LOCK)
+            action->mods.flags |= ACTION_LOCK_NO_LOCK;
+        if (wire->lockmods.flags & XCB_XKB_SA_ISO_LOCK_FLAG_NO_UNLOCK)
+            action->mods.flags |= ACTION_LOCK_NO_UNLOCK;
+        if (wire->lockmods.flags & XCB_XKB_SA_USE_MOD_MAP_MODS)
+            action->mods.flags |= ACTION_MODS_LOOKUP_MODMAP;
+
+        break;
+    case XCB_XKB_SA_TYPE_SET_GROUP:
+        action->type = ACTION_TYPE_GROUP_SET;
+
+        action->group.group = wire->setgroup.group;
+
+        if (wire->setmods.flags & XCB_XKB_SA_CLEAR_LOCKS)
+            action->group.flags |= ACTION_LOCK_CLEAR;
+        if (wire->setmods.flags & XCB_XKB_SA_LATCH_TO_LOCK)
+            action->group.flags |= ACTION_LATCH_TO_LOCK;
+        if (wire->setmods.flags & XCB_XKB_SA_ISO_LOCK_FLAG_GROUP_ABSOLUTE)
+            action->group.flags |= ACTION_ABSOLUTE_SWITCH;
+
+        break;
+    case XCB_XKB_SA_TYPE_LATCH_GROUP:
+        action->type = ACTION_TYPE_GROUP_LATCH;
+
+        action->group.group = wire->latchgroup.group;
+
+        if (wire->latchmods.flags & XCB_XKB_SA_CLEAR_LOCKS)
+            action->group.flags |= ACTION_LOCK_CLEAR;
+        if (wire->latchmods.flags & XCB_XKB_SA_LATCH_TO_LOCK)
+            action->group.flags |= ACTION_LATCH_TO_LOCK;
+        if (wire->latchmods.flags & XCB_XKB_SA_ISO_LOCK_FLAG_GROUP_ABSOLUTE)
+            action->group.flags |= ACTION_ABSOLUTE_SWITCH;
+
+        break;
+    case XCB_XKB_SA_TYPE_LOCK_GROUP:
+        action->type = ACTION_TYPE_GROUP_LOCK;
+
+        action->group.group = wire->lockgroup.group;
+
+        if (wire->lockgroup.flags & XCB_XKB_SA_ISO_LOCK_FLAG_GROUP_ABSOLUTE)
+            action->group.flags |= ACTION_ABSOLUTE_SWITCH;
+
+        break;
+    case XCB_XKB_SA_TYPE_MOVE_PTR:
+        action->type = ACTION_TYPE_PTR_MOVE;
+
+        action->ptr.x = (wire->moveptr.xLow | (wire->moveptr.xHigh << 8));
+        action->ptr.y = (wire->moveptr.yLow | (wire->moveptr.yHigh << 8));
+
+        if (wire->moveptr.flags & XCB_XKB_SA_MOVE_PTR_FLAG_NO_ACCELERATION)
+            action->ptr.flags |= ACTION_NO_ACCEL;
+        if (wire->moveptr.flags & XCB_XKB_SA_MOVE_PTR_FLAG_MOVE_ABSOLUTE_X)
+            action->ptr.flags |= ACTION_ABSOLUTE_X;
+        if (wire->moveptr.flags & XCB_XKB_SA_MOVE_PTR_FLAG_MOVE_ABSOLUTE_Y)
+            action->ptr.flags |= ACTION_ABSOLUTE_Y;
+
+        break;
+    case XCB_XKB_SA_TYPE_PTR_BTN:
+        action->type = ACTION_TYPE_PTR_BUTTON;
+
+        action->btn.count = wire->ptrbtn.count;
+        action->btn.button = wire->ptrbtn.button;
+        action->btn.flags = 0;
+
+        break;
+    case XCB_XKB_SA_TYPE_LOCK_PTR_BTN:
+        action->type = ACTION_TYPE_PTR_LOCK;
+
+        action->btn.button = wire->lockptrbtn.button;
+
+        if (wire->lockptrbtn.flags & XCB_XKB_SA_ISO_LOCK_FLAG_NO_LOCK)
+            action->btn.flags |= ACTION_LOCK_NO_LOCK;
+        if (wire->lockptrbtn.flags & XCB_XKB_SA_ISO_LOCK_FLAG_NO_UNLOCK)
+            action->btn.flags |= ACTION_LOCK_NO_UNLOCK;
+
+        break;
+    case XCB_XKB_SA_TYPE_SET_PTR_DFLT:
+        action->type = ACTION_TYPE_PTR_DEFAULT;
+
+        action->dflt.value = wire->setptrdflt.value;
+
+        if (wire->setptrdflt.flags & XCB_XKB_SA_SET_PTR_DFLT_FLAG_DFLT_BTN_ABSOLUTE)
+            action->dflt.flags |= ACTION_ABSOLUTE_SWITCH;
+
+        break;
+    case XCB_XKB_SA_TYPE_TERMINATE:
+        action->type = ACTION_TYPE_TERMINATE;
+
+        break;
+    case XCB_XKB_SA_TYPE_SWITCH_SCREEN:
+        action->type = ACTION_TYPE_SWITCH_VT;
+
+        action->screen.screen = wire->switchscreen.newScreen;
+
+        if (wire->switchscreen.flags & XCB_XKB_SWITCH_SCREEN_FLAG_APPLICATION)
+            action->screen.flags |= ACTION_SAME_SCREEN;
+        if (wire->switchscreen.flags & XCB_XKB_SWITCH_SCREEN_FLAG_ABSOLUTE)
+            action->screen.flags |= ACTION_ABSOLUTE_SWITCH;
+
+        break;
+    case XCB_XKB_SA_TYPE_SET_CONTROLS:
+        action->type = ACTION_TYPE_CTRL_SET;
+        {
+            const uint16_t mask = (wire->setcontrols.boolCtrlsLow |
+                                   (wire->setcontrols.boolCtrlsHigh << 8));
+            action->ctrls.ctrls = translate_controls_mask(mask);
+        }
+        break;
+    case XCB_XKB_SA_TYPE_LOCK_CONTROLS:
+        action->type = ACTION_TYPE_CTRL_LOCK;
+        {
+            const uint16_t mask = (wire->lockcontrols.boolCtrlsLow |
+                                   (wire->lockcontrols.boolCtrlsHigh << 8));
+            action->ctrls.ctrls = translate_controls_mask(mask);
+        }
+        break;
+
+    case XCB_XKB_SA_TYPE_NO_ACTION:
+    /* We don't support these. */
+    case XCB_XKB_SA_TYPE_ISO_LOCK:
+    case XCB_XKB_SA_TYPE_REDIRECT_KEY:
+    case XCB_XKB_SA_TYPE_ACTION_MESSAGE:
+    case XCB_XKB_SA_TYPE_DEVICE_BTN:
+    case XCB_XKB_SA_TYPE_LOCK_DEVICE_BTN:
+    case XCB_XKB_SA_TYPE_DEVICE_VALUATOR:
+        action->type = ACTION_TYPE_NONE;
+        break;
+    }
+}
+
+static bool
+get_types(struct xkb_keymap *keymap, xcb_connection_t *conn,
+          xcb_xkb_get_map_reply_t *reply, xcb_xkb_get_map_map_t *map)
+{
+    int types_length = xcb_xkb_get_map_map_types_rtrn_length(reply, map);
+    xcb_xkb_key_type_iterator_t types_iter =
+        xcb_xkb_get_map_map_types_rtrn_iterator(reply, map);
+
+    FAIL_UNLESS(reply->firstType == 0);
+
+    keymap->num_types = reply->nTypes;
+    ALLOC_OR_FAIL(keymap->types, keymap->num_types);
+
+    for (int i = 0; i < types_length; i++) {
+        xcb_xkb_key_type_t *wire_type = types_iter.data;
+        struct xkb_key_type *type = &keymap->types[i];
+
+        FAIL_UNLESS(wire_type->numLevels > 0);
+
+        type->mods.mods = translate_mods(wire_type->mods_mods,
+                                         wire_type->mods_vmods, 0);
+        type->mods.mask = translate_mods(wire_type->mods_mask, 0, 0);
+        type->num_levels = wire_type->numLevels;
+
+        {
+            int entries_length = xcb_xkb_key_type_map_length(wire_type);
+            xcb_xkb_kt_map_entry_iterator_t entries_iter =
+                xcb_xkb_key_type_map_iterator(wire_type);
+
+            type->num_entries = wire_type->nMapEntries;
+            ALLOC_OR_FAIL(type->entries, type->num_entries);
+
+            for (int j = 0; j < entries_length; j++) {
+                xcb_xkb_kt_map_entry_t *wire_entry = entries_iter.data;
+                struct xkb_key_type_entry *entry = &type->entries[j];
+
+                FAIL_UNLESS(wire_entry->level < type->num_levels);
+
+                entry->level = wire_entry->level;
+                entry->mods.mods = translate_mods(wire_entry->mods_mods,
+                                                  wire_entry->mods_vmods, 0);
+                entry->mods.mask = translate_mods(wire_entry->mods_mask, 0, 0);
+
+                xcb_xkb_kt_map_entry_next(&entries_iter);
+            }
+        }
+
+        {
+            int preserves_length = xcb_xkb_key_type_preserve_length(wire_type);
+            xcb_xkb_mod_def_iterator_t preserves_iter =
+                xcb_xkb_key_type_preserve_iterator(wire_type);
+
+            FAIL_UNLESS(preserves_length <= type->num_entries);
+
+            for (int j = 0; j < preserves_length; j++) {
+                xcb_xkb_mod_def_t *wire_preserve = preserves_iter.data;
+                struct xkb_key_type_entry *entry = &type->entries[j];
+
+                entry->preserve.mods = translate_mods(wire_preserve->realMods,
+                                                      wire_preserve->vmods, 0);
+                entry->preserve.mask = translate_mods(wire_preserve->mask, 0, 0);
+
+                xcb_xkb_mod_def_next(&preserves_iter);
+            }
+        }
+
+        xcb_xkb_key_type_next(&types_iter);
+    }
+
+    return true;
+
+fail:
+    return false;
+}
+
+static bool
+get_sym_maps(struct xkb_keymap *keymap, xcb_connection_t *conn,
+             xcb_xkb_get_map_reply_t *reply, xcb_xkb_get_map_map_t *map)
+{
+    int sym_maps_length = xcb_xkb_get_map_map_syms_rtrn_length(reply, map);
+    xcb_xkb_key_sym_map_iterator_t sym_maps_iter =
+        xcb_xkb_get_map_map_syms_rtrn_iterator(reply, map);
+
+    FAIL_UNLESS(reply->minKeyCode <= reply->maxKeyCode);
+    FAIL_UNLESS(reply->firstKeySym >= reply->minKeyCode);
+    FAIL_UNLESS(reply->firstKeySym + reply->nKeySyms <= reply->maxKeyCode + 1);
+
+    keymap->min_key_code = reply->minKeyCode;
+    keymap->max_key_code = reply->maxKeyCode;
+
+    ALLOC_OR_FAIL(keymap->keys, keymap->max_key_code + 1);
+
+    for (xkb_keycode_t kc = keymap->min_key_code; kc <= keymap->max_key_code; kc++)
+        keymap->keys[kc].keycode = kc;
+
+    for (int i = 0; i < sym_maps_length; i++) {
+        xcb_xkb_key_sym_map_t *wire_sym_map = sym_maps_iter.data;
+        struct xkb_key *key = &keymap->keys[reply->firstKeySym + i];
+
+        key->num_groups = wire_sym_map->groupInfo & 0x0f;
+        FAIL_UNLESS(key->num_groups <= ARRAY_SIZE(wire_sym_map->kt_index));
+        ALLOC_OR_FAIL(key->groups, key->num_groups);
+
+        for (int j = 0; j < key->num_groups; j++) {
+            FAIL_UNLESS(wire_sym_map->kt_index[j] < keymap->num_types);
+            key->groups[j].type = &keymap->types[wire_sym_map->kt_index[j]];
+
+            ALLOC_OR_FAIL(key->groups[j].levels, key->groups[j].type->num_levels);
+        }
+
+        key->out_of_range_group_number = (wire_sym_map->groupInfo & 0x30) >> 4;
+
+        FAIL_UNLESS(key->out_of_range_group_number <= key->num_groups);
+
+        if (wire_sym_map->groupInfo & XCB_XKB_GROUPS_WRAP_CLAMP_INTO_RANGE)
+            key->out_of_range_group_action = RANGE_SATURATE;
+        else if (wire_sym_map->groupInfo & XCB_XKB_GROUPS_WRAP_REDIRECT_INTO_RANGE)
+            key->out_of_range_group_action = RANGE_REDIRECT;
+        else
+            key->out_of_range_group_action = RANGE_WRAP;
+
+        {
+            int syms_length = xcb_xkb_key_sym_map_syms_length(wire_sym_map);
+            xcb_keysym_t *syms_iter = xcb_xkb_key_sym_map_syms(wire_sym_map);
+
+            FAIL_UNLESS(syms_length == wire_sym_map->width * key->num_groups);
+
+            for (int j = 0; j < syms_length; j++) {
+                xcb_keysym_t wire_keysym = *syms_iter;
+                const xkb_layout_index_t group = j / wire_sym_map->width;
+                const xkb_level_index_t level = j % wire_sym_map->width;
+
+                if (level < key->groups[group].type->num_levels &&
+                    wire_keysym != XKB_KEY_NoSymbol) {
+                    key->groups[group].levels[level].num_syms = 1;
+                    key->groups[group].levels[level].u.sym = wire_keysym;
+                }
+
+                syms_iter++;
+            }
+        }
+
+        xcb_xkb_key_sym_map_next(&sym_maps_iter);
+    }
+
+    return true;
+
+fail:
+    return false;
+}
+
+static bool
+get_actions(struct xkb_keymap *keymap, xcb_connection_t *conn,
+            xcb_xkb_get_map_reply_t *reply, xcb_xkb_get_map_map_t *map)
+{
+    int acts_count_length =
+        xcb_xkb_get_map_map_acts_rtrn_count_length(reply, map);
+    uint8_t *acts_count_iter = xcb_xkb_get_map_map_acts_rtrn_count(map);
+    xcb_xkb_action_iterator_t acts_iter =
+        xcb_xkb_get_map_map_acts_rtrn_acts_iterator(reply, map);
+    xcb_xkb_key_sym_map_iterator_t sym_maps_iter =
+        xcb_xkb_get_map_map_syms_rtrn_iterator(reply, map);
+
+    FAIL_UNLESS(reply->firstKeyAction == keymap->min_key_code);
+    FAIL_UNLESS(reply->firstKeyAction + reply->nKeyActions ==
+                keymap->max_key_code + 1);
+
+    for (int i = 0; i < acts_count_length; i++) {
+        xcb_xkb_key_sym_map_t *wire_sym_map = sym_maps_iter.data;
+        uint8_t wire_count = *acts_count_iter;
+        struct xkb_key *key = &keymap->keys[reply->firstKeyAction + i];
+
+        for (int j = 0; j < wire_count; j++) {
+            xcb_xkb_action_t *wire_action = acts_iter.data;
+            const xkb_layout_index_t group = j / wire_sym_map->width;
+            const xkb_level_index_t level = j % wire_sym_map->width;
+
+            if (level < key->groups[group].type->num_levels) {
+                union xkb_action *action =
+                    &key->groups[group].levels[level].action;
+
+                translate_action(action, wire_action);
+            }
+
+            xcb_xkb_action_next(&acts_iter);
+        }
+
+        acts_count_iter++;
+        xcb_xkb_key_sym_map_next(&sym_maps_iter);
+    }
+
+    return true;
+
+fail:
+    return false;
+}
+
+static bool
+get_vmods(struct xkb_keymap *keymap, xcb_connection_t *conn,
+          xcb_xkb_get_map_reply_t *reply, xcb_xkb_get_map_map_t *map)
+{
+    uint8_t *iter = xcb_xkb_get_map_map_vmods_rtrn(map);
+
+    darray_resize0(keymap->mods,
+                   NUM_REAL_MODS + msb_pos(reply->virtualMods));
+
+    for (int i = 0; i < NUM_VMODS; i++) {
+        if (reply->virtualMods & (1 << i)) {
+            uint8_t wire = *iter;
+            struct xkb_mod *mod = &darray_item(keymap->mods, NUM_REAL_MODS + i);
+
+            mod->type = MOD_VIRT;
+            mod->mapping = translate_mods(wire, 0, 0);
+
+            iter++;
+        }
+    }
+
+    return true;
+}
+
+static bool
+get_explicits(struct xkb_keymap *keymap, xcb_connection_t *conn,
+              xcb_xkb_get_map_reply_t *reply, xcb_xkb_get_map_map_t *map)
+{
+    int length = xcb_xkb_get_map_map_explicit_rtrn_length(reply, map);
+    xcb_xkb_set_explicit_iterator_t iter =
+        xcb_xkb_get_map_map_explicit_rtrn_iterator(reply, map);
+
+    for (int i = 0; i < length; i++) {
+        xcb_xkb_set_explicit_t *wire = iter.data;
+        struct xkb_key *key = &keymap->keys[wire->keycode];
+
+        FAIL_UNLESS(wire->keycode >= keymap->min_key_code &&
+                    wire->keycode <= keymap->max_key_code);
+
+        if ((wire->explicit & XCB_XKB_EXPLICIT_KEY_TYPE_1) &&
+            key->num_groups > 0)
+            key->groups[0].explicit_type = true;
+        if ((wire->explicit & XCB_XKB_EXPLICIT_KEY_TYPE_2) &&
+            key->num_groups > 1)
+            key->groups[1].explicit_type = true;
+        if ((wire->explicit & XCB_XKB_EXPLICIT_KEY_TYPE_3) &&
+            key->num_groups > 2)
+            key->groups[2].explicit_type = true;
+        if ((wire->explicit & XCB_XKB_EXPLICIT_KEY_TYPE_4) &&
+            key->num_groups > 3)
+            key->groups[3].explicit_type = true;
+        if (wire->explicit & XCB_XKB_EXPLICIT_INTERPRET)
+            key->explicit |= EXPLICIT_INTERP;
+        if (wire->explicit & XCB_XKB_EXPLICIT_AUTO_REPEAT)
+            key->explicit |= EXPLICIT_REPEAT;
+        if (wire->explicit & XCB_XKB_EXPLICIT_V_MOD_MAP)
+            key->explicit |= EXPLICIT_VMODMAP;
+
+        xcb_xkb_set_explicit_next(&iter);
+    }
+
+    return true;
+
+fail:
+    return false;
+}
+
+static bool
+get_modmaps(struct xkb_keymap *keymap, xcb_connection_t *conn,
+            xcb_xkb_get_map_reply_t *reply, xcb_xkb_get_map_map_t *map)
+{
+    int length = xcb_xkb_get_map_map_modmap_rtrn_length(reply, map);
+    xcb_xkb_key_mod_map_iterator_t iter =
+        xcb_xkb_get_map_map_modmap_rtrn_iterator(reply, map);
+
+    for (int i = 0; i < length; i++) {
+        xcb_xkb_key_mod_map_t *wire = iter.data;
+        struct xkb_key *key = &keymap->keys[wire->keycode];
+
+        FAIL_UNLESS(wire->keycode >= keymap->min_key_code &&
+                    wire->keycode <= keymap->max_key_code);
+
+        key->modmap = wire->mods;
+
+        xcb_xkb_key_mod_map_next(&iter);
+    }
+
+    return true;
+
+fail:
+    return false;
+}
+
+static bool
+get_vmodmaps(struct xkb_keymap *keymap, xcb_connection_t *conn,
+             xcb_xkb_get_map_reply_t *reply, xcb_xkb_get_map_map_t *map)
+{
+    int length = xcb_xkb_get_map_map_vmodmap_rtrn_length(reply, map);
+    xcb_xkb_key_v_mod_map_iterator_t iter =
+        xcb_xkb_get_map_map_vmodmap_rtrn_iterator(reply, map);
+
+    for (int i = 0; i < length; i++) {
+        xcb_xkb_key_v_mod_map_t *wire = iter.data;
+        struct xkb_key *key = &keymap->keys[wire->keycode];
+
+        FAIL_UNLESS(wire->keycode >= keymap->min_key_code &&
+                    wire->keycode <= keymap->max_key_code);
+
+        key->vmodmap = translate_mods(0, wire->vmods, 0);
+
+        xcb_xkb_key_v_mod_map_next(&iter);
+    }
+
+    return true;
+
+fail:
+    return false;
+}
+
+static bool
+get_map(struct xkb_keymap *keymap, xcb_connection_t *conn, uint16_t device_id)
+{
+    static const xcb_xkb_map_part_t required_components =
+        (XCB_XKB_MAP_PART_KEY_TYPES |
+         XCB_XKB_MAP_PART_KEY_SYMS |
+         XCB_XKB_MAP_PART_MODIFIER_MAP |
+         XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
+         XCB_XKB_MAP_PART_KEY_ACTIONS |
+         XCB_XKB_MAP_PART_VIRTUAL_MODS |
+         XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP);
+
+    xcb_xkb_get_map_cookie_t cookie =
+        xcb_xkb_get_map(conn, device_id, required_components,
+                        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+    xcb_xkb_get_map_reply_t *reply = xcb_xkb_get_map_reply(conn, cookie, NULL);
+    xcb_xkb_get_map_map_t map;
+
+    FAIL_IF_BAD_REPLY(reply, "XkbGetMap");
+
+    if ((reply->present & required_components) != required_components)
+        goto fail;
+
+    xcb_xkb_get_map_map_unpack(xcb_xkb_get_map_map(reply),
+                               reply->nTypes,
+                               reply->nKeySyms,
+                               reply->nKeyActions,
+                               reply->totalActions,
+                               reply->totalKeyBehaviors,
+                               reply->virtualMods,
+                               reply->totalKeyExplicit,
+                               reply->totalModMapKeys,
+                               reply->totalVModMapKeys,
+                               reply->present,
+                               &map);
+
+    if (!get_types(keymap, conn, reply, &map) ||
+        !get_sym_maps(keymap, conn, reply, &map) ||
+        !get_actions(keymap, conn, reply, &map) ||
+        !get_vmods(keymap, conn, reply, &map) ||
+        !get_explicits(keymap, conn, reply, &map) ||
+        !get_modmaps(keymap, conn, reply, &map) ||
+        !get_vmodmaps(keymap, conn, reply, &map))
+        goto fail;
+
+    free(reply);
+    return true;
+
+fail:
+    free(reply);
+    return false;
+}
+
+static bool
+get_indicators(struct xkb_keymap *keymap, xcb_connection_t *conn,
+               xcb_xkb_get_indicator_map_reply_t *reply)
+{
+    xcb_xkb_indicator_map_iterator_t iter =
+        xcb_xkb_get_indicator_map_maps_iterator(reply);
+
+    darray_resize0(keymap->leds, msb_pos(reply->which));
+
+    for (int i = 0; i < NUM_INDICATORS; i++) {
+        if (reply->which & (1 << i)) {
+            xcb_xkb_indicator_map_t *wire = iter.data;
+            struct xkb_led *led = &darray_item(keymap->leds, i);
+
+            if (wire->whichGroups & XCB_XKB_IM_GROUPS_WHICH_USE_BASE)
+                led->which_groups |= XKB_STATE_LAYOUT_DEPRESSED;
+            if (wire->whichGroups & XCB_XKB_IM_GROUPS_WHICH_USE_LATCHED)
+                led->which_groups |= XKB_STATE_LAYOUT_LATCHED;
+            if (wire->whichGroups & XCB_XKB_IM_GROUPS_WHICH_USE_LOCKED)
+                led->which_groups |= XKB_STATE_LAYOUT_LOCKED;
+            if (wire->whichGroups & XCB_XKB_IM_GROUPS_WHICH_USE_EFFECTIVE)
+                led->which_groups |= XKB_STATE_LAYOUT_EFFECTIVE;
+            if (wire->whichGroups & XCB_XKB_IM_GROUPS_WHICH_USE_COMPAT)
+                led->which_groups |= XKB_STATE_LAYOUT_EFFECTIVE;
+
+            led->groups = wire->groups;
+
+            if (wire->whichMods & XCB_XKB_IM_MODS_WHICH_USE_BASE)
+                led->which_mods |= XKB_STATE_MODS_DEPRESSED;
+            if (wire->whichMods & XCB_XKB_IM_MODS_WHICH_USE_LATCHED)
+                led->which_mods |= XKB_STATE_MODS_LATCHED;
+            if (wire->whichMods & XCB_XKB_IM_MODS_WHICH_USE_LOCKED)
+                led->which_mods |= XKB_STATE_MODS_LOCKED;
+            if (wire->whichMods & XCB_XKB_IM_MODS_WHICH_USE_EFFECTIVE)
+                led->which_mods |= XKB_STATE_MODS_EFFECTIVE;
+            if (wire->whichMods & XCB_XKB_IM_MODS_WHICH_USE_COMPAT)
+                led->which_mods |= XKB_STATE_MODS_EFFECTIVE;
+
+            led->mods.mods = translate_mods(wire->realMods, wire->vmods, 0);
+            led->mods.mask = translate_mods(wire->mods, 0, 0);
+
+            led->ctrls = translate_controls_mask(wire->ctrls);
+
+            xcb_xkb_indicator_map_next(&iter);
+        }
+    }
+
+    return true;
+}
+
+static bool
+get_indicator_map(struct xkb_keymap *keymap, xcb_connection_t *conn,
+                  uint16_t device_id)
+{
+    xcb_xkb_get_indicator_map_cookie_t cookie =
+        xcb_xkb_get_indicator_map(conn, device_id, ALL_INDICATORS_MASK);
+    xcb_xkb_get_indicator_map_reply_t *reply =
+        xcb_xkb_get_indicator_map_reply(conn, cookie, NULL);
+
+    FAIL_IF_BAD_REPLY(reply, "XkbGetIndicatorMap");
+
+    if (!get_indicators(keymap, conn, reply))
+        goto fail;
+
+    free(reply);
+    return true;
+
+fail:
+    free(reply);
+    return false;
+}
+
+static bool
+get_sym_interprets(struct xkb_keymap *keymap, xcb_connection_t *conn,
+                   xcb_xkb_get_compat_map_reply_t *reply)
+{
+    int length = xcb_xkb_get_compat_map_si_rtrn_length(reply);
+    xcb_xkb_sym_interpret_iterator_t iter =
+        xcb_xkb_get_compat_map_si_rtrn_iterator(reply);
+
+    FAIL_UNLESS(reply->firstSIRtrn == 0);
+    FAIL_UNLESS(reply->nSIRtrn == reply->nTotalSI);
+
+    keymap->num_sym_interprets = reply->nSIRtrn;
+    ALLOC_OR_FAIL(keymap->sym_interprets, keymap->num_sym_interprets);
+
+    for (int i = 0; i < length; i++) {
+        xcb_xkb_sym_interpret_t *wire = iter.data;
+        struct xkb_sym_interpret *sym_interpret = &keymap->sym_interprets[i];
+
+        sym_interpret->sym = wire->sym;
+
+        switch (wire->match & XCB_XKB_SYM_INTERP_MATCH_OP_MASK) {
+        case XCB_XKB_SYM_INTERPRET_MATCH_NONE_OF:
+            sym_interpret->match = MATCH_NONE;
+            break;
+        case XCB_XKB_SYM_INTERPRET_MATCH_ANY_OF_OR_NONE:
+            sym_interpret->match = MATCH_ANY_OR_NONE;
+            break;
+        case XCB_XKB_SYM_INTERPRET_MATCH_ANY_OF:
+            sym_interpret->match = MATCH_ANY;
+            break;
+        case XCB_XKB_SYM_INTERPRET_MATCH_ALL_OF:
+            sym_interpret->match = MATCH_ALL;
+            break;
+        case XCB_XKB_SYM_INTERPRET_MATCH_EXACTLY:
+            sym_interpret->match = MATCH_EXACTLY;
+            break;
+        }
+
+        sym_interpret->level_one_only =
+            !!(wire->match & XCB_XKB_SYM_INTERP_MATCH_LEVEL_ONE_ONLY);
+        sym_interpret->mods = wire->mods;
+
+        if (wire->virtualMod == NO_MODIFIER)
+            sym_interpret->virtual_mod = XKB_MOD_INVALID;
+        else
+            sym_interpret->virtual_mod = NUM_REAL_MODS + wire->virtualMod;
+
+        sym_interpret->repeat = !!(wire->flags & 0x01);
+        translate_action(&sym_interpret->action,
+                         (xcb_xkb_action_t *) &wire->action);
+
+        xcb_xkb_sym_interpret_next(&iter);
+    }
+
+    return true;
+
+fail:
+    return false;
+}
+
+static bool
+get_compat_map(struct xkb_keymap *keymap, xcb_connection_t *conn,
+               uint16_t device_id)
+{
+    xcb_xkb_get_compat_map_cookie_t cookie =
+        xcb_xkb_get_compat_map(conn, device_id, 0, true, 0, 0);
+    xcb_xkb_get_compat_map_reply_t *reply =
+        xcb_xkb_get_compat_map_reply(conn, cookie, NULL);
+
+    FAIL_IF_BAD_REPLY(reply, "XkbGetCompatMap");
+
+    if (!get_sym_interprets(keymap, conn, reply))
+        goto fail;
+
+    free(reply);
+    return true;
+
+fail:
+    free(reply);
+    return false;
+}
+
+static bool
+get_type_names(struct xkb_keymap *keymap, xcb_connection_t *conn,
+               xcb_xkb_get_names_reply_t *reply,
+               xcb_xkb_get_names_value_list_t *list)
+{
+    int key_type_names_length =
+        xcb_xkb_get_names_value_list_type_names_length(reply, list);
+    xcb_atom_t *key_type_names_iter =
+        xcb_xkb_get_names_value_list_type_names(list);
+    int n_levels_per_type_length =
+        xcb_xkb_get_names_value_list_n_levels_per_type_length(reply, list);
+    uint8_t *n_levels_per_type_iter =
+        xcb_xkb_get_names_value_list_n_levels_per_type(list);
+    xcb_atom_t *kt_level_names_iter =
+        xcb_xkb_get_names_value_list_kt_level_names(list);
+
+    FAIL_UNLESS(reply->nTypes == keymap->num_types);
+    FAIL_UNLESS(key_type_names_length == n_levels_per_type_length);
+
+    for (int i = 0; i < key_type_names_length; i++) {
+        xcb_atom_t wire_type_name = *key_type_names_iter;
+        uint8_t wire_num_levels = *n_levels_per_type_iter;
+        struct xkb_key_type *type = &keymap->types[i];
+
+        /* Levels must have names. */
+        FAIL_UNLESS(type->num_levels == wire_num_levels);
+
+        ALLOC_OR_FAIL(type->level_names, type->num_levels);
+
+        if (!adopt_atom(keymap->ctx, conn, wire_type_name, &type->name))
+            goto fail;
+
+        if (!adopt_atoms(keymap->ctx, conn,
+                         kt_level_names_iter, type->level_names,
+                         wire_num_levels))
+            goto fail;
+
+        kt_level_names_iter += wire_num_levels;
+        key_type_names_iter++;
+        n_levels_per_type_iter++;
+    }
+
+    return true;
+
+fail:
+    return false;
+}
+
+static bool
+get_indicator_names(struct xkb_keymap *keymap, xcb_connection_t *conn,
+                    xcb_xkb_get_names_reply_t *reply,
+                    xcb_xkb_get_names_value_list_t *list)
+{
+    xcb_atom_t *iter = xcb_xkb_get_names_value_list_indicator_names(list);
+
+    FAIL_UNLESS(msb_pos(reply->indicators) <= darray_size(keymap->leds));
+
+    for (int i = 0; i < NUM_INDICATORS; i++) {
+        if (reply->indicators & (1 << i)) {
+            xcb_atom_t wire = *iter;
+            struct xkb_led *led = &darray_item(keymap->leds, i);
+
+            if (!adopt_atom(keymap->ctx, conn, wire, &led->name))
+                return false;
+
+            iter++;
+        }
+    }
+
+    return true;
+
+fail:
+    return false;
+}
+
+static bool
+get_vmod_names(struct xkb_keymap *keymap, xcb_connection_t *conn,
+               xcb_xkb_get_names_reply_t *reply,
+               xcb_xkb_get_names_value_list_t *list)
+{
+    xcb_atom_t *iter = xcb_xkb_get_names_value_list_virtual_mod_names(list);
+
+    /*
+     * GetMap's reply->virtualMods is always 0xffff. This one really
+     * tells us which vmods exist (a vmod must have a name), so we fix
+     * up the size here.
+     */
+    darray_resize0(keymap->mods, NUM_REAL_MODS + msb_pos(reply->virtualMods));
+
+    for (int i = 0; i < NUM_VMODS; i++) {
+        if (reply->virtualMods & (1 << i)) {
+            xcb_atom_t wire = *iter;
+            struct xkb_mod *mod = &darray_item(keymap->mods, NUM_REAL_MODS + i);
+
+            if (!adopt_atom(keymap->ctx, conn, wire, &mod->name))
+                return false;
+
+            iter++;
+        }
+    }
+
+    return true;
+}
+
+static bool
+get_group_names(struct xkb_keymap *keymap, xcb_connection_t *conn,
+                xcb_xkb_get_names_reply_t *reply,
+                xcb_xkb_get_names_value_list_t *list)
+{
+    int length = xcb_xkb_get_names_value_list_groups_length(reply, list);
+    xcb_atom_t *iter = xcb_xkb_get_names_value_list_groups(list);
+
+    keymap->num_group_names = msb_pos(reply->groupNames);
+    ALLOC_OR_FAIL(keymap->group_names, keymap->num_group_names);
+
+    if (!adopt_atoms(keymap->ctx, conn,
+                     iter, keymap->group_names, length))
+        goto fail;
+
+    return true;
+
+fail:
+    return false;
+}
+
+static bool
+get_key_names(struct xkb_keymap *keymap, xcb_connection_t *conn,
+              xcb_xkb_get_names_reply_t *reply,
+              xcb_xkb_get_names_value_list_t *list)
+{
+    int length = xcb_xkb_get_names_value_list_key_names_length(reply, list);
+    xcb_xkb_key_name_iterator_t iter =
+        xcb_xkb_get_names_value_list_key_names_iterator(reply, list);
+
+    FAIL_UNLESS(reply->minKeyCode == keymap->min_key_code);
+    FAIL_UNLESS(reply->maxKeyCode == keymap->max_key_code);
+    FAIL_UNLESS(reply->firstKey == keymap->min_key_code);
+    FAIL_UNLESS(reply->firstKey + reply->nKeys - 1 == keymap->max_key_code);
+
+    for (int i = 0; i < length; i++) {
+        xcb_xkb_key_name_t *wire = iter.data;
+        xkb_atom_t *key_name = &keymap->keys[reply->firstKey + i].name;
+
+        if (wire->name[0] == '\0') {
+            *key_name = XKB_ATOM_NONE;
+        }
+        else {
+            *key_name = xkb_atom_intern(keymap->ctx, wire->name,
+                                        strnlen(wire->name,
+                                                XCB_XKB_CONST_KEY_NAME_LENGTH));
+            if (!*key_name)
+                return false;
+        }
+
+        xcb_xkb_key_name_next(&iter);
+    }
+
+    return true;
+
+fail:
+    return false;
+}
+
+static bool
+get_aliases(struct xkb_keymap *keymap, xcb_connection_t *conn,
+            xcb_xkb_get_names_reply_t *reply,
+            xcb_xkb_get_names_value_list_t *list)
+{
+    int length = xcb_xkb_get_names_value_list_key_aliases_length(reply, list);
+    xcb_xkb_key_alias_iterator_t iter =
+        xcb_xkb_get_names_value_list_key_aliases_iterator(reply, list);
+
+    keymap->num_key_aliases = reply->nKeyAliases;
+    ALLOC_OR_FAIL(keymap->key_aliases, keymap->num_key_aliases);
+
+    for (int i = 0; i < length; i++) {
+        xcb_xkb_key_alias_t *wire = iter.data;
+        struct xkb_key_alias *alias = &keymap->key_aliases[i];
+
+        alias->real =
+            xkb_atom_intern(keymap->ctx, wire->real,
+                            strnlen(wire->real, XCB_XKB_CONST_KEY_NAME_LENGTH));
+        alias->alias =
+            xkb_atom_intern(keymap->ctx, wire->alias,
+                            strnlen(wire->alias, XCB_XKB_CONST_KEY_NAME_LENGTH));
+        if (!alias->real || !alias->alias)
+            goto fail;
+
+        xcb_xkb_key_alias_next(&iter);
+    }
+
+    return true;
+
+fail:
+    return false;
+}
+
+static bool
+get_names(struct xkb_keymap *keymap, xcb_connection_t *conn,
+          uint16_t device_id)
+{
+    static const xcb_xkb_name_detail_t required_names =
+        (XCB_XKB_NAME_DETAIL_KEYCODES |
+         XCB_XKB_NAME_DETAIL_SYMBOLS |
+         XCB_XKB_NAME_DETAIL_TYPES |
+         XCB_XKB_NAME_DETAIL_COMPAT |
+         XCB_XKB_NAME_DETAIL_KEY_TYPE_NAMES |
+         XCB_XKB_NAME_DETAIL_KT_LEVEL_NAMES |
+         XCB_XKB_NAME_DETAIL_INDICATOR_NAMES |
+         XCB_XKB_NAME_DETAIL_KEY_NAMES |
+         XCB_XKB_NAME_DETAIL_KEY_ALIASES |
+         XCB_XKB_NAME_DETAIL_VIRTUAL_MOD_NAMES |
+         XCB_XKB_NAME_DETAIL_GROUP_NAMES);
+
+    xcb_xkb_get_names_cookie_t cookie =
+        xcb_xkb_get_names(conn, device_id, required_names);
+    xcb_xkb_get_names_reply_t *reply =
+        xcb_xkb_get_names_reply(conn, cookie, NULL);
+    xcb_xkb_get_names_value_list_t list;
+
+    FAIL_IF_BAD_REPLY(reply, "XkbGetNames");
+
+    if ((reply->which & required_names) != required_names)
+        goto fail;
+
+    xcb_xkb_get_names_value_list_unpack(xcb_xkb_get_names_value_list(reply),
+                                        reply->nTypes,
+                                        reply->indicators,
+                                        reply->virtualMods,
+                                        reply->groupNames,
+                                        reply->nKeys,
+                                        reply->nKeyAliases,
+                                        reply->nRadioGroups,
+                                        reply->which,
+                                        &list);
+
+    if (!get_atom_name(conn, list.keycodesName, &keymap->keycodes_section_name) ||
+        !get_atom_name(conn, list.symbolsName, &keymap->symbols_section_name) ||
+        !get_atom_name(conn, list.typesName, &keymap->types_section_name) ||
+        !get_atom_name(conn, list.compatName, &keymap->compat_section_name) ||
+        !get_type_names(keymap, conn, reply, &list) ||
+        !get_indicator_names(keymap, conn, reply, &list) ||
+        !get_vmod_names(keymap, conn, reply, &list) ||
+        !get_group_names(keymap, conn, reply, &list) ||
+        !get_key_names(keymap, conn, reply, &list) ||
+        !get_aliases(keymap, conn, reply, &list))
+        goto fail;
+
+    XkbEscapeMapName(keymap->keycodes_section_name);
+    XkbEscapeMapName(keymap->symbols_section_name);
+    XkbEscapeMapName(keymap->types_section_name);
+    XkbEscapeMapName(keymap->compat_section_name);
+
+    free(reply);
+    return true;
+
+fail:
+    free(reply);
+    return false;
+}
+
+static bool
+get_controls(struct xkb_keymap *keymap, xcb_connection_t *conn,
+             uint16_t device_id)
+{
+    xcb_xkb_get_controls_cookie_t cookie =
+        xcb_xkb_get_controls(conn, device_id);
+    xcb_xkb_get_controls_reply_t *reply =
+        xcb_xkb_get_controls_reply(conn, cookie, NULL);
+
+    FAIL_IF_BAD_REPLY(reply, "XkbGetControls");
+
+    keymap->enabled_ctrls = translate_controls_mask(reply->enabledControls);
+    keymap->num_groups = reply->numGroups;
+
+    FAIL_UNLESS(keymap->max_key_code < XCB_XKB_CONST_PER_KEY_BIT_ARRAY_SIZE * 8);
+
+    for (int i = keymap->min_key_code; i <= keymap->max_key_code; i++)
+        keymap->keys[i].repeats = !!(reply->perKeyRepeat[i / 8] & (1 << (i % 8)));
+
+    free(reply);
+    return true;
+
+fail:
+    free(reply);
+    return false;
+}
+
+XKB_EXPORT struct xkb_keymap *
+xkb_x11_keymap_new_from_device(struct xkb_context *ctx,
+                               xcb_connection_t *conn,
+                               int32_t device_id,
+                               enum xkb_keymap_compile_flags flags)
+{
+    struct xkb_keymap *keymap;
+    const enum xkb_keymap_format format = XKB_KEYMAP_FORMAT_TEXT_V1;
+
+    if (flags & ~(XKB_MAP_COMPILE_PLACEHOLDER)) {
+        log_err_func(ctx, "unrecognized flags: %#x\n", flags);
+        return NULL;
+    }
+
+    if (device_id < 0 || device_id > 255) {
+        log_err_func(ctx, "illegal device ID: %d\n", device_id);
+        return NULL;
+    }
+
+    keymap = xkb_keymap_new(ctx, format, flags);
+    if (!keymap)
+        return NULL;
+
+    if (!get_map(keymap, conn, device_id) ||
+        !get_indicator_map(keymap, conn, device_id) ||
+        !get_compat_map(keymap, conn, device_id) ||
+        !get_names(keymap, conn, device_id) ||
+        !get_controls(keymap, conn, device_id)) {
+        xkb_keymap_unref(keymap);
+        return NULL;
+    }
+
+    return keymap;
+}
diff --git a/src/x11/state.c b/src/x11/state.c
new file mode 100644
index 0000000..da7dcc2
--- /dev/null
+++ b/src/x11/state.c
@@ -0,0 +1,71 @@ 
+/*
+ * Copyright © 2013 Ran Benita
+ *
+ * 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 (including the next
+ * paragraph) 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.
+ */
+
+#include "x11-priv.h"
+
+static bool
+update_initial_state(struct xkb_state *state, xcb_connection_t *conn,
+                     uint16_t device_id)
+{
+    xcb_xkb_get_state_cookie_t cookie =
+        xcb_xkb_get_state(conn, device_id);
+    xcb_xkb_get_state_reply_t *reply =
+        xcb_xkb_get_state_reply(conn, cookie, NULL);
+
+    if (!reply)
+        return false;
+
+    xkb_state_update_mask(state,
+                          reply->baseMods,
+                          reply->latchedMods,
+                          reply->lockedMods,
+                          reply->baseGroup,
+                          reply->latchedGroup,
+                          reply->lockedGroup);
+
+    free(reply);
+    return true;
+}
+
+XKB_EXPORT struct xkb_state *
+xkb_x11_state_new_from_device(struct xkb_keymap *keymap,
+                              xcb_connection_t *conn, int32_t device_id)
+{
+    struct xkb_state *state;
+
+    if (device_id < 0 || device_id > 255) {
+        log_err_func(keymap->ctx, "illegal device ID: %d", device_id);
+        return NULL;
+    }
+
+    state = xkb_state_new(keymap);
+    if (!state)
+        return NULL;
+
+    if (!update_initial_state(state, conn, device_id)) {
+        xkb_state_unref(state);
+        return NULL;
+    }
+
+    return state;
+}
diff --git a/src/x11/util.c b/src/x11/util.c
new file mode 100644
index 0000000..58f2024
--- /dev/null
+++ b/src/x11/util.c
@@ -0,0 +1,210 @@ 
+/*
+ * Copyright © 2013 Ran Benita
+ *
+ * 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 (including the next
+ * paragraph) 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.
+ */
+
+#include "x11-priv.h"
+
+XKB_EXPORT int
+xkb_x11_setup_xkb_extension(xcb_connection_t *conn,
+                            uint16_t major_xkb_version,
+                            uint16_t minor_xkb_version,
+                            enum xkb_x11_setup_xkb_extension_flags flags,
+                            uint16_t *major_xkb_version_out,
+                            uint16_t *minor_xkb_version_out,
+                            uint8_t *base_event_out,
+                            uint8_t *base_error_out)
+{
+    uint8_t base_event, base_error;
+    uint16_t server_major, server_minor;
+
+    if (flags & ~(XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS)) {
+        /* log_err_func(ctx, "unrecognized flags: %#x\n", flags); */
+        return 0;
+    }
+
+    {
+        const xcb_query_extension_reply_t *reply =
+            xcb_get_extension_data(conn, &xcb_xkb_id);
+        if (!reply) {
+            /* log_err_func(ctx, "failed to query for XKB extension\n"); */
+            return 0;
+        }
+
+        base_event = reply->first_event;
+        base_error = reply->first_error;
+    }
+
+    {
+        xcb_generic_error_t *error = NULL;
+        xcb_xkb_use_extension_cookie_t cookie =
+            xcb_xkb_use_extension(conn, major_xkb_version, minor_xkb_version);
+        xcb_xkb_use_extension_reply_t *reply =
+            xcb_xkb_use_extension_reply(conn, cookie, &error);
+
+        if (!reply) {
+            /* log_err_func(ctx, */
+            /*              "failed to start using XKB extension: error code %d\n", */
+            /*              error ? error->error_code : -1); */
+            free(error);
+            return -1;
+        }
+
+        if (!reply->supported) {
+            /* log_err_func(ctx, */
+            /*              "failed to start using XKB extension: server doesn't support version %d.%d\n", */
+            /*              major_xkb_version, minor_xkb_version); */
+            free(reply);
+            return 0;
+        }
+
+        server_major = reply->serverMajor;
+        server_minor = reply->serverMinor;
+
+        free(reply);
+    }
+
+    /*
+    * The XkbUseExtension() in libX11 has a *bunch* of legacy stuff, but
+    * it doesn't seem like any of it is useful to us.
+    */
+
+    if (major_xkb_version_out)
+        *major_xkb_version_out = server_major;
+    if (minor_xkb_version_out)
+        *minor_xkb_version_out = server_minor;
+    if (base_event_out)
+        *base_event_out = base_event;
+    if (base_error_out)
+        *base_error_out = base_error;
+
+    return 1;
+}
+
+XKB_EXPORT int32_t
+xkb_x11_get_core_keyboard_device_id(xcb_connection_t *conn)
+{
+    int32_t device_id;
+    xcb_xkb_get_device_info_cookie_t cookie =
+        xcb_xkb_get_device_info(conn, XCB_XKB_ID_USE_CORE_KBD,
+                                0, 0, 0, 0, 0, 0);
+    xcb_xkb_get_device_info_reply_t *reply =
+        xcb_xkb_get_device_info_reply(conn, cookie, NULL);
+
+    if (!reply)
+        return -1;
+
+    device_id = reply->deviceID;
+    free(reply);
+    return device_id;
+}
+
+bool
+get_atom_name(xcb_connection_t *conn, xcb_atom_t atom, char **out)
+{
+    xcb_get_atom_name_cookie_t cookie;
+    xcb_get_atom_name_reply_t *reply;
+    int length;
+    char *name;
+
+    if (atom == 0) {
+        *out = NULL;
+        return true;
+    }
+
+    cookie = xcb_get_atom_name(conn, atom);
+    reply = xcb_get_atom_name_reply(conn, cookie, NULL);
+    if (!reply)
+        return false;
+
+    length = xcb_get_atom_name_name_length(reply);
+    name = xcb_get_atom_name_name(reply);
+
+    *out = strndup(name, length);
+    if (!*out) {
+        free(reply);
+        return false;
+    }
+
+    free(reply);
+    return true;
+}
+
+bool
+adopt_atoms(struct xkb_context *ctx, xcb_connection_t *conn,
+            const xcb_atom_t *from, xkb_atom_t *to, const size_t count)
+{
+    enum { SIZE = 128 };
+    xcb_get_atom_name_cookie_t cookies[SIZE];
+
+    /* Send and collect the atoms in batches of reasonable SIZE. */
+    for (size_t batch = 0; batch <= count / SIZE; batch++) {
+        const size_t start = batch * SIZE;
+        const size_t stop = min((batch + 1) * SIZE, count);
+
+        /* Send. */
+        for (size_t i = start; i < stop; i++)
+            if (from[i] != XCB_ATOM_NONE)
+                cookies[i % SIZE] = xcb_get_atom_name(conn, from[i]);
+
+        /* Collect. */
+        for (size_t i = start; i < stop; i++) {
+            xcb_get_atom_name_reply_t *reply;
+
+            if (from[i] == XCB_ATOM_NONE) {
+                to[i] = XKB_ATOM_NONE;
+                continue;
+            }
+
+            reply = xcb_get_atom_name_reply(conn, cookies[i % SIZE], NULL);
+            if (!reply)
+                goto err_discard;
+
+            to[i] = xkb_atom_intern(ctx,
+                                    xcb_get_atom_name_name(reply),
+                                    xcb_get_atom_name_name_length(reply));
+            free(reply);
+
+            if (to[i] == XKB_ATOM_NONE)
+                goto err_discard;
+
+            continue;
+
+            /*
+             * If we don't discard the uncollected replies, they just
+             * sit there waiting. Sad.
+             */
+err_discard:
+            for (size_t j = i + 1; j < stop; j++)
+                xcb_discard_reply(conn, cookies[j].sequence);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool
+adopt_atom(struct xkb_context *ctx, xcb_connection_t *conn, xcb_atom_t atom,
+           xkb_atom_t *out)
+{
+    return adopt_atoms(ctx, conn, &atom, out, 1);
+}
diff --git a/src/x11/x11-priv.h b/src/x11/x11-priv.h
new file mode 100644
index 0000000..03f9ee6
--- /dev/null
+++ b/src/x11/x11-priv.h
@@ -0,0 +1,54 @@ 
+/*
+ * Copyright © 2013 Ran Benita
+ *
+ * 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 (including the next
+ * paragraph) 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 _XKBCOMMON_X11_PRIV_H
+#define _XKBCOMMON_X11_PRIV_H
+
+#include <xcb/xkb.h>
+
+#include "xkbcommon/xkbcommon-x11.h"
+#include "keymap.h"
+
+/* Get a strdup'd name of an X atom. */
+bool
+get_atom_name(xcb_connection_t *conn, xcb_atom_t atom, char **out);
+
+/*
+ * Make a xkb_atom_t's from X atoms (prefer to send as many as possible
+ * at once, to avoid many roundtrips).
+ *
+ * TODO: We can make this more flexible, such that @to doesn't have to
+ *       be sequential. Then we can convert most adopt_atom() calls to
+ *       adopt_atoms().
+ *       Atom caching would also likely be useful for avoiding quite a
+ *       few requests.
+ */
+bool
+adopt_atoms(struct xkb_context *ctx, xcb_connection_t *conn,
+            const xcb_atom_t *from, xkb_atom_t *to, size_t count);
+
+bool
+adopt_atom(struct xkb_context *ctx, xcb_connection_t *conn, xcb_atom_t atom,
+           xkb_atom_t *out);
+
+#endif
diff --git a/xkbcommon-x11-uninstalled.pc.in b/xkbcommon-x11-uninstalled.pc.in
new file mode 100644
index 0000000..d99ca49
--- /dev/null
+++ b/xkbcommon-x11-uninstalled.pc.in
@@ -0,0 +1,10 @@ 
+libdir=@abs_top_builddir@/.libs
+includedir=@abs_top_srcdir@
+
+Name: xkbcommon-x11
+Description: XKB API common to servers and clients - X11 support (uninstalled)
+Version: @PACKAGE_VERSION@
+Requires: xkbcommon
+Requires.private: xcb xcb-xkb
+Cflags: -I${includedir}
+Libs: -L${libdir} -lxkbcommon-x11
diff --git a/xkbcommon-x11.pc.in b/xkbcommon-x11.pc.in
new file mode 100644
index 0000000..c4efc43
--- /dev/null
+++ b/xkbcommon-x11.pc.in
@@ -0,0 +1,12 @@ 
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: xkbcommon-x11
+Description: XKB API common to servers and clients - X11 support
+Version: @PACKAGE_VERSION@
+Requires: xkbcommon
+Requires.private: xcb xcb-xkb
+Cflags: -I${includedir}
+Libs: -L${libdir} -lxkbcommon-x11
diff --git a/xkbcommon/xkbcommon-x11.h b/xkbcommon/xkbcommon-x11.h
new file mode 100644
index 0000000..c14690b
--- /dev/null
+++ b/xkbcommon/xkbcommon-x11.h
@@ -0,0 +1,163 @@ 
+/*
+ * Copyright © 2013 Ran Benita
+ *
+ * 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 (including the next
+ * paragraph) 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 _XKBCOMMON_X11_H
+#define _XKBCOMMON_X11_H
+
+#include <xcb/xcb.h>
+#include <xkbcommon/xkbcommon.h>
+
+/**
+ * @file
+ * libxkbcommon-x11 API - Additional X11 support for xkbcommon.
+ */
+
+/**
+ * @defgroup x11 X11 support
+ * Additional X11 support for xkbcommon.
+ *
+ * @{
+ */
+
+/**
+ * The minimal compatible major version of the XKB X11 extension which
+ * this library can use.
+ */
+#define XKB_X11_MIN_MAJOR_XKB_VERSION 1
+/**
+ * The minimal compatible minor version of the XKB X11 extension which
+ * this library can use (for the minimal major version).
+ */
+#define XKB_X11_MIN_MINOR_XKB_VERSION 0
+
+/** Flags for the xkb_x11_setup_xkb_extension() function. */
+enum xkb_x11_setup_xkb_extension_flags {
+    /** Do not apply any flags. */
+    XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS = 0
+};
+
+/**
+ * Setup the XKB X11 extension for this X client.
+ *
+ * The xkbcommon-x11 library uses various XKB requests.  Before doing so,
+ * an X client must notify the server that it will be using the extension.
+ * This function (or an XCB equivalent) must be called before any other
+ * function in this library is used.
+ *
+ * You may call this function several times; it is idempotent.
+ *
+ * @param connection
+ *     An XCB connection to the X server.
+ * @param major_xkb_version, minor_xkb_version
+ *     The XKB extension version to request.  To operate correctly, you
+ *     must have (major_xkb_version, minor_xkb_version) >=
+ *     (XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION),
+ *     though this is not enforced.
+ * @param flags
+ *     Optional flags, or 0.
+ * @param[out] major_xkb_version_out, minor_xkb_version_out
+ *     Backfilled with the compatible XKB extension version numbers picked
+ *     by the server.  Can be NULL.
+ * @param[out] base_event_out
+ *     Backfilled with the XKB base (also known as first) event code, needed
+ *     to distinguish XKB events.  Can be NULL.
+ * @param[out] base_error_out
+ *     Backfilled with the XKB base (also known as first) error code, needed
+ *     to distinguish XKB errors.  Can be NULL.
+ *
+ * @returns 1 on success, or 0 on failure.
+ */
+int
+xkb_x11_setup_xkb_extension(xcb_connection_t *connection,
+                            uint16_t major_xkb_version,
+                            uint16_t minor_xkb_version,
+                            enum xkb_x11_setup_xkb_extension_flags flags,
+                            uint16_t *major_xkb_version_out,
+                            uint16_t *minor_xkb_version_out,
+                            uint8_t *base_event_out,
+                            uint8_t *base_error_out);
+
+/**
+ * Get the keyboard device ID of the core X11 keyboard.
+ *
+ * @param connection An XCB connection to the X server.
+ *
+ * @returns A device ID which may be used with other xkb_x11_* functions,
+ *          or -1 on failure.
+ */
+int32_t
+xkb_x11_get_core_keyboard_device_id(xcb_connection_t *connection);
+
+/**
+ * Create a keymap from an X11 keyboard device.
+ *
+ * This function queries the X server with various requests, fetches the
+ * details of the active keymap on a keyboard device, and creates an
+ * xkb_keymap from these details.
+ *
+ * @param context
+ *     The context in which to create the keymap.
+ * @param connection
+ *     An XCB connection to the X server.
+ * @param device_id
+ *     An XInput 1 device ID (in the range 0-255) with input class KEY.
+ *     Passing values outside of this range is an error.
+ * @param flags
+ *     Optional flags for the keymap, or 0.
+ *
+ * @returns A keymap retrieved from the X server, or NULL on failure.
+ *
+ * @memberof xkb_keymap
+ */
+struct xkb_keymap *
+xkb_x11_keymap_new_from_device(struct xkb_context *context,
+                               xcb_connection_t *connection,
+                               int32_t device_id,
+                               enum xkb_keymap_compile_flags flags);
+
+/**
+ * Create a new keyboard state object from an X11 keyboard device.
+ *
+ * This function is the same as xkb_state_new(), only pre-initialized
+ * with the state of the device at the time this function is called.
+ *
+ * @param keymap
+ *     The keymap for which to create the state.
+ * @param connection
+ *     An XCB connection to the X server.
+ * @param device_id
+ *     An XInput 1 device ID (in the range 0-255) with input class KEY.
+ *     Passing values outside of this range is an error.
+ *
+ * @returns A new keyboard state object, or NULL on failure.
+ *
+ * @memberof xkb_state
+ */
+struct xkb_state *
+xkb_x11_state_new_from_device(struct xkb_keymap *keymap,
+                              xcb_connection_t *connection,
+                              int32_t device_id);
+
+/** @} */
+
+#endif

Comments

On 15.01.2014 23:35, Ran Benita wrote:
[...]
> diff --git a/src/x11/util.c b/src/x11/util.c
> new file mode 100644
> index 0000000..58f2024
> --- /dev/null
> +++ b/src/x11/util.c
> @@ -0,0 +1,210 @@
> +/*
> + * Copyright © 2013 Ran Benita
> + *
> + * 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 (including the next
> + * paragraph) 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.
> + */
> +
> +#include "x11-priv.h"
> +
> +XKB_EXPORT int
> +xkb_x11_setup_xkb_extension(xcb_connection_t *conn,
> +                            uint16_t major_xkb_version,
> +                            uint16_t minor_xkb_version,
> +                            enum xkb_x11_setup_xkb_extension_flags flags,
> +                            uint16_t *major_xkb_version_out,
> +                            uint16_t *minor_xkb_version_out,
> +                            uint8_t *base_event_out,
> +                            uint8_t *base_error_out)
> +{
> +    uint8_t base_event, base_error;
> +    uint16_t server_major, server_minor;
> +
> +    if (flags & ~(XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS)) {
> +        /* log_err_func(ctx, "unrecognized flags: %#x\n", flags); */
> +        return 0;
> +    }
> +
> +    {
> +        const xcb_query_extension_reply_t *reply =
> +            xcb_get_extension_data(conn, &xcb_xkb_id);
> +        if (!reply) {

This is missing a "|| !reply->is_present". Otherwise, the xcb connection goes
into an error state when xcb fails at figuring out XKB's major number for
sending the UseExtension request.

> +            /* log_err_func(ctx, "failed to query for XKB extension\n"); */
> +            return 0;
> +        }
> +
> +        base_event = reply->first_event;
> +        base_error = reply->first_error;
> +    }
> +
> +    {
> +        xcb_generic_error_t *error = NULL;
> +        xcb_xkb_use_extension_cookie_t cookie =
> +            xcb_xkb_use_extension(conn, major_xkb_version, minor_xkb_version);
> +        xcb_xkb_use_extension_reply_t *reply =
> +            xcb_xkb_use_extension_reply(conn, cookie, &error);
> +
> +        if (!reply) {
> +            /* log_err_func(ctx, */
> +            /*              "failed to start using XKB extension: error code %d\n", */
> +            /*              error ? error->error_code : -1); */
> +            free(error);
> +            return -1;
[...]

I think you meant "return 0" here.

I have a patched version of xtrace laying around that answers all QueryExtension
requests negatively. With this I found the above two issues:

lt-x11: test/x11.c:59: main: Assertion `device_id != -1' failed.


This makes me wonder about one bit of the commit message:

> This opens up the possibility for X clients to use
> xcb + xcb-xkb + xkbcommon as a proper replacement for Xlib + xkbfile for
> keyboard support.

I am not entirely sure, but doesn't Xlib transparently make things work even if
the XKB extension is missing?

If yes: I have no clue about the details, but could something like this be
possible with this code as well? Otherwise, I doubt a lot that any application
using this new code will work on servers that lack the XKB extension (and I
doubt many of them will provide helpful error messages). At least I would like
to see the error case of xkb_x11_setup_xkb_extension() to be documented more
clearly. To make it clear that its result really must be checked and that it
only works if the server supports XKB.

Uli
who has no clue about keyboards
On Thu, Jan 16, 2014 at 05:57:41PM +0100, Uli Schlachter wrote:
> On 15.01.2014 23:35, Ran Benita wrote:
> [...]
> > diff --git a/src/x11/util.c b/src/x11/util.c
> > new file mode 100644
> > index 0000000..58f2024
> > --- /dev/null
> > +++ b/src/x11/util.c
> > @@ -0,0 +1,210 @@
> > +/*
> > + * Copyright © 2013 Ran Benita
> > + *
> > + * 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 (including the next
> > + * paragraph) 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.
> > + */
> > +
> > +#include "x11-priv.h"
> > +
> > +XKB_EXPORT int
> > +xkb_x11_setup_xkb_extension(xcb_connection_t *conn,
> > +                            uint16_t major_xkb_version,
> > +                            uint16_t minor_xkb_version,
> > +                            enum xkb_x11_setup_xkb_extension_flags flags,
> > +                            uint16_t *major_xkb_version_out,
> > +                            uint16_t *minor_xkb_version_out,
> > +                            uint8_t *base_event_out,
> > +                            uint8_t *base_error_out)
> > +{
> > +    uint8_t base_event, base_error;
> > +    uint16_t server_major, server_minor;
> > +
> > +    if (flags & ~(XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS)) {
> > +        /* log_err_func(ctx, "unrecognized flags: %#x\n", flags); */
> > +        return 0;
> > +    }
> > +
> > +    {
> > +        const xcb_query_extension_reply_t *reply =
> > +            xcb_get_extension_data(conn, &xcb_xkb_id);
> > +        if (!reply) {
> 
> This is missing a "|| !reply->is_present". Otherwise, the xcb connection goes
> into an error state when xcb fails at figuring out XKB's major number for
> sending the UseExtension request.

Right, fixed.

> > +            /* log_err_func(ctx, "failed to query for XKB extension\n"); */
> > +            return 0;
> > +        }
> > +
> > +        base_event = reply->first_event;
> > +        base_error = reply->first_error;
> > +    }
> > +
> > +    {
> > +        xcb_generic_error_t *error = NULL;
> > +        xcb_xkb_use_extension_cookie_t cookie =
> > +            xcb_xkb_use_extension(conn, major_xkb_version, minor_xkb_version);
> > +        xcb_xkb_use_extension_reply_t *reply =
> > +            xcb_xkb_use_extension_reply(conn, cookie, &error);
> > +
> > +        if (!reply) {
> > +            /* log_err_func(ctx, */
> > +            /*              "failed to start using XKB extension: error code %d\n", */
> > +            /*              error ? error->error_code : -1); */
> > +            free(error);
> > +            return -1;
> [...]
> 
> I think you meant "return 0" here.
> 
> I have a patched version of xtrace laying around that answers all QueryExtension
> requests negatively. With this I found the above two issues:
> 
> lt-x11: test/x11.c:59: main: Assertion `device_id != -1' failed.

That's useful, I didn't really think to test it (and I still haven't,
though I will :)
But with your suggestions the x11 test will be skipped.

> 
> This makes me wonder about one bit of the commit message:
> 
> > This opens up the possibility for X clients to use
> > xcb + xcb-xkb + xkbcommon as a proper replacement for Xlib + xkbfile for
> > keyboard support.
> 
> I am not entirely sure, but doesn't Xlib transparently make things work even if
> the XKB extension is missing?
> 
> If yes: I have no clue about the details, but could something like this be
> possible with this code as well? Otherwise, I doubt a lot that any application
> using this new code will work on servers that lack the XKB extension (and I
> doubt many of them will provide helpful error messages). At least I would like
> to see the error case of xkb_x11_setup_xkb_extension() to be documented more
> clearly. To make it clear that its result really must be checked and that it
> only works if the server supports XKB.

Yes, you are right. If XKB is unavailable, Xlib falls back to the core
protocol. We don't do any of that. I am tempted to say we don't care
about that, legacy, etc. But it might be possible to do something
reasonable (maybe borrow code from xcb-keysym). I will have to look
this over. However in the mean time I've added a note to the
documentation, though I've nothing much to say other than "find some
other solution if you care about this".

> Uli
> who has no clue about keyboards

Thanks a lot for you comments.
Ran

> -- 
> "In the beginning the Universe was created. This has made a lot of
>  people very angry and has been widely regarded as a bad move."