Use X11 core protocol when xkb not available

To have a properly working key input in the xcb plugin in the case when
xcb-xkb library is not available we can update the xkb_state struct with the
keyboard state information available in the X11 core events.

The current modifier state is reported to clients in a number of core
protocol events and can be determined using the QueryPointer request.

This is how it is done in Weston, Wayland's reference implementation.

Note: In case the X server doesn't have a xkb support on it (which is very
unlikely), then xkbcommon will only pick up the user's primary layout.
The X server with the xkb support stuffs unused bits (13 and 14) of 'state' in the
core events with the effective keyboard group, which we can use to determine layout
changes.

Change-Id: I9f1ef635109870e7412ef1157ca592f3c8f9271c
Reviewed-by: Samuel Rødal <samuel.rodal@digia.com>
bb10
Gatis Paeglis 2013-04-16 20:24:45 +02:00 committed by The Qt Project
parent 260bfaf056
commit 43578e1901
4 changed files with 236 additions and 20 deletions

View File

@ -299,7 +299,10 @@ QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGra
}
xcb_extension_t *extensions[] = {
&xcb_shm_id, &xcb_xfixes_id, &xcb_randr_id, &xcb_shape_id, &xcb_sync_id, &xcb_xkb_id,
&xcb_shm_id, &xcb_xfixes_id, &xcb_randr_id, &xcb_shape_id, &xcb_sync_id,
#ifndef QT_NO_XKB
&xcb_xkb_id,
#endif
#ifdef XCB_USE_RENDER
&xcb_render_id,
#endif
@ -747,6 +750,7 @@ void QXcbConnection::handleButtonRelease(xcb_generic_event_t *ev)
m_buttons &= ~translateMouseButton(event->detail);
}
#ifndef QT_NO_XKB
namespace {
typedef union {
/* All XKB events share these fields. */
@ -761,6 +765,7 @@ namespace {
xcb_xkb_state_notify_event_t state_notify;
} _xkb_event;
}
#endif
void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
{
@ -786,12 +791,21 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
case XCB_EXPOSE:
HANDLE_PLATFORM_WINDOW_EVENT(xcb_expose_event_t, window, handleExposeEvent);
case XCB_BUTTON_PRESS:
#ifdef QT_NO_XKB
m_keyboard->updateXKBStateFromCore(((xcb_button_press_event_t *)event)->state);
#endif
handleButtonPress(event);
HANDLE_PLATFORM_WINDOW_EVENT(xcb_button_press_event_t, event, handleButtonPressEvent);
case XCB_BUTTON_RELEASE:
#ifdef QT_NO_XKB
m_keyboard->updateXKBStateFromCore(((xcb_button_release_event_t *)event)->state);
#endif
handleButtonRelease(event);
HANDLE_PLATFORM_WINDOW_EVENT(xcb_button_release_event_t, event, handleButtonReleaseEvent);
case XCB_MOTION_NOTIFY:
#ifdef QT_NO_XKB
m_keyboard->updateXKBStateFromCore(((xcb_motion_notify_event_t *)event)->state);
#endif
HANDLE_PLATFORM_WINDOW_EVENT(xcb_motion_notify_event_t, event, handleMotionNotifyEvent);
case XCB_CONFIGURE_NOTIFY:
HANDLE_PLATFORM_WINDOW_EVENT(xcb_configure_notify_event_t, event, handleConfigureNotifyEvent);
@ -805,15 +819,29 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
case XCB_ENTER_NOTIFY:
HANDLE_PLATFORM_WINDOW_EVENT(xcb_enter_notify_event_t, event, handleEnterNotifyEvent);
case XCB_LEAVE_NOTIFY:
#ifdef QT_NO_XKB
m_keyboard->updateXKBStateFromCore(((xcb_leave_notify_event_t *)event)->state);
#endif
HANDLE_PLATFORM_WINDOW_EVENT(xcb_leave_notify_event_t, event, handleLeaveNotifyEvent);
case XCB_FOCUS_IN:
HANDLE_PLATFORM_WINDOW_EVENT(xcb_focus_in_event_t, event, handleFocusInEvent);
case XCB_FOCUS_OUT:
HANDLE_PLATFORM_WINDOW_EVENT(xcb_focus_out_event_t, event, handleFocusOutEvent);
case XCB_KEY_PRESS:
#ifdef QT_NO_XKB
m_keyboard->updateXKBStateFromCore(((xcb_key_press_event_t *)event)->state);
#endif
HANDLE_KEYBOARD_EVENT(xcb_key_press_event_t, handleKeyPressEvent);
case XCB_KEY_RELEASE:
#ifdef QT_NO_XKB
m_keyboard->updateXKBStateFromCore(((xcb_key_release_event_t *)event)->state);
#endif
HANDLE_KEYBOARD_EVENT(xcb_key_release_event_t, handleKeyReleaseEvent);
#ifdef QT_NO_XKB
case XCB_MAPPING_NOTIFY:
m_keyboard->handleMappingNotifyEvent((xcb_mapping_notify_event_t *)event);
break;
#endif
case XCB_SELECTION_REQUEST:
{
xcb_selection_request_event_t *sr = (xcb_selection_request_event_t *)event;
@ -876,6 +904,7 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
}
}
handled = true;
#ifndef QT_NO_XKB
} else if (response_type == xkb_first_event) { // https://bugs.freedesktop.org/show_bug.cgi?id=51295
_xkb_event *xkb_event = reinterpret_cast<_xkb_event *>(event);
if (xkb_event->any.deviceID == m_keyboard->coreDeviceId()) {
@ -892,6 +921,7 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
break;
}
}
#endif
}
}
@ -1574,6 +1604,7 @@ void QXcbConnection::initializeXShape()
void QXcbConnection::initializeXKB()
{
#ifndef QT_NO_XKB
const xcb_query_extension_reply_t *reply = xcb_get_extension_data(m_connection, &xcb_xkb_id);
if (!reply || !reply->present) {
xkb_first_event = 0;
@ -1629,6 +1660,7 @@ void QXcbConnection::initializeXKB()
qWarning() << "Qt: failed to select notify events from xcb-xkb";
return;
}
#endif
}
#if defined(XCB_USE_EGL)

