diff --git a/CMakeLists.txt b/CMakeLists.txt index 3314467..5034224 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,111 +1,111 @@ cmake_minimum_required(VERSION 3.5) set(KF5_VERSION "5.64.0") # handled by release scripts set(KF5_DEP_VERSION "5.63.0") # handled by release scripts project(KGlobalAccel VERSION ${KF5_VERSION}) # ECM setup include(FeatureSummary) find_package(ECM 5.63.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}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) -include(GenerateExportHeader) +include(ECMGenerateExportHeader) include(CMakePackageConfigHelpers) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMAddQch) include(ECMMarkNonGuiExecutable) include(ECMQtDeclareLoggingCategory) - - include(ECMPoQmTools) +set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].") + 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.11.0) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED DBus Widgets) # 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 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() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050d00) # Subdirectories if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ecm_install_po_files_as_qm(po) endif() add_definitions(-DQT_NO_FOREACH) 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_LOGGINGCATEGORIESDIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/autotests/kglobalshortcuttest.cpp b/autotests/kglobalshortcuttest.cpp index 10da517..aad6635 100644 --- a/autotests/kglobalshortcuttest.cpp +++ b/autotests/kglobalshortcuttest.cpp @@ -1,522 +1,522 @@ /* This file is part of the KDE libraries Copyright (c) 2007 Andreas Hartmetz Copyright (c) 2008 Michael Jansen 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 "kglobalshortcuttest.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_XCB_XTEST #include #define XK_MISCELLANY #define XK_XKB_KEYS #include #include #include #endif /* * NOTE * ---- * These tests could be better. They don't include actually triggering actions, * and we just choose very improbable shortcuts to avoid conflicts with real * applications' shortcuts. * */ const QKeySequence sequenceA = QKeySequence(Qt::SHIFT + Qt::META + Qt::CTRL + Qt::ALT + Qt::Key_F12); const QKeySequence sequenceB = QKeySequence(Qt::Key_F29); const QKeySequence sequenceC = QKeySequence(Qt::SHIFT + Qt::META + Qt::CTRL + Qt::Key_F28); const QKeySequence sequenceD = QKeySequence(Qt::META + Qt::ALT + Qt::Key_F30); const QKeySequence sequenceE = QKeySequence(Qt::META + Qt::Key_F29); const QKeySequence sequenceF = QKeySequence(Qt::META + Qt::Key_F27); //we need a GUI so that the implementation can grab keys QTEST_MAIN(KGlobalShortcutTest) void KGlobalShortcutTest::initTestCase() { QStandardPaths::setTestModeEnabled(true); m_daemonInstalled = true; QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface(); if (!bus->isServiceRegistered(QStringLiteral("org.kde.kglobalaccel"))) { QDBusReply reply = bus->startService(QStringLiteral("org.kde.kglobalaccel")); if (!reply.isValid()) { m_daemonInstalled = false; } } } /** * NOTE: This method alters a KDE config file in your home directory. * * KDE4: ~/.kde4/share/config/kglobalshortcutsrc * KF5: ~/.config/kglobalshortcutsrc * * At least on KDE4 this file cannot be modified by hand if the Plasma session * is running. So in case you have to clean the file of spurious entries * (e.g. because of broken or failing test cases) * you have to logout from Plasma. * * The following sections are created and normally removed automatically: * [qttest] */ void KGlobalShortcutTest::setupTest(const QString& id) { QString componentName = "qttest"; if (m_actionA) { KGlobalAccel::self()->removeAllShortcuts(m_actionA); delete m_actionA; } if (m_actionB) { KGlobalAccel::self()->removeAllShortcuts(m_actionB); delete m_actionB; } // Ensure that the previous test did cleanup correctly +#if KGLOBALACCEL_ENABLE_DEPRECATED_SINCE(4, 2) KGlobalAccel *kga = KGlobalAccel::self(); -#ifndef KGLOBALACCEL_NO_DEPRECATED QList components = kga->allMainComponents(); QStringList componentId; componentId << componentName << QString() << "KDE Test Program" << QString(); // QVERIFY(!components.contains(componentId)); #endif m_actionA = new QAction("Text For Action A", this); m_actionA->setObjectName("Action A:" + id); m_actionA->setProperty("componentName", componentName); m_actionA->setProperty("componentDisplayName", "KDE Test Program"); KGlobalAccel::self()->setShortcut(m_actionA, QList() << sequenceA << sequenceB, KGlobalAccel::NoAutoloading); KGlobalAccel::self()->setDefaultShortcut(m_actionA, QList() << sequenceA << sequenceB, KGlobalAccel::NoAutoloading); m_actionB = new QAction("Text For Action B", this); m_actionB->setObjectName("Action B:" + id); m_actionB->setProperty("componentName", componentName); m_actionA->setProperty("componentDisplayName", "KDE Test Program"); KGlobalAccel::self()->setShortcut(m_actionB, QList(), KGlobalAccel::NoAutoloading); KGlobalAccel::self()->setDefaultShortcut(m_actionB, QList(), KGlobalAccel::NoAutoloading); } void KGlobalShortcutTest::testSetShortcut() { setupTest("testSetShortcut"); if (!m_daemonInstalled) { QSKIP("kglobalaccel not installed"); } // Just ensure that the desired values are set for both actions QList cutA; cutA << sequenceA << sequenceB; QCOMPARE(KGlobalAccel::self()->shortcut(m_actionA), cutA); QCOMPARE(KGlobalAccel::self()->defaultShortcut(m_actionA), cutA); QVERIFY(KGlobalAccel::self()->shortcut(m_actionB).isEmpty()); QVERIFY(KGlobalAccel::self()->defaultShortcut(m_actionB).isEmpty()); } void KGlobalShortcutTest::testActivateShortcut() { #ifdef HAVE_XCB_XTEST if (!QX11Info::isPlatformX11()) { QSKIP("This test can only be run on platform xcb"); } setupTest("testActivateShortcut"); if (!m_daemonInstalled) { QSKIP("kglobalaccel not installed"); } QSignalSpy actionASpy(m_actionA, SIGNAL(triggered(bool))); QVERIFY(actionASpy.isValid()); xcb_connection_t *c = QX11Info::connection(); xcb_window_t w = QX11Info::appRootWindow(); xcb_key_symbols_t *syms = xcb_key_symbols_alloc(c); auto getCode = [syms] (int code) { xcb_keycode_t *keyCodes = xcb_key_symbols_get_keycode(syms, code); const xcb_keycode_t ret = keyCodes[0]; free(keyCodes); return ret; }; const xcb_keycode_t shift = getCode(XK_Shift_L); const xcb_keycode_t meta = getCode(XK_Super_L); const xcb_keycode_t control = getCode(XK_Control_L); const xcb_keycode_t alt = getCode(XK_Alt_L); const xcb_keycode_t f12 = getCode(XK_F12); xcb_key_symbols_free(syms); xcb_test_fake_input(c, XCB_KEY_PRESS, meta, XCB_TIME_CURRENT_TIME, w, 0, 0, 0); xcb_test_fake_input(c, XCB_KEY_PRESS, control, XCB_TIME_CURRENT_TIME, w, 0, 0, 0); xcb_test_fake_input(c, XCB_KEY_PRESS, alt, XCB_TIME_CURRENT_TIME, w, 0, 0, 0); xcb_test_fake_input(c, XCB_KEY_PRESS, shift, XCB_TIME_CURRENT_TIME, w, 0, 0, 0); xcb_test_fake_input(c, XCB_KEY_PRESS, f12, XCB_TIME_CURRENT_TIME, w, 0, 0, 0); xcb_test_fake_input(c, XCB_KEY_RELEASE, f12, XCB_TIME_CURRENT_TIME, w, 0, 0, 0); xcb_test_fake_input(c, XCB_KEY_RELEASE, shift, XCB_TIME_CURRENT_TIME, w, 0, 0, 0); xcb_test_fake_input(c, XCB_KEY_RELEASE, meta, XCB_TIME_CURRENT_TIME, w, 0, 0, 0); xcb_test_fake_input(c, XCB_KEY_RELEASE, control, XCB_TIME_CURRENT_TIME, w, 0, 0, 0); xcb_test_fake_input(c, XCB_KEY_RELEASE, alt, XCB_TIME_CURRENT_TIME, w, 0, 0, 0); xcb_flush(c); QVERIFY(actionASpy.wait()); QCOMPARE(actionASpy.count(), 1); #else QSKIP("This test requires to be compiled with XCB-XTEST"); #endif } // Current state // m_actionA: (sequenceA, sequenceB) // m_actionB: (,) void KGlobalShortcutTest::testFindActionByKey() { // Skip this. The above testcase hasn't changed the actions setupTest("testFindActionByKey"); if (!m_daemonInstalled) { QSKIP("kglobalaccel not installed"); } QList actionId = KGlobalAccel::self()->getGlobalShortcutsByKey(sequenceB); QCOMPARE(actionId.size(), 1); QString actionIdAComponentUniqueName("qttest"); QString actionIdAUniqueName("Action A:testFindActionByKey"); QString actionIdAComponentFriendlyName("KDE Test Program"); QString actionIdAFriendlyName("Text For Action A"); QCOMPARE(actionId.first().componentUniqueName(), actionIdAComponentUniqueName); QCOMPARE(actionId.first().uniqueName(), actionIdAUniqueName); QCOMPARE(actionId.first().componentFriendlyName(), actionIdAComponentFriendlyName); QCOMPARE(actionId.first().friendlyName(), actionIdAFriendlyName); actionId = KGlobalAccel::self()->getGlobalShortcutsByKey(sequenceA); QCOMPARE(actionId.size(), 1); QCOMPARE(actionId.first().componentUniqueName(), actionIdAComponentUniqueName); QCOMPARE(actionId.first().uniqueName(), actionIdAUniqueName); QCOMPARE(actionId.first().componentFriendlyName(), actionIdAComponentFriendlyName); QCOMPARE(actionId.first().friendlyName(), actionIdAFriendlyName); } void KGlobalShortcutTest::testChangeShortcut() { // Skip this. The above testcase hasn't changed the actions setupTest("testChangeShortcut"); if (!m_daemonInstalled) { QSKIP("kglobalaccel not installed"); } // Change the shortcut KGlobalAccel::self()->setShortcut(m_actionA, QList() << sequenceC, KGlobalAccel::NoAutoloading); // Ensure that the change is active QCOMPARE(KGlobalAccel::self()->shortcut(m_actionA), QList() << sequenceC); // We haven't changed the default shortcut, ensure it is unchanged QList cutA; cutA << sequenceA << sequenceB; QCOMPARE(KGlobalAccel::self()->defaultShortcut(m_actionA), cutA); // Try to set a already take shortcut QList cutB; cutB << KGlobalAccel::self()->shortcut(m_actionA).first() << QKeySequence(sequenceE); KGlobalAccel::self()->setShortcut(m_actionB, cutB, KGlobalAccel::NoAutoloading); // Ensure that no change was made to the primary active shortcut QVERIFY(KGlobalAccel::self()->shortcut(m_actionB).first().isEmpty()); // Ensure that the change to the secondary active shortcut was made QCOMPARE(KGlobalAccel::self()->shortcut(m_actionB).at(1), QKeySequence(sequenceE)); // Ensure that the default shortcut is still empty QVERIFY(KGlobalAccel::self()->defaultShortcut(m_actionB).isEmpty()); // unchanged // Only change the active shortcut cutB[0] = sequenceD; KGlobalAccel::self()->setShortcut(m_actionB, cutB, KGlobalAccel::NoAutoloading); // Check that the change went through QCOMPARE(KGlobalAccel::self()->shortcut(m_actionB), cutB); // Check that the default shortcut is not active QVERIFY(KGlobalAccel::self()->defaultShortcut(m_actionB).isEmpty()); // unchanged } void KGlobalShortcutTest::testStealShortcut() { setupTest("testStealShortcut"); if (!m_daemonInstalled) { QSKIP("kglobalaccel not installed"); } // Steal a shortcut from an action. First ensure the initial state is // correct QList cutA; cutA << sequenceA << sequenceB; QCOMPARE(KGlobalAccel::self()->shortcut(m_actionA), cutA); QCOMPARE(KGlobalAccel::self()->defaultShortcut(m_actionA), cutA); KGlobalAccel::stealShortcutSystemwide(sequenceA); //let DBus do its thing in case it happens asynchronously QTest::qWait(200); QList shortcuts = KGlobalAccel::self()->shortcut(m_actionA); QVERIFY(!shortcuts.isEmpty()); QVERIFY(shortcuts.first().isEmpty()); } void KGlobalShortcutTest::testSaveRestore() { setupTest("testSaveRestore"); if (!m_daemonInstalled) { QSKIP("kglobalaccel not installed"); } //It /would be nice/ to test persistent storage. That is not so easy... QList cutA = KGlobalAccel::self()->shortcut(m_actionA); // Delete the action delete m_actionA; // Recreate it m_actionA = new QAction("Text For Action A", this); m_actionA->setObjectName("Action A:testSaveRestore"); m_actionA->setProperty("componentName", "qttest"); m_actionA->setProperty("componentDisplayName", "KDE Test Program"); // Now it's empty QVERIFY(KGlobalAccel::self()->shortcut(m_actionA).isEmpty()); KGlobalAccel::self()->setShortcut(m_actionA, QList()); // Now it's restored QCOMPARE(KGlobalAccel::self()->shortcut(m_actionA), cutA); // And again delete m_actionA; m_actionA = new QAction("Text For Action A", this); m_actionA->setObjectName("Action A:testSaveRestore"); m_actionA->setProperty("componentName", "qttest"); m_actionA->setProperty("componentDisplayName", "KDE Test Program"); KGlobalAccel::self()->setShortcut(m_actionA, QList() << QKeySequence() << (cutA.isEmpty() ? QKeySequence() : cutA.first())); QCOMPARE(KGlobalAccel::self()->shortcut(m_actionA), cutA); } // Duplicated again! enum actionIdFields { ComponentUnique = 0, ActionUnique = 1, ComponentFriendly = 2, ActionFriendly = 3 }; void KGlobalShortcutTest::testListActions() { setupTest("testListActions"); if (!m_daemonInstalled) { QSKIP("kglobalaccel not installed"); } // As in kdebase/workspace/kcontrol/keys/globalshortcuts.cpp +#if KGLOBALACCEL_ENABLE_DEPRECATED_SINCE(4, 2) KGlobalAccel *kga = KGlobalAccel::self(); -#ifndef KGLOBALACCEL_NO_DEPRECATED QList components = kga->allMainComponents(); //qDebug() << components; QStringList componentId; componentId << "qttest" << QString() << "KDE Test Program" << QString(); QVERIFY(components.contains(componentId)); #endif -#ifndef KGLOBALACCEL_NO_DEPRECATED +#if KGLOBALACCEL_ENABLE_DEPRECATED_SINCE(4, 2) QList actions = kga->allActionsForComponent(componentId); QVERIFY(!actions.isEmpty()); QStringList actionIdA; actionIdA << "qttest" << "Action A:testListActions" << "KDE Test Program" << "Text For Action A"; QStringList actionIdB; actionIdB << "qttest" << "Action B:testListActions" << "KDE Test Program" << "Text For Action B"; //qDebug() << actions; QVERIFY(actions.contains(actionIdA)); QVERIFY(actions.contains(actionIdB)); #endif } void KGlobalShortcutTest::testComponentAssignment() { // We don't use them here // setupTest(); QString otherComponent("test_component1"); QList cutB; /************************************************************ * Ensure that the actions get a correct component assigned * ************************************************************/ // Action without component name get the global component { QAction action("Text For Action A", nullptr); action.setObjectName("Action C"); QCOMPARE(action.property("componentName").toString(), QString()); KGlobalAccel::self()->setShortcut(&action, cutB, KGlobalAccel::NoAutoloading); QCOMPARE(action.property("componentName").toString(), QString()); // cleanup KGlobalAccel::self()->removeAllShortcuts(&action); } // Action with component name keeps its component name { QAction action("Text for Action C", nullptr); action.setObjectName("Action C"); action.setProperty("componentName", otherComponent); QCOMPARE(action.property("componentName").toString(), otherComponent); KGlobalAccel::self()->setShortcut(&action, cutB, KGlobalAccel::NoAutoloading); QCOMPARE(action.property("componentName").toString(), otherComponent); // cleanup KGlobalAccel::self()->removeAllShortcuts(&action); } } void KGlobalShortcutTest::testConfigurationActions() { setupTest("testConfigurationActions"); if (!m_daemonInstalled) { QSKIP("kglobalaccel not installed"); } // Create a configuration action QAction cfg_action("Text For Action A", nullptr); cfg_action.setObjectName("Action A:testConfigurationActions"); cfg_action.setProperty("isConfigurationAction", true); cfg_action.setProperty("componentName", "qttest"); cfg_action.setProperty("componentDisplayName", "KDE Test Program"); KGlobalAccel::self()->setShortcut(&cfg_action, QList()); // Check that the configuration action has the correct shortcuts QCOMPARE(KGlobalAccel::self()->shortcut(m_actionA), KGlobalAccel::self()->shortcut(&cfg_action)); // TODO: // - change shortcut from configuration action and test for // yourShortcutGotChanged // - Ensure that the config action doesn't trigger(how?) // - Ensure that the original action is still working when the // configuration action is deleted } void KGlobalShortcutTest::testOverrideMainComponentData() { setupTest("testOverrideMainComponentData"); QString otherComponent("test_component1"); QList cutB; // Action without component name QAction *action = new QAction("Text For Action A", this); QCOMPARE(action->property("componentName").toString(), QString()); action->setObjectName("Action A"); KGlobalAccel::self()->setShortcut(action, cutB, KGlobalAccel::NoAutoloading); QCOMPARE(action->property("componentName").toString(), QString()); // Action with component name KGlobalAccel::self()->removeAllShortcuts(action); delete action; action = new QAction("Text For Action A", this); action->setObjectName("Action A"); action->setProperty("componentName", otherComponent); QCOMPARE(action->property("componentName").toString(), otherComponent); KGlobalAccel::self()->setShortcut(action, cutB, KGlobalAccel::NoAutoloading); QCOMPARE(action->property("componentName").toString(), otherComponent); // cleanup KGlobalAccel::self()->removeAllShortcuts(action); } void KGlobalShortcutTest::testNotification() { setupTest("testNotification"); // Action without component name QAction *action = new QAction("Text For Action A", this); QCOMPARE(action->property("componentName").toString(), QString()); action->setObjectName("Action A"); QList cutB; KGlobalAccel::self()->setShortcut(action, cutB, KGlobalAccel::NoAutoloading); QCOMPARE(action->property("componentName").toString(), QString()); // kglobalacceld collects registrations and shows the together. Give it // time to kick in. QThread::sleep(2); KGlobalAccel::self()->removeAllShortcuts(action); } void KGlobalShortcutTest::testGetGlobalShortcut() { setupTest("testLoadShortcutFromGlobalSettings"); // cleanup see testForgetGlobalShortcut if (!m_daemonInstalled) { QSKIP("kglobalaccel not installed"); } // retrieve shortcut list auto shortcutList = KGlobalAccel::self()->globalShortcut("qttest", "Action A:testLoadShortcutFromGlobalSettings"); QCOMPARE(shortcutList.count(), 2); // see setupTest // test for a real shortcut: // shortcutList = KGlobalAccel::self()->shortcut("kwin", "Kill Window"); // QCOMPARE(shortcutList.count(), 1); } void KGlobalShortcutTest::testForgetGlobalShortcut() { setupTest("testForgetGlobalShortcut"); // Ensure that forgetGlobalShortcut can be called on any action. QAction a("Test", nullptr); KGlobalAccel::self()->removeAllShortcuts(&a); if (!m_daemonInstalled) { QSKIP("kglobalaccel not installed"); } // We forget these two shortcuts and check that the component is gone // after that. If not it can mean the forgetGlobalShortcut() call is // broken OR someone messed up these tests to leave an additional global // shortcut behind. KGlobalAccel::self()->removeAllShortcuts(m_actionB); KGlobalAccel::self()->removeAllShortcuts(m_actionA); // kglobalaccel writes asynchronous. QThread::sleep(1); +#if KGLOBALACCEL_ENABLE_DEPRECATED_SINCE(4, 2) KGlobalAccel *kga = KGlobalAccel::self(); -#ifndef KGLOBALACCEL_NO_DEPRECATED QList components = kga->allMainComponents(); QStringList componentId; componentId << "qttest" << QString() << "KDE Test Program" << QString(); QVERIFY(!components.contains(componentId)); #endif } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d5f1dca..e40281b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,91 +1,98 @@ configure_file(config-kglobalaccel.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kglobalaccel.h ) set(kglobalaccel_SRCS kglobalaccel.cpp kglobalshortcutinfo.cpp kglobalshortcutinfo_dbus.cpp ) ecm_qt_declare_logging_category(kglobalaccel_SRCS HEADER kglobalaccel_debug.h IDENTIFIER KGLOBALACCEL_LOG CATEGORY_NAME kf5.kglobalaccel) ecm_create_qm_loader(kglobalaccel_SRCS kglobalaccel5_qt) set(kglobalaccel_xml ${CMAKE_CURRENT_SOURCE_DIR}/org.kde.KGlobalAccel.xml) set_source_files_properties(${kglobalaccel_xml} PROPERTIES INCLUDE "kglobalshortcutinfo_p.h") qt5_add_dbus_interface(kglobalaccel_SRCS ${kglobalaccel_xml} kglobalaccel_interface ) install(FILES ${kglobalaccel_xml} DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} RENAME kf5_org.kde.KGlobalAccel.xml) set(kglobalaccel_component_xml ${CMAKE_CURRENT_SOURCE_DIR}/org.kde.kglobalaccel.Component.xml) set_source_files_properties(${kglobalaccel_component_xml} PROPERTIES INCLUDE "kglobalshortcutinfo_p.h") qt5_add_dbus_interface(kglobalaccel_SRCS ${kglobalaccel_component_xml} kglobalaccel_component_interface ) install(FILES ${kglobalaccel_component_xml} DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} RENAME kf5_org.kde.kglobalaccel.Component.xml) add_library(KF5GlobalAccel ${kglobalaccel_SRCS}) -generate_export_header(KF5GlobalAccel BASE_NAME KGlobalAccel) add_library(KF5::GlobalAccel ALIAS KF5GlobalAccel) +ecm_generate_export_header(KF5GlobalAccel + BASE_NAME KGlobalAccel + # GROUP_BASE_NAME KF <- enable once all of KF modules use ecm_generate_export_header + VERSION ${KF5_VERSION} + DEPRECATED_BASE_VERSION 0 + DEPRECATION_VERSIONS 4.2 4.4 5.9 + EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT} +) target_include_directories(KF5GlobalAccel INTERFACE "$") target_link_libraries(KF5GlobalAccel PUBLIC Qt5::DBus Qt5::Widgets ) if(HAVE_X11) target_link_libraries(KF5GlobalAccel PRIVATE Qt5::X11Extras ) endif() set_target_properties(KF5GlobalAccel PROPERTIES VERSION ${KGLOBALACCEL_VERSION_STRING} SOVERSION ${KGLOBALACCEL_SOVERSION} EXPORT_NAME "GlobalAccel" ) ecm_generate_headers(KGlobalAccel_HEADERS HEADER_NAMES KGlobalAccel KGlobalShortcutInfo REQUIRED_HEADERS KGlobalAccel_HEADERS ) install(TARGETS KF5GlobalAccel EXPORT KF5GlobalAccelTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kglobalaccel_export.h ${KGlobalAccel_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KGlobalAccel COMPONENT Devel ) if(BUILD_QCH) ecm_add_qch( KF5GlobalAccel_QCH NAME KGlobalAccel BASE_NAME KF5GlobalAccel VERSION ${KF5_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${KGlobalAccel_HEADERS} MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" LINK_QCHS Qt5Widgets_QCH Qt5DBus_QCH INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR} BLANK_MACROS KGLOBALACCEL_EXPORT KGLOBALACCEL_DEPRECATED KGLOBALACCEL_DEPRECATED_EXPORT TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KGlobalAccel LIB_NAME KF5GlobalAccel DEPS "dbus widgets" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KGlobalAccel) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) add_subdirectory(runtime) diff --git a/src/kglobalaccel.cpp b/src/kglobalaccel.cpp index 0cf11ed..71f5aea 100644 --- a/src/kglobalaccel.cpp +++ b/src/kglobalaccel.cpp @@ -1,739 +1,741 @@ /* This file is part of the KDE libraries Copyright (C) 2001,2002 Ellis Whitehead Copyright (C) 2006 Hamish Rodda Copyright (C) 2007 Andreas Hartmetz Copyright (C) 2008 Michael Jansen 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.h" #include "kglobalaccel_p.h" #include "kglobalaccel_debug.h" #include #include #include #include #include #include #include #if HAVE_X11 #include #endif org::kde::kglobalaccel::Component *KGlobalAccelPrivate::getComponent(const QString &componentUnique, bool remember = false) { // Check if we already have this component { auto component = components.value(componentUnique); if (component) { return component; } } // Connect to the kglobalaccel daemon org::kde::KGlobalAccel kglobalaccel( QStringLiteral("org.kde.kglobalaccel"), QStringLiteral("/kglobalaccel"), QDBusConnection::sessionBus()); if (!kglobalaccel.isValid()) { qCDebug(KGLOBALACCEL_LOG) << "Failed to connect to the kglobalaccel daemon" << QDBusConnection::sessionBus().lastError(); return nullptr; } // Get the path for our component. We have to do that because // componentUnique is probably not a valid dbus object path QDBusReply reply = kglobalaccel.getComponent(componentUnique); if (!reply.isValid()) { if (reply.error().name() == QLatin1String("org.kde.kglobalaccel.NoSuchComponent")) { // No problem. The component doesn't exists. That's normal return nullptr; } // An unknown error. qCDebug(KGLOBALACCEL_LOG) << "Failed to get dbus path for component " << componentUnique << reply.error(); return nullptr; } // Now get the component org::kde::kglobalaccel::Component *component = new org::kde::kglobalaccel::Component( QStringLiteral("org.kde.kglobalaccel"), reply.value().path(), QDBusConnection::sessionBus(), q); // No component no cleaning if (!component->isValid()) { qCDebug(KGLOBALACCEL_LOG) << "Failed to get component" << componentUnique << QDBusConnection::sessionBus().lastError(); return nullptr; } if (remember) { // Connect to the signals we are interested in. q->connect(component, SIGNAL(globalShortcutPressed(QString,QString,qlonglong)), SLOT(_k_invokeAction(QString,QString,qlonglong))); components[componentUnique] = component; } return component; } namespace { QString serviceName() { return QStringLiteral("org.kde.kglobalaccel"); } } void KGlobalAccelPrivate::cleanup() { qDeleteAll(components); delete m_iface; m_iface = nullptr; delete m_watcher; m_watcher = nullptr; } KGlobalAccelPrivate::KGlobalAccelPrivate(KGlobalAccel *q) : -#ifndef KGLOBALACCEL_NO_DEPRECATED +#if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(4, 4) enabled(true), #endif q(q), m_iface(nullptr) { m_watcher = new QDBusServiceWatcher(serviceName(), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, q); q->connect(m_watcher, SIGNAL(serviceOwnerChanged(QString,QString,QString)), q, SLOT(_k_serviceOwnerChanged(QString,QString,QString))); } org::kde::KGlobalAccel *KGlobalAccelPrivate::iface() { if (!m_iface) { m_iface = new org::kde::KGlobalAccel(serviceName(), QStringLiteral("/kglobalaccel"), QDBusConnection::sessionBus()); // Make sure kglobalaccel is running. The iface declaration above somehow works anyway. QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface(); if (bus && !bus->isServiceRegistered(serviceName())) { QDBusReply reply = bus->startService(serviceName()); if (!reply.isValid()) { qCritical() << "Couldn't start kglobalaccel from org.kde.kglobalaccel.service:" << reply.error(); } } q->connect(m_iface, SIGNAL(yourShortcutGotChanged(QStringList,QList)), SLOT(_k_shortcutGotChanged(QStringList,QList))); } return m_iface; } KGlobalAccel::KGlobalAccel() : d(new KGlobalAccelPrivate(this)) { qDBusRegisterMetaType >(); qDBusRegisterMetaType >(); qDBusRegisterMetaType(); qDBusRegisterMetaType >(); } KGlobalAccel::~KGlobalAccel() { delete d; } void KGlobalAccel::activateGlobalShortcutContext( const QString &contextUnique, const QString &contextFriendly, const QString &programName) { Q_UNUSED(contextFriendly); // TODO: provide contextFriendly self()->d->iface()->activateGlobalShortcutContext(programName, contextUnique); } // static bool KGlobalAccel::cleanComponent(const QString &componentUnique) { org::kde::kglobalaccel::Component *component = self()->getComponent(componentUnique); if (!component) { return false; } return component->cleanUp(); } // static bool KGlobalAccel::isComponentActive(const QString &componentUnique) { org::kde::kglobalaccel::Component *component = self()->getComponent(componentUnique); if (!component) { return false; } return component->isActive(); } -#ifndef KGLOBALACCEL_NO_DEPRECATED +#if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(4, 4) bool KGlobalAccel::isEnabled() const { return d->enabled; } #endif org::kde::kglobalaccel::Component *KGlobalAccel::getComponent(const QString &componentUnique) { return d->getComponent(componentUnique); } -#ifndef KGLOBALACCEL_NO_DEPRECATED +#if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(4, 4) void KGlobalAccel::setEnabled(bool enabled) { d->enabled = enabled; } #endif class KGlobalAccelSingleton { public: KGlobalAccelSingleton(); KGlobalAccel instance; }; Q_GLOBAL_STATIC(KGlobalAccelSingleton, s_instance) KGlobalAccelSingleton::KGlobalAccelSingleton() { qAddPostRoutine([]() { s_instance->instance.d->cleanup(); }); } KGlobalAccel *KGlobalAccel::self() { return &s_instance()->instance; } bool KGlobalAccelPrivate::doRegister(QAction *action) { if (!action || action->objectName().isEmpty() || action->objectName().startsWith(QLatin1String("unnamed-"))) { qWarning() << "Attempt to set global shortcut for action without objectName()." " Read the setGlobalShortcut() documentation."; return false; } const bool isRegistered = actions.contains(action); if (isRegistered) { return true; } QStringList actionId = makeActionId(action); nameToAction.insertMulti(actionId.at(KGlobalAccel::ActionUnique), action); actions.insert(action); iface()->doRegister(actionId); QObject::connect(action, &QObject::destroyed, [this, action](QObject *) { if (actions.contains(action) && (actionShortcuts.contains(action) || actionDefaultShortcuts.contains(action))) { remove(action, KGlobalAccelPrivate::SetInactive); } }); return true; } void KGlobalAccelPrivate::remove(QAction *action, Removal removal) { if (!action || action->objectName().isEmpty()) { return; } const bool isRegistered = actions.contains(action); if (!isRegistered) { return; } QStringList actionId = makeActionId(action); nameToAction.remove(actionId.at(KGlobalAccel::ActionUnique), action); actions.remove(action); if (removal == UnRegister) { // Complete removal of the shortcut is requested // (forgetGlobalShortcut) iface()->unRegister(actionId); } else { // If the action is a configurationAction wen only remove it from our // internal registry. That happened above. if (!action->property("isConfigurationAction").toBool()) { // If it's a session shortcut unregister it. action->objectName().startsWith(QLatin1String("_k_session:")) ? iface()->unRegister(actionId) : iface()->setInactive(actionId); } } actionDefaultShortcuts.remove(action); actionShortcuts.remove(action); } void KGlobalAccelPrivate::updateGlobalShortcut(/*const would be better*/QAction* action, ShortcutTypes actionFlags, KGlobalAccel::GlobalShortcutLoading globalFlags) { // No action or no objectname -> Do nothing if (!action || action->objectName().isEmpty()) { return; } QStringList actionId = makeActionId(action); uint setterFlags = 0; if (globalFlags & NoAutoloading) { setterFlags |= NoAutoloading; } if (actionFlags & ActiveShortcut) { const QList activeShortcut = actionShortcuts.value(action); bool isConfigurationAction = action->property("isConfigurationAction").toBool(); uint activeSetterFlags = setterFlags; // setPresent tells kglobalaccel that the shortcut is active if (!isConfigurationAction) { activeSetterFlags |= SetPresent; } // Sets the shortcut, returns the active/real keys const auto result = iface()->setShortcut( actionId, intListFromShortcut(activeShortcut), activeSetterFlags); // Make sure we get informed about changes in the component by kglobalaccel getComponent(componentUniqueForAction(action), true); // Create a shortcut from the result const QList scResult(shortcutFromIntList(result)); if (isConfigurationAction && (globalFlags & NoAutoloading)) { // If this is a configuration action and we have set the shortcut, // inform the real owner of the change. // Note that setForeignShortcut will cause a signal to be sent to applications // even if it did not "see" that the shortcut has changed. This is Good because // at the time of comparison (now) the action *already has* the new shortcut. // We called setShortcut(), remember? // Also note that we will see our own signal so we may not need to call // setActiveGlobalShortcutNoEnable - _k_shortcutGotChanged() does it. // In practice it's probably better to get the change propagated here without // DBus delay as we do below. iface()->setForeignShortcut(actionId, result); } if (scResult != activeShortcut) { // If kglobalaccel returned a shortcut that differs from the one we // sent, use that one. There must have been clashes or some other problem. actionShortcuts.insert(action, scResult); emit q->globalShortcutChanged(action, scResult.isEmpty() ? QKeySequence() : scResult.first()); } } if (actionFlags & DefaultShortcut) { const QList defaultShortcut = actionDefaultShortcuts.value(action); iface()->setShortcut(actionId, intListFromShortcut(defaultShortcut), setterFlags | IsDefault); } } QStringList KGlobalAccelPrivate::makeActionId(const QAction *action) { QStringList ret(componentUniqueForAction(action)); // Component Unique Id ( see actionIdFields ) Q_ASSERT(!ret.at(KGlobalAccel::ComponentUnique).isEmpty()); Q_ASSERT(!action->objectName().isEmpty()); ret.append(action->objectName()); // Action Unique Name ret.append(componentFriendlyForAction(action)); // Component Friendly name const QString actionText = action->text().replace(QLatin1Char('&'), QStringLiteral("")); ret.append(actionText); // Action Friendly Name return ret; } QList KGlobalAccelPrivate::intListFromShortcut(const QList &cut) { QList ret; for (const QKeySequence &sequence : cut) { ret.append(sequence[0]); } while (!ret.isEmpty() && ret.last() == 0) { ret.removeLast(); } return ret; } QList KGlobalAccelPrivate::shortcutFromIntList(const QList &list) { QList ret; for (int i : list) { ret.append(i); } return ret; } QString KGlobalAccelPrivate::componentUniqueForAction(const QAction *action) { if (!action->property("componentName").isValid()) { return QCoreApplication::applicationName(); } else { return action->property("componentName").toString(); } } QString KGlobalAccelPrivate::componentFriendlyForAction(const QAction *action) { QString property = action->property("componentDisplayName").toString(); if (!property.isEmpty()) { return property; } if (!QGuiApplication::applicationDisplayName().isEmpty()) { return QGuiApplication::applicationDisplayName(); } return QCoreApplication::applicationName(); } #if HAVE_X11 int _k_timestampCompare(unsigned long time1_, unsigned long time2_) // like strcmp() { quint32 time1 = time1_; quint32 time2 = time2_; if (time1 == time2) { return 0; } return quint32(time1 - time2) < 0x7fffffffU ? 1 : -1; // time1 > time2 -> 1, handle wrapping } #endif void KGlobalAccelPrivate::_k_invokeAction( const QString &componentUnique, const QString &actionUnique, qlonglong timestamp) { QAction *action = nullptr; const QList candidates = nameToAction.values(actionUnique); for (QAction *const a : candidates) { if (componentUniqueForAction(a) == componentUnique) { action = a; } } // We do not trigger if // - there is no action // - the action is not enabled // - the action is an configuration action if (!action || !action->isEnabled() || action->property("isConfigurationAction").toBool()) { return; } #if HAVE_X11 // Update this application's X timestamp if needed. // TODO The 100%-correct solution should probably be handling this action // in the proper place in relation to the X events queue in order to avoid // the possibility of wrong ordering of user events. if (QX11Info::isPlatformX11()) { if (_k_timestampCompare(timestamp, QX11Info::appTime()) > 0) { QX11Info::setAppTime(timestamp); } if (_k_timestampCompare(timestamp, QX11Info::appUserTime()) > 0) { QX11Info::setAppUserTime(timestamp); } } #endif action->setProperty("org.kde.kglobalaccel.activationTimestamp", timestamp); action->trigger(); } void KGlobalAccelPrivate::_k_shortcutGotChanged(const QStringList &actionId, const QList &keys) { QAction *action = nameToAction.value(actionId.at(KGlobalAccel::ActionUnique)); if (!action) { return; } const QList shortcuts = shortcutFromIntList(keys); actionShortcuts.insert(action, shortcuts); emit q->globalShortcutChanged(action, keys.isEmpty() ? QKeySequence() : shortcuts.first()); } void KGlobalAccelPrivate::_k_serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(oldOwner); if (name == QLatin1String("org.kde.kglobalaccel") && !newOwner.isEmpty()) { // kglobalaccel was restarted qCDebug(KGLOBALACCEL_LOG) << "detected kglobalaccel restarting, re-registering all shortcut keys"; reRegisterAll(); } } void KGlobalAccelPrivate::reRegisterAll() { //We clear all our data, assume that all data on the other side is clear too, //and register each action as if it just was allowed to have global shortcuts. //If the kded side still has the data it doesn't matter because of the //autoloading mechanism. The worst case I can imagine is that an action's //shortcut was changed but the kded side died before it got the message so //autoloading will now assign an old shortcut to the action. Particularly //picky apps might assert or misbehave. const QSet allActions = actions; nameToAction.clear(); actions.clear(); for (QAction *const action : allActions) { if (doRegister(action)) { updateGlobalShortcut(action, ActiveShortcut, KGlobalAccel::Autoloading); } } } -#ifndef KGLOBALACCEL_NO_DEPRECATED +#if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(4, 2) QList KGlobalAccel::allMainComponents() { return d->iface()->allMainComponents(); } #endif -#ifndef KGLOBALACCEL_NO_DEPRECATED +#if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(4, 2) QList KGlobalAccel::allActionsForComponent(const QStringList &actionId) { return d->iface()->allActionsForComponent(actionId); } #endif //static -#ifndef KGLOBALACCEL_NO_DEPRECATED +#if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(4, 2) QStringList KGlobalAccel::findActionNameSystemwide(const QKeySequence &seq) { return self()->d->iface()->action(seq[0]); } #endif QList KGlobalAccel::getGlobalShortcutsByKey(const QKeySequence &seq) { return self()->d->iface()->getGlobalShortcutsByKey(seq[0]); } bool KGlobalAccel::isGlobalShortcutAvailable(const QKeySequence &seq, const QString &comp) { return self()->d->iface()->isGlobalShortcutAvailable(seq[0], comp); } //static -#ifndef KGLOBALACCEL_NO_DEPRECATED +#if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(4, 2) bool KGlobalAccel::promptStealShortcutSystemwide(QWidget *parent, const QStringList &actionIdentifier, const QKeySequence &seq) { if (actionIdentifier.size() < 4) { return false; } QString title = tr("Conflict with Global Shortcut"); QString message = tr("The '%1' key combination has already been allocated " "to the global action \"%2\" in %3.\n" "Do you want to reassign it from that action to the current one?") .arg(seq.toString(), actionIdentifier.at(KGlobalAccel::ActionFriendly), actionIdentifier.at(KGlobalAccel::ComponentFriendly)); QMessageBox box(parent); box.setWindowTitle(title); box.setText(message); box.addButton(QMessageBox::Ok)->setText(tr("Reassign")); box.addButton(QMessageBox::Cancel); return box.exec() == QMessageBox::Ok; } #endif //static bool KGlobalAccel::promptStealShortcutSystemwide( QWidget *parent, const QList &shortcuts, const QKeySequence &seq) { if (shortcuts.isEmpty()) { // Usage error. Just say no return false; } QString component = shortcuts[0].componentFriendlyName(); QString message; if (shortcuts.size() == 1) { message = tr("The '%1' key combination is registered by application %2 for action %3:") .arg(seq.toString(), component, shortcuts[0].friendlyName()); } else { QString actionList; for (const KGlobalShortcutInfo &info : shortcuts) { actionList += tr("In context '%1' for action '%2'\n") .arg(info.contextFriendlyName(), info.friendlyName()); } message = tr("The '%1' key combination is registered by application %2.\n%3") .arg(seq.toString(), component, actionList); } QString title = tr("Conflict With Registered Global Shortcut"); QMessageBox box(parent); box.setWindowTitle(title); box.setText(message); box.addButton(QMessageBox::Ok)->setText(tr("Reassign")); box.addButton(QMessageBox::Cancel); return box.exec() == QMessageBox::Ok; } //static void KGlobalAccel::stealShortcutSystemwide(const QKeySequence &seq) { //get the shortcut, remove seq, and set the new shortcut const QStringList actionId = self()->d->iface()->action(seq[0]); if (actionId.size() < 4) { // not a global shortcut return; } QList sc = self()->d->iface()->shortcut(actionId); for (int i = 0; i < sc.count(); i++) if (sc[i] == seq[0]) { sc[i] = 0; } self()->d->iface()->setForeignShortcut(actionId, sc); } bool checkGarbageKeycode(const QList &shortcut) { // protect against garbage keycode -1 that Qt sometimes produces for exotic keys; // at the moment (~mid 2008) Multimedia PlayPause is one of those keys. for (const QKeySequence &sequence : shortcut) { for (int i = 0; i < 4; i++) { if (sequence[i] == -1) { qWarning() << "Encountered garbage keycode (keycode = -1) in input, not doing anything."; return true; } } } return false; } bool KGlobalAccel::setDefaultShortcut(QAction *action, const QList &shortcut, GlobalShortcutLoading loadFlag) { if (checkGarbageKeycode(shortcut)) { return false; } if (!d->doRegister(action)) { return false; } d->actionDefaultShortcuts.insert(action, shortcut); d->updateGlobalShortcut(action, KGlobalAccelPrivate::DefaultShortcut, loadFlag); return true; } bool KGlobalAccel::setShortcut(QAction *action, const QList &shortcut, GlobalShortcutLoading loadFlag) { if (checkGarbageKeycode(shortcut)) { return false; } if (!d->doRegister(action)) { return false; } d->actionShortcuts.insert(action, shortcut); d->updateGlobalShortcut(action, KGlobalAccelPrivate::ActiveShortcut, loadFlag); return true; } QList KGlobalAccel::defaultShortcut(const QAction *action) const { return d->actionDefaultShortcuts.value(action); } QList KGlobalAccel::shortcut(const QAction *action) const { return d->actionShortcuts.value(action); } QList KGlobalAccel::globalShortcut(const QString& componentName, const QString& actionId) const { // see also d->updateGlobalShortcut(action, KGlobalAccelPrivate::ActiveShortcut, KGlobalAccel::Autoloading); // how componentName and actionId map to QAction, e.g: // action->setProperty("componentName", "kwin"); // action->setObjectName("Kill Window"); const QList result = self()->d->iface()->shortcut({ componentName, actionId, QString(), QString() }); const QList scResult(d->shortcutFromIntList(result)); return scResult; } void KGlobalAccel::removeAllShortcuts(QAction *action) { d->remove(action, KGlobalAccelPrivate::UnRegister); } bool KGlobalAccel::hasShortcut(const QAction *action) const { return d->actionShortcuts.contains(action) || d->actionDefaultShortcuts.contains(action); } +#if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(5, 9) bool KGlobalAccel::eventFilter(QObject *watched, QEvent *event) { return QObject::eventFilter(watched, event); } +#endif bool KGlobalAccel::setGlobalShortcut(QAction *action, const QList &shortcut) { KGlobalAccel *g = KGlobalAccel::self(); return g->d->setShortcutWithDefault(action, shortcut, Autoloading); } bool KGlobalAccel::setGlobalShortcut(QAction *action, const QKeySequence &shortcut) { return KGlobalAccel::setGlobalShortcut(action, QList() << shortcut); } bool KGlobalAccelPrivate::setShortcutWithDefault(QAction* action, const QList& shortcut, KGlobalAccel::GlobalShortcutLoading loadFlag) { if (checkGarbageKeycode(shortcut)) { return false; } if (!doRegister(action)) { return false; } actionDefaultShortcuts.insert(action, shortcut); actionShortcuts.insert(action, shortcut); updateGlobalShortcut(action, KGlobalAccelPrivate::DefaultShortcut | KGlobalAccelPrivate::ActiveShortcut, loadFlag); return true; } #include "moc_kglobalaccel.cpp" diff --git a/src/kglobalaccel.h b/src/kglobalaccel.h index 52121bf..45ab99b 100644 --- a/src/kglobalaccel.h +++ b/src/kglobalaccel.h @@ -1,388 +1,390 @@ /* This file is part of the KDE libraries Copyright (C) 2001,2002 Ellis Whitehead Copyright (C) 2006 Hamish Rodda Copyright (C) 2007 Andreas Hartmetz 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_H_ #define _KGLOBALACCEL_H_ #include #include "kglobalshortcutinfo.h" #include #include #include class QAction; class OrgKdeKglobalaccelComponentInterface; /** * @short Configurable global shortcut support * * KGlobalAccel allows you to have global accelerators that are independent of * the focused window. Unlike regular shortcuts, the application's window does not need focus * for them to be activated. * * @see KKeyChooser * @see KKeyDialog */ class KGLOBALACCEL_EXPORT KGlobalAccel : public QObject { Q_OBJECT public: /** * An enum about global shortcut setter semantics */ enum GlobalShortcutLoading { /// Look up the action in global settings (using its main component's name and text()) /// and set the shortcut as saved there. /// @see setGlobalShortcut() Autoloading = 0x0, /// Prevent autoloading of saved global shortcut for action NoAutoloading = 0x4 }; /** * Index for actionId QStringLists */ enum actionIdFields { ComponentUnique = 0, //!< Components Unique Name (ID) ActionUnique = 1, //!< Actions Unique Name(ID) ComponentFriendly = 2, //!< Components Friendly Translated Name ActionFriendly = 3 //!< Actions Friendly Translated Name }; /** * Returns (and creates if necessary) the singleton instance */ static KGlobalAccel *self(); /** * Take away the given shortcut from the named action it belongs to. * This applies to all actions with global shortcuts in any KDE application. * * @see promptStealShortcutSystemwide() */ static void stealShortcutSystemwide(const QKeySequence &seq); /** * Set global shortcut context. * * A global shortcut context allows an application to have different sets * of global shortcuts and to switch between them. This is used by * plasma to switch the active global shortcuts when switching between * activities. * * @param context the name of the context. * * @since 4.2 */ static void activateGlobalShortcutContext( const QString &contextUnique, const QString &contextFriendly, const QString &programName); /** * Clean the shortcuts for component @a componentUnique. * * If the component is not active all global shortcut registrations are * purged and the component is removed completely. * * If the component is active all global shortcut registrations not in use * will be purged. If there is no shortcut registration left the component * is purged too. * * If a purged component or shortcut is activated the next time it will * reregister itself. All you probably will lose on wrong usage are the * user's set shortcuts. * * If you make sure your component is running and all global shortcuts it * has are active this function can be used to clean up the registry. * * Handle with care! * * If the method return @c true at least one shortcut was purged so handle * all previously acquired information with care. */ static bool cleanComponent(const QString &componentUnique); /** * Check if @a component is active. * * @param componentUnique the components unique identifier * @return @c true if active, @false if not */ static bool isComponentActive(const QString &componentName); /** * Returns a list of global shortcuts registered for the shortcut @seq. * * If the list contains more that one entry it means the component * that registered the shortcuts uses global shortcut contexts. All * returned shortcuts belong to the same component. * * @since 4.2 */ static QList getGlobalShortcutsByKey(const QKeySequence &seq); /** * Check if the shortcut @seq is available for the @p component. The * component is only of interest if the current application uses global shortcut * contexts. In that case a global shortcut by @p component in an inactive * global shortcut contexts does not block the @p seq for us. * * @since 4.2 */ static bool isGlobalShortcutAvailable( const QKeySequence &seq, const QString &component = QString()); /** * Show a messagebox to inform the user that a global shortcut is already occupied, * and ask to take it away from its current action(s). This is GUI only, so nothing will * be actually changed. * * @see stealShortcutSystemwide() * * @since 4.2 */ static bool promptStealShortcutSystemwide( QWidget *parent, const QList &shortcuts, const QKeySequence &seq); /** * Assign a default global shortcut for a given QAction. * For more information about global shortcuts @see setShortcut * Upon shortcut change the globalShortcutChanged will be triggered so other applications get notified * * @sa globalShortcutChanged * * @since 5.0 */ bool setDefaultShortcut(QAction *action, const QList &shortcut, GlobalShortcutLoading loadFlag = Autoloading); /** * Assign a global shortcut for the given action. Global shortcuts * allow an action to respond to key shortcuts independently of the focused window, * i.e. the action will trigger if the keys were pressed no matter where in the X session. * * The action must have a per main component unique * action->objectName() to enable cross-application bookeeping. If the action->objectName() is empty this method will * do nothing and will return false. * * It is mandatory that the action->objectName() doesn't change once the shortcut has been sucessfully registered. * * \note KActionCollection::insert(name, action) will set action's objectName to name so you often * don't have to set an objectName explicitly. * * When an action, identified by main component name and objectName(), is assigned * a global shortcut for the first time on a KDE installation the assignment will * be saved. The shortcut will then be restored every time setGlobalShortcut() is * called with @p loading == Autoloading. * * If you actually want to change the global shortcut you have to set * @p loading to NoAutoloading. The new shortcut will be automatically saved again. * * @param action the action for which the shortcut will be assigned * @param shortcut global shortcut(s) to assign. Will be ignored unless @p loading is set to NoAutoloading or this is the first time ever you call this method (see above). * @param loadFlag if Autoloading, assign the global shortcut this action has previously had if any. * That way user preferences and changes made to avoid clashes will be conserved. * if NoAutoloading the given shortcut will be assigned without looking up old values. * You should only do this if the user wants to change the shortcut or if you have * another very good reason. Key combinations that clash with other shortcuts will be * dropped. * * \note the default shortcut will never be influenced by autoloading - it will be set as given. * @sa shortcut() * @sa globalShortcutChanged * @since 5.0 */ bool setShortcut(QAction *action, const QList &shortcut, GlobalShortcutLoading loadFlag = Autoloading); /** * Convenient method to set both active and default shortcut. * * If more control for loading the shortcuts is needed use the variants offering more control. * * @sa setShortcut * @sa setDefaultShortcut * @since 5.0 **/ static bool setGlobalShortcut(QAction *action, const QList &shortcut); /** * Convenient method to set both active and default shortcut. * * This method is suited for the case that only one shortcut is to be configured. * * If more control for loading the shortcuts is needed use the variants offering more control. * * @sa setShortcut * @sa setDefaultShortcut * @since 5.0 **/ static bool setGlobalShortcut(QAction *action, const QKeySequence &shortcut); /** * Get the global default shortcut for this action, if one exists. Global shortcuts * allow your actions to respond to accellerators independently of the focused window. * Unlike regular shortcuts, the application's window does not need focus * for them to be activated. * * @sa setDefaultShortcut() * @since 5.0 */ QList defaultShortcut(const QAction *action) const; /** * Get the global shortcut for this action, if one exists. Global shortcuts * allow your actions to respond to accellerators independently of the focused window. * Unlike regular shortcuts, the application's window does not need focus * for them to be activated. * * @note that this method only works together with setShortcut() because the action pointer * is used to retrieve the result. If you would like to retrieve the shortcut as stored * in the global settings, use the globalShortcut(componentName, actionId) instead. * * @sa setShortcut() * @since 5.0 */ QList shortcut(const QAction *action) const; /** * Retrieves the shortcut as defined in global settings by * componentName (e.g. "kwin") and actionId (e.g. "Kill Window"). * * @since 5.10 */ QList globalShortcut(const QString& componentName, const QString& actionId) const; /** * Unregister and remove all defined global shortcuts for the given action. * * @since 5.0 */ void removeAllShortcuts(QAction *action); /** * Returns true if a shortcut or a default shortcut has been registered for the given action * * @since 5.0 */ bool hasShortcut(const QAction *action) const; +#if KGLOBALACCEL_ENABLE_DEPRECATED_SINCE(4, 4) /** * No effect. * - * @deprecated + * @deprecated Since 4.4. */ -#ifndef KGLOBALACCEL_NO_DEPRECATED - KGLOBALACCEL_DEPRECATED bool isEnabled() const; + KGLOBALACCEL_DEPRECATED_VERSION(4, 4, "Property no longer used") + bool isEnabled() const; #endif +#if KGLOBALACCEL_ENABLE_DEPRECATED_SINCE(4, 4) /** * No effect. * - * @deprecated + * @deprecated Since 4.4. */ -#ifndef KGLOBALACCEL_NO_DEPRECATED - KGLOBALACCEL_DEPRECATED void setEnabled(bool enabled); + KGLOBALACCEL_DEPRECATED_VERSION(4, 4, "Property no longer used") + void setEnabled(bool enabled); #endif +#if KGLOBALACCEL_ENABLE_DEPRECATED_SINCE(4, 2) /** * Return the unique and common names of all main components that have global shortcuts. * The action strings of the returned actionId stringlists will be empty. * - * @deprecated + * @deprecated Since 4.2. Do not use. */ -#ifndef KGLOBALACCEL_NO_DEPRECATED - KGLOBALACCEL_DEPRECATED QList allMainComponents(); + KGLOBALACCEL_DEPRECATED_VERSION(4, 2, "Do not use") + QList allMainComponents(); #endif +#if KGLOBALACCEL_ENABLE_DEPRECATED_SINCE(4, 2) /** - * @see getGlobalShortcutsByComponent - * - * @deprecated + * @deprecated Since 4.2. Do not use */ -#ifndef KGLOBALACCEL_NO_DEPRECATED - KGLOBALACCEL_DEPRECATED QList allActionsForComponent(const QStringList &actionId); + KGLOBALACCEL_DEPRECATED_VERSION(4, 2, "Do not use") + QList allActionsForComponent(const QStringList &actionId); #endif +#if KGLOBALACCEL_ENABLE_DEPRECATED_SINCE(4, 2) /** - * @see getGlobalShortcutsByKey - * - * @deprecated + * @deprecated Since 4.2, use KGlobalAccel::getGlobalShortcutsByKey(const QKeySequence &) */ -#ifndef KGLOBALACCEL_NO_DEPRECATED - KGLOBALACCEL_DEPRECATED static QStringList findActionNameSystemwide(const QKeySequence &seq); + KGLOBALACCEL_DEPRECATED_VERSION(4, 2, "Use KGlobalAccel::getGlobalShortcutsByKey(const QKeySequence &)") + static QStringList findActionNameSystemwide(const QKeySequence &seq); #endif +#if KGLOBALACCEL_ENABLE_DEPRECATED_SINCE(4, 2) /** - * @see promptStealShortcutSystemwide below - * - * @deprecated + * @deprecated Since 4.2, use KGlobalAccel::promptStealShortcutSystemwide(QWidget *, const QList &, const QKeySequence &) */ -#ifndef KGLOBALACCEL_NO_DEPRECATED - KGLOBALACCEL_DEPRECATED static bool promptStealShortcutSystemwide(QWidget *parent, const QStringList &actionIdentifier, const QKeySequence &seq); + KGLOBALACCEL_DEPRECATED_VERSION(4, 2, "Use KGlobalAccel::promptStealShortcutSystemwide(QWidget *, const QList &, const QKeySequence &)") + static bool promptStealShortcutSystemwide(QWidget *parent, const QStringList &actionIdentifier, const QKeySequence &seq); #endif +#if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(5, 9) /** - * TODO KF6 remove * @internal + * Override no longer needed, left for BIC */ bool eventFilter(QObject *watched, QEvent *event) override; +#endif Q_SIGNALS: /** * Emitted when the global shortcut is changed. A global shortcut is * subject to be changed by the global shortcuts kcm. * * @param action pointer to the action for which the changed shortcut was registered * @param seq the key sequence that corresponds to the changed shortcut * * @see setGlobalShortcut * @see setDefaultShortcut * @since 5.0 * * @todo KF6: add const to the QAction parameter */ void globalShortcutChanged(/*const would be better*/QAction *action, const QKeySequence &seq); private: /// Creates a new KGlobalAccel object KGlobalAccel(); /// Destructor ~KGlobalAccel(); //! get component @p componentUnique OrgKdeKglobalaccelComponentInterface *getComponent(const QString &componentUnique); class KGlobalAccelPrivate *const d; Q_PRIVATE_SLOT(d, void _k_invokeAction(const QString &, const QString &, qlonglong)) Q_PRIVATE_SLOT(d, void _k_shortcutGotChanged(const QStringList &, const QList &)) Q_PRIVATE_SLOT(d, void _k_serviceOwnerChanged(const QString &, const QString &, const QString &)) friend class KGlobalAccelSingleton; }; #endif // _KGLOBALACCEL_H_ diff --git a/src/kglobalaccel_p.h b/src/kglobalaccel_p.h index cdd88a1..ea2835e 100644 --- a/src/kglobalaccel_p.h +++ b/src/kglobalaccel_p.h @@ -1,112 +1,114 @@ /* This file is part of the KDE libraries Copyright (C) 2001,2002 Ellis Whitehead Copyright (C) 2006 Hamish Rodda Copyright (C) 2007 Andreas Hartmetz 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_P_H #define KGLOBALACCEL_P_H #include #include #include #include #include "kglobalaccel.h" #include "kglobalaccel_interface.h" #include "kglobalaccel_component_interface.h" enum SetShortcutFlag { SetPresent = 2, NoAutoloading = 4, IsDefault = 8 }; class KGlobalAccelPrivate { public: enum ShortcutType { /// The shortcut will immediately become active but may be reset to "default". ActiveShortcut = 0x1, /// The shortcut is a default shortcut - it becomes active when somebody decides to /// reset shortcuts to default. DefaultShortcut = 0x2 }; Q_DECLARE_FLAGS(ShortcutTypes, ShortcutType) enum Removal { SetInactive = 0, ///< Forget the action in this class and mark it as not present in the KDED module UnRegister ///< Remove any trace of the action in this class and in the KDED module }; KGlobalAccelPrivate(KGlobalAccel *); ///Propagate any shortcut changes to the KDED module that does the bookkeeping ///and the key grabbing. ///@todo KF6 void updateGlobalShortcut(/*const would be better*/QAction* action, KGlobalAccelPrivate::ShortcutTypes actionFlags, KGlobalAccel::GlobalShortcutLoading globalFlags); ///Register the action in this class and in the KDED module bool doRegister(QAction *action); //"register" is a C keyword :p ///cf. the RemoveAction enum void remove(QAction *action, Removal r); //"private" helpers QString componentUniqueForAction(const QAction *action); QString componentFriendlyForAction(const QAction *action); QStringList makeActionId(const QAction *action); QList intListFromShortcut(const QList &cut); QList shortcutFromIntList(const QList &list); void cleanup(); //private slot implementations void _k_invokeAction(const QString &, const QString &, qlonglong); void _k_shortcutGotChanged(const QStringList &, const QList &); void _k_serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner); void reRegisterAll(); //for all actions with (isEnabled() && globalShortcutAllowed()) QMultiHash nameToAction; QSet actions; +#if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(4, 4) bool enabled; +#endif org::kde::KGlobalAccel *iface(); //! Get the component @p componentUnique. If @p remember is true the instance is cached and we //! subscribe to signals about changes to the component. org::kde::kglobalaccel::Component *getComponent(const QString &componentUnique, bool remember); //! Our owner KGlobalAccel *q; //! The components the application is using QHash components; QMap > actionDefaultShortcuts; QMap > actionShortcuts; bool setShortcutWithDefault(QAction *action, const QList &shortcut, KGlobalAccel::GlobalShortcutLoading loadFlag); private: org::kde::KGlobalAccel *m_iface; QDBusServiceWatcher *m_watcher; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KGlobalAccelPrivate::ShortcutTypes) #endif diff --git a/src/runtime/CMakeLists.txt b/src/runtime/CMakeLists.txt index 985ccc8..8054622 100644 --- a/src/runtime/CMakeLists.txt +++ b/src/runtime/CMakeLists.txt @@ -1,66 +1,74 @@ remove_definitions(-DQT_NO_CAST_FROM_BYTEARRAY) remove_definitions(-DQT_NO_CAST_FROM_ASCII) ############################################################################### ### KDED Global Accel Daemon set(kglobalaccelprivate_SRCS kglobalacceld.cpp kglobalaccel_interface.cpp kserviceactioncomponent.cpp component.cpp logging.cpp globalshortcut.cpp globalshortcutsregistry.cpp globalshortcutcontext.cpp ) ecm_create_qm_loader(kglobalaccelprivate_SRCS kglobalaccel5_qt) configure_file(config-kglobalaccel.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kglobalaccel.h ) add_library(KF5GlobalAccelPrivate ${kglobalaccelprivate_SRCS}) -generate_export_header(KF5GlobalAccelPrivate BASE_NAME KF5GlobalAccelPrivate) add_library(KF5::GlobalAccelPrivate ALIAS KF5GlobalAccelPrivate) +ecm_generate_export_header(KF5GlobalAccelPrivate + EXPORT_FILE_NAME kf5globalaccelprivate_export.h + BASE_NAME KGlobalAccelPrivate + # GROUP_BASE_NAME KF <- enable once all of KF modules use ecm_generate_export_header + VERSION ${KF5_VERSION} + DEPRECATED_BASE_VERSION 0 + DEPRECATION_VERSIONS 4.3 + EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT} +) target_include_directories(KF5GlobalAccelPrivate INTERFACE "$") target_link_libraries(KF5GlobalAccelPrivate Qt5::DBus KF5::GlobalAccel KF5::WindowSystem # KKeyServer KF5::CoreAddons # KAboutData KF5::ConfigCore ) set_target_properties(KF5GlobalAccelPrivate PROPERTIES VERSION ${KGLOBALACCEL_VERSION_STRING} SOVERSION ${KGLOBALACCEL_SOVERSION} EXPORT_NAME "GlobalAccelPrivate" ) if (${XCB_XCB_FOUND}) target_link_libraries(KF5GlobalAccelPrivate Qt5::X11Extras) endif() add_executable(kglobalaccel5 main.cpp logging.cpp) ecm_mark_nongui_executable(kglobalaccel5) target_link_libraries(kglobalaccel5 KF5GlobalAccelPrivate KF5::DBusAddons # KDBusService KF5::Crash ) add_subdirectory(plugins) # Install application and configuration install(TARGETS KF5GlobalAccelPrivate EXPORT KF5GlobalAccelTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) install( TARGETS kglobalaccel5 ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ) install( FILES kglobalaccel.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} RENAME kglobalaccel5.desktop) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kf5globalaccelprivate_export.h kglobalacceld.h kglobalaccel_interface.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KGlobalAccel/private COMPONENT Devel ) kdbusaddons_generate_dbus_service_file(kglobalaccel5 org.kde.kglobalaccel ${KDE_INSTALL_FULL_BINDIR}) diff --git a/src/runtime/kglobalaccel_interface.h b/src/runtime/kglobalaccel_interface.h index 3f5bf02..aedb135 100644 --- a/src/runtime/kglobalaccel_interface.h +++ b/src/runtime/kglobalaccel_interface.h @@ -1,85 +1,85 @@ /* This file is part of the KDE libraries Copyright (C) 2001,2002 Ellis Whitehead Copyright (C) 2015 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. */ #ifndef KGLOBALACCEL_INTERFACE_H #define KGLOBALACCEL_INTERFACE_H #include #include "kf5globalaccelprivate_export.h" class GlobalShortcutsRegistry; /** * Abstract interface for plugins to implement */ -class KF5GLOBALACCELPRIVATE_EXPORT KGlobalAccelInterface : public QObject +class KGLOBALACCELPRIVATE_EXPORT KGlobalAccelInterface : public QObject { Q_OBJECT public: explicit KGlobalAccelInterface(QObject *parent); virtual ~KGlobalAccelInterface(); 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. */ virtual bool grabKey(int key, bool grab) = 0; /* * Enable/disable all shortcuts. There will not be any grabbed shortcuts at this point. */ virtual void setEnabled(bool) = 0; /** * Allows implementing plugins to synchronize with the windowing system. * Default implementation does nothing. **/ virtual void syncWindowingSystem(); void setRegistry(GlobalShortcutsRegistry *registry); protected: /** * called by the implementation to inform us about key presses * @returns @c true if the key was handled **/ bool keyPressed(int keyQt); void grabKeys(); void ungrabKeys(); private: class Private; QScopedPointer d; }; Q_DECLARE_INTERFACE(KGlobalAccelInterface, "org.kde.kglobalaccel5.KGlobalAccelInterface") #endif diff --git a/src/runtime/kglobalacceld.cpp b/src/runtime/kglobalacceld.cpp index 4496046..c64db4e 100644 --- a/src/runtime/kglobalacceld.cpp +++ b/src/runtime/kglobalacceld.cpp @@ -1,584 +1,585 @@ /* This file is part of the KDE libraries Copyright (c) 2007 Andreas Hartmetz Copyright (c) 2007 Michael Jansen 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 "kglobalacceld.h" #include "component.h" #include "globalshortcut.h" #include "globalshortcutcontext.h" #include "globalshortcutsregistry.h" #include "logging_p.h" #include "kserviceactioncomponent.h" #include #include #include #include #include "kglobalaccel.h" struct KGlobalAccelDPrivate { KGlobalAccelDPrivate(KGlobalAccelD *q) : q(q) {} GlobalShortcut *findAction(const QStringList &actionId) const; /** * Find the action @a shortcutUnique in @a componentUnique. * * @return the action or @c nullptr if doesn't exist */ GlobalShortcut *findAction( const QString &componentUnique, const QString &shortcutUnique) const; GlobalShortcut *addAction(const QStringList &actionId); KdeDGlobalAccel::Component *component(const QStringList &actionId) const; void splitComponent(QString &component, QString &context) const { context = QStringLiteral("default"); if (component.indexOf('|')!=-1) { QStringList tmp = component.split('|'); Q_ASSERT(tmp.size()==2); component= tmp.at(0); context= tmp.at(1); } } //! Timer for delayed writing to kglobalshortcutsrc QTimer writeoutTimer; //! Our holder KGlobalAccelD *q; }; GlobalShortcut *KGlobalAccelDPrivate::findAction(const QStringList &actionId) const { // Check if actionId is valid if (actionId.size() != 4) { qCDebug(KGLOBALACCELD) << "Invalid! '" << actionId << "'"; return nullptr; } return findAction( actionId.at(KGlobalAccel::ComponentUnique), actionId.at(KGlobalAccel::ActionUnique)); } GlobalShortcut *KGlobalAccelDPrivate::findAction( const QString &_componentUnique, const QString &shortcutUnique) const { QString componentUnique = _componentUnique; KdeDGlobalAccel::Component *component; QString contextUnique; if (componentUnique.indexOf('|')==-1) { component = GlobalShortcutsRegistry::self()->getComponent( componentUnique); if (component) contextUnique = component->currentContext()->uniqueName(); } else { splitComponent(componentUnique, contextUnique); component = GlobalShortcutsRegistry::self()->getComponent( componentUnique); } if (!component) { #ifdef KDEDGLOBALACCEL_TRACE qCDebug(KGLOBALACCELD) << componentUnique << "not found"; #endif return nullptr; } GlobalShortcut *shortcut = component->getShortcutByName(shortcutUnique, contextUnique); #ifdef KDEDGLOBALACCEL_TRACE if (shortcut) { qCDebug(KGLOBALACCELD) << componentUnique << contextUnique << shortcut->uniqueName(); } else { qCDebug(KGLOBALACCELD) << "No match for" << shortcutUnique; } #endif return shortcut; } KdeDGlobalAccel::Component *KGlobalAccelDPrivate::component(const QStringList &actionId) const { // Get the component for the action. If we have none create a new one KdeDGlobalAccel::Component *component = GlobalShortcutsRegistry::self()->getComponent(actionId.at(KGlobalAccel::ComponentUnique)); if (!component) { if (actionId.at(KGlobalAccel::ComponentUnique).endsWith(QLatin1String(".desktop"))) { component = new KdeDGlobalAccel::KServiceActionComponent( actionId.at(KGlobalAccel::ComponentUnique), actionId.at(KGlobalAccel::ComponentFriendly), GlobalShortcutsRegistry::self()); component->activateGlobalShortcutContext(QStringLiteral("default")); static_cast(component)->loadFromService(); } else { component = new KdeDGlobalAccel::Component( actionId.at(KGlobalAccel::ComponentUnique), actionId.at(KGlobalAccel::ComponentFriendly), GlobalShortcutsRegistry::self()); } Q_ASSERT(component); } return component; } GlobalShortcut *KGlobalAccelDPrivate::addAction(const QStringList &actionId) { Q_ASSERT(actionId.size() >= 4); QString componentUnique = actionId.at(KGlobalAccel::ComponentUnique); QString contextUnique = QStringLiteral("default"); if (componentUnique.indexOf('|')!=-1) { QStringList tmp = componentUnique.split('|'); Q_ASSERT(tmp.size()==2); componentUnique = tmp.at(0); contextUnique = tmp.at(1); } QStringList actionIdTmp = actionId; actionIdTmp.replace(KGlobalAccel::ComponentUnique, componentUnique); // Create the component if necessary KdeDGlobalAccel::Component *component = this->component(actionIdTmp); Q_ASSERT(component); // Create the context if necessary if (component->getShortcutContexts().count(contextUnique)==0) { component->createGlobalShortcutContext(contextUnique); } Q_ASSERT(!component->getShortcutByName(componentUnique, contextUnique)); return new GlobalShortcut( actionId.at(KGlobalAccel::ActionUnique), actionId.at(KGlobalAccel::ActionFriendly), component->shortcutContext(contextUnique)); } Q_DECLARE_METATYPE(QStringList) KGlobalAccelD::KGlobalAccelD(QObject* parent) : QObject(parent), d(new KGlobalAccelDPrivate(this)) {} bool KGlobalAccelD::init() { qDBusRegisterMetaType< QList >(); qDBusRegisterMetaType< QList >(); qDBusRegisterMetaType< QList >(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType< QList >(); GlobalShortcutsRegistry *reg = GlobalShortcutsRegistry::self(); Q_ASSERT(reg); d->writeoutTimer.setSingleShot(true); connect(&d->writeoutTimer, SIGNAL(timeout()), reg, SLOT(writeSettings())); if (!QDBusConnection::sessionBus().registerService(QLatin1String("org.kde.kglobalaccel"))) { qCWarning(KGLOBALACCELD) << "Failed to register service org.kde.kglobalaccel"; return false; } if (!QDBusConnection::sessionBus().registerObject( QStringLiteral("/kglobalaccel"), this, QDBusConnection::ExportScriptableContents)) { qCWarning(KGLOBALACCELD) << "Failed to register object kglobalaccel in org.kde.kglobalaccel"; return false; } GlobalShortcutsRegistry::self()->setDBusPath(QDBusObjectPath("/")); GlobalShortcutsRegistry::self()->loadSettings(); return true; } KGlobalAccelD::~KGlobalAccelD() { GlobalShortcutsRegistry *const reg = GlobalShortcutsRegistry::self(); if (d->writeoutTimer.isActive()) { d->writeoutTimer.stop(); reg->writeSettings(); } reg->deactivateShortcuts(); delete d; } QList KGlobalAccelD::allMainComponents() const { QList ret; QStringList emptyList; emptyList.reserve(4); for (int i = 0; i < 4; i++) { emptyList.append(QString()); } const auto components = GlobalShortcutsRegistry::self()->allMainComponents(); ret.reserve(components.size() * 4); for (const KdeDGlobalAccel::Component *component : components) { QStringList actionId(emptyList); actionId[KGlobalAccel::ComponentUnique] = component->uniqueName(); actionId[KGlobalAccel::ComponentFriendly] = component->friendlyName(); ret.append(actionId); } return ret; } QList KGlobalAccelD::allActionsForComponent(const QStringList &actionId) const { //### Would it be advantageous to sort the actions by unique name? QList ret; KdeDGlobalAccel::Component *const component = GlobalShortcutsRegistry::self()->getComponent(actionId[KGlobalAccel::ComponentUnique]); if (!component) { return ret; } QStringList partialId(actionId[KGlobalAccel::ComponentUnique]); //ComponentUnique partialId.append(QString()); //ActionUnique //Use our internal friendlyName, not the one passed in. We should have the latest data. partialId.append(component->friendlyName()); //ComponentFriendly partialId.append(QString()); //ActionFriendly const auto listShortcuts = component->allShortcuts(); for (const GlobalShortcut *const shortcut : listShortcuts) { if (shortcut->isFresh()) { // isFresh is only an intermediate state, not to be reported outside. continue; } QStringList actionId(partialId); actionId[KGlobalAccel::ActionUnique] = shortcut->uniqueName(); actionId[KGlobalAccel::ActionFriendly] = shortcut->friendlyName(); ret.append(actionId); } return ret; } QStringList KGlobalAccelD::action(int key) const { GlobalShortcut *shortcut = GlobalShortcutsRegistry::self()->getShortcutByKey(key); QStringList ret; if (shortcut) { ret.append(shortcut->context()->component()->uniqueName()); ret.append(shortcut->uniqueName()); ret.append(shortcut->context()->component()->friendlyName()); ret.append(shortcut->friendlyName()); } return ret; } void KGlobalAccelD::activateGlobalShortcutContext( const QString &component, const QString &uniqueName) { KdeDGlobalAccel::Component *const comp = GlobalShortcutsRegistry::self()->getComponent(component); if (comp) comp->activateGlobalShortcutContext(uniqueName); } QList KGlobalAccelD::allComponents() const { QList allComp; const auto lstMainComponents = GlobalShortcutsRegistry::self()->allMainComponents(); for (const KdeDGlobalAccel::Component *component : lstMainComponents) { allComp.append(component->dbusPath()); } return allComp; } void KGlobalAccelD::blockGlobalShortcuts(bool block) { #ifdef KDEDGLOBALACCEL_TRACE qCDebug(KGLOBALACCELD) << block; #endif block ? GlobalShortcutsRegistry::self()->deactivateShortcuts(true) : GlobalShortcutsRegistry::self()->activateShortcuts(); } QList KGlobalAccelD::shortcut(const QStringList &action) const { GlobalShortcut *shortcut = d->findAction(action); if (shortcut) return shortcut->keys(); return QList(); } QList KGlobalAccelD::defaultShortcut(const QStringList &action) const { GlobalShortcut *shortcut = d->findAction(action); if (shortcut) return shortcut->defaultKeys(); return QList(); } // This method just registers the action. Nothing else. Shortcut has to be set // later. void KGlobalAccelD::doRegister(const QStringList &actionId) { #ifdef KDEDGLOBALACCEL_TRACE qCDebug(KGLOBALACCELD) << actionId; #endif // Check because we would not want to add a action for an invalid // actionId. findAction returns nullptr in that case. if (actionId.size() < 4) { return; } GlobalShortcut *shortcut = d->findAction(actionId); if (!shortcut) { shortcut = d->addAction(actionId); } else { //a switch of locales is one common reason for a changing friendlyName if ((!actionId[KGlobalAccel::ActionFriendly].isEmpty()) && shortcut->friendlyName() != actionId[KGlobalAccel::ActionFriendly]) { shortcut->setFriendlyName(actionId[KGlobalAccel::ActionFriendly]); scheduleWriteSettings(); } if ((!actionId[KGlobalAccel::ComponentFriendly].isEmpty()) && shortcut->context()->component()->friendlyName() != actionId[KGlobalAccel::ComponentFriendly]) { shortcut->context()->component()->setFriendlyName(actionId[KGlobalAccel::ComponentFriendly]); scheduleWriteSettings(); } } } QDBusObjectPath KGlobalAccelD::getComponent(const QString &componentUnique) const { #ifdef KDEDGLOBALACCEL_TRACE qCDebug(KGLOBALACCELD) << componentUnique; #endif KdeDGlobalAccel::Component *component = GlobalShortcutsRegistry::self()->getComponent(componentUnique); if (component) { return component->dbusPath(); } else { sendErrorReply("org.kde.kglobalaccel.NoSuchComponent", QString("The component '%1' doesn't exist.").arg(componentUnique)); return QDBusObjectPath("/"); } } QList KGlobalAccelD::getGlobalShortcutsByKey(int key) const { #ifdef KDEDGLOBALACCEL_TRACE qCDebug(KGLOBALACCELD) << key; #endif const QList shortcuts = GlobalShortcutsRegistry::self()->getShortcutsByKey(key); QList rc; for (const GlobalShortcut *sc : shortcuts) { #ifdef KDEDGLOBALACCEL_TRACE qCDebug(KGLOBALACCELD) << sc->context()->uniqueName() << sc->uniqueName(); #endif rc.append(static_cast(*sc)); } return rc; } bool KGlobalAccelD::isGlobalShortcutAvailable(int shortcut, const QString &component) const { QString realComponent = component; QString context; d->splitComponent(realComponent, context); return GlobalShortcutsRegistry::self()->isShortcutAvailable(shortcut, realComponent, context); } void KGlobalAccelD::setInactive(const QStringList &actionId) { #ifdef KDEDGLOBALACCEL_TRACE qCDebug(KGLOBALACCELD) << actionId; #endif GlobalShortcut *shortcut = d->findAction(actionId); if (shortcut) shortcut->setIsPresent(false); } bool KGlobalAccelD::unregister(const QString &componentUnique, const QString &shortcutUnique) { #ifdef KDEDGLOBALACCEL_TRACE qCDebug(KGLOBALACCELD) << componentUnique << shortcutUnique; #endif // Stop grabbing the key GlobalShortcut *shortcut = d->findAction(componentUnique, shortcutUnique); if (shortcut) { shortcut->unRegister(); scheduleWriteSettings(); } return shortcut; } +#if KGLOBALACCELPRIVATE_BUILD_DEPRECATED_SINCE(4, 3) void KGlobalAccelD::unRegister(const QStringList &actionId) { #ifdef KDEDGLOBALACCEL_TRACE qCDebug(KGLOBALACCELD) << actionId; #endif // Stop grabbing the key GlobalShortcut *shortcut = d->findAction(actionId); if (shortcut) { shortcut->unRegister(); scheduleWriteSettings(); } } - +#endif QList KGlobalAccelD::setShortcut(const QStringList &actionId, const QList &keys, uint flags) { //spare the DBus framework some work const bool setPresent = (flags & SetPresent); const bool isAutoloading = !(flags & NoAutoloading); const bool isDefault = (flags & IsDefault); GlobalShortcut *shortcut = d->findAction(actionId); if (!shortcut) { return QList(); } //default shortcuts cannot clash because they don't do anything if (isDefault) { if (shortcut->defaultKeys() != keys) { shortcut->setDefaultKeys(keys); scheduleWriteSettings(); } return keys; //doesn't matter } if (isAutoloading && !shortcut->isFresh()) { //the trivial and common case - synchronize the action from our data //and exit. if (!shortcut->isPresent() && setPresent) { shortcut->setIsPresent(true); } // We are finished here. Return the list of current active keys. return shortcut->keys(); } //now we are actually changing the shortcut of the action shortcut->setKeys(keys); if (setPresent) { shortcut->setIsPresent(true); } //maybe isFresh should really only be set if setPresent, but only two things should use !setPresent: //- the global shortcuts KCM: very unlikely to catch KWin/etc.'s actions in isFresh state //- KGlobalAccel::stealGlobalShortcutSystemwide(): only applies to actions with shortcuts // which can never be fresh if created the usual way shortcut->setIsFresh(false); scheduleWriteSettings(); return shortcut->keys(); } void KGlobalAccelD::setForeignShortcut(const QStringList &actionId, const QList &keys) { #ifdef KDEDGLOBALACCEL_TRACE qCDebug(KGLOBALACCELD) << actionId; #endif GlobalShortcut *shortcut = d->findAction(actionId); if (!shortcut) return; QList newKeys = setShortcut(actionId, keys, NoAutoloading); emit yourShortcutGotChanged(actionId, newKeys); } void KGlobalAccelD::scheduleWriteSettings() const { if (!d->writeoutTimer.isActive()) d->writeoutTimer.start(500); } #include "moc_kglobalacceld.cpp" diff --git a/src/runtime/kglobalacceld.h b/src/runtime/kglobalacceld.h index c9dbf14..5608370 100644 --- a/src/runtime/kglobalacceld.h +++ b/src/runtime/kglobalacceld.h @@ -1,155 +1,158 @@ /* This file is part of the KDE libraries Copyright (c) 2007 Andreas Hartmetz Copyright (c) 2008 Michael Jansen 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 KGLOBALACCELD_H #define KGLOBALACCELD_H #include "kf5globalaccelprivate_export.h" #include #include #include #include struct KGlobalAccelDPrivate; /** * @todo get rid of all of those QStringList parameters. */ -class KF5GLOBALACCELPRIVATE_EXPORT KGlobalAccelD : public QObject, protected QDBusContext +class KGLOBALACCELPRIVATE_EXPORT KGlobalAccelD : public QObject, protected QDBusContext { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KGlobalAccel") public: enum SetShortcutFlag { SetPresent = 2, NoAutoloading = 4, IsDefault = 8 }; Q_ENUM(SetShortcutFlag) Q_DECLARE_FLAGS(SetShortcutFlags, SetShortcutFlag) Q_FLAG(SetShortcutFlags) explicit KGlobalAccelD(QObject* parent = nullptr); ~KGlobalAccelD(); bool init(); public Q_SLOTS: /** * Get the dbus path for all known components. * * The returned path is absolute. No need to prepend anything. */ Q_SCRIPTABLE QList allComponents() const; Q_SCRIPTABLE QList allMainComponents() const; Q_SCRIPTABLE QList allActionsForComponent(const QStringList &actionId) const; Q_SCRIPTABLE QStringList action(int key) const; //to be called by main components not owning the action Q_SCRIPTABLE QList shortcut(const QStringList &actionId) const; //to be called by main components not owning the action Q_SCRIPTABLE QList defaultShortcut(const QStringList &actionId) const; /** * Get the dbus path for @ componentUnique * * @param componentUnique the components unique identifier * * @return the absolute dbus path */ Q_SCRIPTABLE QDBusObjectPath getComponent(const QString &componentUnique) const; //to be called by main components owning the action Q_SCRIPTABLE QList setShortcut(const QStringList &actionId, const QList &keys, uint flags); //this is used if application A wants to change shortcuts of application B Q_SCRIPTABLE void setForeignShortcut(const QStringList &actionId, const QList &keys); //to be called when a KAction is destroyed. The shortcut stays in the data structures for //conflict resolution but won't trigger. Q_SCRIPTABLE void setInactive(const QStringList &actionId); Q_SCRIPTABLE void doRegister(const QStringList &actionId); - //! @see unregister - Q_SCRIPTABLE QT_DEPRECATED void unRegister(const QStringList &actionId); +#if KGLOBALACCELPRIVATE_ENABLE_DEPRECATED_SINCE(4, 3) + //! @deprecated Since 4.3, use KGlobalAccelD::unregister + KGLOBALACCELPRIVATE_DEPRECATED_VERSION(4, 3, "Use KGlobalAccelD::unregister(const QString&, const QString&") + Q_SCRIPTABLE void unRegister(const QStringList &actionId); +#endif Q_SCRIPTABLE void activateGlobalShortcutContext( const QString &component, const QString &context); /** * Returns the shortcuts registered for @p key. * * If there is more than one shortcut they are guaranteed to be from the * same component but different contexts. All shortcuts are searched. */ Q_SCRIPTABLE QList getGlobalShortcutsByKey(int key) const; /** * Return true if the @p shortcut is available for @p component. */ Q_SCRIPTABLE bool isGlobalShortcutAvailable( int key, const QString &component) const; /** * Delete the shortcut with @a component and @name. * * The shortcut is removed from the registry even if it is currently * present. It is removed from all contexts. * * @param componentUnique the components unique identifier * @param shortcutUnique the shortcut id * * @return @c true if the shortcuts was deleted, @c false if it didn't * exist. */ Q_SCRIPTABLE bool unregister( const QString &componentUnique, const QString &shortcutUnique); Q_SCRIPTABLE void blockGlobalShortcuts(bool); Q_SIGNALS: Q_SCRIPTABLE void yourShortcutGotChanged(const QStringList &actionId, const QList &newKeys); private: void scheduleWriteSettings() const; KGlobalAccelDPrivate *const d; }; #endif //KGLOBALACCELD_H