File indexing completed on 2024-04-14 14:21:02

0001 /*
0002     SPDX-FileCopyrightText: 2009 Michael Leupold <lemma@confuego.org>
0003     SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #include "kmodifierkeyinfoprovider_xcb.h"
0009 #include "kmodifierkeyinfo.h"
0010 
0011 #include <QGuiApplication>
0012 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0013 #include <QX11Info>
0014 #endif
0015 
0016 #define XK_MISCELLANY
0017 #define XK_XKB_KEYS
0018 #include <X11/XKBlib.h>
0019 #include <X11/keysymdef.h>
0020 #include <xcb/xcb.h>
0021 
0022 struct ModifierDefinition {
0023     ModifierDefinition(Qt::Key _key, unsigned int _mask, const char *_name, KeySym _keysym)
0024         : key(_key)
0025         , mask(_mask)
0026         , name(_name)
0027         , keysym(_keysym)
0028     {
0029     }
0030     Qt::Key key;
0031     unsigned int mask;
0032     const char *name; // virtual modifier name
0033     KeySym keysym;
0034 };
0035 
0036 /*
0037  * Get the real modifiers related to a virtual modifier.
0038  */
0039 unsigned int xkbVirtualModifier(XkbDescPtr xkb, const char *name)
0040 {
0041     Q_ASSERT(xkb != nullptr);
0042 
0043     unsigned int mask = 0;
0044     bool nameEqual;
0045     for (int i = 0; i < XkbNumVirtualMods; ++i) {
0046         char *modStr = XGetAtomName(xkb->dpy, xkb->names->vmods[i]);
0047         if (modStr != nullptr) {
0048             nameEqual = (strcmp(name, modStr) == 0);
0049             XFree(modStr);
0050             if (nameEqual) {
0051                 XkbVirtualModsToReal(xkb, 1 << i, &mask);
0052                 break;
0053             }
0054         }
0055     }
0056     return mask;
0057 }
0058 
0059 static Display *display()
0060 {
0061 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0062     return QX11Info::display();
0063 #else
0064     return qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display();
0065 #endif
0066 }
0067 
0068 KModifierKeyInfoProviderXcb::KModifierKeyInfoProviderXcb()
0069     : KModifierKeyInfoProvider()
0070     , m_xkbEv(0)
0071     , m_xkbAvailable(false)
0072 {
0073     if (qGuiApp) {
0074         if (qGuiApp->platformName() == QLatin1String("xcb")) {
0075             int code;
0076             int xkberr;
0077             int maj;
0078             int min;
0079             m_xkbAvailable = XkbQueryExtension(display(), &code, &m_xkbEv, &xkberr, &maj, &min);
0080         }
0081     }
0082     if (m_xkbAvailable) {
0083         /* clang-format off */
0084         XkbSelectEvents(display(),
0085                         XkbUseCoreKbd,
0086                         XkbStateNotifyMask | XkbMapNotifyMask,
0087                         XkbStateNotifyMask | XkbMapNotifyMask);
0088 
0089         unsigned long int stateMask = XkbModifierStateMask
0090                                       | XkbModifierBaseMask
0091                                       | XkbModifierLatchMask
0092                                       | XkbModifierLockMask
0093                                       | XkbPointerButtonMask;
0094         /* clang-format on */
0095 
0096         XkbSelectEventDetails(display(), XkbUseCoreKbd, XkbStateNotifyMask, stateMask, stateMask);
0097     }
0098 
0099     xkbUpdateModifierMapping();
0100 
0101     // add known pointer buttons
0102     m_xkbButtons.insert(Qt::LeftButton, Button1Mask);
0103     m_xkbButtons.insert(Qt::MiddleButton, Button2Mask);
0104     m_xkbButtons.insert(Qt::RightButton, Button3Mask);
0105     m_xkbButtons.insert(Qt::XButton1, Button4Mask);
0106     m_xkbButtons.insert(Qt::XButton2, Button5Mask);
0107 
0108     // get the initial state
0109     if (m_xkbAvailable) {
0110         XkbStateRec state;
0111         XkbGetState(display(), XkbUseCoreKbd, &state);
0112         xkbModifierStateChanged(state.mods, state.latched_mods, state.locked_mods);
0113         xkbButtonStateChanged(state.ptr_buttons);
0114 
0115         QCoreApplication::instance()->installNativeEventFilter(this);
0116     }
0117 }
0118 
0119 KModifierKeyInfoProviderXcb::~KModifierKeyInfoProviderXcb()
0120 {
0121     if (m_xkbAvailable) {
0122         QCoreApplication::instance()->removeNativeEventFilter(this);
0123     }
0124 }
0125 
0126 bool KModifierKeyInfoProviderXcb::setKeyLatched(Qt::Key key, bool latched)
0127 {
0128     if (!m_xkbModifiers.contains(key)) {
0129         return false;
0130     }
0131 
0132     return XkbLatchModifiers(display(), XkbUseCoreKbd, m_xkbModifiers[key], latched ? m_xkbModifiers[key] : 0);
0133 }
0134 
0135 bool KModifierKeyInfoProviderXcb::setKeyLocked(Qt::Key key, bool locked)
0136 {
0137     if (!m_xkbModifiers.contains(key)) {
0138         return false;
0139     }
0140 
0141     return XkbLockModifiers(display(), XkbUseCoreKbd, m_xkbModifiers[key], locked ? m_xkbModifiers[key] : 0);
0142 }
0143 
0144 // HACK: xcb-xkb is not yet a public part of xcb. Because of that we have to include the event structure.
0145 namespace
0146 {
0147 typedef struct _xcb_xkb_map_notify_event_t {
0148     uint8_t response_type;
0149     uint8_t xkbType;
0150     uint16_t sequence;
0151     xcb_timestamp_t time;
0152     uint8_t deviceID;
0153     uint8_t ptrBtnActions;
0154     uint16_t changed;
0155     xcb_keycode_t minKeyCode;
0156     xcb_keycode_t maxKeyCode;
0157     uint8_t firstType;
0158     uint8_t nTypes;
0159     xcb_keycode_t firstKeySym;
0160     uint8_t nKeySyms;
0161     xcb_keycode_t firstKeyAct;
0162     uint8_t nKeyActs;
0163     xcb_keycode_t firstKeyBehavior;
0164     uint8_t nKeyBehavior;
0165     xcb_keycode_t firstKeyExplicit;
0166     uint8_t nKeyExplicit;
0167     xcb_keycode_t firstModMapKey;
0168     uint8_t nModMapKeys;
0169     xcb_keycode_t firstVModMapKey;
0170     uint8_t nVModMapKeys;
0171     uint16_t virtualMods;
0172     uint8_t pad0[2];
0173 } _xcb_xkb_map_notify_event_t;
0174 typedef struct _xcb_xkb_state_notify_event_t {
0175     uint8_t response_type;
0176     uint8_t xkbType;
0177     uint16_t sequence;
0178     xcb_timestamp_t time;
0179     uint8_t deviceID;
0180     uint8_t mods;
0181     uint8_t baseMods;
0182     uint8_t latchedMods;
0183     uint8_t lockedMods;
0184     uint8_t group;
0185     int16_t baseGroup;
0186     int16_t latchedGroup;
0187     uint8_t lockedGroup;
0188     uint8_t compatState;
0189     uint8_t grabMods;
0190     uint8_t compatGrabMods;
0191     uint8_t lookupMods;
0192     uint8_t compatLoockupMods;
0193     uint16_t ptrBtnState;
0194     uint16_t changed;
0195     xcb_keycode_t keycode;
0196     uint8_t eventType;
0197     uint8_t requestMajor;
0198     uint8_t requestMinor;
0199 } _xcb_xkb_state_notify_event_t;
0200 typedef union {
0201     /* All XKB events share these fields. */
0202     struct {
0203         uint8_t response_type;
0204         uint8_t xkbType;
0205         uint16_t sequence;
0206         xcb_timestamp_t time;
0207         uint8_t deviceID;
0208     } any;
0209     _xcb_xkb_map_notify_event_t map_notify;
0210     _xcb_xkb_state_notify_event_t state_notify;
0211 } _xkb_event;
0212 }
0213 
0214 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0215 bool KModifierKeyInfoProviderXcb::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *)
0216 #else
0217 bool KModifierKeyInfoProviderXcb::nativeEventFilter(const QByteArray &eventType, void *message, long *)
0218 #endif
0219 {
0220     if (!m_xkbAvailable || eventType != "xcb_generic_event_t") {
0221         return false;
0222     }
0223     xcb_generic_event_t *event = static_cast<xcb_generic_event_t *>(message);
0224     if ((event->response_type & ~0x80) == m_xkbEv + XkbEventCode) {
0225         _xkb_event *kbevt = reinterpret_cast<_xkb_event *>(event);
0226         unsigned int stateMask = XkbModifierStateMask | XkbModifierBaseMask | XkbModifierLatchMask | XkbModifierLockMask;
0227         if (kbevt->any.xkbType == XkbMapNotify) {
0228             xkbUpdateModifierMapping();
0229         } else if (kbevt->any.xkbType == XkbStateNotify) {
0230             if (kbevt->state_notify.changed & stateMask) {
0231                 xkbModifierStateChanged(kbevt->state_notify.mods, kbevt->state_notify.latchedMods, kbevt->state_notify.lockedMods);
0232             } else if (kbevt->state_notify.changed & XkbPointerButtonMask) {
0233                 xkbButtonStateChanged(kbevt->state_notify.ptrBtnState);
0234             }
0235         }
0236     }
0237     return false;
0238 }
0239 
0240 void KModifierKeyInfoProviderXcb::xkbModifierStateChanged(unsigned char mods, unsigned char latched_mods, unsigned char locked_mods)
0241 {
0242     // detect keyboard modifiers
0243     ModifierStates newState;
0244 
0245     QHash<Qt::Key, unsigned int>::const_iterator it;
0246     QHash<Qt::Key, unsigned int>::const_iterator end = m_xkbModifiers.constEnd();
0247     for (it = m_xkbModifiers.constBegin(); it != end; ++it) {
0248         if (!m_modifierStates.contains(it.key())) {
0249             continue;
0250         }
0251         newState = Nothing;
0252 
0253         // determine the new state
0254         if (mods & it.value()) {
0255             newState |= Pressed;
0256         }
0257         if (latched_mods & it.value()) {
0258             newState |= Latched;
0259         }
0260         if (locked_mods & it.value()) {
0261             newState |= Locked;
0262         }
0263 
0264         stateUpdated(it.key(), newState);
0265     }
0266 }
0267 
0268 void KModifierKeyInfoProviderXcb::xkbButtonStateChanged(unsigned short ptr_buttons)
0269 {
0270     // detect mouse button states
0271     bool newButtonState;
0272 
0273     QHash<Qt::MouseButton, unsigned short>::const_iterator it;
0274     QHash<Qt::MouseButton, unsigned short>::const_iterator end = m_xkbButtons.constEnd();
0275     for (it = m_xkbButtons.constBegin(); it != end; ++it) {
0276         newButtonState = (ptr_buttons & it.value());
0277         if (newButtonState != m_buttonStates[it.key()]) {
0278             m_buttonStates[it.key()] = newButtonState;
0279             Q_EMIT buttonPressed(it.key(), newButtonState);
0280         }
0281     }
0282 }
0283 
0284 void KModifierKeyInfoProviderXcb::xkbUpdateModifierMapping()
0285 {
0286     if (!m_xkbAvailable) {
0287         return;
0288     }
0289     m_xkbModifiers.clear();
0290 
0291     QList<ModifierDefinition> srcModifiers;
0292     /* clang-format off */
0293     srcModifiers << ModifierDefinition(Qt::Key_Shift, ShiftMask, nullptr, 0)
0294                  << ModifierDefinition(Qt::Key_Control, ControlMask, nullptr, 0)
0295                  << ModifierDefinition(Qt::Key_Alt, 0, "Alt", XK_Alt_L)
0296                  // << { 0, 0, I18N_NOOP("Win"), "superkey", "" }
0297                  << ModifierDefinition(Qt::Key_Meta, 0, "Meta", XK_Meta_L)
0298                  << ModifierDefinition(Qt::Key_Super_L, 0, "Super", XK_Super_L)
0299                  << ModifierDefinition(Qt::Key_Hyper_L, 0, "Hyper", XK_Hyper_L)
0300                  << ModifierDefinition(Qt::Key_AltGr, 0, "AltGr", 0)
0301                  << ModifierDefinition(Qt::Key_NumLock, 0, "NumLock", XK_Num_Lock)
0302                  << ModifierDefinition(Qt::Key_CapsLock, LockMask, nullptr, 0)
0303                  << ModifierDefinition(Qt::Key_ScrollLock, 0, "ScrollLock", XK_Scroll_Lock);
0304     /* clang-format on */
0305 
0306     XkbDescPtr xkb = XkbGetKeyboard(display(), XkbAllComponentsMask, XkbUseCoreKbd);
0307 
0308     QList<ModifierDefinition>::const_iterator it;
0309     QList<ModifierDefinition>::const_iterator end = srcModifiers.constEnd();
0310     for (it = srcModifiers.constBegin(); it != end; ++it) {
0311         unsigned int mask = it->mask;
0312         if (mask == 0 && xkb != nullptr) {
0313             // try virtual modifier first
0314             if (it->name != nullptr) {
0315                 mask = xkbVirtualModifier(xkb, it->name);
0316             }
0317             if (mask == 0 && it->keysym != 0) {
0318                 mask = XkbKeysymToModifiers(display(), it->keysym);
0319             } else if (mask == 0) {
0320                 // special case for AltGr
0321                 /* clang-format off */
0322                 mask = XkbKeysymToModifiers(display(), XK_Mode_switch)
0323                        | XkbKeysymToModifiers(display(), XK_ISO_Level3_Shift)
0324                        | XkbKeysymToModifiers(display(), XK_ISO_Level3_Latch)
0325                        | XkbKeysymToModifiers(display(), XK_ISO_Level3_Lock);
0326                 /* clang-format on */
0327             }
0328         }
0329 
0330         if (mask != 0) {
0331             m_xkbModifiers.insert(it->key, mask);
0332             // previously unknown modifier
0333             if (!m_modifierStates.contains(it->key)) {
0334                 m_modifierStates.insert(it->key, Nothing);
0335                 Q_EMIT keyAdded(it->key);
0336             }
0337         }
0338     }
0339 
0340     // remove modifiers which are no longer available
0341     QMutableHashIterator<Qt::Key, ModifierStates> i(m_modifierStates);
0342     while (i.hasNext()) {
0343         i.next();
0344         if (!m_xkbModifiers.contains(i.key())) {
0345             Qt::Key key = i.key();
0346             i.remove();
0347             Q_EMIT keyRemoved(key);
0348         }
0349     }
0350 
0351     if (xkb != nullptr) {
0352         XkbFreeKeyboard(xkb, 0, true);
0353     }
0354 }
0355 
0356 #include "moc_kmodifierkeyinfoprovider_xcb.cpp"