From 761bccd408c72e1d1d10d6c69f9f1d01fff30a0c Mon Sep 17 00:00:00 2001 From: "Bradley T. Hughes" Date: Wed, 10 Mar 2010 11:13:33 +0100 Subject: Improve keyboard layout detection on X11 When the keyboard configuration includes multiple layouts in separate groups, the _XKB_RULES_NAME property lists the layouts separated by a comma. Previously we did not handle this. We query the current keyboard group on startup and monitor XkbStateNotify events for group changes. The existing infrastructre from QKeyMapper deals with sending the KeyboardLayoutChange event to all toplevel windows already. This change includes a bit of refactoring to put the XKB detection and initialization in qapplication_x11.cpp and XKB opcode, eventbase, and errorbase into qt_x11_p.h (like with the other extensions). Task-number: QTBUG-3631 Reviewed-by: denis --- src/gui/kernel/qapplication_x11.cpp | 63 +++++++++++++++++++++++++++++++------ src/gui/kernel/qkeymapper_p.h | 2 +- src/gui/kernel/qkeymapper_x11.cpp | 34 ++++++++++---------- src/gui/kernel/qt_x11_p.h | 6 ++++ 4 files changed, 78 insertions(+), 27 deletions(-) diff --git a/src/gui/kernel/qapplication_x11.cpp b/src/gui/kernel/qapplication_x11.cpp index 20a7ff2..985f825 100644 --- a/src/gui/kernel/qapplication_x11.cpp +++ b/src/gui/kernel/qapplication_x11.cpp @@ -709,6 +709,10 @@ static int qt_x_errhandler(Display *dpy, XErrorEvent *err) extensionName = "XInputExtension"; else if (err->request_code == X11->mitshm_major) extensionName = "MIT-SHM"; +#ifndef QT_NO_XKB + else if(err->request_code == X11->xkb_major) + extensionName = "XKEYBOARD"; +#endif char minor_str[256]; if (extensionName) { @@ -1635,6 +1639,11 @@ void qt_init(QApplicationPrivate *priv, int, X11->xinput_eventbase = 0; X11->xinput_errorbase = 0; + X11->use_xkb = false; + X11->xkb_major = 0; + X11->xkb_eventbase = 0; + X11->xkb_errorbase = 0; + // MIT-SHM X11->use_mitshm = false; X11->use_mitshm_pixmaps = false; @@ -2108,6 +2117,33 @@ void qt_init(QApplicationPrivate *priv, int, } #endif // QT_NO_XINPUT +#ifndef QT_NO_XKB + int xkblibMajor = XkbMajorVersion; + int xkblibMinor = XkbMinorVersion; + X11->use_xkb = XkbQueryExtension(X11->display, + &X11->xkb_major, + &X11->xkb_eventbase, + &X11->xkb_errorbase, + &xkblibMajor, + &xkblibMinor); + if (X11->use_xkb) { + // If XKB is detected, set the GrabsUseXKBState option so input method + // compositions continue to work (ie. deadkeys) + unsigned int state = XkbPCF_GrabsUseXKBStateMask; + (void) XkbSetPerClientControls(X11->display, state, &state); + + // select for group change events + XkbSelectEventDetails(X11->display, + XkbUseCoreKbd, + XkbStateNotify, + XkbAllStateComponentsMask, + XkbGroupStateMask); + + // current group state is queried when creating the keymapper, no need to do it here + } +#endif + + #if !defined(QT_NO_FONTCONFIG) int dpi = 0; getXDefault("Xft", FC_DPI, &dpi); @@ -2186,15 +2222,6 @@ void qt_init(QApplicationPrivate *priv, int, // initialize key mapper QKeyMapper::changeKeyboard(); -#ifndef QT_NO_XKB - if (qt_keymapper_private()->useXKB) { - // If XKB is detected, set the GrabsUseXKBState option so input method - // compositions continue to work (ie. deadkeys) - unsigned int state = XkbPCF_GrabsUseXKBStateMask; - (void) XkbSetPerClientControls(X11->display, state, &state); - } -#endif // QT_NO_XKB - // Misc. initialization #if 0 //disabled for now.. QSegfaultHandler::initialize(priv->argv, priv->argc); @@ -3229,6 +3256,24 @@ int QApplication::x11ProcessEvent(XEvent* event) QKeyMapper::changeKeyboard(); return 0; } +#ifndef QT_NO_XKB + else if (X11->use_xkb && event->type == X11->xkb_eventbase) { + XkbAnyEvent *xkbevent = (XkbAnyEvent *) event; + switch (xkbevent->xkb_type) { + case XkbStateNotify: + { + XkbStateNotifyEvent *xkbstateevent = (XkbStateNotifyEvent *) xkbevent; + if ((xkbstateevent->changed & XkbGroupStateMask) != 0) { + qt_keymapper_private()->xkb_currentGroup = xkbstateevent->group; + QKeyMapper::changeKeyboard(); + } + break; + } + default: + break; + } + } +#endif if (!widget) { // don't know this windows QWidget* popup = QApplication::activePopupWidget(); diff --git a/src/gui/kernel/qkeymapper_p.h b/src/gui/kernel/qkeymapper_p.h index 3e42d6e..38f141e 100644 --- a/src/gui/kernel/qkeymapper_p.h +++ b/src/gui/kernel/qkeymapper_p.h @@ -183,7 +183,7 @@ public: const XEvent *, bool grab); - bool useXKB; + int xkb_currentGroup; QXCoreDesc coreDesc; #elif defined(Q_WS_MAC) diff --git a/src/gui/kernel/qkeymapper_x11.cpp b/src/gui/kernel/qkeymapper_x11.cpp index d6d1042..428ac3e 100644 --- a/src/gui/kernel/qkeymapper_x11.cpp +++ b/src/gui/kernel/qkeymapper_x11.cpp @@ -248,22 +248,17 @@ qt_XTranslateKey(register QXCoreDesc *dpy, QKeyMapperPrivate::QKeyMapperPrivate() - : keyboardInputDirection(Qt::LeftToRight), useXKB(false) + : keyboardInputDirection(Qt::LeftToRight), xkb_currentGroup(0) { memset(&coreDesc, 0, sizeof(coreDesc)); #ifndef QT_NO_XKB - int opcode = -1; - int xkbEventBase = -1; - int xkbErrorBase = -1; - int xkblibMajor = XkbMajorVersion; - int xkblibMinor = XkbMinorVersion; - if (XkbQueryExtension(X11->display, &opcode, &xkbEventBase, &xkbErrorBase, &xkblibMajor, &xkblibMinor)) - useXKB = true; -#endif - -#if 0 - qDebug() << "useXKB =" << useXKB; + if (X11->use_xkb) { + // get the current group + XkbStateRec xkbState; + if (XkbGetState(X11->display, XkbUseCoreKbd, &xkbState) == Success) + xkb_currentGroup = xkbState.group; + } #endif } @@ -276,7 +271,7 @@ QKeyMapperPrivate::~QKeyMapperPrivate() QList QKeyMapperPrivate::possibleKeys(QKeyEvent *event) { #ifndef QT_NO_XKB - if (useXKB) + if (X11->use_xkb) return possibleKeysXKB(event); #endif return possibleKeysCore(event); @@ -486,7 +481,7 @@ enum { void QKeyMapperPrivate::clearMappings() { #ifndef QT_NO_XKB - if (useXKB) { + if (X11->use_xkb) { // try to determine the layout name and input direction by reading the _XKB_RULES_NAMES property off // the root window QByteArray layoutName; @@ -515,8 +510,13 @@ void QKeyMapperPrivate::clearMappings() p += qstrlen(p) + 1; } while (p < end); - layoutName = QByteArray::fromRawData(names[2], qstrlen(names[2])); - variantName = QByteArray::fromRawData(names[3], qstrlen(names[3])); + // the layout names and variants are saved in the _XKB_RULES_NAMES property as a comma separated list + QList layoutNames = QByteArray::fromRawData(names[2], qstrlen(names[2])).split(','); + if (uint(xkb_currentGroup) < uint(layoutNames.count())) + layoutName = layoutNames.at(xkb_currentGroup); + QList variantNames = QByteArray::fromRawData(names[3], qstrlen(names[3])).split(','); + if (uint(xkb_currentGroup) < uint(variantNames.count())) + variantName = variantNames.at(xkb_currentGroup); } // ### ??? @@ -574,7 +574,7 @@ void QKeyMapperPrivate::clearMappings() // look at the modifier mapping, and get the correct masks for alt, meta, super, hyper, and mode_switch #ifndef QT_NO_XKB - if (useXKB) { + if (X11->use_xkb) { XkbDescPtr xkbDesc = XkbGetMap(X11->display, XkbAllClientInfoMask, XkbUseCoreKbd); for (int i = xkbDesc->min_key_code; i < xkbDesc->max_key_code; ++i) { const uint mask = xkbDesc->map->modmap ? xkbDesc->map->modmap[i] : 0; diff --git a/src/gui/kernel/qt_x11_p.h b/src/gui/kernel/qt_x11_p.h index 8af4df5..e1b2625 100644 --- a/src/gui/kernel/qt_x11_p.h +++ b/src/gui/kernel/qt_x11_p.h @@ -439,6 +439,12 @@ struct QX11Data int xinput_eventbase; int xinput_errorbase; + // for XKEYBOARD support + bool use_xkb; + int xkb_major; + int xkb_eventbase; + int xkb_errorbase; + QList deferred_map; struct ScrollInProgress { long id; -- cgit v0.12