blob: d0e02ecdd1ba1f129041867ec4abae4abfc983f0 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qxcbkeyboard.h"
#include "qxcbwindow.h"
#include "qxcbscreen.h"
#include <qpa/qwindowsysteminterface.h>
#include <qpa/qplatforminputcontext.h>
#include <qpa/qplatformintegration.h>
#include <qpa/qplatformcursor.h>
#include <QtCore/QMetaEnum>
#include <private/qguiapplication_p.h>
#if QT_CONFIG(xkb)
#include <xkbcommon/xkbcommon-x11.h>
#endif
#if QT_CONFIG(xcb_xinput)
#include <xcb/xinput.h>
#endif
QT_BEGIN_NAMESPACE
Qt::KeyboardModifiers QXcbKeyboard::translateModifiers(int s) const
{
Qt::KeyboardModifiers ret = Qt::NoModifier;
if (s & XCB_MOD_MASK_SHIFT)
ret |= Qt::ShiftModifier;
if (s & XCB_MOD_MASK_CONTROL)
ret |= Qt::ControlModifier;
if (s & rmod_masks.alt)
ret |= Qt::AltModifier;
if (s & rmod_masks.meta)
ret |= Qt::MetaModifier;
if (s & rmod_masks.altgr)
ret |= Qt::GroupSwitchModifier;
return ret;
}
/* 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;
xcb_keysym_t xlower;
xcb_keysym_t xupper;
QXkbCommon::xkbcommon_XConvertCase(unshifted, &xlower, &xupper);
if (xlower != xupper // Check if symbol is cased
&& unshifted == xupper) { // Unshifted must be upper case
return 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(const KeysymModifierMap &keysymMods)
{
/* 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;
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(m_xkbContext.get(),
keymap.constData(),
keymap.size(),
XKB_KEYMAP_FORMAT_TEXT_V1,
XKB_KEYMAP_COMPILE_NO_FLAGS);
}
void QXcbKeyboard::updateKeymap(xcb_mapping_notify_event_t *event)
{
if (connection()->hasXKB() || event->request == XCB_MAPPING_POINTER)
return;
xcb_refresh_keyboard_mapping(m_key_symbols, event);
updateKeymap();
}
void QXcbKeyboard::updateKeymap()
{
KeysymModifierMap keysymMods;
if (!connection()->hasXKB())
keysymMods = keysymsToModifiers();
updateModifiers(keysymMods);
m_config = true;
if (!m_xkbContext) {
m_xkbContext.reset(xkb_context_new(XKB_CONTEXT_NO_DEFAULT_INCLUDES));
if (!m_xkbContext) {
qCWarning(lcQpaKeyboard, "failed to create XKB context");
m_config = false;
return;
}
xkb_log_level logLevel = lcQpaKeyboard().isDebugEnabled() ?
XKB_LOG_LEVEL_DEBUG : XKB_LOG_LEVEL_CRITICAL;
xkb_context_set_log_level(m_xkbContext.get(), logLevel);
}
#if QT_CONFIG(xkb)
if (connection()->hasXKB()) {
m_xkbKeymap.reset(xkb_x11_keymap_new_from_device(m_xkbContext.get(), xcb_connection(),
core_device_id, XKB_KEYMAP_COMPILE_NO_FLAGS));
if (m_xkbKeymap)
m_xkbState.reset(xkb_x11_state_new_from_device(m_xkbKeymap.get(), xcb_connection(), core_device_id));
} else {
#endif
m_xkbKeymap.reset(keymapFromCore(keysymMods));
if (m_xkbKeymap)
m_xkbState.reset(xkb_state_new(m_xkbKeymap.get()));
#if QT_CONFIG(xkb)
}
#endif
if (!m_xkbKeymap) {
qCWarning(lcQpaKeyboard, "failed to compile a keymap");
m_config = false;
return;
}
if (!m_xkbState) {
qCWarning(lcQpaKeyboard, "failed to create XKB state");
m_config = false;
return;
}
updateXKBMods();
QXkbCommon::verifyHasLatinLayout(m_xkbKeymap.get());
}
QList<int> QXcbKeyboard::possibleKeys(const QKeyEvent *event) const
{
return QXkbCommon::possibleKeys(m_xkbState.get(), event, m_superAsMeta, m_hyperAsMeta);
}
#if QT_CONFIG(xkb)
void QXcbKeyboard::updateXKBState(xcb_xkb_state_notify_event_t *state)
{
if (m_config && connection()->hasXKB()) {
const xkb_state_component changedComponents
= xkb_state_update_mask(m_xkbState.get(),
state->baseMods,
state->latchedMods,
state->lockedMods,
state->baseGroup,
state->latchedGroup,
state->lockedGroup);
handleStateChanges(changedComponents);
}
}
#endif
static xkb_layout_index_t lockedGroup(quint16 state)
{
return (state >> 13) & 3; // bits 13 and 14 report the state keyboard group
}
void QXcbKeyboard::updateXKBStateFromCore(quint16 state)
{
if (m_config && !connection()->hasXKB()) {
struct xkb_state *xkbState = m_xkbState.get();
xkb_mod_mask_t modsDepressed = xkb_state_serialize_mods(xkbState, XKB_STATE_MODS_DEPRESSED);
xkb_mod_mask_t modsLatched = xkb_state_serialize_mods(xkbState, XKB_STATE_MODS_LATCHED);
xkb_mod_mask_t modsLocked = xkb_state_serialize_mods(xkbState, XKB_STATE_MODS_LOCKED);
xkb_mod_mask_t xkbMask = xkbModMask(state);
xkb_mod_mask_t latched = modsLatched & xkbMask;
xkb_mod_mask_t locked = modsLocked & xkbMask;
xkb_mod_mask_t depressed = modsDepressed & xkbMask;
// set modifiers in depressed if they don't appear in any of the final masks
depressed |= ~(depressed | latched | locked) & xkbMask;
xkb_state_component changedComponents = xkb_state_update_mask(
xkbState, depressed, latched, locked, 0, 0, lockedGroup(state));
handleStateChanges(changedComponents);
}
}
#if QT_CONFIG(xcb_xinput)
void QXcbKeyboard::updateXKBStateFromXI(void *modInfo, void *groupInfo)
{
if (m_config && !connection()->hasXKB()) {
auto *mods = static_cast<xcb_input_modifier_info_t *>(modInfo);
auto *group = static_cast<xcb_input_group_info_t *>(groupInfo);
const xkb_state_component changedComponents
= xkb_state_update_mask(m_xkbState.get(),
mods->base,
mods->latched,
mods->locked,
group->base,
group->latched,
group->locked);
handleStateChanges(changedComponents);
}
}
#endif
void QXcbKeyboard::handleStateChanges(xkb_state_component changedComponents)
{
// Note: Ubuntu (with Unity) always creates a new keymap when layout is changed
// via system settings, which means that the layout change would not be detected
// by this code. That can be solved by emitting KeyboardLayoutChange also from updateKeymap().
if ((changedComponents & XKB_STATE_LAYOUT_EFFECTIVE) == XKB_STATE_LAYOUT_EFFECTIVE)
qCDebug(lcQpaKeyboard, "TODO: Support KeyboardLayoutChange on QPA (QTBUG-27681)");
}
xkb_mod_mask_t QXcbKeyboard::xkbModMask(quint16 state)
{
xkb_mod_mask_t 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_keymap_mod_get_index(m_xkbKeymap.get(), XKB_MOD_NAME_SHIFT);
xkb_mods.lock = xkb_keymap_mod_get_index(m_xkbKeymap.get(), XKB_MOD_NAME_CAPS);
xkb_mods.control = xkb_keymap_mod_get_index(m_xkbKeymap.get(), XKB_MOD_NAME_CTRL);
xkb_mods.mod1 = xkb_keymap_mod_get_index(m_xkbKeymap.get(), "Mod1");
xkb_mods.mod2 = xkb_keymap_mod_get_index(m_xkbKeymap.get(), "Mod2");
xkb_mods.mod3 = xkb_keymap_mod_get_index(m_xkbKeymap.get(), "Mod3");
xkb_mods.mod4 = xkb_keymap_mod_get_index(m_xkbKeymap.get(), "Mod4");
xkb_mods.mod5 = xkb_keymap_mod_get_index(m_xkbKeymap.get(), "Mod5");
}
QXcbKeyboard::QXcbKeyboard(QXcbConnection *connection)
: QXcbObject(connection)
{
#if QT_CONFIG(xkb)
core_device_id = 0;
if (connection->hasXKB()) {
selectEvents();
core_device_id = xkb_x11_get_core_keyboard_device_id(xcb_connection());
if (core_device_id == -1) {
qCWarning(lcQpaXcb, "failed to get core keyboard device info");
return;
}
} else {
#endif
m_key_symbols = xcb_key_symbols_alloc(xcb_connection());
#if QT_CONFIG(xkb)
}
#endif
updateKeymap();
}
QXcbKeyboard::~QXcbKeyboard()
{
if (m_key_symbols)
xcb_key_symbols_free(m_key_symbols);
}
void QXcbKeyboard::initialize()
{
auto inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext();
QXkbCommon::setXkbContext(inputContext, m_xkbContext.get());
}
void QXcbKeyboard::selectEvents()
{
#if QT_CONFIG(xkb)
const uint16_t required_map_parts = (XCB_XKB_MAP_PART_KEY_TYPES |
XCB_XKB_MAP_PART_KEY_SYMS |
XCB_XKB_MAP_PART_MODIFIER_MAP |
XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS |
XCB_XKB_MAP_PART_KEY_ACTIONS |
XCB_XKB_MAP_PART_KEY_BEHAVIORS |
XCB_XKB_MAP_PART_VIRTUAL_MODS |
XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP);
const uint16_t required_events = (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
XCB_XKB_EVENT_TYPE_STATE_NOTIFY);
// XKB events are reported to all interested clients without regard
// to the current keyboard input focus or grab state
xcb_void_cookie_t select = xcb_xkb_select_events_checked(
xcb_connection(),
XCB_XKB_ID_USE_CORE_KBD,
required_events,
0,
required_events,
required_map_parts,
required_map_parts,
0);
xcb_generic_error_t *error = xcb_request_check(xcb_connection(), select);
if (error) {
free(error);
qCWarning(lcQpaXcb, "failed to select notify events from XKB");
}
#endif
}
void QXcbKeyboard::updateVModMapping()
{
#if QT_CONFIG(xkb)
xcb_xkb_get_names_value_list_t names_list;
memset(&vmod_masks, 0, sizeof(vmod_masks));
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.get());
xcb_xkb_get_names_value_list_unpack(buffer,
name_reply->nTypes,
name_reply->indicators,
name_reply->virtualMods,
name_reply->groupNames,
name_reply->nKeys,
name_reply->nKeyAliases,
name_reply->nRadioGroups,
name_reply->which,
&names_list);
int count = 0;
uint vmod_mask, bit;
char *vmod_name;
vmod_mask = name_reply->virtualMods;
// find the virtual modifiers for which names are defined.
for (bit = 1; vmod_mask; bit <<= 1) {
vmod_name = 0;
if (!(vmod_mask & bit))
continue;
vmod_mask &= ~bit;
// virtualModNames - the list of virtual modifier atoms beginning with the lowest-numbered
// virtual modifier for which a name is defined and proceeding to the highest.
QByteArray atomName = connection()->atomName(names_list.virtualModNames[count]);
vmod_name = atomName.data();
count++;
if (!vmod_name)
continue;
// similarly we could retrieve NumLock, Super, Hyper modifiers if needed.
if (qstrcmp(vmod_name, "Alt") == 0)
vmod_masks.alt = bit;
else if (qstrcmp(vmod_name, "Meta") == 0)
vmod_masks.meta = bit;
else if (qstrcmp(vmod_name, "AltGr") == 0)
vmod_masks.altgr = bit;
else if (qstrcmp(vmod_name, "Super") == 0)
vmod_masks.super = bit;
else if (qstrcmp(vmod_name, "Hyper") == 0)
vmod_masks.hyper = bit;
}
#endif
}
void QXcbKeyboard::updateVModToRModMapping()
{
#if QT_CONFIG(xkb)
xcb_xkb_get_map_map_t map;
memset(&rmod_masks, 0, sizeof(rmod_masks));
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.get());
xcb_xkb_get_map_map_unpack(buffer,
map_reply->nTypes,
map_reply->nKeySyms,
map_reply->nKeyActions,
map_reply->totalActions,
map_reply->totalKeyBehaviors,
map_reply->nVModMapKeys,
map_reply->totalKeyExplicit,
map_reply->totalModMapKeys,
map_reply->totalVModMapKeys,
map_reply->present,
&map);
uint vmod_mask, bit;
// the virtual modifiers mask for which a set of corresponding
// real modifiers is to be returned
vmod_mask = map_reply->virtualMods;
int count = 0;
for (bit = 1; vmod_mask; bit <<= 1) {
uint modmap;
if (!(vmod_mask & bit))
continue;
vmod_mask &= ~bit;
// real modifier bindings for the specified virtual modifiers
modmap = map.vmods_rtrn[count];
count++;
if (vmod_masks.alt == bit)
rmod_masks.alt = modmap;
else if (vmod_masks.meta == bit)
rmod_masks.meta = modmap;
else if (vmod_masks.altgr == bit)
rmod_masks.altgr = modmap;
else if (vmod_masks.super == bit)
rmod_masks.super = modmap;
else if (vmod_masks.hyper == bit)
rmod_masks.hyper = modmap;
}
#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(const KeysymModifierMap &keysymMods)
{
if (connection()->hasXKB()) {
updateVModMapping();
updateVModToRModMapping();
} else {
memset(&rmod_masks, 0, sizeof(rmod_masks));
// Compute X modifier bits for Qt modifiers
applyModifier(&rmod_masks.alt, keysymMods.value(XKB_KEY_Alt_L, -1));
applyModifier(&rmod_masks.alt, keysymMods.value(XKB_KEY_Alt_R, -1));
applyModifier(&rmod_masks.meta, keysymMods.value(XKB_KEY_Meta_L, -1));
applyModifier(&rmod_masks.meta, keysymMods.value(XKB_KEY_Meta_R, -1));
applyModifier(&rmod_masks.altgr, keysymMods.value(XKB_KEY_Mode_switch, -1));
applyModifier(&rmod_masks.super, keysymMods.value(XKB_KEY_Super_L, -1));
applyModifier(&rmod_masks.super, keysymMods.value(XKB_KEY_Super_R, -1));
applyModifier(&rmod_masks.hyper, keysymMods.value(XKB_KEY_Hyper_L, -1));
applyModifier(&rmod_masks.hyper, keysymMods.value(XKB_KEY_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.
KeysymModifierMap map;
auto modMapReply = Q_XCB_REPLY(xcb_get_modifier_mapping, xcb_connection());
if (!modMapReply) {
qWarning("Qt: failed to get modifier mapping");
return map;
}
// for Alt and Meta L and R are the same
static const xcb_keysym_t symbols[] = {
XKB_KEY_Alt_L, XKB_KEY_Meta_L, XKB_KEY_Mode_switch, XKB_KEY_Super_L, XKB_KEY_Super_R,
XKB_KEY_Hyper_L, XKB_KEY_Hyper_R
};
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.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]);
return map;
}
void QXcbKeyboard::resolveMaskConflicts()
{
// if we don't have a meta key (or it's hidden behind alt), use super or hyper to generate
// Qt::Key_Meta and Qt::MetaModifier, since most newer XFree86/Xorg installations map the Windows
// key to Super
if (rmod_masks.alt == rmod_masks.meta)
rmod_masks.meta = 0;
if (rmod_masks.meta == 0) {
// no meta keys... s/meta/super,
rmod_masks.meta = rmod_masks.super;
if (rmod_masks.meta == 0) {
// no super keys either? guess we'll use hyper then
rmod_masks.meta = rmod_masks.hyper;
}
}
// translate Super/Hyper keys to Meta if we're using them as the MetaModifier
if (rmod_masks.meta && rmod_masks.meta == rmod_masks.super)
m_superAsMeta = true;
if (rmod_masks.meta && rmod_masks.meta == rmod_masks.hyper)
m_hyperAsMeta = true;
}
void QXcbKeyboard::handleKeyEvent(xcb_window_t sourceWindow, QEvent::Type type, xcb_keycode_t code,
quint16 state, xcb_timestamp_t time, bool fromSendEvent)
{
if (!m_config)
return;
QXcbWindow *source = connection()->platformWindowFromId(sourceWindow);
QXcbWindow *targetWindow = connection()->focusWindow() ? connection()->focusWindow() : source;
if (!targetWindow || !source)
return;
if (type == QEvent::KeyPress)
targetWindow->updateNetWmUserTime(time);
QXkbCommon::ScopedXKBState sendEventState;
if (fromSendEvent) {
// Have a temporary keyboard state filled in from state
// this way we allow for synthetic events to have different state
// from the current state i.e. you can have Alt+Ctrl pressed
// and receive a synthetic key event that has neither Alt nor Ctrl pressed
sendEventState.reset(xkb_state_new(m_xkbKeymap.get()));
if (!sendEventState)
return;
xkb_mod_mask_t depressed = xkbModMask(state);
xkb_state_update_mask(sendEventState.get(), depressed, 0, 0, 0, 0, lockedGroup(state));
}
struct xkb_state *xkbState = fromSendEvent ? sendEventState.get() : m_xkbState.get();
xcb_keysym_t sym = xkb_state_key_get_one_sym(xkbState, code);
QString text = QXkbCommon::lookupString(xkbState, code);
Qt::KeyboardModifiers modifiers = translateModifiers(state);
if (QXkbCommon::isKeypad(sym))
modifiers |= Qt::KeypadModifier;
int qtcode = QXkbCommon::keysymToQtKey(sym, modifiers, xkbState, code, m_superAsMeta, m_hyperAsMeta);
if (type == QEvent::KeyPress) {
if (m_isAutoRepeat && m_autoRepeatCode != code)
// Some other key was pressed while we are auto-repeating on a different key.
m_isAutoRepeat = false;
} else {
m_isAutoRepeat = false;
// Look at the next event in the queue to see if we are auto-repeating.
connection()->eventQueue()->peek(QXcbEventQueue::PeekRetainMatch,
[this, time, code](xcb_generic_event_t *event, int type) {
if (type == XCB_KEY_PRESS) {
auto keyPress = reinterpret_cast<xcb_key_press_event_t *>(event);
m_isAutoRepeat = keyPress->time == time && keyPress->detail == code;
if (m_isAutoRepeat)
m_autoRepeatCode = code;
}
return true;
});
}
bool filtered = false;
if (auto inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext()) {
QKeyEvent event(type, qtcode, modifiers, code, sym, state, text, m_isAutoRepeat, text.size());
event.setTimestamp(time);
filtered = inputContext->filterEvent(&event);
}
if (!filtered) {
QWindow *window = targetWindow->window();
#ifndef QT_NO_CONTEXTMENU
if (type == QEvent::KeyPress && qtcode == Qt::Key_Menu) {
const QPoint globalPos = window->screen()->handle()->cursor()->pos();
const QPoint pos = window->mapFromGlobal(globalPos);
QWindowSystemInterface::handleContextMenuEvent(window, false, pos, globalPos, modifiers);
}
#endif
QWindowSystemInterface::handleExtendedKeyEvent(window, time, type, qtcode, modifiers,
code, sym, state, text, m_isAutoRepeat);
}
}
static bool fromSendEvent(const void *event)
{
// From X11 protocol: Every event contains an 8-bit type code. The most
// significant bit in this code is set if the event was generated from
// a SendEvent request.
const xcb_generic_event_t *e = reinterpret_cast<const xcb_generic_event_t *>(event);
return (e->response_type & 0x80) != 0;
}
void QXcbKeyboard::handleKeyPressEvent(const xcb_key_press_event_t *e)
{
handleKeyEvent(e->event, QEvent::KeyPress, e->detail, e->state, e->time, fromSendEvent(e));
}
void QXcbKeyboard::handleKeyReleaseEvent(const xcb_key_release_event_t *e)
{
handleKeyEvent(e->event, QEvent::KeyRelease, e->detail, e->state, e->time, fromSendEvent(e));
}
QT_END_NAMESPACE