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>
master
Ran Benita 2013-07-20 23:21:44 +03:00
parent ddbefda383
commit eb34825560
10 changed files with 1730 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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);

1146
src/x11/keymap.c Normal file

File diff suppressed because it is too large Load Diff

71
src/x11/state.c Normal file
View File

@ -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;
}

215
src/x11/util.c Normal file
View File

@ -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);
}

54
src/x11/x11-priv.h Normal file
View File

@ -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

View File

@ -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

12
xkbcommon-x11.pc.in Normal file
View File

@ -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

166
xkbcommon/xkbcommon-x11.h Normal file
View File

@ -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