diff options
author | Ran Benita <ran234@gmail.com> | 2013-07-20 23:21:44 +0300 |
---|---|---|
committer | Ran Benita <ran234@gmail.com> | 2014-02-02 11:16:40 +0200 |
commit | eb34825560edf570d883d3e52a8fe657c17b3d9c (patch) | |
tree | dc1d7d3dba0acc6a07ccbbf297093c9ea08ce3f5 | |
parent | ddbefda383cccbf8d537b30471fc2ce893826d35 (diff) | |
download | xorg-lib-libxkbcommon-eb34825560edf570d883d3e52a8fe657c17b3d9c.tar.gz |
x11: add XKB protocol keymap and state creation support
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>
-rw-r--r-- | Makefile.am | 27 | ||||
-rw-r--r-- | configure.ac | 13 | ||||
-rw-r--r-- | src/utils.h | 16 | ||||
-rw-r--r-- | src/x11/keymap.c | 1146 | ||||
-rw-r--r-- | src/x11/state.c | 71 | ||||
-rw-r--r-- | src/x11/util.c | 215 | ||||
-rw-r--r-- | src/x11/x11-priv.h | 54 | ||||
-rw-r--r-- | xkbcommon-x11-uninstalled.pc.in | 10 | ||||
-rw-r--r-- | xkbcommon-x11.pc.in | 12 | ||||
-rw-r--r-- | xkbcommon/xkbcommon-x11.h | 166 |
10 files changed, 1730 insertions, 0 deletions
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..9f5afa0 100644 --- a/configure.ac +++ b/configure.ac @@ -143,10 +143,23 @@ if ! test "x$DEFAULT_XKB_OPTIONS" = x; then [Default XKB options]) fi +AC_ARG_ENABLE([x11], + [AS_HELP_STRING([--disable-x11], + [Disable support for creating keymaps with the X11 protocol (default: enabled)])], + [], [enable_x11=yes]) +if test "x$enable_x11" == xyes; then + PKG_CHECK_MODULES([XCB_XKB], [xcb xcb-xkb >= 1.10], [], + [AC_MSG_ERROR([xkbcommon-x11 requires xcb-xkb >= 1.10 which was not found. \ +You can disable X11 support with --disable-x11.])]) +fi +AM_CONDITIONAL([ENABLE_X11], [test "x$enable_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..92ff2e6 --- /dev/null +++ b/src/x11/util.c @@ -0,0 +1,215 @@ +/* + * 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; + } + + if (!reply->present) { + /* log_err_func(ctx, "failed to start using XKB extension: not available in server\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 0; + } + + 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..4ec9b64 --- /dev/null +++ b/xkbcommon/xkbcommon-x11.h @@ -0,0 +1,166 @@ +/* + * 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. + * + * Some X servers may not support or disable the XKB extension. If you + * want to support such servers, you need to use a different fallback. + * + * 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 |