File indexing completed on 2024-04-28 16:49:18

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2013, 2016, 2017 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "xkb.h"
0010 #include "dbusproperties_interface.h"
0011 #include "inputmethod.h"
0012 #include "utils/c_ptr.h"
0013 #include "utils/common.h"
0014 #include "wayland/inputmethod_v1_interface.h"
0015 #include "wayland/keyboard_interface.h"
0016 #include "wayland/seat_interface.h"
0017 // frameworks
0018 #include <KConfigGroup>
0019 // Qt
0020 #include <QKeyEvent>
0021 #include <QTemporaryFile>
0022 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0023 #include <QtXkbCommonSupport/private/qxkbcommon_p.h>
0024 #else
0025 #include <QtGui/private/qxkbcommon_p.h>
0026 #endif
0027 // xkbcommon
0028 #include <xkbcommon/xkbcommon-compose.h>
0029 #include <xkbcommon/xkbcommon-keysyms.h>
0030 // system
0031 #include "main.h"
0032 #include <bitset>
0033 #include <linux/input-event-codes.h>
0034 #include <sys/mman.h>
0035 #include <unistd.h>
0036 
0037 Q_LOGGING_CATEGORY(KWIN_XKB, "kwin_xkbcommon", QtWarningMsg)
0038 
0039 /* The offset between KEY_* numbering, and keycodes in the XKB evdev
0040  * dataset. */
0041 static const int EVDEV_OFFSET = 8;
0042 static const char *s_locale1Interface = "org.freedesktop.locale1";
0043 
0044 namespace KWin
0045 {
0046 
0047 static void xkbLogHandler(xkb_context *context, xkb_log_level priority, const char *format, va_list args)
0048 {
0049     char buf[1024];
0050     int length = std::vsnprintf(buf, 1023, format, args);
0051     while (length > 0 && std::isspace(buf[length - 1])) {
0052         --length;
0053     }
0054     if (length <= 0) {
0055         return;
0056     }
0057     switch (priority) {
0058     case XKB_LOG_LEVEL_DEBUG:
0059         qCDebug(KWIN_XKB, "XKB: %.*s", length, buf);
0060         break;
0061     case XKB_LOG_LEVEL_INFO:
0062         qCInfo(KWIN_XKB, "XKB: %.*s", length, buf);
0063         break;
0064     case XKB_LOG_LEVEL_WARNING:
0065         qCWarning(KWIN_XKB, "XKB: %.*s", length, buf);
0066         break;
0067     case XKB_LOG_LEVEL_ERROR:
0068     case XKB_LOG_LEVEL_CRITICAL:
0069     default:
0070         qCCritical(KWIN_XKB, "XKB: %.*s", length, buf);
0071         break;
0072     }
0073 }
0074 
0075 #if HAVE_XKBCOMMON_NO_SECURE_GETENV
0076 constexpr xkb_context_flags KWIN_XKB_CONTEXT_FLAGS = XKB_CONTEXT_NO_SECURE_GETENV;
0077 #else
0078 constexpr xkb_context_flags KWIN_XKB_CONTEXT_FLAGS = XKB_CONTEXT_NO_FLAGS;
0079 #endif
0080 
0081 Xkb::Xkb(bool followLocale1)
0082     : m_context(xkb_context_new(KWIN_XKB_CONTEXT_FLAGS))
0083     , m_keymap(nullptr)
0084     , m_state(nullptr)
0085     , m_shiftModifier(0)
0086     , m_capsModifier(0)
0087     , m_controlModifier(0)
0088     , m_altModifier(0)
0089     , m_metaModifier(0)
0090     , m_numModifier(0)
0091     , m_numLock(0)
0092     , m_capsLock(0)
0093     , m_scrollLock(0)
0094     , m_modifiers(Qt::NoModifier)
0095     , m_consumedModifiers(Qt::NoModifier)
0096     , m_keysym(XKB_KEY_NoSymbol)
0097     , m_leds()
0098     , m_followLocale1(followLocale1)
0099 {
0100     qRegisterMetaType<KWin::LEDs>();
0101     if (!m_context) {
0102         qCDebug(KWIN_XKB) << "Could not create xkb context";
0103     } else {
0104         xkb_context_set_log_level(m_context, XKB_LOG_LEVEL_DEBUG);
0105         xkb_context_set_log_fn(m_context, &xkbLogHandler);
0106 
0107         // get locale as described in xkbcommon doc
0108         // cannot use QLocale as it drops the modifier part
0109         QByteArray locale = qgetenv("LC_ALL");
0110         if (locale.isEmpty()) {
0111             locale = qgetenv("LC_CTYPE");
0112         }
0113         if (locale.isEmpty()) {
0114             locale = qgetenv("LANG");
0115         }
0116         if (locale.isEmpty()) {
0117             locale = QByteArrayLiteral("C");
0118         }
0119 
0120         m_compose.table = xkb_compose_table_new_from_locale(m_context, locale.constData(), XKB_COMPOSE_COMPILE_NO_FLAGS);
0121         if (m_compose.table) {
0122             m_compose.state = xkb_compose_state_new(m_compose.table, XKB_COMPOSE_STATE_NO_FLAGS);
0123         }
0124     }
0125 
0126     if (m_followLocale1) {
0127         bool connected = QDBusConnection::systemBus().connect(s_locale1Interface, "/org/freedesktop/locale1", QStringLiteral("org.freedesktop.DBus.Properties"),
0128                                                               QStringLiteral("PropertiesChanged"),
0129                                                               this,
0130                                                               SLOT(reconfigure()));
0131         if (!connected) {
0132             qCWarning(KWIN_XKB) << "Could not connect to org.freedesktop.locale1";
0133         }
0134     }
0135 }
0136 
0137 Xkb::~Xkb()
0138 {
0139     xkb_compose_state_unref(m_compose.state);
0140     xkb_compose_table_unref(m_compose.table);
0141     xkb_state_unref(m_state);
0142     xkb_keymap_unref(m_keymap);
0143     xkb_context_unref(m_context);
0144 }
0145 
0146 void Xkb::setConfig(const KSharedConfigPtr &config)
0147 {
0148     m_configGroup = config->group("Layout");
0149 }
0150 
0151 void Xkb::setNumLockConfig(const KSharedConfigPtr &config)
0152 {
0153     m_numLockConfig = config;
0154 }
0155 
0156 void Xkb::reconfigure()
0157 {
0158     if (!m_context) {
0159         return;
0160     }
0161 
0162     xkb_keymap *keymap = nullptr;
0163     if (!qEnvironmentVariableIsSet("KWIN_XKB_DEFAULT_KEYMAP")) {
0164         if (m_followLocale1) {
0165             keymap = loadKeymapFromLocale1();
0166         } else {
0167             keymap = loadKeymapFromConfig();
0168         }
0169     }
0170     if (!keymap) {
0171         qCDebug(KWIN_XKB) << "Could not create xkb keymap from configuration";
0172         keymap = loadDefaultKeymap();
0173     }
0174     if (keymap) {
0175         updateKeymap(keymap);
0176     } else {
0177         qCDebug(KWIN_XKB) << "Could not create default xkb keymap";
0178     }
0179 }
0180 
0181 static bool stringIsEmptyOrNull(const char *str)
0182 {
0183     return str == nullptr || str[0] == '\0';
0184 }
0185 
0186 /**
0187  * libxkbcommon uses secure_getenv to read the XKB_DEFAULT_* variables.
0188  * As kwin_wayland may have the CAP_SET_NICE capability, it returns nullptr
0189  * so we need to do it ourselves (see xkb_context_sanitize_rule_names).
0190  **/
0191 void Xkb::applyEnvironmentRules(xkb_rule_names &ruleNames)
0192 {
0193     if (stringIsEmptyOrNull(ruleNames.rules)) {
0194         ruleNames.rules = getenv("XKB_DEFAULT_RULES");
0195     }
0196 
0197     if (stringIsEmptyOrNull(ruleNames.model)) {
0198         ruleNames.model = getenv("XKB_DEFAULT_MODEL");
0199     }
0200 
0201     if (stringIsEmptyOrNull(ruleNames.layout)) {
0202         ruleNames.layout = getenv("XKB_DEFAULT_LAYOUT");
0203         ruleNames.variant = getenv("XKB_DEFAULT_VARIANT");
0204     }
0205 
0206     if (ruleNames.options == nullptr) {
0207         ruleNames.options = getenv("XKB_DEFAULT_OPTIONS");
0208     }
0209 }
0210 
0211 xkb_keymap *Xkb::loadKeymapFromConfig()
0212 {
0213     // load config
0214     if (!m_configGroup.isValid()) {
0215         return nullptr;
0216     }
0217     const QByteArray model = m_configGroup.readEntry("Model", "pc104").toLatin1();
0218     const QByteArray layout = m_configGroup.readEntry("LayoutList").toLatin1();
0219     const QByteArray variant = m_configGroup.readEntry("VariantList").toLatin1();
0220     const QByteArray options = m_configGroup.readEntry("Options").toLatin1();
0221 
0222     xkb_rule_names ruleNames = {
0223         .rules = nullptr,
0224         .model = model.constData(),
0225         .layout = layout.constData(),
0226         .variant = variant.constData(),
0227         .options = nullptr,
0228     };
0229 
0230     if (m_configGroup.readEntry("ResetOldOptions", false)) {
0231         ruleNames.options = options.constData();
0232     }
0233 
0234     applyEnvironmentRules(ruleNames);
0235 
0236     m_layoutList = QString::fromLatin1(ruleNames.layout).split(QLatin1Char(','));
0237 
0238     return xkb_keymap_new_from_names(m_context, &ruleNames, XKB_KEYMAP_COMPILE_NO_FLAGS);
0239 }
0240 
0241 xkb_keymap *Xkb::loadDefaultKeymap()
0242 {
0243     xkb_rule_names ruleNames = {};
0244     applyEnvironmentRules(ruleNames);
0245     m_layoutList = QString::fromLatin1(ruleNames.layout).split(QLatin1Char(','));
0246     return xkb_keymap_new_from_names(m_context, &ruleNames, XKB_KEYMAP_COMPILE_NO_FLAGS);
0247 }
0248 
0249 xkb_keymap *Xkb::loadKeymapFromLocale1()
0250 {
0251     OrgFreedesktopDBusPropertiesInterface locale1Properties(s_locale1Interface, "/org/freedesktop/locale1", QDBusConnection::systemBus(), this);
0252     const QVariantMap properties = locale1Properties.GetAll(s_locale1Interface);
0253 
0254     const QByteArray model = properties["X11Model"].toByteArray();
0255     const QByteArray layout = properties["X11Layout"].toByteArray();
0256     const QByteArray variant = properties["X11Variant"].toByteArray();
0257     const QByteArray options = properties["X11Options"].toByteArray();
0258 
0259     xkb_rule_names ruleNames = {
0260         .rules = nullptr,
0261         .model = model.constData(),
0262         .layout = layout.constData(),
0263         .variant = variant.constData(),
0264         .options = options.constData(),
0265     };
0266 
0267     applyEnvironmentRules(ruleNames);
0268 
0269     m_layoutList = QString::fromLatin1(ruleNames.layout).split(QLatin1Char(','));
0270 
0271     return xkb_keymap_new_from_names(m_context, &ruleNames, XKB_KEYMAP_COMPILE_NO_FLAGS);
0272 }
0273 
0274 void Xkb::updateKeymap(xkb_keymap *keymap)
0275 {
0276     Q_ASSERT(keymap);
0277     xkb_state *state = xkb_state_new(keymap);
0278     if (!state) {
0279         qCDebug(KWIN_XKB) << "Could not create XKB state";
0280         xkb_keymap_unref(keymap);
0281         return;
0282     }
0283 
0284     // save Locks
0285     bool numLockIsOn, capsLockIsOn;
0286     static bool s_startup = true;
0287     if (!s_startup) {
0288         numLockIsOn = xkb_state_mod_index_is_active(m_state, m_numModifier, XKB_STATE_MODS_LOCKED);
0289         capsLockIsOn = xkb_state_mod_index_is_active(m_state, m_capsModifier, XKB_STATE_MODS_LOCKED);
0290     }
0291 
0292     // now release the old ones
0293     xkb_state_unref(m_state);
0294     xkb_keymap_unref(m_keymap);
0295 
0296     m_keymap = keymap;
0297     m_state = state;
0298 
0299     m_shiftModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_SHIFT);
0300     m_capsModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_CAPS);
0301     m_controlModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_CTRL);
0302     m_altModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_ALT);
0303     m_metaModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_LOGO);
0304     m_numModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_NUM);
0305 
0306     m_numLock = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_NUM);
0307     m_capsLock = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_CAPS);
0308     m_scrollLock = xkb_keymap_led_get_index(m_keymap, XKB_LED_NAME_SCROLL);
0309 
0310     m_currentLayout = xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE);
0311 
0312     m_modifierState.depressed = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_DEPRESSED));
0313     m_modifierState.latched = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LATCHED));
0314     m_modifierState.locked = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED));
0315 
0316     auto setLock = [this](xkb_mod_index_t modifier, bool value) {
0317         if (modifier != XKB_MOD_INVALID) {
0318             std::bitset<sizeof(xkb_mod_mask_t) * 8> mask{m_modifierState.locked};
0319             if (mask.size() > modifier) {
0320                 mask[modifier] = value;
0321                 m_modifierState.locked = mask.to_ulong();
0322                 xkb_state_update_mask(m_state, m_modifierState.depressed, m_modifierState.latched, m_modifierState.locked, 0, 0, m_currentLayout);
0323                 m_modifierState.locked = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED));
0324             }
0325         }
0326     };
0327 
0328     if (s_startup || qEnvironmentVariableIsSet("KWIN_FORCE_NUM_LOCK_EVALUATION")) {
0329         s_startup = false;
0330         if (m_numLockConfig) {
0331             const KConfigGroup config = m_numLockConfig->group("Keyboard");
0332             // STATE_ON = 0,  STATE_OFF = 1, STATE_UNCHANGED = 2, see plasma-desktop/kcms/keyboard/kcmmisc.h
0333             const auto setting = config.readEntry("NumLock", 2);
0334             if (setting != 2) {
0335                 setLock(m_numModifier, !setting);
0336             }
0337         }
0338     } else {
0339         setLock(m_numModifier, numLockIsOn);
0340         setLock(m_capsModifier, capsLockIsOn);
0341     }
0342 
0343     createKeymapFile();
0344     forwardModifiers();
0345     if (auto *inputmethod = kwinApp()->inputMethod()) {
0346         inputmethod->forwardModifiers(InputMethod::Force);
0347     }
0348     updateModifiers();
0349 }
0350 
0351 void Xkb::createKeymapFile()
0352 {
0353     const auto currentKeymap = keymapContents();
0354     if (currentKeymap.isEmpty()) {
0355         return;
0356     }
0357     m_seat->keyboard()->setKeymap(currentKeymap);
0358     auto *inputmethod = kwinApp()->inputMethod();
0359     if (!inputmethod) {
0360         return;
0361     }
0362     if (auto *keyboardGrab = inputmethod->keyboardGrab()) {
0363         keyboardGrab->sendKeymap(currentKeymap);
0364     }
0365 }
0366 
0367 QByteArray Xkb::keymapContents() const
0368 {
0369     if (!m_seat || !m_seat->keyboard()) {
0370         return {};
0371     }
0372     // TODO: uninstall keymap on server?
0373     if (!m_keymap) {
0374         return {};
0375     }
0376 
0377     UniqueCPtr<char> keymapString(xkb_keymap_get_as_string(m_keymap, XKB_KEYMAP_FORMAT_TEXT_V1));
0378     if (!keymapString) {
0379         return {};
0380     }
0381     return keymapString.get();
0382 }
0383 
0384 void Xkb::updateModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
0385 {
0386     if (!m_keymap || !m_state) {
0387         return;
0388     }
0389     // Avoid to create a infinite loop between input method and compositor.
0390     if (xkb_state_update_mask(m_state, modsDepressed, modsLatched, modsLocked, 0, 0, group) == 0) {
0391         return;
0392     }
0393     updateModifiers();
0394     forwardModifiers();
0395 }
0396 
0397 void Xkb::updateKey(uint32_t key, InputRedirection::KeyboardKeyState state)
0398 {
0399     if (!m_keymap || !m_state) {
0400         return;
0401     }
0402     xkb_state_update_key(m_state, key + EVDEV_OFFSET, static_cast<xkb_key_direction>(state));
0403     if (state == InputRedirection::KeyboardKeyPressed) {
0404         const auto sym = toKeysym(key);
0405         if (m_compose.state && xkb_compose_state_feed(m_compose.state, sym) == XKB_COMPOSE_FEED_ACCEPTED) {
0406             switch (xkb_compose_state_get_status(m_compose.state)) {
0407             case XKB_COMPOSE_NOTHING:
0408                 m_keysym = sym;
0409                 break;
0410             case XKB_COMPOSE_COMPOSED:
0411                 m_keysym = xkb_compose_state_get_one_sym(m_compose.state);
0412                 break;
0413             default:
0414                 m_keysym = XKB_KEY_NoSymbol;
0415                 break;
0416             }
0417         } else {
0418             m_keysym = sym;
0419         }
0420     }
0421     updateModifiers();
0422     updateConsumedModifiers(key);
0423 }
0424 
0425 void Xkb::updateModifiers()
0426 {
0427     Qt::KeyboardModifiers mods = Qt::NoModifier;
0428     if (xkb_state_mod_index_is_active(m_state, m_shiftModifier, XKB_STATE_MODS_EFFECTIVE) == 1 || xkb_state_mod_index_is_active(m_state, m_capsModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
0429         mods |= Qt::ShiftModifier;
0430     }
0431     if (xkb_state_mod_index_is_active(m_state, m_altModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
0432         mods |= Qt::AltModifier;
0433     }
0434     if (xkb_state_mod_index_is_active(m_state, m_controlModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
0435         mods |= Qt::ControlModifier;
0436     }
0437     if (xkb_state_mod_index_is_active(m_state, m_metaModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
0438         mods |= Qt::MetaModifier;
0439     }
0440     if (m_keysym >= XKB_KEY_KP_Space && m_keysym <= XKB_KEY_KP_9) {
0441         mods |= Qt::KeypadModifier;
0442     }
0443     m_modifiers = mods;
0444 
0445     // update LEDs
0446     LEDs leds;
0447     if (xkb_state_led_index_is_active(m_state, m_numLock) == 1) {
0448         leds = leds | LED::NumLock;
0449     }
0450     if (xkb_state_led_index_is_active(m_state, m_capsLock) == 1) {
0451         leds = leds | LED::CapsLock;
0452     }
0453     if (xkb_state_led_index_is_active(m_state, m_scrollLock) == 1) {
0454         leds = leds | LED::ScrollLock;
0455     }
0456     if (m_leds != leds) {
0457         m_leds = leds;
0458         Q_EMIT ledsChanged(m_leds);
0459     }
0460 
0461     const uint32_t newLayout = xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE);
0462     const uint32_t depressed = xkb_state_serialize_mods(m_state, XKB_STATE_MODS_DEPRESSED);
0463     const uint32_t latched = xkb_state_serialize_mods(m_state, XKB_STATE_MODS_LATCHED);
0464     const uint32_t locked = xkb_state_serialize_mods(m_state, XKB_STATE_MODS_LOCKED);
0465 
0466     if (newLayout != m_currentLayout || depressed != m_modifierState.depressed || latched != m_modifierState.latched || locked != m_modifierState.locked) {
0467         m_currentLayout = newLayout;
0468         m_modifierState.depressed = depressed;
0469         m_modifierState.latched = latched;
0470         m_modifierState.locked = locked;
0471 
0472         Q_EMIT modifierStateChanged();
0473     }
0474 }
0475 
0476 void Xkb::forwardModifiers()
0477 {
0478     if (!m_seat || !m_seat->keyboard()) {
0479         return;
0480     }
0481     m_seat->notifyKeyboardModifiers(m_modifierState.depressed,
0482                                     m_modifierState.latched,
0483                                     m_modifierState.locked,
0484                                     m_currentLayout);
0485 }
0486 
0487 QString Xkb::layoutName(xkb_layout_index_t index) const
0488 {
0489     if (!m_keymap) {
0490         return QString{};
0491     }
0492     return QString::fromLocal8Bit(xkb_keymap_layout_get_name(m_keymap, index));
0493 }
0494 
0495 QString Xkb::layoutName() const
0496 {
0497     return layoutName(m_currentLayout);
0498 }
0499 
0500 QString Xkb::layoutShortName(int index) const
0501 {
0502     return m_layoutList.value(index);
0503 }
0504 
0505 void Xkb::updateConsumedModifiers(uint32_t key)
0506 {
0507     Qt::KeyboardModifiers mods = Qt::NoModifier;
0508     if (xkb_state_mod_index_is_consumed2(m_state, key + EVDEV_OFFSET, m_shiftModifier, XKB_CONSUMED_MODE_GTK) == 1) {
0509         mods |= Qt::ShiftModifier;
0510     }
0511     if (xkb_state_mod_index_is_consumed2(m_state, key + EVDEV_OFFSET, m_altModifier, XKB_CONSUMED_MODE_GTK) == 1) {
0512         mods |= Qt::AltModifier;
0513     }
0514     if (xkb_state_mod_index_is_consumed2(m_state, key + EVDEV_OFFSET, m_controlModifier, XKB_CONSUMED_MODE_GTK) == 1) {
0515         mods |= Qt::ControlModifier;
0516     }
0517     if (xkb_state_mod_index_is_consumed2(m_state, key + EVDEV_OFFSET, m_metaModifier, XKB_CONSUMED_MODE_GTK) == 1) {
0518         mods |= Qt::MetaModifier;
0519     }
0520     m_consumedModifiers = mods;
0521 }
0522 
0523 Qt::KeyboardModifiers Xkb::modifiersRelevantForGlobalShortcuts(uint32_t scanCode) const
0524 {
0525     if (!m_state) {
0526         return Qt::NoModifier;
0527     }
0528     Qt::KeyboardModifiers mods = Qt::NoModifier;
0529     if (xkb_state_mod_index_is_active(m_state, m_shiftModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
0530         mods |= Qt::ShiftModifier;
0531     }
0532     if (xkb_state_mod_index_is_active(m_state, m_altModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
0533         mods |= Qt::AltModifier;
0534     }
0535     if (xkb_state_mod_index_is_active(m_state, m_controlModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
0536         mods |= Qt::ControlModifier;
0537     }
0538     if (xkb_state_mod_index_is_active(m_state, m_metaModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
0539         mods |= Qt::MetaModifier;
0540     }
0541 
0542     Qt::KeyboardModifiers consumedMods = m_consumedModifiers;
0543     if ((mods & Qt::ShiftModifier) && (consumedMods == Qt::ShiftModifier)) {
0544         // test whether current keysym is a letter
0545         // in that case the shift should be removed from the consumed modifiers again
0546         // otherwise it would not be possible to trigger e.g. Shift+W as a shortcut
0547         // see BUG: 370341
0548         if (QChar::isLetter(toQtKey(m_keysym, scanCode, Qt::ControlModifier))) {
0549             consumedMods = Qt::KeyboardModifiers();
0550         }
0551     }
0552 
0553     return mods & ~consumedMods;
0554 }
0555 
0556 Qt::KeyboardModifiers Xkb::modifiersRelevantForTabBox() const
0557 {
0558     if (!m_state) {
0559         return Qt::NoModifier;
0560     }
0561     Qt::KeyboardModifiers mods = Qt::NoModifier;
0562     if (xkb_state_mod_index_is_active(m_state, m_shiftModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
0563         mods |= Qt::ShiftModifier;
0564     }
0565     if (xkb_state_mod_index_is_active(m_state, m_altModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
0566         mods |= Qt::AltModifier;
0567     }
0568     if (xkb_state_mod_index_is_active(m_state, m_controlModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
0569         mods |= Qt::ControlModifier;
0570     }
0571     if (xkb_state_mod_index_is_active(m_state, m_metaModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
0572         mods |= Qt::MetaModifier;
0573     }
0574     return mods;
0575 }
0576 
0577 xkb_keysym_t Xkb::toKeysym(uint32_t key)
0578 {
0579     if (!m_state) {
0580         return XKB_KEY_NoSymbol;
0581     }
0582 
0583     // Workaround because there's some kind of overlap between KEY_ZENKAKUHANKAKU and TLDE
0584     // This key is important because some hardware manufacturers use it to indicate touchpad toggling.
0585     xkb_keysym_t ret = xkb_state_key_get_one_sym(m_state, key + EVDEV_OFFSET);
0586     if (ret == 0 && key == KEY_ZENKAKUHANKAKU) {
0587         ret = XKB_KEY_Zenkaku_Hankaku;
0588     }
0589     return ret;
0590 }
0591 
0592 QString Xkb::toString(xkb_keysym_t keysym)
0593 {
0594     if (!m_state || keysym == XKB_KEY_NoSymbol) {
0595         return QString();
0596     }
0597     QByteArray byteArray(7, 0);
0598     int ok = xkb_keysym_to_utf8(keysym, byteArray.data(), byteArray.size());
0599     if (ok == -1 || ok == 0) {
0600         return QString();
0601     }
0602     return QString::fromUtf8(byteArray.constData());
0603 }
0604 
0605 Qt::Key Xkb::toQtKey(xkb_keysym_t keySym,
0606                      uint32_t scanCode,
0607                      Qt::KeyboardModifiers modifiers,
0608                      bool superAsMeta) const
0609 {
0610     // FIXME: passing superAsMeta doesn't have impact due to bug in the Qt function, so handle it below
0611     Qt::Key qtKey = Qt::Key(QXkbCommon::keysymToQtKey(keySym, modifiers, m_state, scanCode + EVDEV_OFFSET, superAsMeta));
0612 
0613     // FIXME: workarounds for symbols currently wrong/not mappable via keysymToQtKey()
0614     if (superAsMeta && (qtKey == Qt::Key_Super_L || qtKey == Qt::Key_Super_R)) {
0615         // translate Super/Hyper keys to Meta if we're using them as the MetaModifier
0616         qtKey = Qt::Key_Meta;
0617     } else if (qtKey > 0xff && keySym <= 0xff) {
0618         // XKB_KEY_mu, XKB_KEY_ydiaeresis go here
0619         qtKey = Qt::Key(keySym);
0620 #if QT_VERSION_MAJOR < 6 // since Qt 5 LTS is frozen
0621     } else if (keySym == XKB_KEY_Sys_Req) {
0622         // fixed in QTBUG-92087
0623         qtKey = Qt::Key_SysReq;
0624 #endif
0625     }
0626     return qtKey;
0627 }
0628 
0629 bool Xkb::shouldKeyRepeat(quint32 key) const
0630 {
0631     if (!m_keymap) {
0632         return false;
0633     }
0634     return xkb_keymap_key_repeats(m_keymap, key + EVDEV_OFFSET) != 0;
0635 }
0636 
0637 void Xkb::switchToNextLayout()
0638 {
0639     if (!m_keymap || !m_state) {
0640         return;
0641     }
0642     const xkb_layout_index_t numLayouts = xkb_keymap_num_layouts(m_keymap);
0643     const xkb_layout_index_t nextLayout = (xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE) + 1) % numLayouts;
0644     switchToLayout(nextLayout);
0645 }
0646 
0647 void Xkb::switchToPreviousLayout()
0648 {
0649     if (!m_keymap || !m_state) {
0650         return;
0651     }
0652     const xkb_layout_index_t previousLayout = m_currentLayout == 0 ? numberOfLayouts() - 1 : m_currentLayout - 1;
0653     switchToLayout(previousLayout);
0654 }
0655 
0656 bool Xkb::switchToLayout(xkb_layout_index_t layout)
0657 {
0658     if (!m_keymap || !m_state || layout >= numberOfLayouts()) {
0659         return false;
0660     }
0661     const xkb_mod_mask_t depressed = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_DEPRESSED));
0662     const xkb_mod_mask_t latched = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LATCHED));
0663     const xkb_mod_mask_t locked = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED));
0664     xkb_state_update_mask(m_state, depressed, latched, locked, 0, 0, layout);
0665     updateModifiers();
0666     forwardModifiers();
0667     return true;
0668 }
0669 
0670 quint32 Xkb::numberOfLayouts() const
0671 {
0672     if (!m_keymap) {
0673         return 0;
0674     }
0675     return xkb_keymap_num_layouts(m_keymap);
0676 }
0677 
0678 void Xkb::setSeat(KWaylandServer::SeatInterface *seat)
0679 {
0680     m_seat = QPointer<KWaylandServer::SeatInterface>(seat);
0681 }
0682 
0683 std::optional<int> Xkb::keycodeFromKeysym(xkb_keysym_t keysym)
0684 {
0685     auto layout = xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE);
0686     const xkb_keycode_t max = xkb_keymap_max_keycode(m_keymap);
0687     for (xkb_keycode_t keycode = xkb_keymap_min_keycode(m_keymap); keycode < max; keycode++) {
0688         uint levelCount = xkb_keymap_num_levels_for_key(m_keymap, keycode, layout);
0689         for (uint currentLevel = 0; currentLevel < levelCount; currentLevel++) {
0690             const xkb_keysym_t *syms;
0691             uint num_syms = xkb_keymap_key_get_syms_by_level(m_keymap, keycode, layout, currentLevel, &syms);
0692             for (uint sym = 0; sym < num_syms; sym++) {
0693                 if (syms[sym] == keysym) {
0694                     return {keycode - EVDEV_OFFSET};
0695                 }
0696             }
0697         }
0698     }
0699     return {};
0700 }
0701 }