Warning, file /frameworks/kglobalaccel/src/runtime/globalshortcutsregistry.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz> 0003 SPDX-FileCopyrightText: 2022 Ahmad Samir <a.samirh78@gmail.com> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "globalshortcutsregistry.h" 0009 #include "component.h" 0010 #include "globalshortcut.h" 0011 #include "globalshortcutcontext.h" 0012 #include "kglobalaccel_interface.h" 0013 #include "kglobalshortcutinfo_p.h" 0014 #include "kserviceactioncomponent.h" 0015 #include "logging_p.h" 0016 #include <config-kglobalaccel.h> 0017 0018 #include <KDesktopFile> 0019 #include <KFileUtils> 0020 #include <KPluginMetaData> 0021 0022 #include <QDBusConnection> 0023 #include <QDir> 0024 #include <QGuiApplication> 0025 #include <QJsonArray> 0026 #include <QPluginLoader> 0027 #include <QStandardPaths> 0028 0029 static bool checkPlatform(const QJsonObject &metadata, const QString &platformName) 0030 { 0031 const QJsonArray platforms = metadata.value(QStringLiteral("MetaData")).toObject().value(QStringLiteral("platforms")).toArray(); 0032 return std::any_of(platforms.begin(), platforms.end(), [&platformName](const QJsonValue &value) { 0033 return QString::compare(platformName, value.toString(), Qt::CaseInsensitive) == 0; 0034 }); 0035 } 0036 0037 static KGlobalAccelInterface *loadPlugin(GlobalShortcutsRegistry *parent) 0038 { 0039 QString platformName = QString::fromLocal8Bit(qgetenv("KGLOBALACCELD_PLATFORM")); 0040 if (platformName.isEmpty()) { 0041 platformName = QGuiApplication::platformName(); 0042 } 0043 0044 const QVector<QStaticPlugin> staticPlugins = QPluginLoader::staticPlugins(); 0045 for (const QStaticPlugin &staticPlugin : staticPlugins) { 0046 const QJsonObject metadata = staticPlugin.metaData(); 0047 if (metadata.value(QLatin1String("IID")) != QLatin1String(KGlobalAccelInterface_iid)) { 0048 continue; 0049 } 0050 if (checkPlatform(metadata, platformName)) { 0051 KGlobalAccelInterface *interface = qobject_cast<KGlobalAccelInterface *>(staticPlugin.instance()); 0052 if (interface) { 0053 qCDebug(KGLOBALACCELD) << "Loaded a static plugin for platform" << platformName; 0054 interface->setRegistry(parent); 0055 return interface; 0056 } 0057 } 0058 } 0059 0060 const QVector<KPluginMetaData> candidates = KPluginMetaData::findPlugins(QStringLiteral("org.kde.kglobalaccel5.platforms")); 0061 for (const KPluginMetaData &candidate : candidates) { 0062 QPluginLoader loader(candidate.fileName()); 0063 if (checkPlatform(loader.metaData(), platformName)) { 0064 KGlobalAccelInterface *interface = qobject_cast<KGlobalAccelInterface *>(loader.instance()); 0065 if (interface) { 0066 qCDebug(KGLOBALACCELD) << "Loaded plugin" << candidate.fileName() << "for platform" << platformName; 0067 interface->setRegistry(parent); 0068 return interface; 0069 } 0070 } 0071 } 0072 0073 qCWarning(KGLOBALACCELD) << "Could not find any platform plugin"; 0074 return nullptr; 0075 } 0076 0077 static QString getConfigFile() 0078 { 0079 return qEnvironmentVariableIsSet("KGLOBALACCEL_TEST_MODE") ? QString() : QStringLiteral("kglobalshortcutsrc"); 0080 } 0081 0082 GlobalShortcutsRegistry::GlobalShortcutsRegistry() 0083 : QObject() 0084 , _manager(loadPlugin(this)) 0085 , _config(getConfigFile(), KConfig::SimpleConfig) 0086 { 0087 if (_manager) { 0088 _manager->setEnabled(true); 0089 } 0090 } 0091 0092 GlobalShortcutsRegistry::~GlobalShortcutsRegistry() 0093 { 0094 m_components.clear(); 0095 0096 if (_manager) { 0097 _manager->setEnabled(false); 0098 0099 // Ungrab all keys. We don't go over GlobalShortcuts because 0100 // GlobalShortcutsRegistry::self() doesn't work anymore. 0101 const auto listKeys = _active_keys.keys(); 0102 for (const QKeySequence &key : listKeys) { 0103 for (int i = 0; i < key.count(); i++) { 0104 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0105 _manager->grabKey(key[i].toCombined(), false); 0106 #else 0107 _manager->grabKey(key[i], false); 0108 #endif 0109 } 0110 } 0111 } 0112 _active_keys.clear(); 0113 _keys_count.clear(); 0114 } 0115 0116 Component *GlobalShortcutsRegistry::registerComponent(ComponentPtr component) 0117 { 0118 m_components.push_back(std::move(component)); 0119 auto *comp = m_components.back().get(); 0120 QDBusConnection conn(QDBusConnection::sessionBus()); 0121 conn.registerObject(comp->dbusPath().path(), comp, QDBusConnection::ExportScriptableContents); 0122 return comp; 0123 } 0124 0125 void GlobalShortcutsRegistry::activateShortcuts() 0126 { 0127 for (auto &component : m_components) { 0128 component->activateShortcuts(); 0129 } 0130 } 0131 0132 QList<QDBusObjectPath> GlobalShortcutsRegistry::componentsDbusPaths() const 0133 { 0134 QList<QDBusObjectPath> dbusPaths; 0135 dbusPaths.reserve(m_components.size()); 0136 std::transform(m_components.cbegin(), m_components.cend(), std::back_inserter(dbusPaths), [](const auto &comp) { 0137 return comp->dbusPath(); 0138 }); 0139 return dbusPaths; 0140 } 0141 0142 QList<QStringList> GlobalShortcutsRegistry::allComponentNames() const 0143 { 0144 QList<QStringList> ret; 0145 ret.reserve(m_components.size()); 0146 std::transform(m_components.cbegin(), m_components.cend(), std::back_inserter(ret), [](const auto &component) { 0147 // A string for each enumerator in KGlobalAccel::actionIdFields 0148 return QStringList{component->uniqueName(), component->friendlyName(), {}, {}}; 0149 }); 0150 0151 return ret; 0152 } 0153 0154 void GlobalShortcutsRegistry::clear() 0155 { 0156 m_components.clear(); 0157 0158 // The shortcuts should have deregistered themselves 0159 Q_ASSERT(_active_keys.isEmpty()); 0160 } 0161 0162 QDBusObjectPath GlobalShortcutsRegistry::dbusPath() const 0163 { 0164 return _dbusPath; 0165 } 0166 0167 void GlobalShortcutsRegistry::deactivateShortcuts(bool temporarily) 0168 { 0169 for (ComponentPtr &component : m_components) { 0170 component->deactivateShortcuts(temporarily); 0171 } 0172 } 0173 0174 Component *GlobalShortcutsRegistry::getComponent(const QString &uniqueName) 0175 { 0176 auto it = findByName(uniqueName); 0177 return it != m_components.cend() ? (*it).get() : nullptr; 0178 } 0179 0180 GlobalShortcut *GlobalShortcutsRegistry::getShortcutByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const 0181 { 0182 for (const ComponentPtr &component : m_components) { 0183 GlobalShortcut *rc = component->getShortcutByKey(key, type); 0184 if (rc) { 0185 return rc; 0186 } 0187 } 0188 return nullptr; 0189 } 0190 0191 QList<GlobalShortcut *> GlobalShortcutsRegistry::getShortcutsByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const 0192 { 0193 QList<GlobalShortcut *> rc; 0194 for (const ComponentPtr &component : m_components) { 0195 rc = component->getShortcutsByKey(key, type); 0196 if (!rc.isEmpty()) { 0197 return rc; 0198 } 0199 } 0200 return {}; 0201 } 0202 0203 bool GlobalShortcutsRegistry::isShortcutAvailable(const QKeySequence &shortcut, const QString &componentName, const QString &contextName) const 0204 { 0205 return std::all_of(m_components.cbegin(), m_components.cend(), [&shortcut, &componentName, &contextName](const ComponentPtr &component) { 0206 return component->isShortcutAvailable(shortcut, componentName, contextName); 0207 }); 0208 } 0209 0210 Q_GLOBAL_STATIC(GlobalShortcutsRegistry, _self) 0211 GlobalShortcutsRegistry *GlobalShortcutsRegistry::self() 0212 { 0213 return _self; 0214 } 0215 0216 /** 0217 * When we are provided just a Shift key press, interpret it as "Shift" not as "Shift+Shift" 0218 */ 0219 static void correctKeyEvent(int &keyQt) 0220 { 0221 switch (keyQt) { 0222 case Qt::ShiftModifier | Qt::Key_Shift: 0223 keyQt = Qt::Key_Shift; 0224 break; 0225 case Qt::ControlModifier | Qt::Key_Control: 0226 keyQt = Qt::Key_Control; 0227 break; 0228 case Qt::AltModifier | Qt::Key_Alt: 0229 keyQt = Qt::Key_Alt; 0230 break; 0231 case Qt::MetaModifier | Qt::Key_Meta: 0232 keyQt = Qt::Key_Meta; 0233 break; 0234 } 0235 } 0236 0237 bool GlobalShortcutsRegistry::keyPressed(int keyQt) 0238 { 0239 correctKeyEvent(keyQt); 0240 int keys[maxSequenceLength] = {0, 0, 0, 0}; 0241 int count = _active_sequence.count(); 0242 if (count == maxSequenceLength) { 0243 // buffer is full, rotate it 0244 for (int i = 1; i < count; i++) { 0245 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0246 keys[i - 1] = _active_sequence[i].toCombined(); 0247 #else 0248 keys[i - 1] = _active_sequence[i]; 0249 #endif 0250 } 0251 keys[maxSequenceLength - 1] = keyQt; 0252 } else { 0253 // just append the new key 0254 for (int i = 0; i < count; i++) { 0255 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0256 keys[i] = _active_sequence[i].toCombined(); 0257 #else 0258 keys[i] = _active_sequence[i]; 0259 #endif 0260 } 0261 keys[count] = keyQt; 0262 } 0263 0264 _active_sequence = QKeySequence(keys[0], keys[1], keys[2], keys[3]); 0265 0266 GlobalShortcut *shortcut = nullptr; 0267 QKeySequence tempSequence; 0268 for (int length = 1; length <= _active_sequence.count(); length++) { 0269 // We have to check all possible matches from the end since we're rotating active sequence 0270 // instead of cleaning it when it's full 0271 int sequenceToCheck[maxSequenceLength] = {0, 0, 0, 0}; 0272 for (int i = 0; i < length; i++) { 0273 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0274 sequenceToCheck[i] = _active_sequence[_active_sequence.count() - length + i].toCombined(); 0275 #else 0276 sequenceToCheck[i] = _active_sequence[_active_sequence.count() - length + i]; 0277 #endif 0278 } 0279 tempSequence = QKeySequence(sequenceToCheck[0], sequenceToCheck[1], sequenceToCheck[2], sequenceToCheck[3]); 0280 shortcut = getShortcutByKey(tempSequence); 0281 0282 if (shortcut) { 0283 break; 0284 } 0285 } 0286 0287 qCDebug(KGLOBALACCELD) << "Pressed key" << QKeySequence(keyQt).toString() << ", current sequence" << _active_sequence.toString() << "=" 0288 << (shortcut ? shortcut->uniqueName() : QStringLiteral("(no shortcut found)")); 0289 if (!shortcut) { 0290 // This can happen for example with the ALT-Print shortcut of kwin. 0291 // ALT+PRINT is SYSREQ on my keyboard. So we grab something we think 0292 // is ALT+PRINT but symXToKeyQt and modXToQt make ALT+SYSREQ of it 0293 // when pressed (correctly). We can't match that. 0294 qCDebug(KGLOBALACCELD) << "Got unknown key" << QKeySequence(keyQt).toString(); 0295 0296 // In production mode just do nothing. 0297 return false; 0298 } else if (!shortcut->isActive()) { 0299 qCDebug(KGLOBALACCELD) << "Got inactive key" << QKeySequence(keyQt).toString(); 0300 0301 // In production mode just do nothing. 0302 return false; 0303 } 0304 0305 qCDebug(KGLOBALACCELD) << QKeySequence(keyQt).toString() << "=" << shortcut->uniqueName(); 0306 0307 // shortcut is found, reset active sequence 0308 _active_sequence = QKeySequence(); 0309 0310 QStringList data(shortcut->context()->component()->uniqueName()); 0311 data.append(shortcut->uniqueName()); 0312 data.append(shortcut->context()->component()->friendlyName()); 0313 data.append(shortcut->friendlyName()); 0314 0315 // Make sure kglobalacceld has ungrabbed the keyboard after receiving the 0316 // keypress, otherwise actions in application that try to grab the 0317 // keyboard (e.g. in kwin) may fail to do so. There is still a small race 0318 // condition with this being out-of-process. 0319 if (_manager) { 0320 _manager->syncWindowingSystem(); 0321 } 0322 0323 if (m_lastShortcut && m_lastShortcut != shortcut) { 0324 m_lastShortcut->context()->component()->emitGlobalShortcutReleased(*m_lastShortcut); 0325 } 0326 0327 // Invoke the action 0328 shortcut->context()->component()->emitGlobalShortcutPressed(*shortcut); 0329 m_lastShortcut = shortcut; 0330 0331 return true; 0332 } 0333 0334 bool GlobalShortcutsRegistry::keyReleased(int keyQt) 0335 { 0336 Q_UNUSED(keyQt) 0337 if (m_lastShortcut) { 0338 m_lastShortcut->context()->component()->emitGlobalShortcutReleased(*m_lastShortcut); 0339 m_lastShortcut = nullptr; 0340 } 0341 return false; 0342 } 0343 0344 Component *GlobalShortcutsRegistry::createComponent(const QString &uniqueName, const QString &friendlyName) 0345 { 0346 auto it = findByName(uniqueName); 0347 if (it != m_components.cend()) { 0348 Q_ASSERT_X(false, // 0349 "GlobalShortcutsRegistry::createComponent", 0350 QLatin1String("A Component with the name: %1, already exists").arg(uniqueName).toUtf8().constData()); 0351 return (*it).get(); 0352 } 0353 0354 auto *c = registerComponent(ComponentPtr(new Component(uniqueName, friendlyName), &unregisterComponent)); 0355 return c; 0356 } 0357 0358 void GlobalShortcutsRegistry::unregisterComponent(Component *component) 0359 { 0360 QDBusConnection::sessionBus().unregisterObject(component->dbusPath().path()); 0361 delete component; 0362 } 0363 0364 KServiceActionComponent *GlobalShortcutsRegistry::createServiceActionComponent(const QString &uniqueName, const QString &friendlyName) 0365 { 0366 auto it = findByName(uniqueName); 0367 if (it != m_components.cend()) { 0368 Q_ASSERT_X(false, // 0369 "GlobalShortcutsRegistry::createServiceActionComponent", 0370 QLatin1String("A KServiceActionComponent with the name: %1, already exists").arg(uniqueName).toUtf8().constData()); 0371 return static_cast<KServiceActionComponent *>((*it).get()); 0372 } 0373 0374 auto *c = registerComponent(ComponentPtr(new KServiceActionComponent(uniqueName, friendlyName), &unregisterComponent)); 0375 return static_cast<KServiceActionComponent *>(c); 0376 } 0377 0378 void GlobalShortcutsRegistry::loadSettings() 0379 { 0380 if (!m_components.empty()) { 0381 qCDebug(KGLOBALACCELD) << "Registry settings already loaded. Skipped loading again."; 0382 return; 0383 } 0384 0385 const auto groupList = _config.groupList(); 0386 for (const QString &groupName : groupList) { 0387 qCDebug(KGLOBALACCELD) << "Loading group " << groupName; 0388 0389 Q_ASSERT(groupName.indexOf(QLatin1Char('\x1d')) == -1); 0390 0391 // loadSettings isn't designed to be called in between. Only at the 0392 // beginning. 0393 Q_ASSERT(!getComponent(groupName)); 0394 0395 KConfigGroup configGroup(&_config, groupName); 0396 0397 const QString friendlyName = configGroup.readEntry("_k_friendly_name"); 0398 0399 const bool isDesktop = groupName.endsWith(QLatin1String(".desktop")); 0400 // Create the component 0401 Component *component = isDesktop ? createServiceActionComponent(groupName, friendlyName) // 0402 : createComponent(groupName, friendlyName); 0403 0404 // Now load the contexts 0405 const auto groupList = configGroup.groupList(); 0406 for (const QString &context : groupList) { 0407 // Skip the friendly name group, this was previously used instead of _k_friendly_name 0408 if (context == QLatin1String("Friendly Name")) { 0409 continue; 0410 } 0411 0412 KConfigGroup contextGroup(&configGroup, context); 0413 QString contextFriendlyName = contextGroup.readEntry("_k_friendly_name"); 0414 component->createGlobalShortcutContext(context, contextFriendlyName); 0415 component->activateGlobalShortcutContext(context); 0416 component->loadSettings(contextGroup); 0417 } 0418 0419 // Load the default context 0420 component->activateGlobalShortcutContext(QStringLiteral("default")); 0421 component->loadSettings(configGroup); 0422 } 0423 0424 // Load the configured KServiceActions 0425 const QStringList desktopPaths = 0426 QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kglobalaccel"), QStandardPaths::LocateDirectory); 0427 0428 const QStringList desktopFiles = KFileUtils::findAllUniqueFiles(desktopPaths, {QStringLiteral("*.desktop")}); 0429 0430 for (const QString &file : desktopFiles) { 0431 const QString fileName = QFileInfo(file).fileName(); 0432 auto it = findByName(fileName); 0433 if (it != m_components.cend()) { 0434 continue; 0435 } 0436 0437 KDesktopFile deskF(file); 0438 if (deskF.noDisplay()) { 0439 continue; 0440 } 0441 0442 auto *actionComp = createServiceActionComponent(fileName, deskF.readName()); 0443 actionComp->activateGlobalShortcutContext(QStringLiteral("default")); 0444 actionComp->loadFromService(); 0445 } 0446 } 0447 0448 void GlobalShortcutsRegistry::grabKeys() 0449 { 0450 activateShortcuts(); 0451 } 0452 0453 bool GlobalShortcutsRegistry::registerKey(const QKeySequence &key, GlobalShortcut *shortcut) 0454 { 0455 if (!_manager) { 0456 return false; 0457 } 0458 if (key.isEmpty()) { 0459 qCDebug(KGLOBALACCELD) << shortcut->uniqueName() << ": Key '" << QKeySequence(key).toString() << "' already taken by " 0460 << _active_keys.value(key)->uniqueName() << "."; 0461 return false; 0462 } else if (_active_keys.value(key)) { 0463 qCDebug(KGLOBALACCELD) << shortcut->uniqueName() << ": Attempt to register key 0."; 0464 return false; 0465 } 0466 0467 qCDebug(KGLOBALACCELD) << "Registering key" << QKeySequence(key).toString() << "for" << shortcut->context()->component()->uniqueName() << ":" 0468 << shortcut->uniqueName(); 0469 0470 bool error = false; 0471 int i; 0472 for (i = 0; i < key.count(); i++) { 0473 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0474 const int combined = key[i].toCombined(); 0475 #else 0476 const int combined(key[i]); 0477 #endif 0478 if (!_manager->grabKey(combined, true)) { 0479 error = true; 0480 break; 0481 } 0482 ++_keys_count[combined]; 0483 } 0484 0485 if (error) { 0486 // Last key was not registered, rewind index by 1 0487 for (--i; i >= 0; i--) { 0488 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0489 const int combined = key[i].toCombined(); 0490 #else 0491 const int combined(key[i]); 0492 #endif 0493 auto it = _keys_count.find(combined); 0494 if (it == _keys_count.end()) { 0495 continue; 0496 } 0497 0498 if (it.value() == 1) { 0499 _keys_count.erase(it); 0500 _manager->grabKey(combined, false); 0501 } else { 0502 --(it.value()); 0503 } 0504 } 0505 return false; 0506 } 0507 0508 _active_keys.insert(key, shortcut); 0509 0510 return true; 0511 } 0512 0513 void GlobalShortcutsRegistry::setAccelManager(KGlobalAccelInterface *manager) 0514 { 0515 _manager = manager; 0516 } 0517 0518 void GlobalShortcutsRegistry::setDBusPath(const QDBusObjectPath &path) 0519 { 0520 _dbusPath = path; 0521 } 0522 0523 void GlobalShortcutsRegistry::ungrabKeys() 0524 { 0525 deactivateShortcuts(); 0526 } 0527 0528 bool GlobalShortcutsRegistry::unregisterKey(const QKeySequence &key, GlobalShortcut *shortcut) 0529 { 0530 if (!_manager) { 0531 return false; 0532 } 0533 if (_active_keys.value(key) != shortcut) { 0534 // The shortcut doesn't own the key or the key isn't grabbed 0535 return false; 0536 } 0537 0538 for (int i = 0; i < key.count(); i++) { 0539 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0540 auto iter = _keys_count.find(key[i].toCombined()); 0541 0542 #else 0543 auto iter = _keys_count.find(key[i]); 0544 #endif 0545 if ((iter == _keys_count.end()) || (iter.value() <= 0)) { 0546 continue; 0547 } 0548 0549 // Unregister if there's only one ref to given key 0550 // We should fail earlier when key is not registered 0551 if (iter.value() == 1) { 0552 qCDebug(KGLOBALACCELD) << "Unregistering key" << QKeySequence(key[i]).toString() << "for" << shortcut->context()->component()->uniqueName() << ":" 0553 << shortcut->uniqueName(); 0554 0555 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0556 _manager->grabKey(key[i].toCombined(), false); 0557 0558 #else 0559 _manager->grabKey(key[i], false); 0560 #endif 0561 _keys_count.erase(iter); 0562 } else { 0563 qCDebug(KGLOBALACCELD) << "Refused to unregister key" << QKeySequence(key[i]).toString() << ": used by another global shortcut"; 0564 --(iter.value()); 0565 } 0566 } 0567 0568 if (shortcut && shortcut == m_lastShortcut) { 0569 m_lastShortcut->context()->component()->emitGlobalShortcutReleased(*m_lastShortcut); 0570 m_lastShortcut = nullptr; 0571 } 0572 0573 _active_keys.remove(key); 0574 return true; 0575 } 0576 0577 void GlobalShortcutsRegistry::writeSettings() 0578 { 0579 auto it = std::remove_if(m_components.begin(), m_components.end(), [this](const ComponentPtr &component) { 0580 KConfigGroup configGroup(&_config, component->uniqueName()); 0581 if (component->allShortcuts().isEmpty()) { 0582 configGroup.deleteGroup(); 0583 return true; 0584 } else { 0585 component->writeSettings(configGroup); 0586 return false; 0587 } 0588 }); 0589 0590 m_components.erase(it, m_components.end()); 0591 _config.sync(); 0592 } 0593 0594 #include "moc_globalshortcutsregistry.cpp"