File indexing completed on 2024-11-24 04:55:40
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2001, 2002 Ellis Whitehead <ellis@kde.org> 0004 SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "kglobalaccel_x11.h" 0010 0011 #include "logging_p.h" 0012 #include <KKeyServer> 0013 #include <netwm.h> 0014 0015 #include <QDebug> 0016 #include <QSocketNotifier> 0017 0018 #include <QApplication> 0019 #include <QWidget> 0020 #include <private/qtx11extras_p.h> 0021 0022 #include <X11/keysym.h> 0023 0024 // xcb 0025 0026 // It uses "explicit" as a variable name, which is not allowed in C++ 0027 #define explicit xcb_explicit 0028 #include <xcb/record.h> 0029 #include <xcb/xcb.h> 0030 #include <xcb/xcb_keysyms.h> 0031 #include <xcb/xcbext.h> 0032 #include <xcb/xkb.h> 0033 #undef explicit 0034 0035 // g_keyModMaskXAccel 0036 // mask of modifiers which can be used in shortcuts 0037 // (meta, alt, ctrl, shift) 0038 // g_keyModMaskXOnOrOff 0039 // mask of modifiers where we don't care whether they are on or off 0040 // (caps lock, num lock, scroll lock) 0041 static uint g_keyModMaskXAccel = 0; 0042 static uint g_keyModMaskXOnOrOff = 0; 0043 0044 static void calculateGrabMasks() 0045 { 0046 g_keyModMaskXAccel = KKeyServer::accelModMaskX(); 0047 g_keyModMaskXOnOrOff = KKeyServer::modXLock() | KKeyServer::modXNumLock() | KKeyServer::modXScrollLock() | KKeyServer::modXModeSwitch(); 0048 // qCDebug(KGLOBALACCELD) << "g_keyModMaskXAccel = " << g_keyModMaskXAccel 0049 // << "g_keyModMaskXOnOrOff = " << g_keyModMaskXOnOrOff << endl; 0050 } 0051 0052 //---------------------------------------------------- 0053 0054 KGlobalAccelImpl::KGlobalAccelImpl(QObject *parent) 0055 : KGlobalAccelInterface(parent) 0056 , m_keySymbols(nullptr) 0057 , m_xkb_first_event(0) 0058 { 0059 Q_ASSERT(QX11Info::connection()); 0060 0061 int events = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE; 0062 xcb_change_window_attributes(QX11Info::connection(), QX11Info::appRootWindow(), XCB_CW_EVENT_MASK, &events); 0063 0064 const xcb_query_extension_reply_t *reply = xcb_get_extension_data(QX11Info::connection(), &xcb_xkb_id); 0065 if (reply && reply->present) { 0066 m_xkb_first_event = reply->first_event; 0067 } 0068 0069 // We use XRecord to get the released keys because we need a way to get notified about 0070 // them without needing to hold a grab 0071 // Holding a grab would be a problem for the cases when a process (looking at you KWin's 0072 // toolbox) replies to a global shortcut trigger with another grab 0073 m_display = XOpenDisplay(nullptr); 0074 auto connection = xcb_connect(XDisplayString((Display *)m_display), nullptr); 0075 auto context = xcb_generate_id(connection); 0076 xcb_record_range_t range; 0077 memset(&range, 0, sizeof(range)); 0078 range.device_events.first = XCB_KEY_RELEASE; 0079 range.device_events.last = XCB_KEY_RELEASE; 0080 xcb_record_client_spec_t cs = XCB_RECORD_CS_ALL_CLIENTS; 0081 xcb_record_create_context(connection, context, 0, 1, 1, &cs, &range); 0082 auto cookie = xcb_record_enable_context(connection, context); 0083 xcb_flush(connection); 0084 0085 m_xrecordCookieSequence = cookie.sequence; 0086 0087 auto m_notifier = new QSocketNotifier(xcb_get_file_descriptor(connection), QSocketNotifier::Read, this); 0088 connect(m_notifier, &QSocketNotifier::activated, this, [this, connection] { 0089 xcb_generic_event_t *event; 0090 while ((event = xcb_poll_for_event(connection))) { 0091 std::free(event); 0092 } 0093 0094 xcb_record_enable_context_reply_t *reply = nullptr; 0095 xcb_generic_error_t *error = nullptr; 0096 while (m_xrecordCookieSequence && xcb_poll_for_reply(connection, m_xrecordCookieSequence, (void **)&reply, &error)) { 0097 // xcb_poll_for_reply may set both reply and error to null if connection has error. 0098 // break if xcb_connection has error, no point to continue anyway. 0099 if (xcb_connection_has_error(connection)) { 0100 break; 0101 } 0102 0103 if (error) { 0104 std::free(error); 0105 break; 0106 } 0107 0108 if (!reply) { 0109 continue; 0110 } 0111 0112 QScopedPointer<xcb_record_enable_context_reply_t, QScopedPointerPodDeleter> data(reinterpret_cast<xcb_record_enable_context_reply_t *>(reply)); 0113 xcb_key_press_event_t *events = reinterpret_cast<xcb_key_press_event_t *>(xcb_record_enable_context_data(reply)); 0114 int nEvents = xcb_record_enable_context_data_length(reply) / sizeof(xcb_key_press_event_t); 0115 for (xcb_key_press_event_t *e = events; e < events + nEvents; e++) { 0116 Q_ASSERT(e->response_type == XCB_KEY_RELEASE); 0117 qCDebug(KGLOBALACCELD) << "Got XKeyRelease event"; 0118 x11KeyRelease(e); 0119 } 0120 } 0121 }); 0122 m_notifier->setEnabled(true); 0123 0124 calculateGrabMasks(); 0125 } 0126 0127 KGlobalAccelImpl::~KGlobalAccelImpl() 0128 { 0129 XCloseDisplay((Display *)m_display); 0130 if (m_keySymbols) { 0131 xcb_key_symbols_free(m_keySymbols); 0132 } 0133 } 0134 0135 bool KGlobalAccelImpl::grabKey(int keyQt, bool grab) 0136 { 0137 // grabKey is called during shutdown 0138 // shutdown might be due to the X server being killed 0139 // if so, fail immediately before trying to make other xcb calls 0140 if (!QX11Info::connection() || xcb_connection_has_error(QX11Info::connection())) { 0141 return false; 0142 } 0143 0144 if (!m_keySymbols) { 0145 m_keySymbols = xcb_key_symbols_alloc(QX11Info::connection()); 0146 if (!m_keySymbols) { 0147 return false; 0148 } 0149 } 0150 0151 if (!keyQt) { 0152 qCDebug(KGLOBALACCELD) << "Tried to grab key with null code."; 0153 return false; 0154 } 0155 0156 uint keyModX; 0157 0158 // Resolve the modifier 0159 if (!KKeyServer::keyQtToModX(keyQt, &keyModX)) { 0160 qCDebug(KGLOBALACCELD) << "keyQt (0x" << Qt::hex << keyQt << ") failed to resolve to x11 modifier"; 0161 return false; 0162 } 0163 0164 // Resolve the X symbol 0165 const QList<int> keySymXs(KKeyServer::keyQtToSymXs(keyQt)); 0166 if (keySymXs.empty()) { 0167 qCDebug(KGLOBALACCELD) << "keyQt (0x" << Qt::hex << keyQt << ") failed to resolve to x11 keycode"; 0168 return false; 0169 } 0170 xcb_keycode_t *keyCodes = nullptr; 0171 xcb_keysym_t keySymX; 0172 for (xcb_keysym_t sym : keySymXs) { 0173 keyCodes = xcb_key_symbols_get_keycode(m_keySymbols, sym); 0174 if (keyCodes) { 0175 keySymX = sym; 0176 break; 0177 } 0178 } 0179 0180 if (!keyCodes) { 0181 return false; 0182 } 0183 int i = 0; 0184 bool success = !grab; 0185 while (keyCodes[i] != XCB_NO_SYMBOL) { 0186 xcb_keycode_t keyCodeX = keyCodes[i++]; 0187 0188 // Check if shift needs to be added to the grab since KKeySequenceWidget 0189 // can remove shift for some keys. (all the %&* and such) 0190 /* clang-format off */ 0191 if (!(keyQt & Qt::SHIFT) 0192 && !KKeyServer::isShiftAsModifierAllowed(keyQt) 0193 && !(keyQt & Qt::KeypadModifier) 0194 && keySymX != xcb_key_symbols_get_keysym(m_keySymbols, keyCodeX, 0) 0195 && keySymX == xcb_key_symbols_get_keysym(m_keySymbols, keyCodeX, 1)) { /* clang-format on */ 0196 qCDebug(KGLOBALACCELD) << "adding shift to the grab"; 0197 keyModX |= KKeyServer::modXShift(); 0198 } 0199 0200 keyModX &= g_keyModMaskXAccel; // Get rid of any non-relevant bits in mod 0201 0202 if (!keyCodeX) { 0203 qCDebug(KGLOBALACCELD) << "keyQt (0x" << Qt::hex << keyQt << ") was resolved to x11 keycode 0"; 0204 continue; 0205 } 0206 0207 // We'll have to grab 8 key modifier combinations in order to cover all 0208 // combinations of CapsLock, NumLock, ScrollLock. 0209 // Does anyone with more X-savvy know how to set a mask on QX11Info::appRootWindow so that 0210 // the irrelevant bits are always ignored and we can just make one XGrabKey 0211 // call per accelerator? -- ellis 0212 #ifndef NDEBUG 0213 QString sDebug = QStringLiteral("\tcode: 0x%1 state: 0x%2 | ").arg(keyCodeX, 0, 16).arg(keyModX, 0, 16); 0214 #endif 0215 uint keyModMaskX = ~g_keyModMaskXOnOrOff; 0216 QList<xcb_void_cookie_t> cookies; 0217 for (uint irrelevantBitsMask = 0; irrelevantBitsMask <= 0xff; irrelevantBitsMask++) { 0218 if ((irrelevantBitsMask & keyModMaskX) == 0) { 0219 #ifndef NDEBUG 0220 sDebug += QStringLiteral("0x%3, ").arg(irrelevantBitsMask, 0, 16); 0221 #endif 0222 if (grab) { 0223 cookies << xcb_grab_key_checked(QX11Info::connection(), 0224 true, 0225 QX11Info::appRootWindow(), 0226 keyModX | irrelevantBitsMask, 0227 keyCodeX, 0228 XCB_GRAB_MODE_ASYNC, 0229 XCB_GRAB_MODE_SYNC); 0230 } else { 0231 /* clang-format off */ 0232 cookies << xcb_ungrab_key_checked(QX11Info::connection(), 0233 keyCodeX, QX11Info::appRootWindow(), 0234 keyModX | irrelevantBitsMask); 0235 /* clang-format on */ 0236 } 0237 } 0238 } 0239 0240 bool failed = false; 0241 if (grab) { 0242 for (int i = 0; i < cookies.size(); ++i) { 0243 QScopedPointer<xcb_generic_error_t, QScopedPointerPodDeleter> error(xcb_request_check(QX11Info::connection(), cookies.at(i))); 0244 if (!error.isNull()) { 0245 failed = true; 0246 } 0247 } 0248 if (failed) { 0249 qCDebug(KGLOBALACCELD) << "grab failed!\n"; 0250 for (uint m = 0; m <= 0xff; m++) { 0251 if ((m & keyModMaskX) == 0) { 0252 xcb_ungrab_key(QX11Info::connection(), keyCodeX, QX11Info::appRootWindow(), keyModX | m); 0253 } 0254 } 0255 } else { 0256 success = true; 0257 } 0258 } 0259 } 0260 free(keyCodes); 0261 return success; 0262 } 0263 0264 bool KGlobalAccelImpl::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) 0265 { 0266 if (eventType != "xcb_generic_event_t") { 0267 return false; 0268 } 0269 xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(message); 0270 const uint8_t responseType = event->response_type & ~0x80; 0271 if (responseType == XCB_MAPPING_NOTIFY) { 0272 x11MappingNotify(); 0273 0274 // Make sure to let Qt handle it as well 0275 return false; 0276 } else if (responseType == XCB_KEY_PRESS) { 0277 qCDebug(KGLOBALACCELD) << "Got XKeyPress event"; 0278 return x11KeyPress(reinterpret_cast<xcb_key_press_event_t *>(event)); 0279 } else if (m_xkb_first_event && responseType == m_xkb_first_event) { 0280 const uint8_t xkbEvent = event->pad0; 0281 switch (xkbEvent) { 0282 case XCB_XKB_MAP_NOTIFY: 0283 x11MappingNotify(); 0284 break; 0285 case XCB_XKB_NEW_KEYBOARD_NOTIFY: { 0286 const xcb_xkb_new_keyboard_notify_event_t *ev = reinterpret_cast<xcb_xkb_new_keyboard_notify_event_t *>(event); 0287 if (ev->changed & XCB_XKB_NKN_DETAIL_KEYCODES) { 0288 x11MappingNotify(); 0289 } 0290 break; 0291 } 0292 default: 0293 break; 0294 } 0295 0296 // Make sure to let Qt handle it as well 0297 return false; 0298 } else { 0299 // We get all XEvents. Just ignore them. 0300 return false; 0301 } 0302 } 0303 0304 void KGlobalAccelImpl::x11MappingNotify() 0305 { 0306 qCDebug(KGLOBALACCELD) << "Got XMappingNotify event"; 0307 0308 // Maybe the X modifier map has been changed. 0309 // uint oldKeyModMaskXAccel = g_keyModMaskXAccel; 0310 // uint oldKeyModMaskXOnOrOff = g_keyModMaskXOnOrOff; 0311 0312 // First ungrab all currently grabbed keys. This is needed because we 0313 // store the keys as qt keycodes and use KKeyServer to map them to x11 key 0314 // codes. After calling KKeyServer::initializeMods() they could map to 0315 // different keycodes. 0316 ungrabKeys(); 0317 0318 if (m_keySymbols) { 0319 // Force reloading of the keySym mapping 0320 xcb_key_symbols_free(m_keySymbols); 0321 m_keySymbols = nullptr; 0322 } 0323 0324 KKeyServer::initializeMods(); 0325 calculateGrabMasks(); 0326 0327 grabKeys(); 0328 } 0329 0330 bool KGlobalAccelImpl::x11KeyPress(xcb_key_press_event_t *pEvent) 0331 { 0332 if (QWidget::keyboardGrabber() || QApplication::activePopupWidget()) { 0333 qCWarning(KGLOBALACCELD) << "kglobalacceld should be popup and keyboard grabbing free!"; 0334 } 0335 0336 // Keyboard needs to be ungrabed after XGrabKey() activates the grab, 0337 // otherwise it becomes frozen. 0338 xcb_connection_t *c = QX11Info::connection(); 0339 xcb_void_cookie_t cookie = xcb_ungrab_keyboard_checked(c, XCB_TIME_CURRENT_TIME); 0340 xcb_flush(c); 0341 // xcb_flush() only makes sure that the ungrab keyboard request has been 0342 // sent, but is not enough to make sure that request has been fulfilled. Use 0343 // xcb_request_check() to make sure that the request has been processed. 0344 xcb_request_check(c, cookie); 0345 0346 int keyQt; 0347 if (!KKeyServer::xcbKeyPressEventToQt(pEvent, &keyQt)) { 0348 qCWarning(KGLOBALACCELD) << "KKeyServer::xcbKeyPressEventToQt failed"; 0349 return false; 0350 } 0351 // qDebug() << "keyQt=" << QString::number(keyQt, 16); 0352 0353 // All that work for this hey... argh... 0354 if (NET::timestampCompare(pEvent->time, QX11Info::appTime()) > 0) { 0355 QX11Info::setAppTime(pEvent->time); 0356 } 0357 return keyPressed(keyQt); 0358 } 0359 0360 bool KGlobalAccelImpl::x11KeyRelease(xcb_key_press_event_t *pEvent) 0361 { 0362 if (QWidget::keyboardGrabber() || QApplication::activePopupWidget()) { 0363 qCWarning(KGLOBALACCELD) << "kglobalacceld should be popup and keyboard grabbing free!"; 0364 } 0365 0366 int keyQt; 0367 if (!KKeyServer::xcbKeyPressEventToQt(pEvent, &keyQt)) { 0368 return false; 0369 } 0370 return keyReleased(keyQt); 0371 } 0372 0373 void KGlobalAccelImpl::setEnabled(bool enable) 0374 { 0375 if (enable && qApp->platformName() == QLatin1String("xcb")) { 0376 qApp->installNativeEventFilter(this); 0377 } else { 0378 qApp->removeNativeEventFilter(this); 0379 } 0380 }