diff options
Diffstat (limited to 'src/plugins/platforms/xcb/qxcbkeyboard.cpp')
-rw-r--r-- | src/plugins/platforms/xcb/qxcbkeyboard.cpp | 635 |
1 files changed, 525 insertions, 110 deletions
diff --git a/src/plugins/platforms/xcb/qxcbkeyboard.cpp b/src/plugins/platforms/xcb/qxcbkeyboard.cpp index 2e29c208c7..2ed66394c9 100644 --- a/src/plugins/platforms/xcb/qxcbkeyboard.cpp +++ b/src/plugins/platforms/xcb/qxcbkeyboard.cpp @@ -53,24 +53,91 @@ #include <stdio.h> #include <X11/keysym.h> -#ifdef XCB_USE_XINPUT22 +#if QT_CONFIG(xinput2) #include <X11/extensions/XI2proto.h> #undef KeyPress #undef KeyRelease #endif +#if QT_CONFIG(xcb_xlib) +#include <X11/Xutil.h> +#undef KeyPress +#undef KeyRelease +#endif + #ifndef XK_ISO_Left_Tab #define XK_ISO_Left_Tab 0xFE20 #endif -#ifndef XK_dead_hook -#define XK_dead_hook 0xFE61 +#ifndef XK_dead_a +#define XK_dead_a 0xFE80 +#endif + +#ifndef XK_dead_A +#define XK_dead_A 0xFE81 +#endif + +#ifndef XK_dead_e +#define XK_dead_e 0xFE82 +#endif + +#ifndef XK_dead_E +#define XK_dead_E 0xFE83 +#endif + +#ifndef XK_dead_i +#define XK_dead_i 0xFE84 +#endif + +#ifndef XK_dead_I +#define XK_dead_I 0xFE85 +#endif + +#ifndef XK_dead_o +#define XK_dead_o 0xFE86 +#endif + +#ifndef XK_dead_O +#define XK_dead_O 0xFE87 +#endif + +#ifndef XK_dead_u +#define XK_dead_u 0xFE88 +#endif + +#ifndef XK_dead_U +#define XK_dead_U 0xFE89 +#endif + +#ifndef XK_dead_small_schwa +#define XK_dead_small_schwa 0xFE8A +#endif + +#ifndef XK_dead_capital_schwa +#define XK_dead_capital_schwa 0xFE8B +#endif + +#ifndef XK_dead_greek +#define XK_dead_greek 0xFE8C +#endif + +#ifndef XK_dead_lowline +#define XK_dead_lowline 0xFE90 #endif -#ifndef XK_dead_horn -#define XK_dead_horn 0xFE62 +#ifndef XK_dead_aboveverticalline +#define XK_dead_aboveverticalline 0xFE91 #endif +#ifndef XK_dead_belowverticalline +#define XK_dead_belowverticalline 0xFE92 +#endif + +#ifndef XK_dead_longsolidusoverlay +#define XK_dead_longsolidusoverlay 0xFE93 +#endif + + #ifndef XK_Codeinput #define XK_Codeinput 0xFF37 #endif @@ -429,6 +496,36 @@ static const unsigned int KeyTbl[] = { XK_dead_belowdot, Qt::Key_Dead_Belowdot, XK_dead_hook, Qt::Key_Dead_Hook, XK_dead_horn, Qt::Key_Dead_Horn, + XK_dead_stroke, Qt::Key_Dead_Stroke, + XK_dead_abovecomma, Qt::Key_Dead_Abovecomma, + XK_dead_abovereversedcomma, Qt::Key_Dead_Abovereversedcomma, + XK_dead_doublegrave, Qt::Key_Dead_Doublegrave, + XK_dead_belowring, Qt::Key_Dead_Belowring, + XK_dead_belowmacron, Qt::Key_Dead_Belowmacron, + XK_dead_belowcircumflex, Qt::Key_Dead_Belowcircumflex, + XK_dead_belowtilde, Qt::Key_Dead_Belowtilde, + XK_dead_belowbreve, Qt::Key_Dead_Belowbreve, + XK_dead_belowdiaeresis, Qt::Key_Dead_Belowdiaeresis, + XK_dead_invertedbreve, Qt::Key_Dead_Invertedbreve, + XK_dead_belowcomma, Qt::Key_Dead_Belowcomma, + XK_dead_currency, Qt::Key_Dead_Currency, + XK_dead_a, Qt::Key_Dead_a, + XK_dead_A, Qt::Key_Dead_A, + XK_dead_e, Qt::Key_Dead_e, + XK_dead_E, Qt::Key_Dead_E, + XK_dead_i, Qt::Key_Dead_i, + XK_dead_I, Qt::Key_Dead_I, + XK_dead_o, Qt::Key_Dead_o, + XK_dead_O, Qt::Key_Dead_O, + XK_dead_u, Qt::Key_Dead_u, + XK_dead_U, Qt::Key_Dead_U, + XK_dead_small_schwa, Qt::Key_Dead_Small_Schwa, + XK_dead_capital_schwa, Qt::Key_Dead_Capital_Schwa, + XK_dead_greek, Qt::Key_Dead_Greek, + XK_dead_lowline, Qt::Key_Dead_Lowline, + XK_dead_aboveverticalline, Qt::Key_Dead_Aboveverticalline, + XK_dead_belowverticalline, Qt::Key_Dead_Belowverticalline, + XK_dead_longsolidusoverlay, Qt::Key_Dead_Longsolidusoverlay, // Special keys from X.org - This include multimedia keys, // wireless/bluetooth/uwb keys, special launcher keys, etc. @@ -612,23 +709,18 @@ Qt::KeyboardModifiers QXcbKeyboard::translateModifiers(int s) const void QXcbKeyboard::readXKBConfig() { clearXKBConfig(); - xcb_generic_error_t *error; - xcb_get_property_cookie_t cookie; - xcb_get_property_reply_t *config_reply; xcb_connection_t *c = xcb_connection(); xcb_window_t rootWindow = connection()->rootWindow(); - cookie = xcb_get_property(c, 0, rootWindow, - atom(QXcbAtom::_XKB_RULES_NAMES), XCB_ATOM_STRING, 0, 1024); - - config_reply = xcb_get_property_reply(c, cookie, &error); + auto config_reply = Q_XCB_REPLY(xcb_get_property, c, 0, rootWindow, + atom(QXcbAtom::_XKB_RULES_NAMES), XCB_ATOM_STRING, 0, 1024); if (!config_reply) { qWarning("Qt: Couldn't interpret the _XKB_RULES_NAMES property"); return; } - char *xkb_config = (char *)xcb_get_property_value(config_reply); - int length = xcb_get_property_value_length(config_reply); + char *xkb_config = (char *)xcb_get_property_value(config_reply.get()); + int length = xcb_get_property_value_length(config_reply.get()); // on old X servers xkb_config can be 0 even if config_reply indicates a succesfull read if (!xkb_config || length == 0) @@ -653,8 +745,6 @@ void QXcbKeyboard::readXKBConfig() xkb_names.layout = qstrdup(names[2]); xkb_names.variant = qstrdup(names[3]); xkb_names.options = qstrdup(names[4]); - - free(config_reply); } void QXcbKeyboard::clearXKBConfig() @@ -685,9 +775,303 @@ void QXcbKeyboard::printKeymapError(const char *error) const "directory contains recent enough contents, to update please see http://cgit.freedesktop.org/xkeyboard-config/ ."); } +#if QT_CONFIG(xcb_xlib) +/* Look at a pair of unshifted and shifted key symbols. + * If the 'unshifted' symbol is uppercase and there is no shifted symbol, + * return the matching lowercase symbol; otherwise return 0. + * The caller can then use the previously 'unshifted' symbol as the new + * 'shifted' (uppercase) symbol and the symbol returned by the function + * as the new 'unshifted' (lowercase) symbol.) */ +static xcb_keysym_t getUnshiftedXKey(xcb_keysym_t unshifted, xcb_keysym_t shifted) +{ + if (shifted != XKB_KEY_NoSymbol) // Has a shifted symbol + return 0; + + KeySym xlower; + KeySym xupper; + /* libxkbcommon >= 0.8.0 will have public API functions providing + * functionality equivalent to XConvertCase(), use these once the + * minimal libxkbcommon version is high enough. After that the + * xcb-xlib dependency can be removed */ + XConvertCase(static_cast<KeySym>(unshifted), &xlower, &xupper); + + if (xlower != xupper // Check if symbol is cased + && unshifted == static_cast<xcb_keysym_t>(xupper)) { // Unshifted must be upper case + return static_cast<xcb_keysym_t>(xlower); + } + return 0; +} + +static QByteArray symbolsGroupString(const xcb_keysym_t *symbols, int count) +{ + // Don't output trailing NoSymbols + while (count > 0 && symbols[count - 1] == XKB_KEY_NoSymbol) + count--; + + QByteArray groupString; + for (int symIndex = 0; symIndex < count; symIndex++) { + xcb_keysym_t sym = symbols[symIndex]; + char symString[64]; + if (sym == XKB_KEY_NoSymbol) + strcpy(symString, "NoSymbol"); + else + xkb_keysym_get_name(sym, symString, sizeof(symString)); + + if (!groupString.isEmpty()) + groupString += ", "; + groupString += symString; + } + return groupString; +} + +struct xkb_keymap *QXcbKeyboard::keymapFromCore() +{ + /* Construct an XKB keymap string from information queried from + * the X server */ + QByteArray keymap; + keymap += "xkb_keymap {\n"; + + const xcb_keycode_t minKeycode = connection()->setup()->min_keycode; + const xcb_keycode_t maxKeycode = connection()->setup()->max_keycode; + + // Generate symbolic names from keycodes + { + keymap += + "xkb_keycodes \"core\" {\n" + "\tminimum = " + QByteArray::number(minKeycode) + ";\n" + "\tmaximum = " + QByteArray::number(maxKeycode) + ";\n"; + for (int code = minKeycode; code <= maxKeycode; code++) { + auto codeStr = QByteArray::number(code); + keymap += "<K" + codeStr + "> = " + codeStr + ";\n"; + } + /* TODO: indicators? + */ + keymap += "};\n"; // xkb_keycodes + } + + /* Set up default types (xkbcommon automatically assigns these to + * symbols, but doesn't have shift info) */ + keymap += + "xkb_types \"core\" {\n" + "virtual_modifiers NumLock,Alt,LevelThree;\n" + "type \"ONE_LEVEL\" {\n" + "modifiers= none;\n" + "level_name[Level1] = \"Any\";\n" + "};\n" + "type \"TWO_LEVEL\" {\n" + "modifiers= Shift;\n" + "map[Shift]= Level2;\n" + "level_name[Level1] = \"Base\";\n" + "level_name[Level2] = \"Shift\";\n" + "};\n" + "type \"ALPHABETIC\" {\n" + "modifiers= Shift+Lock;\n" + "map[Shift]= Level2;\n" + "map[Lock]= Level2;\n" + "level_name[Level1] = \"Base\";\n" + "level_name[Level2] = \"Caps\";\n" + "};\n" + "type \"KEYPAD\" {\n" + "modifiers= Shift+NumLock;\n" + "map[Shift]= Level2;\n" + "map[NumLock]= Level2;\n" + "level_name[Level1] = \"Base\";\n" + "level_name[Level2] = \"Number\";\n" + "};\n" + "type \"FOUR_LEVEL\" {\n" + "modifiers= Shift+LevelThree;\n" + "map[Shift]= Level2;\n" + "map[LevelThree]= Level3;\n" + "map[Shift+LevelThree]= Level4;\n" + "level_name[Level1] = \"Base\";\n" + "level_name[Level2] = \"Shift\";\n" + "level_name[Level3] = \"Alt Base\";\n" + "level_name[Level4] = \"Shift Alt\";\n" + "};\n" + "type \"FOUR_LEVEL_ALPHABETIC\" {\n" + "modifiers= Shift+Lock+LevelThree;\n" + "map[Shift]= Level2;\n" + "map[Lock]= Level2;\n" + "map[LevelThree]= Level3;\n" + "map[Shift+LevelThree]= Level4;\n" + "map[Lock+LevelThree]= Level4;\n" + "map[Shift+Lock+LevelThree]= Level3;\n" + "level_name[Level1] = \"Base\";\n" + "level_name[Level2] = \"Shift\";\n" + "level_name[Level3] = \"Alt Base\";\n" + "level_name[Level4] = \"Shift Alt\";\n" + "};\n" + "type \"FOUR_LEVEL_SEMIALPHABETIC\" {\n" + "modifiers= Shift+Lock+LevelThree;\n" + "map[Shift]= Level2;\n" + "map[Lock]= Level2;\n" + "map[LevelThree]= Level3;\n" + "map[Shift+LevelThree]= Level4;\n" + "map[Lock+LevelThree]= Level3;\n" + "preserve[Lock+LevelThree]= Lock;\n" + "map[Shift+Lock+LevelThree]= Level4;\n" + "preserve[Shift+Lock+LevelThree]= Lock;\n" + "level_name[Level1] = \"Base\";\n" + "level_name[Level2] = \"Shift\";\n" + "level_name[Level3] = \"Alt Base\";\n" + "level_name[Level4] = \"Shift Alt\";\n" + "};\n" + "type \"FOUR_LEVEL_KEYPAD\" {\n" + "modifiers= Shift+NumLock+LevelThree;\n" + "map[Shift]= Level2;\n" + "map[NumLock]= Level2;\n" + "map[LevelThree]= Level3;\n" + "map[Shift+LevelThree]= Level4;\n" + "map[NumLock+LevelThree]= Level4;\n" + "map[Shift+NumLock+LevelThree]= Level3;\n" + "level_name[Level1] = \"Base\";\n" + "level_name[Level2] = \"Number\";\n" + "level_name[Level3] = \"Alt Base\";\n" + "level_name[Level4] = \"Alt Number\";\n" + "};\n" + "};\n"; // xkb_types + + // Generate mapping between symbolic names and keysyms + { + QVector<xcb_keysym_t> xkeymap; + int keysymsPerKeycode = 0; + { + int keycodeCount = maxKeycode - minKeycode + 1; + if (auto keymapReply = Q_XCB_REPLY(xcb_get_keyboard_mapping, xcb_connection(), + minKeycode, keycodeCount)) { + keysymsPerKeycode = keymapReply->keysyms_per_keycode; + int numSyms = keycodeCount * keysymsPerKeycode; + auto keymapPtr = xcb_get_keyboard_mapping_keysyms(keymapReply.get()); + xkeymap.resize(numSyms); + for (int i = 0; i < numSyms; i++) + xkeymap[i] = keymapPtr[i]; + } + } + if (xkeymap.isEmpty()) + return nullptr; + + KeysymModifierMap keysymMods(keysymsToModifiers()); + static const char *const builtinModifiers[] = + { "Shift", "Lock", "Control", "Mod1", "Mod2", "Mod3", "Mod4", "Mod5" }; + + /* Level 3 symbols (e.g. AltGr+something) seem to come in two flavors: + * - as a proper level 3 in group 1, at least on recent X.org versions + * - 'disguised' as group 2, on 'legacy' X servers + * In the 2nd case, remap group 2 to level 3, that seems to work better + * in practice */ + bool mapGroup2ToLevel3 = keysymsPerKeycode < 5; + + keymap += "xkb_symbols \"core\" {\n"; + for (int code = minKeycode; code <= maxKeycode; code++) { + auto codeMap = xkeymap.constData() + (code - minKeycode) * keysymsPerKeycode; + + const int maxGroup1 = 4; // We only support 4 shift states anyway + const int maxGroup2 = 2; // Only 3rd and 4th keysym are group 2 + xcb_keysym_t symbolsGroup1[maxGroup1]; + xcb_keysym_t symbolsGroup2[maxGroup2]; + for (int i = 0; i < maxGroup1 + maxGroup2; i++) { + xcb_keysym_t sym = i < keysymsPerKeycode ? codeMap[i] : XKB_KEY_NoSymbol; + if (mapGroup2ToLevel3) { + // Merge into single group + if (i < maxGroup1) + symbolsGroup1[i] = sym; + } else { + // Preserve groups + if (i < 2) + symbolsGroup1[i] = sym; + else if (i < 4) + symbolsGroup2[i - 2] = sym; + else + symbolsGroup1[i - 2] = sym; + } + } + + /* Fix symbols so the unshifted and shifted symbols have + * lower resp. upper case */ + if (auto lowered = getUnshiftedXKey(symbolsGroup1[0], symbolsGroup1[1])) { + symbolsGroup1[1] = symbolsGroup1[0]; + symbolsGroup1[0] = lowered; + } + if (auto lowered = getUnshiftedXKey(symbolsGroup2[0], symbolsGroup2[1])) { + symbolsGroup2[1] = symbolsGroup2[0]; + symbolsGroup2[0] = lowered; + } + + QByteArray groupStr1 = symbolsGroupString(symbolsGroup1, maxGroup1); + if (groupStr1.isEmpty()) + continue; + + keymap += "key <K" + QByteArray::number(code) + "> { "; + keymap += "symbols[Group1] = [ " + groupStr1 + " ]"; + QByteArray groupStr2 = symbolsGroupString(symbolsGroup2, maxGroup2); + if (!groupStr2.isEmpty()) + keymap += ", symbols[Group2] = [ " + groupStr2 + " ]"; + + // See if this key code is for a modifier + xcb_keysym_t modifierSym = XKB_KEY_NoSymbol; + for (int symIndex = 0; symIndex < keysymsPerKeycode; symIndex++) { + xcb_keysym_t sym = codeMap[symIndex]; + + if (sym == XKB_KEY_Alt_L + || sym == XKB_KEY_Meta_L + || sym == XKB_KEY_Mode_switch + || sym == XKB_KEY_Super_L + || sym == XKB_KEY_Super_R + || sym == XKB_KEY_Hyper_L + || sym == XKB_KEY_Hyper_R) { + modifierSym = sym; + break; + } + } + + // AltGr + if (modifierSym == XKB_KEY_Mode_switch) + keymap += ", virtualMods=LevelThree"; + keymap += " };\n"; // key + + // Generate modifier mappings + int modNum = keysymMods.value(modifierSym, -1); + if (modNum != -1) { + // Here modNum is always < 8 (see keysymsToModifiers()) + keymap += QByteArray("modifier_map ") + builtinModifiers[modNum] + + " { <K" + QByteArray::number(code) + "> };\n"; + } + } + // TODO: indicators? + keymap += "};\n"; // xkb_symbols + } + + // We need an "Alt" modifier, provide via the xkb_compatibility section + keymap += + "xkb_compatibility \"core\" {\n" + "virtual_modifiers NumLock,Alt,LevelThree;\n" + "interpret Alt_L+AnyOf(all) {\n" + "virtualModifier= Alt;\n" + "action= SetMods(modifiers=modMapMods,clearLocks);\n" + "};\n" + "interpret Alt_R+AnyOf(all) {\n" + "virtualModifier= Alt;\n" + "action= SetMods(modifiers=modMapMods,clearLocks);\n" + "};\n" + "};\n"; + + /* TODO: There is an issue with modifier state not being handled + * correctly if using Xming with XKEYBOARD disabled. */ + + keymap += "};\n"; // xkb_keymap + + return xkb_keymap_new_from_buffer(xkb_context, + keymap.constData(), + keymap.size(), + XKB_KEYMAP_FORMAT_TEXT_V1, + static_cast<xkb_keymap_compile_flags>(0)); +} +#endif + void QXcbKeyboard::updateKeymap() { m_config = true; + m_keymap_is_core = false; // set xkb context object if (!xkb_context) { if (qEnvironmentVariableIsSet("QT_XKB_CONFIG_ROOT")) { @@ -721,9 +1105,23 @@ void QXcbKeyboard::updateKeymap() } #endif if (!xkb_keymap) { - // Compile a keymap from RMLVO (rules, models, layouts, variants and options) names + // Read xkb RMLVO (rules, models, layouts, variants and options) names readXKBConfig(); - xkb_keymap = xkb_keymap_new_from_names(xkb_context, &xkb_names, (xkb_keymap_compile_flags)0); +#if QT_CONFIG(xcb_xlib) + bool rmlvo_is_incomplete = !xkb_names.rules || !(*xkb_names.rules) + || !xkb_names.model || !(*xkb_names.model) + || !xkb_names.layout || !(*xkb_names.layout); + if (rmlvo_is_incomplete) { + // Try to build xkb map from core mapping information + xkb_keymap = keymapFromCore(); + m_keymap_is_core = xkb_keymap != 0; + } +#endif + if (!xkb_keymap) { + // Compile a keymap from RMLVO + xkb_keymap = xkb_keymap_new_from_names(xkb_context, &xkb_names, + static_cast<xkb_keymap_compile_flags> (0)); + } if (!xkb_keymap) { // last fallback is to used hard-coded keymap name, see DEFAULT_XKB_* in xkbcommon.pri qWarning() << "Qt: Could not determine keyboard configuration data" @@ -807,7 +1205,7 @@ void QXcbKeyboard::updateXKBStateFromCore(quint16 state) } } -#ifdef XCB_USE_XINPUT22 +#if QT_CONFIG(xinput2) void QXcbKeyboard::updateXKBStateFromXI(void *modInfo, void *groupInfo) { if (m_config && !connection()->hasXKB()) { @@ -915,9 +1313,11 @@ xkb_keysym_t QXcbKeyboard::lookupLatinKeysym(xkb_keycode_t keycode) const // If user layouts don't contain any layout that results in a latin key, we query a // key from "US" layout, this allows for latin-key-based shorcuts to work even when // users have only one (non-latin) layout set. + // But don't do this if using keymap obtained through the core protocol, as the key + // codes may not match up with those expected by the XKB keymap. xkb_mod_mask_t latchedMods = xkb_state_serialize_mods(xkb_state, XKB_STATE_MODS_LATCHED); xkb_mod_mask_t lockedMods = xkb_state_serialize_mods(xkb_state, XKB_STATE_MODS_LOCKED); - if (sym == XKB_KEY_NoSymbol && !m_hasLatinLayout) { + if (sym == XKB_KEY_NoSymbol && !m_hasLatinLayout && !m_keymap_is_core) { if (!latin_keymap) { const struct xkb_rule_names names = { xkb_names.rules, xkb_names.model, "us", 0, 0 }; latin_keymap = xkb_keymap_new_from_names(xkb_context, &names, (xkb_keymap_compile_flags)0); @@ -1124,7 +1524,7 @@ int QXcbKeyboard::keysymToQtKey(xcb_keysym_t keysym, Qt::KeyboardModifiers &modi modifiers |= Qt::KeypadModifier; } else if (text.length() == 1 && text.unicode()->unicode() > 0x1f && text.unicode()->unicode() != 0x7f - && !(keysym >= XK_dead_grave && keysym <= XK_dead_currency)) { + && !(keysym >= XK_dead_grave && keysym <= XK_dead_longsolidusoverlay)) { code = text.unicode()->toUpper().unicode(); } else { // any other keys @@ -1172,23 +1572,19 @@ QXcbKeyboard::~QXcbKeyboard() void QXcbKeyboard::updateVModMapping() { #if QT_CONFIG(xkb) - xcb_xkb_get_names_cookie_t names_cookie; - xcb_xkb_get_names_reply_t *name_reply; xcb_xkb_get_names_value_list_t names_list; memset(&vmod_masks, 0, sizeof(vmod_masks)); - names_cookie = xcb_xkb_get_names(xcb_connection(), - XCB_XKB_ID_USE_CORE_KBD, - XCB_XKB_NAME_DETAIL_VIRTUAL_MOD_NAMES); - - name_reply = xcb_xkb_get_names_reply(xcb_connection(), names_cookie, 0); + auto name_reply = Q_XCB_REPLY(xcb_xkb_get_names, xcb_connection(), + XCB_XKB_ID_USE_CORE_KBD, + XCB_XKB_NAME_DETAIL_VIRTUAL_MOD_NAMES); if (!name_reply) { qWarning("Qt: failed to retrieve the virtual modifier names from XKB"); return; } - const void *buffer = xcb_xkb_get_names_value_list(name_reply); + const void *buffer = xcb_xkb_get_names_value_list(name_reply.get()); xcb_xkb_get_names_value_list_unpack(buffer, name_reply->nTypes, name_reply->indicators, @@ -1233,32 +1629,27 @@ void QXcbKeyboard::updateVModMapping() else if (qstrcmp(vmod_name, "Hyper") == 0) vmod_masks.hyper = bit; } - - free(name_reply); #endif } void QXcbKeyboard::updateVModToRModMapping() { #if QT_CONFIG(xkb) - xcb_xkb_get_map_cookie_t map_cookie; - xcb_xkb_get_map_reply_t *map_reply; xcb_xkb_get_map_map_t map; memset(&rmod_masks, 0, sizeof(rmod_masks)); - map_cookie = xcb_xkb_get_map(xcb_connection(), - XCB_XKB_ID_USE_CORE_KBD, - XCB_XKB_MAP_PART_VIRTUAL_MODS, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - - map_reply = xcb_xkb_get_map_reply(xcb_connection(), map_cookie, 0); + auto map_reply = Q_XCB_REPLY(xcb_xkb_get_map, + xcb_connection(), + XCB_XKB_ID_USE_CORE_KBD, + XCB_XKB_MAP_PART_VIRTUAL_MODS, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); if (!map_reply) { qWarning("Qt: failed to retrieve the virtual modifier map from XKB"); return; } - const void *buffer = xcb_xkb_get_map_map(map_reply); + const void *buffer = xcb_xkb_get_map_map(map_reply.get()); xcb_xkb_get_map_map_unpack(buffer, map_reply->nTypes, map_reply->nKeySyms, @@ -1301,29 +1692,60 @@ void QXcbKeyboard::updateVModToRModMapping() rmod_masks.hyper = modmap; } - free(map_reply); resolveMaskConflicts(); #endif } +// Small helper: set modifier bit, if modifier position is valid +static inline void applyModifier(uint *mask, int modifierBit) +{ + if (modifierBit >= 0 && modifierBit < 8) + *mask |= 1 << modifierBit; +} + void QXcbKeyboard::updateModifiers() { + memset(&rmod_masks, 0, sizeof(rmod_masks)); + + // Compute X modifier bits for Qt modifiers + KeysymModifierMap keysymMods(keysymsToModifiers()); + applyModifier(&rmod_masks.alt, keysymMods.value(XK_Alt_L, -1)); + applyModifier(&rmod_masks.alt, keysymMods.value(XK_Alt_R, -1)); + applyModifier(&rmod_masks.meta, keysymMods.value(XK_Meta_L, -1)); + applyModifier(&rmod_masks.meta, keysymMods.value(XK_Meta_R, -1)); + applyModifier(&rmod_masks.altgr, keysymMods.value(XK_Mode_switch, -1)); + applyModifier(&rmod_masks.super, keysymMods.value(XK_Super_L, -1)); + applyModifier(&rmod_masks.super, keysymMods.value(XK_Super_R, -1)); + applyModifier(&rmod_masks.hyper, keysymMods.value(XK_Hyper_L, -1)); + applyModifier(&rmod_masks.hyper, keysymMods.value(XK_Hyper_R, -1)); + + resolveMaskConflicts(); +} + +// Small helper: check if an array of xcb_keycode_t contains a certain code +static inline bool keycodes_contains(xcb_keycode_t *codes, xcb_keycode_t which) +{ + while (*codes != XCB_NO_SYMBOL) { + if (*codes == which) return true; + codes++; + } + return false; +} + +QXcbKeyboard::KeysymModifierMap QXcbKeyboard::keysymsToModifiers() +{ // 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) { + KeysymModifierMap map; + + auto modMapReply = Q_XCB_REPLY(xcb_get_modifier_mapping, xcb_connection()); + if (!modMapReply) { qWarning("Qt: failed to get modifier mapping"); - free(error); - return; + return map; } // for Alt and Meta L and R are the same @@ -1338,36 +1760,63 @@ void QXcbKeyboard::updateModifiers() 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; - if ((sym == XK_Super_L) || (sym == XK_Super_R)) - rmod_masks.super = mask; - if ((sym == XK_Hyper_L) || (sym == XK_Hyper_R)) - rmod_masks.hyper = mask; - } + xcb_keycode_t *modMap = xcb_get_modifier_mapping_keycodes(modMapReply.get()); + const int modMapLength = xcb_get_modifier_mapping_keycodes_length(modMapReply.get()); + /* For each modifier of "Shift, Lock, Control, Mod1, Mod2, Mod3, + * Mod4, and Mod5" the modifier map contains keycodes_per_modifier + * key codes that are associated with a modifier. + * + * As an example, take this 'xmodmap' output: + * xmodmap: up to 4 keys per modifier, (keycodes in parentheses): + * + * shift Shift_L (0x32), Shift_R (0x3e) + * lock Caps_Lock (0x42) + * control Control_L (0x25), Control_R (0x69) + * mod1 Alt_L (0x40), Alt_R (0x6c), Meta_L (0xcd) + * mod2 Num_Lock (0x4d) + * mod3 + * mod4 Super_L (0x85), Super_R (0x86), Super_L (0xce), Hyper_L (0xcf) + * mod5 ISO_Level3_Shift (0x5c), Mode_switch (0xcb) + * + * The corresponding raw modifier map would contain keycodes for: + * Shift_L (0x32), Shift_R (0x3e), 0, 0, + * Caps_Lock (0x42), 0, 0, 0, + * Control_L (0x25), Control_R (0x69), 0, 0, + * Alt_L (0x40), Alt_R (0x6c), Meta_L (0xcd), 0, + * Num_Lock (0x4d), 0, 0, 0, + * 0,0,0,0, + * Super_L (0x85), Super_R (0x86), Super_L (0xce), Hyper_L (0xcf), + * ISO_Level3_Shift (0x5c), Mode_switch (0xcb), 0, 0 + */ + + /* Create a map between a modifier keysym (as per the symbols array) + * and the modifier bit it's associated with (if any). + * As modMap contains key codes, search modKeyCodes for a match; + * if one is found we can look up the associated keysym. + * Together with the modifier index this will be used + * to compute a mapping between X modifier bits and Qt's + * modifiers (Alt, Ctrl etc). */ + for (int i = 0; i < modMapLength; i++) { + if (modMap[i] == XCB_NO_SYMBOL) + continue; + // Get key symbol for key code + for (size_t k = 0; k < numSymbols; k++) { + if (modKeyCodes[k] && keycodes_contains(modKeyCodes[k], modMap[i])) { + // Key code is for modifier. Record mapping + xcb_keysym_t sym = symbols[k]; + /* As per modMap layout explanation above, dividing + * by keycodes_per_modifier gives the 'row' in the + * modifier map, which in turn is the modifier bit. */ + map[sym] = i / modMapReply->keycodes_per_modifier; + break; } } } for (size_t i = 0; i < numSymbols; ++i) free(modKeyCodes[i]); - free(modMapReply); - resolveMaskConflicts(); + + return map; } void QXcbKeyboard::resolveMaskConflicts() @@ -1449,8 +1898,6 @@ private: void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type, xcb_keycode_t code, quint16 state, xcb_timestamp_t time) { - Q_XCB_NOOP(connection()); - if (!m_config) return; @@ -1471,30 +1918,6 @@ void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type, updateXKBStateFromState(kb_state, state); xcb_keysym_t sym = xkb_state_key_get_one_sym(kb_state, code); - - QPlatformInputContext *inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext(); - QMetaMethod method; - - if (inputContext) { - int methodIndex = inputContext->metaObject()->indexOfMethod("x11FilterEvent(uint,uint,uint,bool)"); - if (methodIndex != -1) - method = inputContext->metaObject()->method(methodIndex); - } - - if (method.isValid()) { - bool retval = false; - method.invoke(inputContext, Qt::DirectConnection, - Q_RETURN_ARG(bool, retval), - Q_ARG(uint, sym), - Q_ARG(uint, code), - Q_ARG(uint, state), - Q_ARG(bool, type == QEvent::KeyPress)); - if (retval) { - xkb_state_unref(kb_state); - return; - } - } - QString string = lookupString(kb_state, code); // Ιf control modifier is set we should prefer latin character, this is @@ -1526,6 +1949,7 @@ void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type, } bool filtered = false; + QPlatformInputContext *inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext(); if (inputContext) { QKeyEvent event(type, qtcode, modifiers, code, sym, state, string, isAutoRepeat, string.length()); event.setTimestamp(time); @@ -1548,16 +1972,7 @@ void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type, if (isAutoRepeat && type == QEvent::KeyRelease) { // since we removed it from the event queue using checkEvent we need to send the key press here filtered = false; - if (method.isValid()) { - method.invoke(inputContext, Qt::DirectConnection, - Q_RETURN_ARG(bool, filtered), - Q_ARG(uint, sym), - Q_ARG(uint, code), - Q_ARG(uint, state), - Q_ARG(bool, true)); - } - - if (!filtered && inputContext) { + if (inputContext) { QKeyEvent event(QEvent::KeyPress, qtcode, modifiers, code, sym, state, string, isAutoRepeat, string.length()); event.setTimestamp(time); filtered = inputContext->filterEvent(&event); |