summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGatis Paeglis <gatis.paeglis@theqtcompany.com>2014-10-23 14:45:18 +0200
committerGatis Paeglis <gatis.paeglis@digia.com>2014-10-27 15:19:02 +0100
commit2b666d9576210aa98700e219dba6b1bd4f93d793 (patch)
tree07ed6da88b28d9e68818d422595a1650ab161b94
parentcc1435952d796476616f54319c1ddf57eeaf80f7 (diff)
Fix shortcut handling with non-latin layouts
Shortcut handling with non-latin keyboard layouts were broken for checks like: QKeyEvent key = ...; key == QKeySequence::SelectAll In Qt4 this was handled transparently when getting keysym from XLookupString. When ctrl modifier was set, XLookupString performed "keysym transformation" to obtain a latin keysym. Still this did not solve the non-latin shortcut issues for all cases due to some of Xlib assumptions which could not be controled by user. This patch implements XLookupString-like behavior in lookupLatinKeysym(). It is not a 1-to-1 copy of how XLookupString perfoms latin keysym lookup, but it serves our needs just fine. lookupLatinKeysym() also handles the cases that did not work in Qt4 with XLookupString, thanks to libxkbcommon keymap query API. And lookupLatinKeysym() replaces the fragile implementation of "fallback latin key" code path in possibleKeys(). Done-with: Ruslan Nigmatullin Task-number: QTBUG-33182 Task-number: QTBUG-32274 Change-Id: I56a5b624487ca6c2c3768220301a37dac39b439a Reviewed-by: Ruslan Nigmatullin <euroelessar@yandex.ru> Reviewed-by: Shawn Rutledge <shawn.rutledge@digia.com>
-rw-r--r--src/plugins/platforms/xcb/qxcbkeyboard.cpp190
-rw-r--r--src/plugins/platforms/xcb/qxcbkeyboard.h5
2 files changed, 147 insertions, 48 deletions
diff --git a/src/plugins/platforms/xcb/qxcbkeyboard.cpp b/src/plugins/platforms/xcb/qxcbkeyboard.cpp
index dae3a79628..260fb46309 100644
--- a/src/plugins/platforms/xcb/qxcbkeyboard.cpp
+++ b/src/plugins/platforms/xcb/qxcbkeyboard.cpp
@@ -721,7 +721,7 @@ void QXcbKeyboard::updateKeymap()
if (xkb_keymap) {
new_state = xkb_state_new(xkb_keymap);
} else {
- printKeymapError("Failed to compile a keymap!");
+ printKeymapError("Qt: Failed to compile a keymap!");
m_config = false;
return;
}
@@ -737,6 +737,8 @@ void QXcbKeyboard::updateKeymap()
xkb_state = new_state;
if (!connection()->hasXKB())
updateXKBMods();
+
+ checkForLatinLayout();
}
#ifndef QT_NO_XKB
@@ -824,37 +826,137 @@ void QXcbKeyboard::updateXKBMods()
xkb_mods.mod5 = xkb_keymap_mod_get_index(xkb_keymap, "Mod5");
}
+static bool isLatin(xkb_keysym_t sym)
+{
+ return ((sym >= 'a' && sym <= 'z') || (sym >= 'A' && sym <= 'Z'));
+}
+
+void QXcbKeyboard::checkForLatinLayout()
+{
+ m_hasLatinLayout = false;
+ const xkb_layout_index_t layoutCount = xkb_keymap_num_layouts(xkb_keymap);
+ const xcb_keycode_t minKeycode = connection()->setup()->min_keycode;
+ const xcb_keycode_t maxKeycode = connection()->setup()->max_keycode;
+ struct xkb_state *kb_state = xkb_state_new(xkb_keymap);
+ for (xkb_layout_index_t layout = 0; layout < layoutCount; ++layout) {
+ xkb_state_update_mask(kb_state, 0, 0, 0, 0, 0, layout);
+ for (xcb_keycode_t code = minKeycode; code < maxKeycode; ++code) {
+ xkb_keysym_t sym = xkb_state_key_get_one_sym(kb_state, code);
+ // if layout can produce any of these latin letters (chosen
+ // arbitrarily) then it must be a latin key based layout
+ if (sym == XK_q || sym == XK_a || sym == XK_e) {
+ m_hasLatinLayout = true;
+ xkb_state_unref(kb_state);
+ return;
+ }
+ }
+ }
+ xkb_state_unref(kb_state);
+}
+
+xkb_keysym_t QXcbKeyboard::lookupLatinKeysym(xkb_keycode_t keycode) const
+{
+ xkb_layout_index_t layout;
+ xkb_keysym_t sym = XKB_KEY_NoSymbol;
+ const xkb_layout_index_t layoutCount = xkb_keymap_num_layouts_for_key(xkb_keymap, keycode);
+ const xkb_layout_index_t currentLayout = xkb_state_key_get_layout(xkb_state, keycode);
+ // Look at user layouts in the order in which they are defined in system
+ // settings to find a latin keysym.
+ for (layout = 0; layout < layoutCount; ++layout) {
+ if (layout == currentLayout)
+ continue;
+ const xkb_keysym_t *syms;
+ xkb_level_index_t level = xkb_state_key_get_level(xkb_state, keycode, layout);
+ if (xkb_keymap_key_get_syms_by_level(xkb_keymap, keycode, layout, level, &syms) != 1)
+ continue;
+ if (isLatin(syms[0])) {
+ sym = syms[0];
+ break;
+ }
+ }
+ // 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.
+ 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 (!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);
+ static bool printFailure = true;
+ if (!latin_keymap && printFailure) {
+ // print message about failure to compile US keymap only once,
+ // no need to do this on every key press.
+ printFailure = false;
+ printKeymapError("Qt: Failed to compile US keymap, shortcut handling with "
+ "non-Latin keyboard layouts may not be fully functional!");
+ }
+ }
+ if (latin_keymap) {
+ struct xkb_state *latin_state = xkb_state_new(latin_keymap);
+ if (latin_state) {
+ xkb_state_update_mask(latin_state, 0, latchedMods, lockedMods, 0, 0, 0);
+ sym = xkb_state_key_get_one_sym(latin_state, keycode);
+ xkb_state_unref(latin_state);
+ } else {
+ qWarning("QXcbKeyboard: failed to create a state for US keymap!");
+ }
+ }
+ }
+ if (sym == XKB_KEY_NoSymbol)
+ return sym;
+ // Check for uniqueness, consider the following setup:
+ // setxkbmap -layout us,ru,us -variant dvorak,, -option 'grp:ctrl_alt_toggle' (set 'ru' as active).
+ // In this setup, the user would expect to trigger a ctrl+q shortcut by pressing ctrl+<physical x key>,
+ // because "US dvorak" is higher up in the layout settings list. This check verifies that an obtained
+ // 'sym' can not be acquired by any other layout higher up in the user's layout list. If it can be acquired
+ // then the obtained key is not unique. This prevents ctrl+<physical q key> from generating a ctrl+q
+ // shortcut in the above described setup. We don't want ctrl+<physical x key> and ctrl+<physical q key> to
+ // generate the same shortcut event in this case.
+ const xcb_keycode_t minKeycode = connection()->setup()->min_keycode;
+ const xcb_keycode_t maxKeycode = connection()->setup()->max_keycode;
+ struct xkb_state *kb_state = xkb_state_new(xkb_keymap);
+ for (xkb_layout_index_t prevLayout = 0; prevLayout < layout; ++prevLayout) {
+ xkb_state_update_mask(kb_state, 0, latchedMods, lockedMods, 0, 0, prevLayout);
+ for (xcb_keycode_t code = minKeycode; code < maxKeycode; ++code) {
+ xkb_keysym_t prevSym = xkb_state_key_get_one_sym(kb_state, code);
+ if (prevSym == sym) {
+ sym = XKB_KEY_NoSymbol;
+ break;
+ }
+ }
+ }
+ xkb_state_unref(kb_state);
+ return sym;
+}
+
QList<int> QXcbKeyboard::possibleKeys(const QKeyEvent *event) const
{
// turn off the modifier bits which doesn't participate in shortcuts
Qt::KeyboardModifiers notNeeded = Qt::MetaModifier | Qt::KeypadModifier | Qt::GroupSwitchModifier;
Qt::KeyboardModifiers modifiers = event->modifiers() &= ~notNeeded;
// create a fresh kb state and test against the relevant modifier combinations
- // NOTE: it should be possible to query the keymap directly, once it gets
- // supported by libxkbcommon
- struct xkb_state * kb_state = xkb_state_new(xkb_keymap);
+ struct xkb_state *kb_state = xkb_state_new(xkb_keymap);
if (!kb_state) {
- qWarning("QXcbKeyboard: failed to compile xkb keymap");
+ qWarning("QXcbKeyboard: failed to compile xkb keymap!");
return QList<int>();
}
// get kb state from the master xkb_state and update the temporary kb_state
- xkb_layout_index_t baseLayout = xkb_state_serialize_layout(xkb_state, XKB_STATE_LAYOUT_DEPRESSED);
- xkb_layout_index_t latchedLayout = xkb_state_serialize_layout(xkb_state, XKB_STATE_LAYOUT_LATCHED);
xkb_layout_index_t lockedLayout = xkb_state_serialize_layout(xkb_state, XKB_STATE_LAYOUT_LOCKED);
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);
- xkb_state_update_mask(kb_state, 0, latchedMods, lockedMods,
- baseLayout, latchedLayout, lockedLayout);
+ xkb_state_update_mask(kb_state, 0, latchedMods, lockedMods, 0, 0, lockedLayout);
- xkb_keysym_t sym = xkb_state_key_get_one_sym(kb_state, event->nativeScanCode());
+ quint32 keycode = event->nativeScanCode();
+ xkb_keysym_t sym = xkb_state_key_get_one_sym(kb_state, keycode);
if (sym == XKB_KEY_NoSymbol) {
xkb_state_unref(kb_state);
return QList<int>();
}
QList<int> result;
- int baseQtKey = keysymToQtKey(sym, modifiers, lookupString(kb_state, event->nativeScanCode()));
+ int baseQtKey = keysymToQtKey(sym, modifiers, lookupString(kb_state, keycode));
result += (baseQtKey + modifiers); // The base key is _always_ valid, of course
xkb_mod_index_t shiftMod = xkb_keymap_mod_get_index(xkb_keymap, "Shift");
@@ -866,48 +968,33 @@ QList<int> QXcbKeyboard::possibleKeys(const QKeyEvent *event) const
Q_ASSERT(controlMod < 32);
xkb_mod_mask_t depressed;
- struct xkb_keymap *fallback_keymap = 0;
int qtKey = 0;
- //obtain a list of possible shortcuts for the given key event
+ // obtain a list of possible shortcuts for the given key event
for (uint i = 1; i < sizeof(ModsTbl) / sizeof(*ModsTbl) ; ++i) {
Qt::KeyboardModifiers neededMods = ModsTbl[i];
if ((modifiers & neededMods) == neededMods) {
-
- depressed = 0;
- if (neededMods & Qt::AltModifier)
- depressed |= (1 << altMod);
- if (neededMods & Qt::ShiftModifier)
- depressed |= (1 << shiftMod);
- if (neededMods & Qt::ControlModifier)
- depressed |= (1 << controlMod);
-
- // update a keyboard state from a set of explicit masks
if (i == 8) {
- // Add a fall back key for layouts with non Latin-1 characters
- if (baseQtKey > 255) {
- struct xkb_rule_names names = { xkb_names.rules, xkb_names.model, "us", 0, 0 };
- fallback_keymap = xkb_keymap_new_from_names(xkb_context, &names, (xkb_keymap_compile_flags)0);
- if (!fallback_keymap)
- continue;
- xkb_state_unref(kb_state);
- kb_state = xkb_state_new(fallback_keymap);
- if (!kb_state)
- continue;
- } else
+ if (isLatin(baseQtKey))
continue;
+ // add a latin key as a fall back key
+ sym = lookupLatinKeysym(keycode);
} else {
- xkb_state_update_mask(kb_state, depressed, latchedMods, lockedMods,
- baseLayout, latchedLayout, lockedLayout);
+ depressed = 0;
+ if (neededMods & Qt::AltModifier)
+ depressed |= (1 << altMod);
+ if (neededMods & Qt::ShiftModifier)
+ depressed |= (1 << shiftMod);
+ if (neededMods & Qt::ControlModifier)
+ depressed |= (1 << controlMod);
+ xkb_state_update_mask(kb_state, depressed, latchedMods, lockedMods, 0, 0, lockedLayout);
+ sym = xkb_state_key_get_one_sym(kb_state, keycode);
}
- sym = xkb_state_key_get_one_sym(kb_state, event->nativeScanCode());
-
if (sym == XKB_KEY_NoSymbol)
continue;
Qt::KeyboardModifiers mods = modifiers & ~neededMods;
- qtKey = keysymToQtKey(sym, mods, lookupString(kb_state, event->nativeScanCode()));
-
- if (qtKey == baseQtKey || qtKey == 0)
+ qtKey = keysymToQtKey(sym, mods, lookupString(kb_state, keycode));
+ if (!qtKey || qtKey == baseQtKey)
continue;
// catch only more specific shortcuts, i.e. Ctrl+Shift+= also generates Ctrl++ and +,
@@ -926,8 +1013,6 @@ QList<int> QXcbKeyboard::possibleKeys(const QKeyEvent *event) const
}
}
xkb_state_unref(kb_state);
- xkb_keymap_unref(fallback_keymap);
-
return result;
}
@@ -1002,6 +1087,8 @@ QXcbKeyboard::QXcbKeyboard(QXcbConnection *connection)
, xkb_context(0)
, xkb_keymap(0)
, xkb_state(0)
+ , latin_keymap(0)
+ , m_hasLatinLayout(false)
{
memset(&xkb_names, 0, sizeof(xkb_names));
#ifndef QT_NO_XKB
@@ -1029,6 +1116,7 @@ QXcbKeyboard::~QXcbKeyboard()
xkb_state_unref(xkb_state);
xkb_keymap_unref(xkb_keymap);
xkb_context_unref(xkb_context);
+ xkb_keymap_unref(latin_keymap);
if (!connection()->hasXKB())
xcb_key_symbols_free(m_key_symbols);
clearXKBConfig();
@@ -1324,7 +1412,6 @@ void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type,
if (type == QEvent::KeyPress)
targetWindow->updateNetWmUserTime(time);
- // 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);
QPlatformInputContext *inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext();
@@ -1348,15 +1435,22 @@ void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type,
return;
}
- Qt::KeyboardModifiers modifiers = translateModifiers(state);
-
QString string = lookupString(xkb_state, code);
int count = string.size();
string.truncate(count);
- int qtcode = keysymToQtKey(sym, modifiers, string);
- bool isAutoRepeat = false;
+ // Ιf control modifier is set we should prefer latin character, this is
+ // used for standard shortcuts in checks like "key == QKeySequence::Copy",
+ // users can still see the actual X11 keysym with QKeyEvent::nativeVirtualKey
+ Qt::KeyboardModifiers modifiers = translateModifiers(state);
+ xcb_keysym_t translatedSym = XKB_KEY_NoSymbol;
+ if (modifiers & Qt::ControlModifier && !isLatin(sym))
+ translatedSym = lookupLatinKeysym(code);
+ if (translatedSym == XKB_KEY_NoSymbol)
+ translatedSym = sym;
+ int qtcode = keysymToQtKey(translatedSym, modifiers, string);
+ bool isAutoRepeat = false;
if (type == QEvent::KeyPress) {
if (m_autorepeat_code == code) {
isAutoRepeat = true;
diff --git a/src/plugins/platforms/xcb/qxcbkeyboard.h b/src/plugins/platforms/xcb/qxcbkeyboard.h
index e71165d824..9f1cf165cb 100644
--- a/src/plugins/platforms/xcb/qxcbkeyboard.h
+++ b/src/plugins/platforms/xcb/qxcbkeyboard.h
@@ -91,6 +91,9 @@ protected:
void updateVModMapping();
void updateVModToRModMapping();
+ xkb_keysym_t lookupLatinKeysym(xkb_keycode_t keycode) const;
+ void checkForLatinLayout();
+
private:
bool m_config;
xcb_keycode_t m_autorepeat_code;
@@ -99,6 +102,7 @@ private:
struct xkb_keymap *xkb_keymap;
struct xkb_state *xkb_state;
struct xkb_rule_names xkb_names;
+ mutable struct xkb_keymap *latin_keymap;
struct _mod_masks {
uint alt;
@@ -128,6 +132,7 @@ private:
_mod_masks vmod_masks;
int core_device_id;
#endif
+ bool m_hasLatinLayout;
};
QT_END_NAMESPACE