diff --git a/CMakeLists.txt b/CMakeLists.txt index 85c5913..c12fb4d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,111 +1,111 @@ cmake_minimum_required(VERSION 3.0) set(KF5_VERSION "5.52.0") # handled by release scripts set(KF5_DEP_VERSION "5.52.0") # handled by release scripts project(KGlobalAccel VERSION ${KF5_VERSION}) # ECM setup include(FeatureSummary) find_package(ECM 5.52.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(GenerateExportHeader) include(CMakePackageConfigHelpers) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMAddQch) include(ECMMarkNonGuiExecutable) include(ECMQtDeclareLoggingCategory) include(ECMPoQmTools) option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") ecm_setup_version(PROJECT VARIABLE_PREFIX KGLOBALACCEL VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kglobalaccel_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5GlobalAccelConfigVersion.cmake" SOVERSION 5) # Dependencies set(REQUIRED_QT_VERSION 5.8.0) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED DBus Widgets) include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) # Dependencies of runtime component find_package(KF5Config ${KF5_DEP_VERSION} REQUIRED) find_package(KF5CoreAddons ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Crash ${KF5_DEP_VERSION} REQUIRED) find_package(KF5DBusAddons ${KF5_DEP_VERSION} REQUIRED) find_package(KF5WindowSystem ${KF5_DEP_VERSION} REQUIRED) # no X11 stuff on mac if (NOT APPLE) - find_package(XCB MODULE COMPONENTS XCB KEYSYMS XTEST) + find_package(XCB MODULE COMPONENTS XCB KEYSYMS XTEST XKB) set_package_properties(XCB PROPERTIES DESCRIPTION "X protocol C-language Binding" URL "http://xcb.freedesktop.org" TYPE OPTIONAL ) find_package(X11) endif() set(HAVE_X11 0) if(X11_FOUND AND XCB_XCB_FOUND) set(HAVE_X11 1) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED X11Extras) endif() # Subdirectories if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ecm_install_po_files_as_qm(po) endif() add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) endif() # create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5GlobalAccel") if (BUILD_QCH) ecm_install_qch_export( TARGETS KF5GlobalAccel_QCH FILE KF5GlobalAccelQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5GlobalAccelQchTargets.cmake\")") endif() configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5GlobalAccelConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5GlobalAccelConfig.cmake" PATH_VARS KDE_INSTALL_DBUSINTERFACEDIR INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5GlobalAccelConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5GlobalAccelConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5GlobalAccelTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5GlobalAccelTargets.cmake NAMESPACE KF5:: ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kglobalaccel_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) # contains list of debug categories, for kdebugsettings install(FILES kglobalaccel.categories DESTINATION ${KDE_INSTALL_CONFDIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/runtime/plugins/CMakeLists.txt b/src/runtime/plugins/CMakeLists.txt index 316b9bf..29e45d4 100644 --- a/src/runtime/plugins/CMakeLists.txt +++ b/src/runtime/plugins/CMakeLists.txt @@ -1,9 +1,9 @@ -if (${XCB_XCB_FOUND} AND ${XCB_KEYSYMS_FOUND}) +if (${XCB_XCB_FOUND} AND ${XCB_KEYSYMS_FOUND} AND ${XCB_XKB_FOUND}) add_subdirectory(xcb) endif() # if (APPLE) # add_subdirectory(osx) # endif() # if (WIN32) # add_subdirectory(windows) # endif() diff --git a/src/runtime/plugins/xcb/CMakeLists.txt b/src/runtime/plugins/xcb/CMakeLists.txt index 45bf4dc..b76477f 100644 --- a/src/runtime/plugins/xcb/CMakeLists.txt +++ b/src/runtime/plugins/xcb/CMakeLists.txt @@ -1,18 +1,19 @@ set(xcb_plugin_SRCS kglobalaccel_x11.cpp ../../logging.cpp ) add_library(KF5GlobalAccelPrivateXcb MODULE ${xcb_plugin_SRCS}) target_link_libraries(KF5GlobalAccelPrivateXcb KF5GlobalAccelPrivate XCB::XCB XCB::KEYSYMS + XCB::XKB ) install( TARGETS KF5GlobalAccelPrivateXcb DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kglobalaccel5.platforms/ ) diff --git a/src/runtime/plugins/xcb/kglobalaccel_x11.cpp b/src/runtime/plugins/xcb/kglobalaccel_x11.cpp index 9b75ec4..cdee881 100644 --- a/src/runtime/plugins/xcb/kglobalaccel_x11.cpp +++ b/src/runtime/plugins/xcb/kglobalaccel_x11.cpp @@ -1,280 +1,314 @@ /* This file is part of the KDE libraries Copyright (C) 2001,2002 Ellis Whitehead Copyright (C) 2013 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kglobalaccel_x11.h" #include "logging_p.h" #include "kkeyserver.h" #include #include #include #include #include #include // xcb + +// It uses "explicit" as a variable name, which is not allowed in C++ +#define explicit xcb_explicit #include #include - +#include +#undef explicit // g_keyModMaskXAccel // mask of modifiers which can be used in shortcuts // (meta, alt, ctrl, shift) // g_keyModMaskXOnOrOff // mask of modifiers where we don't care whether they are on or off // (caps lock, num lock, scroll lock) static uint g_keyModMaskXAccel = 0; static uint g_keyModMaskXOnOrOff = 0; static void calculateGrabMasks() { g_keyModMaskXAccel = KKeyServer::accelModMaskX(); g_keyModMaskXOnOrOff = KKeyServer::modXLock() | KKeyServer::modXNumLock() | KKeyServer::modXScrollLock() | KKeyServer::modXModeSwitch(); //qCDebug(KGLOBALACCELD) << "g_keyModMaskXAccel = " << g_keyModMaskXAccel // << "g_keyModMaskXOnOrOff = " << g_keyModMaskXOnOrOff << endl; } //---------------------------------------------------- KGlobalAccelImpl::KGlobalAccelImpl(QObject *parent) : KGlobalAccelInterface(parent) , m_keySymbols(nullptr) + , m_xkb_first_event(0) { + Q_ASSERT(QX11Info::connection()); + + const xcb_query_extension_reply_t *reply = xcb_get_extension_data(QX11Info::connection(), &xcb_xkb_id); + if (reply && reply->present) { + m_xkb_first_event = reply->first_event; + } + calculateGrabMasks(); - if (QX11Info::isPlatformX11()) { - m_keySymbols = xcb_key_symbols_alloc(QX11Info::connection()); - } } KGlobalAccelImpl::~KGlobalAccelImpl() { if (m_keySymbols) { xcb_key_symbols_free(m_keySymbols); } } bool KGlobalAccelImpl::grabKey( int keyQt, bool grab ) { //grabKey is called during shutdown //shutdown might be due to the X server being killed //if so, fail immediately before trying to make other xcb calls if (!QX11Info::connection() || xcb_connection_has_error(QX11Info::connection())) { return false; } if (!m_keySymbols) { - return false; + m_keySymbols = xcb_key_symbols_alloc(QX11Info::connection()); + if (!m_keySymbols) { + return false; + } } + if( !keyQt ) { qCDebug(KGLOBALACCELD) << "Tried to grab key with null code."; return false; } uint keyModX; xcb_keysym_t keySymX; // Resolve the modifier if( !KKeyServer::keyQtToModX(keyQt, &keyModX) ) { qCDebug(KGLOBALACCELD) << "keyQt (0x" << hex << keyQt << ") failed to resolve to x11 modifier"; return false; } // Resolve the X symbol if( !KKeyServer::keyQtToSymX(keyQt, (int *)&keySymX) ) { qCDebug(KGLOBALACCELD) << "keyQt (0x" << hex << keyQt << ") failed to resolve to x11 keycode"; return false; } xcb_keycode_t *keyCodes = xcb_key_symbols_get_keycode(m_keySymbols, keySymX); if (!keyCodes) { return false; } int i = 0; bool success = !grab; while (keyCodes[i] != XCB_NO_SYMBOL) { xcb_keycode_t keyCodeX = keyCodes[i++]; // Check if shift needs to be added to the grab since KKeySequenceWidget // can remove shift for some keys. (all the %&* and such) if( !(keyQt & Qt::SHIFT) && !KKeyServer::isShiftAsModifierAllowed( keyQt ) && !(keyQt & Qt::KeypadModifier) && keySymX != xcb_key_symbols_get_keysym(m_keySymbols, keyCodeX, 0) && keySymX == xcb_key_symbols_get_keysym(m_keySymbols, keyCodeX, 1) ) { qCDebug(KGLOBALACCELD) << "adding shift to the grab"; keyModX |= KKeyServer::modXShift(); } keyModX &= g_keyModMaskXAccel; // Get rid of any non-relevant bits in mod if( !keyCodeX ) { qCDebug(KGLOBALACCELD) << "keyQt (0x" << hex << keyQt << ") was resolved to x11 keycode 0"; continue; } // We'll have to grab 8 key modifier combinations in order to cover all // combinations of CapsLock, NumLock, ScrollLock. // Does anyone with more X-savvy know how to set a mask on QX11Info::appRootWindow so that // the irrelevant bits are always ignored and we can just make one XGrabKey // call per accelerator? -- ellis #ifndef NDEBUG QString sDebug = QString("\tcode: 0x%1 state: 0x%2 | ").arg(keyCodeX,0,16).arg(keyModX,0,16); #endif uint keyModMaskX = ~g_keyModMaskXOnOrOff; QVector cookies; for( uint irrelevantBitsMask = 0; irrelevantBitsMask <= 0xff; irrelevantBitsMask++ ) { if( (irrelevantBitsMask & keyModMaskX) == 0 ) { #ifndef NDEBUG sDebug += QString("0x%3, ").arg(irrelevantBitsMask, 0, 16); #endif if( grab ) cookies << xcb_grab_key_checked(QX11Info::connection(), true, QX11Info::appRootWindow(), keyModX | irrelevantBitsMask, keyCodeX, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_SYNC); else cookies << xcb_ungrab_key_checked(QX11Info::connection(), keyCodeX, QX11Info::appRootWindow(), keyModX | irrelevantBitsMask); } } bool failed = false; if( grab ) { for (int i = 0; i < cookies.size(); ++i) { QScopedPointer error(xcb_request_check(QX11Info::connection(), cookies.at(i))); if (!error.isNull()) { failed = true; } } if( failed ) { qCDebug(KGLOBALACCELD) << "grab failed!\n"; for( uint m = 0; m <= 0xff; m++ ) { if(( m & keyModMaskX ) == 0 ) xcb_ungrab_key(QX11Info::connection(), keyCodeX, QX11Info::appRootWindow(), keyModX | m); } } else { success = true; } } } free(keyCodes); return success; } bool KGlobalAccelImpl::nativeEventFilter(const QByteArray &eventType, void *message, long *) { if (eventType != "xcb_generic_event_t") { return false; } xcb_generic_event_t *event = reinterpret_cast(message); const uint8_t responseType = event->response_type & ~0x80; - switch (responseType) { - case XCB_MAPPING_NOTIFY: - qCDebug(KGLOBALACCELD) << "Got XMappingNotify event"; - xcb_refresh_keyboard_mapping(m_keySymbols, reinterpret_cast(event)); - x11MappingNotify(); - return true; - - case XCB_KEY_PRESS: + if (responseType == XCB_MAPPING_NOTIFY) { + x11MappingNotify(); + + // Make sure to let Qt handle it as well + return false; + } else if (responseType == XCB_KEY_PRESS) { #ifdef KDEDGLOBALACCEL_TRACE - qCDebug(KGLOBALACCELD) << "Got XKeyPress event"; + qCDebug(KGLOBALACCELD) << "Got XKeyPress event"; #endif - return x11KeyPress(reinterpret_cast(event)); + return x11KeyPress(reinterpret_cast(event)); + } else if (m_xkb_first_event && responseType == m_xkb_first_event) { + const uint8_t xkbEvent = event->pad0; + switch (xkbEvent) { + case XCB_XKB_MAP_NOTIFY: + x11MappingNotify(); + break; + case XCB_XKB_NEW_KEYBOARD_NOTIFY: { + const xcb_xkb_new_keyboard_notify_event_t *ev = + reinterpret_cast(event); + if (ev->changed & XCB_XKB_NKN_DETAIL_KEYCODES) + x11MappingNotify(); + break; + } + default: + break; + } - default: - // We get all XEvents. Just ignore them. - return false; + // Make sure to let Qt handle it as well + return false; + } else { + // We get all XEvents. Just ignore them. + return false; } - - Q_ASSERT(false); - return false; } void KGlobalAccelImpl::x11MappingNotify() { + qCDebug(KGLOBALACCELD) << "Got XMappingNotify event"; + // Maybe the X modifier map has been changed. // uint oldKeyModMaskXAccel = g_keyModMaskXAccel; // uint oldKeyModMaskXOnOrOff = g_keyModMaskXOnOrOff; // First ungrab all currently grabbed keys. This is needed because we // store the keys as qt keycodes and use KKeyServer to map them to x11 key // codes. After calling KKeyServer::initializeMods() they could map to // different keycodes. ungrabKeys(); + if (m_keySymbols) { + // Force reloading of the keySym mapping + xcb_key_symbols_free(m_keySymbols); + m_keySymbols = nullptr; + } + KKeyServer::initializeMods(); calculateGrabMasks(); grabKeys(); } bool KGlobalAccelImpl::x11KeyPress(xcb_key_press_event_t *pEvent) { if (QWidget::keyboardGrabber() || QApplication::activePopupWidget()) { qCWarning(KGLOBALACCELD) << "kglobalacceld should be popup and keyboard grabbing free!"; } // Keyboard needs to be ungrabed after XGrabKey() activates the grab, // otherwise it becomes frozen. xcb_connection_t *c = QX11Info::connection(); xcb_void_cookie_t cookie = xcb_ungrab_keyboard_checked(c, XCB_TIME_CURRENT_TIME); xcb_flush(c); // xcb_flush() only makes sure that the ungrab keyboard request has been // sent, but is not enough to make sure that request has been fulfilled. Use // xcb_request_check() to make sure that the request has been processed. xcb_request_check(c, cookie); int keyQt; if (!KKeyServer::xcbKeyPressEventToQt(pEvent, &keyQt)) { qCWarning(KGLOBALACCELD) << "KKeyServer::xcbKeyPressEventToQt failed"; return false; } //qDebug() << "keyQt=" << QString::number(keyQt, 16); // All that work for this hey... argh... if (NET::timestampCompare(pEvent->time, QX11Info::appTime()) > 0) { QX11Info::setAppTime(pEvent->time); } return keyPressed(keyQt); } void KGlobalAccelImpl::setEnabled( bool enable ) { if (enable && qApp->platformName() == QLatin1String("xcb")) { qApp->installNativeEventFilter(this); } else { qApp->removeNativeEventFilter(this); } } void KGlobalAccelImpl::syncX() { xcb_connection_t *c = QX11Info::connection(); auto *value = xcb_get_input_focus_reply(c, xcb_get_input_focus_unchecked(c), nullptr); free(value); } diff --git a/src/runtime/plugins/xcb/kglobalaccel_x11.h b/src/runtime/plugins/xcb/kglobalaccel_x11.h index 4011719..1ed75d1 100644 --- a/src/runtime/plugins/xcb/kglobalaccel_x11.h +++ b/src/runtime/plugins/xcb/kglobalaccel_x11.h @@ -1,82 +1,83 @@ /* This file is part of the KDE libraries Copyright (C) 2001,2002 Ellis Whitehead This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KGLOBALACCEL_X11_H #define _KGLOBALACCEL_X11_H #include "../../kglobalaccel_interface.h" #include #include struct xcb_key_press_event_t; typedef struct _XCBKeySymbols xcb_key_symbols_t; /** * @internal * * The KGlobalAccel private class handles grabbing of global keys, * and notification of when these keys are pressed. */ class KGlobalAccelImpl : public KGlobalAccelInterface, public QAbstractNativeEventFilter { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.kglobalaccel5.KGlobalAccelInterface" FILE "xcb.json") Q_INTERFACES(KGlobalAccelInterface) public: KGlobalAccelImpl(QObject *parent = nullptr); virtual ~KGlobalAccelImpl(); public: /** * This function registers or unregisters a certain key for global capture, * depending on \b grab. * * Before destruction, every grabbed key will be released, so this * object does not need to do any tracking. * * \param key the Qt keycode to grab or release. * \param grab true to grab they key, false to release the key. * * \return true if successful, otherwise false. */ bool grabKey(int key, bool grab) override; /// Enable/disable all shortcuts. There will not be any grabbed shortcuts at this point. void setEnabled(bool) override; bool nativeEventFilter(const QByteArray &eventType, void *message, long *) override; static void syncX(); private: /** * Filters X11 events ev for key bindings in the accelerator dictionary. * If a match is found the activated activated is emitted and the function * returns true. Return false if the event is not processed. * * This is public for compatibility only. You do not need to call it. */ void x11MappingNotify(); bool x11KeyPress(xcb_key_press_event_t *event); xcb_key_symbols_t *m_keySymbols; + uint8_t m_xkb_first_event; }; #endif // _KGLOBALACCEL_X11_H