xkbcommon: make shortcuts persistent across layouts

It can happen the same key has different Latin symbols on different
layouts: for example, "`" symbol on English layout, no symbol ("^" dead
key) on German and ";" symbol on Czech and Hebrew layouts. This creates
a problem as these keys will produce different shortcuts depending on
what layout is currently active.

This patch makes keys generate the same shortcuts corresponding to the
first Latin layout configured in the system, independently of layout
actually active.

For example, when having settings like:

  setxkbmap -model pc105 -layout "us,de" -option "grp:alt_shift_toggle"

After Alt+Shift, the layout changes to de from us. But Ctrl+"^" still
generates Ctrl+` shortcut which is from the first(or default) layout.

[ChangeLog][QtGui][QXkbCommon] make keys produce the same shortcuts
independently of current layout

Fixes: QTBUG-108761
Change-Id: Id204a1609ca731f9c56ed3d32847ca18b94be4a0
Reviewed-by: Liang Qi <liang.qi@qt.io>
Reviewed-by: Andrey Butirsky <butirsky@gmail.com>
This commit is contained in:
Andrey Butirsky 2022-12-12 22:50:10 +04:00 committed by Liang Qi
parent d53607225a
commit 5e76a9569e

View File

@ -488,11 +488,9 @@ int QXkbCommon::keysymToQtKey(xkb_keysym_t keysym, Qt::KeyboardModifiers modifie
// With standard shortcuts we should prefer a latin character, this is
// for checks like "some qkeyevent == QKeySequence::Copy" to work even
// when using for example 'russian' keyboard layout.
if (!QXkbCommon::isLatin1(keysym)) {
xkb_keysym_t latinKeysym = QXkbCommon::lookupLatinKeysym(state, code);
if (latinKeysym != XKB_KEY_NoSymbol)
keysym = latinKeysym;
}
xkb_keysym_t latinKeysym = QXkbCommon::lookupLatinKeysym(state, code);
if (latinKeysym != XKB_KEY_NoSymbol)
keysym = latinKeysym;
}
return keysymToQtKey_internal(keysym, modifiers, state, code, superAsMeta, hyperAsMeta);
@ -732,12 +730,9 @@ xkb_keysym_t QXkbCommon::lookupLatinKeysym(xkb_state *state, xkb_keycode_t keyco
xkb_keysym_t sym = XKB_KEY_NoSymbol;
xkb_keymap *keymap = xkb_state_get_keymap(state);
const xkb_layout_index_t layoutCount = xkb_keymap_num_layouts_for_key(keymap, keycode);
const xkb_layout_index_t currentLayout = xkb_state_key_get_layout(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 = nullptr;
xkb_level_index_t level = xkb_state_key_get_level(state, keycode, layout);
if (xkb_keymap_key_get_syms_by_level(keymap, keycode, layout, level, &syms) != 1)
@ -748,34 +743,6 @@ xkb_keysym_t QXkbCommon::lookupLatinKeysym(xkb_state *state, xkb_keycode_t keyco
}
}
if (sym == XKB_KEY_NoSymbol)
return sym;
xkb_mod_mask_t latchedMods = xkb_state_serialize_mods(state, XKB_STATE_MODS_LATCHED);
xkb_mod_mask_t lockedMods = xkb_state_serialize_mods(state, XKB_STATE_MODS_LOCKED);
// 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 xkb_keycode_t minKeycode = xkb_keymap_min_keycode(keymap);
const xkb_keycode_t maxKeycode = xkb_keymap_max_keycode(keymap);
ScopedXKBState queryState(xkb_state_new(keymap));
for (xkb_layout_index_t prevLayout = 0; prevLayout < layout; ++prevLayout) {
xkb_state_update_mask(queryState.get(), 0, latchedMods, lockedMods, 0, 0, prevLayout);
for (xkb_keycode_t code = minKeycode; code < maxKeycode; ++code) {
xkb_keysym_t prevSym = xkb_state_key_get_one_sym(queryState.get(), code);
if (prevSym == sym) {
sym = XKB_KEY_NoSymbol;
break;
}
}
}
return sym;
}