View File

@ -673,8 +673,9 @@ void QXcbKeyboard::updateKeymap()
xkb_state_unref(xkb_state);
xkb_state = new_state;
} else {
// get initial state from the X server (and keep it up-to-date at all times)
xkb_state = new_state;
#ifndef QT_NO_XKB
// get initial state from the X server (and keep it up-to-date at all times)
xcb_xkb_get_state_cookie_t state;
xcb_xkb_get_state_reply_t *init_state;
@ -695,9 +696,13 @@ void QXcbKeyboard::updateKeymap()
init_state->latchedGroup,
init_state->lockedGroup);
free(init_state);
#else
updateXKBMods();
#endif
}
}
#ifndef QT_NO_XKB
void QXcbKeyboard::updateXKBState(xcb_xkb_state_notify_event_t *state)
{
if (!m_config)
@ -715,11 +720,74 @@ void QXcbKeyboard::updateXKBState(xcb_xkb_state_notify_event_t *state)
state->lockedGroup);
if ((newState & XKB_STATE_LAYOUT_EFFECTIVE) == XKB_STATE_LAYOUT_EFFECTIVE) {
qWarning("TODO: Support KeyboardLayoutChange on QPA (QTBUG-27681)");
//qWarning("TODO: Support KeyboardLayoutChange on QPA (QTBUG-27681)");
}
}
}
#else
void QXcbKeyboard::updateXKBStateFromCore(quint16 state)
{
if (!m_config)
return;
quint32 modsDepressed, modsLatched, modsLocked;
modsDepressed = xkb_state_serialize_mods(xkb_state, XKB_STATE_MODS_DEPRESSED);
modsLatched = xkb_state_serialize_mods(xkb_state, XKB_STATE_MODS_LATCHED);
modsLocked = xkb_state_serialize_mods(xkb_state, XKB_STATE_MODS_LOCKED);
quint32 xkbMask = xkbModMask(state);
xkb_state_component newState;
newState = xkb_state_update_mask(xkb_state,
modsDepressed & xkbMask,
modsLatched & xkbMask,
modsLocked & xkbMask,
0,
0,
(state >> 13) & 3); // bits 13 and 14 report the state keyboard group
if ((newState & XKB_STATE_LAYOUT_EFFECTIVE) == XKB_STATE_LAYOUT_EFFECTIVE) {
//qWarning("TODO: Support KeyboardLayoutChange on QPA (QTBUG-27681)");
}
}
quint32 QXcbKeyboard::xkbModMask(quint16 state)
{
quint32 xkb_mask = 0;
if ((state & XCB_MOD_MASK_SHIFT) && xkb_mods.shift != XKB_MOD_INVALID)
xkb_mask |= (1 << xkb_mods.shift);
if ((state & XCB_MOD_MASK_LOCK) && xkb_mods.lock != XKB_MOD_INVALID)
xkb_mask |= (1 << xkb_mods.lock);
if ((state & XCB_MOD_MASK_CONTROL) && xkb_mods.control != XKB_MOD_INVALID)
xkb_mask |= (1 << xkb_mods.control);
if ((state & XCB_MOD_MASK_1) && xkb_mods.mod1 != XKB_MOD_INVALID)
xkb_mask |= (1 << xkb_mods.mod1);
if ((state & XCB_MOD_MASK_2) && xkb_mods.mod2 != XKB_MOD_INVALID)
xkb_mask |= (1 << xkb_mods.mod2);
if ((state & XCB_MOD_MASK_3) && xkb_mods.mod3 != XKB_MOD_INVALID)
xkb_mask |= (1 << xkb_mods.mod3);
if ((state & XCB_MOD_MASK_4) && xkb_mods.mod4 != XKB_MOD_INVALID)
xkb_mask |= (1 << xkb_mods.mod4);
if ((state & XCB_MOD_MASK_5) && xkb_mods.mod5 != XKB_MOD_INVALID)
xkb_mask |= (1 << xkb_mods.mod5);
return xkb_mask;
}
void QXcbKeyboard::updateXKBMods()
{
xkb_mods.shift = xkb_map_mod_get_index(xkb_keymap, XKB_MOD_NAME_SHIFT);
xkb_mods.lock = xkb_map_mod_get_index(xkb_keymap, XKB_MOD_NAME_CAPS);
xkb_mods.control = xkb_map_mod_get_index(xkb_keymap, XKB_MOD_NAME_CTRL);
xkb_mods.mod1 = xkb_map_mod_get_index(xkb_keymap, "Mod1");
xkb_mods.mod2 = xkb_map_mod_get_index(xkb_keymap, "Mod2");
xkb_mods.mod3 = xkb_map_mod_get_index(xkb_keymap, "Mod3");
xkb_mods.mod4 = xkb_map_mod_get_index(xkb_keymap, "Mod4");
xkb_mods.mod5 = xkb_map_mod_get_index(xkb_keymap, "Mod5");
}
#endif
QList<int> QXcbKeyboard::possibleKeys(const QKeyEvent *event) const
{
// turn off the modifier bits which doesn't participate in shortcuts
@ -846,11 +914,14 @@ QXcbKeyboard::QXcbKeyboard(QXcbConnection *connection)
, xkb_context(0)
, xkb_keymap(0)
, xkb_state(0)
#ifndef QT_NO_XKB
, core_device_id(0)
#endif
{
updateKeymap();
#ifndef QT_NO_XKB
if (connection->hasXKB()) {
updateKeymap();
updateVModMapping();
updateVModToRModMapping();
@ -871,6 +942,10 @@ QXcbKeyboard::QXcbKeyboard(QXcbConnection *connection)
core_device_id = device_id->deviceID;
free(device_id);
}
#else
m_key_symbols = xcb_key_symbols_alloc(xcb_connection());
updateModifiers();
#endif
}
QXcbKeyboard::~QXcbKeyboard()
@ -881,8 +956,12 @@ QXcbKeyboard::~QXcbKeyboard()
xkb_keymap_unref(xkb_keymap);
if (xkb_context)
xkb_context_unref(xkb_context);
#ifdef QT_NO_XKB
xcb_key_symbols_free(m_key_symbols);
#endif
}
#ifndef QT_NO_XKB
void QXcbKeyboard::updateVModMapping()
{
xcb_xkb_get_names_cookie_t names_cookie;
@ -1004,14 +1083,67 @@ void QXcbKeyboard::updateVModToRModMapping()
rmod_masks.altgr = modmap;
}
#if 0
qDebug() << "alt: " << rmod_masks.alt;
qDebug() << "meta: " << rmod_masks.meta;
qDebug() << "altgr: " << rmod_masks.altgr;
#endif
free(map_reply);
}
#else
void QXcbKeyboard::updateModifiers()
{
// The core protocol does not provide a convenient way to determine the mapping
// of modifier bits. Clients must retrieve and search the modifier map to determine
// the keycodes bound to each modifier, and then retrieve and search the keyboard
// mapping to determine the keysyms bound to the keycodes. They must repeat this
// process for all modifiers whenever any part of the modifier mapping is changed.
memset(&rmod_masks, 0, sizeof(rmod_masks));
xcb_generic_error_t *error = 0;
xcb_connection_t *conn = xcb_connection();
xcb_get_modifier_mapping_cookie_t modMapCookie = xcb_get_modifier_mapping(conn);
xcb_get_modifier_mapping_reply_t *modMapReply =
xcb_get_modifier_mapping_reply(conn, modMapCookie, &error);
if (error) {
qWarning("Qt: failed to get modifier mapping");
free(error);
return;
}
// for Alt and Meta L and R are the same
static const xcb_keysym_t symbols[] = {
XK_Alt_L, XK_Meta_L, XK_Mode_switch
};
static const size_t numSymbols = sizeof symbols / sizeof *symbols;
// Figure out the modifier mapping, ICCCM 6.6
xcb_keycode_t* modKeyCodes[numSymbols];
for (size_t i = 0; i < numSymbols; ++i)
modKeyCodes[i] = xcb_key_symbols_get_keycode(m_key_symbols, symbols[i]);
xcb_keycode_t *modMap = xcb_get_modifier_mapping_keycodes(modMapReply);
const int w = modMapReply->keycodes_per_modifier;
for (size_t i = 0; i < numSymbols; ++i) {
for (int bit = 0; bit < 8; ++bit) {
uint mask = 1 << bit;
for (int x = 0; x < w; ++x) {
xcb_keycode_t keyCode = modMap[x + bit * w];
xcb_keycode_t *itk = modKeyCodes[i];
while (itk && *itk != XCB_NO_SYMBOL)
if (*itk++ == keyCode) {
uint sym = symbols[i];
if ((sym == XK_Alt_L || sym == XK_Alt_R))
rmod_masks.alt = mask;
if ((sym == XK_Meta_L || sym == XK_Meta_R))
rmod_masks.meta = mask;
if (sym == XK_Mode_switch)
rmod_masks.altgr = mask;
}
}
}
}
for (size_t i = 0; i < numSymbols; ++i)
free(modKeyCodes[i]);
free(modMapReply);
}
#endif
class KeyChecker
{
@ -1076,8 +1208,17 @@ void QXcbKeyboard::handleKeyEvent(QWindow *window, QEvent::Type type, xcb_keycod
if (!m_config)
return;
// It is crucial the order of xkb_state_key_get_one_sym &
// xkb_state_update_key operations is not reversed!
xcb_keysym_t sym = xkb_state_key_get_one_sym(xkb_state, code);
#ifdef QT_NO_XKB
enum xkb_key_direction direction;
if (type == QEvent::KeyPress)
direction = XKB_KEY_DOWN;
else
direction = XKB_KEY_UP;
xkb_state_update_key(xkb_state, code, direction);
#endif
QPlatformInputContext *inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext();
QMetaMethod method;
@ -1194,13 +1335,20 @@ void QXcbKeyboard::handleKeyReleaseEvent(QXcbWindowEventListener *eventListener,
handleKeyEvent(window->window(), QEvent::KeyRelease, event->detail, event->state, event->time);
}
void QXcbKeyboard::handleMappingNotifyEvent(const xcb_xkb_map_notify_event_t *)
void QXcbKeyboard::handleMappingNotifyEvent(const void *event)
{
updateKeymap();
#ifdef QT_NO_XKB
void *ev = const_cast<void *>(event);
xcb_refresh_keyboard_mapping(m_key_symbols, static_cast<xcb_mapping_notify_event_t *>(ev));
updateModifiers();
#else
Q_UNUSED(event)
if (connection()->hasXKB()) {
updateKeymap();
updateVModMapping();
updateVModToRModMapping();
}
#endif
}
QT_END_NAMESPACE

View File

@ -44,6 +44,10 @@
#include "qxcbobject.h"
#ifdef QT_NO_XKB
#include <xcb/xcb_keysyms.h>
#endif
#include <xkbcommon/xkbcommon.h>
#include <QEvent>
@ -62,14 +66,21 @@ public:
void handleKeyPressEvent(QXcbWindowEventListener *eventListener, const xcb_key_press_event_t *event);
void handleKeyReleaseEvent(QXcbWindowEventListener *eventListener, const xcb_key_release_event_t *event);
void handleMappingNotifyEvent(const xcb_xkb_map_notify_event_t *event);
void handleMappingNotifyEvent(const void *event);
Qt::KeyboardModifiers translateModifiers(int s) const;
QList<int> possibleKeys(const QKeyEvent *e) const;
void updateKeymap();
void updateXKBState(xcb_xkb_state_notify_event_t *state);
QList<int> possibleKeys(const QKeyEvent *e) const;
#ifdef QT_NO_XKB
void updateXKBStateFromCore(quint16 state);
void updateXKBMods();
quint32 xkbModMask(quint16 state);
#else
int coreDeviceId() { return core_device_id; }
void updateXKBState(xcb_xkb_state_notify_event_t *state);
#endif
protected:
void handleKeyEvent(QWindow *window, QEvent::Type type, xcb_keycode_t code, quint16 state, xcb_timestamp_t time);
@ -80,10 +91,16 @@ protected:
int keysymToQtKey(xcb_keysym_t keysym, Qt::KeyboardModifiers &modifiers, QString text) const;
void readXKBConfig(struct xkb_rule_names *names);
#ifdef QT_NO_XKB
void updateModifiers();
#else
void updateVModMapping();
void updateVModToRModMapping();
#endif
private:
bool m_config;
xcb_keycode_t m_autorepeat_code;
struct xkb_context *xkb_context;
@ -96,12 +113,27 @@ private:
uint meta;
};
_mod_masks vmod_masks;
_mod_masks rmod_masks;
int core_device_id;
#ifdef QT_NO_XKB
xcb_key_symbols_t *m_key_symbols;
bool m_config;
struct _xkb_mods {
xkb_mod_index_t shift;
xkb_mod_index_t lock;
xkb_mod_index_t control;
xkb_mod_index_t mod1;
xkb_mod_index_t mod2;
xkb_mod_index_t mod3;
xkb_mod_index_t mod4;
xkb_mod_index_t mod5;
};
_xkb_mods xkb_mods;
#else
_mod_masks vmod_masks;
int core_device_id;
#endif
};
QT_END_NAMESPACE

View File

@ -111,7 +111,11 @@ contains(QT_CONFIG, xcb-qt) {
} else {
LIBS += -lxcb -lxcb-image -lxcb-icccm -lxcb-sync -lxcb-xfixes -lxcb-shm -lxcb-randr
!contains(DEFINES, QT_NO_SHAPE):LIBS += -lxcb-shape
!contains(DEFINES, QT_NO_XKB):LIBS += -lxcb-xkb
contains(DEFINES, QT_NO_XKB) {
LIBS += -lxcb-keysyms
} else {
LIBS += -lxcb-xkb
}
}
# libxkbcommon