File indexing completed on 2024-05-19 05:29:56
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 <KApplicationTrader> 0023 #include <QDBusConnection> 0024 #include <QDir> 0025 #include <QGuiApplication> 0026 #include <QJsonArray> 0027 #include <QPluginLoader> 0028 #include <QStandardPaths> 0029 0030 static bool checkPlatform(const QJsonObject &metadata, const QString &platformName) 0031 { 0032 const QJsonArray platforms = metadata.value(QStringLiteral("MetaData")).toObject().value(QStringLiteral("platforms")).toArray(); 0033 return std::any_of(platforms.begin(), platforms.end(), [&platformName](const QJsonValue &value) { 0034 return QString::compare(platformName, value.toString(), Qt::CaseInsensitive) == 0; 0035 }); 0036 } 0037 0038 static KGlobalAccelInterface *loadPlugin(GlobalShortcutsRegistry *parent) 0039 { 0040 QString platformName = QString::fromLocal8Bit(qgetenv("KGLOBALACCELD_PLATFORM")); 0041 if (platformName.isEmpty()) { 0042 platformName = QGuiApplication::platformName(); 0043 } 0044 0045 const QList<QStaticPlugin> staticPlugins = QPluginLoader::staticPlugins(); 0046 for (const QStaticPlugin &staticPlugin : staticPlugins) { 0047 const QJsonObject metadata = staticPlugin.metaData(); 0048 if (metadata.value(QLatin1String("IID")) != QLatin1String(KGlobalAccelInterface_iid)) { 0049 continue; 0050 } 0051 if (checkPlatform(metadata, platformName)) { 0052 KGlobalAccelInterface *interface = qobject_cast<KGlobalAccelInterface *>(staticPlugin.instance()); 0053 if (interface) { 0054 qCDebug(KGLOBALACCELD) << "Loaded a static plugin for platform" << platformName; 0055 interface->setRegistry(parent); 0056 return interface; 0057 } 0058 } 0059 } 0060 0061 const QList<KPluginMetaData> candidates = KPluginMetaData::findPlugins(QStringLiteral("org.kde.kglobalacceld.platforms")); 0062 for (const KPluginMetaData &candidate : candidates) { 0063 QPluginLoader loader(candidate.fileName()); 0064 if (checkPlatform(loader.metaData(), platformName)) { 0065 KGlobalAccelInterface *interface = qobject_cast<KGlobalAccelInterface *>(loader.instance()); 0066 if (interface) { 0067 qCDebug(KGLOBALACCELD) << "Loaded plugin" << candidate.fileName() << "for platform" << platformName; 0068 interface->setRegistry(parent); 0069 return interface; 0070 } 0071 } 0072 } 0073 0074 qCWarning(KGLOBALACCELD) << "Could not find any platform plugin"; 0075 return nullptr; 0076 } 0077 0078 static QString getConfigFile() 0079 { 0080 return qEnvironmentVariableIsSet("KGLOBALACCEL_TEST_MODE") ? QString() : QStringLiteral("kglobalshortcutsrc"); 0081 } 0082 0083 void GlobalShortcutsRegistry::migrateKHotkeys() 0084 { 0085 KConfig hotkeys(QStringLiteral("khotkeysrc")); 0086 0087 int dataCount = hotkeys.group(QStringLiteral("Data")).readEntry("DataCount", 0); 0088 0089 for (int i = 1; i <= dataCount; ++i) { 0090 const QString groupName = QStringLiteral("Data_") + QString::number(i); 0091 0092 KConfigGroup dataGroup(&hotkeys, groupName); 0093 0094 if (dataGroup.readEntry("Type") != QLatin1String("SIMPLE_ACTION_DATA")) { 0095 continue; 0096 } 0097 0098 const QString name = dataGroup.readEntry("Name"); 0099 0100 QString exec; 0101 QString uuid; 0102 0103 int actionsCount = KConfigGroup(&hotkeys, groupName + QLatin1String("Actions")).readEntry("ActionsCount", 0); 0104 0105 for (int i = 0; i < actionsCount; ++i) { 0106 KConfigGroup actionGroup = KConfigGroup(&hotkeys, groupName + QLatin1String("Actions") + QString::number(i)); 0107 const QString type = actionGroup.readEntry("Type"); 0108 0109 if (type == QLatin1String("COMMAND_URL")) { 0110 exec = actionGroup.readEntry("CommandURL"); 0111 } else if (type == QLatin1String("DBUS")) { 0112 exec = QStringLiteral(QT_STRINGIFY(QDBUS) " %1 %2 %3") 0113 .arg(actionGroup.readEntry("RemoteApp"), actionGroup.readEntry("RemoteObj"), actionGroup.readEntry("Call")); 0114 0115 const QString args = actionGroup.readEntry("Arguments"); 0116 0117 if (!args.isEmpty()) { 0118 exec += QLatin1Char(' ') + args; 0119 } 0120 } 0121 } 0122 0123 if (exec.isEmpty()) { 0124 continue; 0125 } 0126 0127 int triggerCount = KConfigGroup(&hotkeys, groupName + QLatin1String("Triggers")).readEntry("TriggersCount", 0); 0128 0129 for (int i = 0; i < triggerCount; ++i) { 0130 KConfigGroup triggerGroup = KConfigGroup(&hotkeys, groupName + QLatin1String("Triggers") + QString::number(i)); 0131 if (triggerGroup.readEntry("Type") != QLatin1String("SHORTCUT")) { 0132 continue; 0133 } 0134 0135 uuid = triggerGroup.readEntry("Uuid"); 0136 } 0137 0138 const QString kglobalaccelEntry = _config.group(QStringLiteral("khotkeys")).readEntry(uuid); 0139 0140 if (kglobalaccelEntry.isEmpty()) { 0141 continue; 0142 } 0143 0144 const QString key = kglobalaccelEntry.split(QLatin1Char(',')).first(); 0145 0146 KDesktopFile file(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kglobalaccel/") + uuid 0147 + QLatin1String(".desktop")); 0148 file.desktopGroup().writeEntry("Type", "Application"); 0149 file.desktopGroup().writeEntry("Name", name); 0150 file.desktopGroup().writeEntry("Exec", exec); 0151 file.desktopGroup().writeEntry("X-KDE-GlobalAccel-CommandShortcut", true); 0152 file.desktopGroup().writeEntry("StartupNotify", false); 0153 0154 _config.group(QStringLiteral("services")).group(uuid + QLatin1String(".desktop")).writeEntry("_launch", kglobalaccelEntry); 0155 _config.group(QStringLiteral("khotkeys")).revertToDefault(uuid); 0156 } 0157 } 0158 0159 /* 0160 * Migrate the Plasma 5 config for service actions to a new format that only stores the actual shortcut if not default. 0161 * All other information is read from the desktop file. 0162 */ 0163 void GlobalShortcutsRegistry::migrateConfig() 0164 { 0165 const QStringList groups = _config.groupList(); 0166 0167 KConfigGroup services = _config.group(QStringLiteral("services")); 0168 0169 for (const QString &componentName : groups) { 0170 if (!componentName.endsWith(QLatin1String(".desktop"))) { 0171 continue; 0172 } 0173 0174 KConfigGroup component = _config.group(componentName); 0175 KConfigGroup newGroup = services.group(componentName); 0176 0177 for (auto [key, value] : component.entryMap().asKeyValueRange()) { 0178 if (key == QLatin1String("_k_friendly_name")) { 0179 continue; 0180 } 0181 0182 const QString shortcut = value.split(QLatin1Char(','))[0]; 0183 const QString defaultShortcut = value.split(QLatin1Char(','))[1]; 0184 0185 if (shortcut != defaultShortcut) { 0186 newGroup.writeEntry(key, shortcut); 0187 } 0188 } 0189 0190 component.deleteGroup(); 0191 } 0192 0193 // Migrate dynamic shortcuts to service-based shortcuts 0194 const QStringList desktopPaths = 0195 QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kglobalaccel"), QStandardPaths::LocateDirectory); 0196 0197 const QStringList desktopFiles = KFileUtils::findAllUniqueFiles(desktopPaths, {QStringLiteral("*.desktop")}); 0198 0199 for (const QString &fileName : desktopFiles) { 0200 KDesktopFile file(fileName); 0201 const QString componentName = QFileInfo(fileName).fileName(); 0202 0203 auto migrateTo = [this, componentName](KConfigGroup &group, const QString &actionName) { 0204 QString migrateFrom = group.readEntry<QString>(QStringLiteral("X-KDE-Migrate-Shortcut"), QString()); 0205 0206 if (migrateFrom.isEmpty()) { 0207 return; 0208 } 0209 0210 const QStringList migrateFromParts = migrateFrom.split(QLatin1Char(',')); 0211 0212 if (!_config.group(migrateFromParts[0]).hasKey(migrateFromParts[1])) { 0213 // Probably already migrated 0214 return; 0215 } 0216 0217 const QStringList shortcutTriple = _config.group(migrateFromParts[0]).readEntry<QStringList>(migrateFromParts[1], QStringList()); 0218 const QString oldShortcut = shortcutTriple[0]; 0219 const QString oldDefaultShortcut = shortcutTriple[1]; 0220 const QString newDefaultShortcut = group.readEntry<QString>("X-KDE-Shortcuts", QString()); 0221 0222 // Only write value if it is not the old or new default 0223 if (oldShortcut != oldDefaultShortcut && oldShortcut != newDefaultShortcut) { 0224 _config.group(QStringLiteral("services")).group(componentName).writeEntry(actionName, oldShortcut); 0225 } 0226 0227 _config.group(migrateFromParts[0]).deleteEntry(migrateFromParts[1]); 0228 0229 if (_config.group(migrateFromParts[0]).entryMap().size() == 1) { 0230 // only _k_friendly_name left, remove the group 0231 _config.deleteGroup(migrateFromParts[0]); 0232 } 0233 }; 0234 0235 KConfigGroup desktopGroup = file.desktopGroup(); 0236 migrateTo(desktopGroup, QStringLiteral("_launch")); 0237 0238 const QStringList actions = file.readActions(); 0239 for (const QString &action : actions) { 0240 KConfigGroup actionGroup = file.actionGroup(action); 0241 migrateTo(actionGroup, action); 0242 } 0243 } 0244 0245 _config.sync(); 0246 } 0247 0248 GlobalShortcutsRegistry::GlobalShortcutsRegistry() 0249 : QObject() 0250 , _manager(loadPlugin(this)) 0251 , _config(getConfigFile(), KConfig::SimpleConfig) 0252 { 0253 migrateKHotkeys(); 0254 migrateConfig(); 0255 0256 if (_manager) { 0257 _manager->setEnabled(true); 0258 } 0259 } 0260 0261 GlobalShortcutsRegistry::~GlobalShortcutsRegistry() 0262 { 0263 m_components.clear(); 0264 0265 if (_manager) { 0266 _manager->setEnabled(false); 0267 0268 // Ungrab all keys. We don't go over GlobalShortcuts because 0269 // GlobalShortcutsRegistry::self() doesn't work anymore. 0270 const auto listKeys = _active_keys.keys(); 0271 for (const QKeySequence &key : listKeys) { 0272 for (int i = 0; i < key.count(); i++) { 0273 _manager->grabKey(key[i].toCombined(), false); 0274 } 0275 } 0276 } 0277 _active_keys.clear(); 0278 _keys_count.clear(); 0279 } 0280 0281 Component *GlobalShortcutsRegistry::registerComponent(ComponentPtr component) 0282 { 0283 m_components.push_back(std::move(component)); 0284 auto *comp = m_components.back().get(); 0285 Q_ASSERT(!comp->dbusPath().path().isEmpty()); 0286 QDBusConnection conn(QDBusConnection::sessionBus()); 0287 conn.registerObject(comp->dbusPath().path(), comp, QDBusConnection::ExportScriptableContents); 0288 return comp; 0289 } 0290 0291 void GlobalShortcutsRegistry::activateShortcuts() 0292 { 0293 for (auto &component : m_components) { 0294 component->activateShortcuts(); 0295 } 0296 } 0297 0298 QList<QDBusObjectPath> GlobalShortcutsRegistry::componentsDbusPaths() const 0299 { 0300 QList<QDBusObjectPath> dbusPaths; 0301 dbusPaths.reserve(m_components.size()); 0302 std::transform(m_components.cbegin(), m_components.cend(), std::back_inserter(dbusPaths), [](const auto &comp) { 0303 return comp->dbusPath(); 0304 }); 0305 return dbusPaths; 0306 } 0307 0308 QList<QStringList> GlobalShortcutsRegistry::allComponentNames() const 0309 { 0310 QList<QStringList> ret; 0311 ret.reserve(m_components.size()); 0312 std::transform(m_components.cbegin(), m_components.cend(), std::back_inserter(ret), [](const auto &component) { 0313 // A string for each enumerator in KGlobalAccel::actionIdFields 0314 return QStringList{component->uniqueName(), component->friendlyName(), {}, {}}; 0315 }); 0316 0317 return ret; 0318 } 0319 0320 void GlobalShortcutsRegistry::clear() 0321 { 0322 m_components.clear(); 0323 0324 // The shortcuts should have deregistered themselves 0325 Q_ASSERT(_active_keys.isEmpty()); 0326 } 0327 0328 QDBusObjectPath GlobalShortcutsRegistry::dbusPath() const 0329 { 0330 return _dbusPath; 0331 } 0332 0333 void GlobalShortcutsRegistry::deactivateShortcuts(bool temporarily) 0334 { 0335 for (ComponentPtr &component : m_components) { 0336 component->deactivateShortcuts(temporarily); 0337 } 0338 } 0339 0340 Component *GlobalShortcutsRegistry::getComponent(const QString &uniqueName) 0341 { 0342 auto it = findByName(uniqueName); 0343 return it != m_components.cend() ? (*it).get() : nullptr; 0344 } 0345 0346 GlobalShortcut *GlobalShortcutsRegistry::getShortcutByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const 0347 { 0348 for (const ComponentPtr &component : m_components) { 0349 GlobalShortcut *rc = component->getShortcutByKey(key, type); 0350 if (rc) { 0351 return rc; 0352 } 0353 } 0354 return nullptr; 0355 } 0356 0357 QList<GlobalShortcut *> GlobalShortcutsRegistry::getShortcutsByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const 0358 { 0359 QList<GlobalShortcut *> rc; 0360 for (const ComponentPtr &component : m_components) { 0361 rc = component->getShortcutsByKey(key, type); 0362 if (!rc.isEmpty()) { 0363 return rc; 0364 } 0365 } 0366 return {}; 0367 } 0368 0369 bool GlobalShortcutsRegistry::isShortcutAvailable(const QKeySequence &shortcut, const QString &componentName, const QString &contextName) const 0370 { 0371 return std::all_of(m_components.cbegin(), m_components.cend(), [&shortcut, &componentName, &contextName](const ComponentPtr &component) { 0372 return component->isShortcutAvailable(shortcut, componentName, contextName); 0373 }); 0374 } 0375 0376 Q_GLOBAL_STATIC(GlobalShortcutsRegistry, _self) 0377 GlobalShortcutsRegistry *GlobalShortcutsRegistry::self() 0378 { 0379 return _self; 0380 } 0381 0382 static void correctKeyEvent(int &keyQt) 0383 { 0384 // When we are provided just a Shift key press, interpret it as "Shift" not as "Shift+Shift" 0385 switch (keyQt) { 0386 case (Qt::ShiftModifier | Qt::Key_Shift).toCombined(): 0387 keyQt = Qt::Key_Shift; 0388 break; 0389 case (Qt::ControlModifier | Qt::Key_Control).toCombined(): 0390 keyQt = Qt::Key_Control; 0391 break; 0392 case (Qt::AltModifier | Qt::Key_Alt).toCombined(): 0393 keyQt = Qt::Key_Alt; 0394 break; 0395 case (Qt::MetaModifier | Qt::Key_Meta).toCombined(): 0396 keyQt = Qt::Key_Meta; 0397 break; 0398 } 0399 // Known limitation: 0400 // When shortcut is Mod(s)+Alt+Print, only works when Alt is released before Mod(s), 0401 // Does not work with multikey shortcuts. 0402 // When the user presses Mod(s)+Alt+Print, the SysReq event is fired only 0403 // when the Alt key is released. Before we get the Mod(s)+SysReq event, we 0404 // first get a Mod(s)+Alt event, breaking multikey shortcuts. 0405 if ((keyQt & ~Qt::KeyboardModifierMask) == Qt::Key_SysReq) { 0406 keyQt = Qt::Key_Print | (keyQt & Qt::KeyboardModifierMask) | Qt::AltModifier; 0407 } 0408 } 0409 0410 bool GlobalShortcutsRegistry::keyPressed(int keyQt) 0411 { 0412 correctKeyEvent(keyQt); 0413 int keys[maxSequenceLength] = {0, 0, 0, 0}; 0414 int count = _active_sequence.count(); 0415 if (count == maxSequenceLength) { 0416 // buffer is full, rotate it 0417 for (int i = 1; i < count; i++) { 0418 keys[i - 1] = _active_sequence[i].toCombined(); 0419 } 0420 keys[maxSequenceLength - 1] = keyQt; 0421 } else { 0422 // just append the new key 0423 for (int i = 0; i < count; i++) { 0424 keys[i] = _active_sequence[i].toCombined(); 0425 } 0426 keys[count] = keyQt; 0427 } 0428 0429 _active_sequence = QKeySequence(keys[0], keys[1], keys[2], keys[3]); 0430 0431 GlobalShortcut *shortcut = nullptr; 0432 QKeySequence tempSequence; 0433 for (int length = 1; length <= _active_sequence.count(); length++) { 0434 // We have to check all possible matches from the end since we're rotating active sequence 0435 // instead of cleaning it when it's full 0436 int sequenceToCheck[maxSequenceLength] = {0, 0, 0, 0}; 0437 for (int i = 0; i < length; i++) { 0438 sequenceToCheck[i] = _active_sequence[_active_sequence.count() - length + i].toCombined(); 0439 } 0440 tempSequence = QKeySequence(sequenceToCheck[0], sequenceToCheck[1], sequenceToCheck[2], sequenceToCheck[3]); 0441 shortcut = getShortcutByKey(tempSequence); 0442 0443 if (shortcut) { 0444 break; 0445 } 0446 } 0447 0448 qCDebug(KGLOBALACCELD) << "Pressed key" << QKeySequence(keyQt).toString() << ", current sequence" << _active_sequence.toString() << "=" 0449 << (shortcut ? shortcut->uniqueName() : QStringLiteral("(no shortcut found)")); 0450 if (!shortcut) { 0451 // This can happen for example with the ALT-Print shortcut of kwin. 0452 // ALT+PRINT is SYSREQ on my keyboard. So we grab something we think 0453 // is ALT+PRINT but symXToKeyQt and modXToQt make ALT+SYSREQ of it 0454 // when pressed (correctly). We can't match that. 0455 qCDebug(KGLOBALACCELD) << "Got unknown key" << QKeySequence(keyQt).toString(); 0456 0457 // In production mode just do nothing. 0458 return false; 0459 } else if (!shortcut->isActive()) { 0460 qCDebug(KGLOBALACCELD) << "Got inactive key" << QKeySequence(keyQt).toString(); 0461 0462 // In production mode just do nothing. 0463 return false; 0464 } 0465 0466 qCDebug(KGLOBALACCELD) << QKeySequence(keyQt).toString() << "=" << shortcut->uniqueName(); 0467 0468 // shortcut is found, reset active sequence 0469 _active_sequence = QKeySequence(); 0470 0471 QStringList data(shortcut->context()->component()->uniqueName()); 0472 data.append(shortcut->uniqueName()); 0473 data.append(shortcut->context()->component()->friendlyName()); 0474 data.append(shortcut->friendlyName()); 0475 0476 if (m_lastShortcut && m_lastShortcut != shortcut) { 0477 m_lastShortcut->context()->component()->emitGlobalShortcutReleased(*m_lastShortcut); 0478 } 0479 0480 // Invoke the action 0481 shortcut->context()->component()->emitGlobalShortcutPressed(*shortcut); 0482 m_lastShortcut = shortcut; 0483 0484 return true; 0485 } 0486 0487 bool GlobalShortcutsRegistry::keyReleased(int keyQt) 0488 { 0489 Q_UNUSED(keyQt) 0490 if (m_lastShortcut) { 0491 m_lastShortcut->context()->component()->emitGlobalShortcutReleased(*m_lastShortcut); 0492 m_lastShortcut = nullptr; 0493 } 0494 return false; 0495 } 0496 0497 Component *GlobalShortcutsRegistry::createComponent(const QString &uniqueName, const QString &friendlyName) 0498 { 0499 auto it = findByName(uniqueName); 0500 if (it != m_components.cend()) { 0501 Q_ASSERT_X(false, // 0502 "GlobalShortcutsRegistry::createComponent", 0503 QLatin1String("A Component with the name: %1, already exists").arg(uniqueName).toUtf8().constData()); 0504 return (*it).get(); 0505 } 0506 0507 auto *c = registerComponent(ComponentPtr(new Component(uniqueName, friendlyName), &unregisterComponent)); 0508 return c; 0509 } 0510 0511 void GlobalShortcutsRegistry::unregisterComponent(Component *component) 0512 { 0513 QDBusConnection::sessionBus().unregisterObject(component->dbusPath().path()); 0514 delete component; 0515 } 0516 0517 KServiceActionComponent *GlobalShortcutsRegistry::createServiceActionComponent(KService::Ptr service) 0518 { 0519 auto it = findByName(service->storageId()); 0520 if (it != m_components.cend()) { 0521 Q_ASSERT_X(false, // 0522 "GlobalShortcutsRegistry::createServiceActionComponent", 0523 QLatin1String("A KServiceActionComponent with the name: %1, already exists").arg(service->storageId()).toUtf8().constData()); 0524 return static_cast<KServiceActionComponent *>((*it).get()); 0525 } 0526 0527 auto *c = registerComponent(ComponentPtr(new KServiceActionComponent(service), &unregisterComponent)); 0528 return static_cast<KServiceActionComponent *>(c); 0529 } 0530 0531 KServiceActionComponent *GlobalShortcutsRegistry::createServiceActionComponent(const QString &uniqueName) 0532 0533 { 0534 auto it = findByName(uniqueName); 0535 if (it != m_components.cend()) { 0536 Q_ASSERT_X(false, // 0537 "GlobalShortcutsRegistry::createServiceActionComponent", 0538 QLatin1String("A KServiceActionComponent with the name: %1, already exists").arg(uniqueName).toUtf8().constData()); 0539 return static_cast<KServiceActionComponent *>((*it).get()); 0540 } 0541 0542 KService::Ptr service = KService::serviceByStorageId(uniqueName); 0543 0544 if (!service) { 0545 const QString filePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kglobalaccel/") + uniqueName); 0546 if (filePath.isEmpty()) { 0547 return nullptr; 0548 } 0549 service = new KService(filePath); 0550 } 0551 0552 auto *c = registerComponent(ComponentPtr(new KServiceActionComponent(service), &unregisterComponent)); 0553 0554 return static_cast<KServiceActionComponent *>(c); 0555 } 0556 0557 void GlobalShortcutsRegistry::loadSettings() 0558 { 0559 if (!m_components.empty()) { 0560 qCDebug(KGLOBALACCELD) << "Registry settings already loaded. Skipped loading again."; 0561 return; 0562 } 0563 0564 auto groupList = _config.groupList(); 0565 for (const QString &groupName : groupList) { 0566 if (groupName == QLatin1String("services")) { 0567 continue; 0568 } 0569 0570 if (groupName.endsWith(QLatin1String(".desktop"))) { 0571 continue; 0572 } 0573 0574 qCDebug(KGLOBALACCELD) << "Loading group " << groupName; 0575 0576 Q_ASSERT(groupName.indexOf(QLatin1Char('\x1d')) == -1); 0577 0578 // loadSettings isn't designed to be called in between. Only at the 0579 // beginning. 0580 Q_ASSERT(!getComponent(groupName)); 0581 0582 KConfigGroup configGroup(&_config, groupName); 0583 0584 const QString friendlyName = configGroup.readEntry("_k_friendly_name"); 0585 0586 Component *component = createComponent(groupName, friendlyName); 0587 0588 // Now load the contexts 0589 const auto groupList = configGroup.groupList(); 0590 for (const QString &context : groupList) { 0591 // Skip the friendly name group, this was previously used instead of _k_friendly_name 0592 if (context == QLatin1String("Friendly Name")) { 0593 continue; 0594 } 0595 0596 KConfigGroup contextGroup(&configGroup, context); 0597 QString contextFriendlyName = contextGroup.readEntry("_k_friendly_name"); 0598 component->createGlobalShortcutContext(context, contextFriendlyName); 0599 component->activateGlobalShortcutContext(context); 0600 component->loadSettings(contextGroup); 0601 } 0602 0603 // Load the default context 0604 component->activateGlobalShortcutContext(QStringLiteral("default")); 0605 component->loadSettings(configGroup); 0606 } 0607 0608 groupList = _config.group(QStringLiteral("services")).groupList(); 0609 for (const QString &groupName : groupList) { 0610 qCDebug(KGLOBALACCELD) << "Loading group " << groupName; 0611 0612 Q_ASSERT(groupName.indexOf(QLatin1Char('\x1d')) == -1); 0613 0614 // loadSettings isn't designed to be called in between. Only at the 0615 // beginning. 0616 Q_ASSERT(!getComponent(groupName)); 0617 0618 KConfigGroup configGroup = _config.group(QStringLiteral("services")).group(groupName); 0619 0620 Component *component = createServiceActionComponent(groupName); 0621 0622 if (!component) { 0623 qDebug() << "could not create a component for " << groupName; 0624 continue; 0625 } 0626 Q_ASSERT(!component->uniqueName().isEmpty()); 0627 0628 // Now load the contexts 0629 const auto groupList = configGroup.groupList(); 0630 for (const QString &context : groupList) { 0631 // Skip the friendly name group, this was previously used instead of _k_friendly_name 0632 if (context == QLatin1String("Friendly Name")) { 0633 continue; 0634 } 0635 0636 KConfigGroup contextGroup(&configGroup, context); 0637 QString contextFriendlyName = contextGroup.readEntry("_k_friendly_name"); 0638 component->createGlobalShortcutContext(context, contextFriendlyName); 0639 component->activateGlobalShortcutContext(context); 0640 component->loadSettings(contextGroup); 0641 } 0642 0643 // Load the default context 0644 component->activateGlobalShortcutContext(QStringLiteral("default")); 0645 component->loadSettings(configGroup); 0646 } 0647 0648 // Load the configured KServiceActions 0649 const QStringList desktopPaths = 0650 QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kglobalaccel"), QStandardPaths::LocateDirectory); 0651 0652 const QStringList desktopFiles = KFileUtils::findAllUniqueFiles(desktopPaths, {QStringLiteral("*.desktop")}); 0653 0654 for (const QString &file : desktopFiles) { 0655 const QString fileName = QFileInfo(file).fileName(); 0656 auto it = findByName(fileName); 0657 if (it != m_components.cend()) { 0658 continue; 0659 } 0660 0661 KService::Ptr service(new KService(file)); 0662 if (service->noDisplay()) { 0663 continue; 0664 } 0665 0666 auto *actionComp = createServiceActionComponent(service); 0667 actionComp->activateGlobalShortcutContext(QStringLiteral("default")); 0668 actionComp->loadFromService(); 0669 } 0670 0671 auto appsWithShortcuts = KApplicationTrader::query([](const KService::Ptr &service) { 0672 return !service->property<QString>(QStringLiteral("X-KDE-Shortcuts")).isEmpty(); 0673 }); 0674 0675 for (auto service : appsWithShortcuts) { 0676 auto it = findByName(service->storageId()); 0677 if (it != m_components.cend()) { 0678 continue; 0679 } 0680 0681 auto *actionComp = createServiceActionComponent(service); 0682 actionComp->activateGlobalShortcutContext(QStringLiteral("default")); 0683 actionComp->loadFromService(); 0684 } 0685 } 0686 0687 void GlobalShortcutsRegistry::grabKeys() 0688 { 0689 activateShortcuts(); 0690 } 0691 0692 bool GlobalShortcutsRegistry::registerKey(const QKeySequence &key, GlobalShortcut *shortcut) 0693 { 0694 if (!_manager) { 0695 return false; 0696 } 0697 if (key.isEmpty()) { 0698 qCDebug(KGLOBALACCELD) << shortcut->uniqueName() << ": Key '" << QKeySequence(key).toString() << "' already taken by " 0699 << _active_keys.value(key)->uniqueName() << "."; 0700 return false; 0701 } else if (_active_keys.value(key)) { 0702 qCDebug(KGLOBALACCELD) << shortcut->uniqueName() << ": Attempt to register key 0."; 0703 return false; 0704 } 0705 0706 qCDebug(KGLOBALACCELD) << "Registering key" << QKeySequence(key).toString() << "for" << shortcut->context()->component()->uniqueName() << ":" 0707 << shortcut->uniqueName(); 0708 0709 bool error = false; 0710 int i; 0711 for (i = 0; i < key.count(); i++) { 0712 const int combined = key[i].toCombined(); 0713 if (!_manager->grabKey(combined, true)) { 0714 error = true; 0715 break; 0716 } 0717 ++_keys_count[combined]; 0718 } 0719 0720 if (error) { 0721 // Last key was not registered, rewind index by 1 0722 for (--i; i >= 0; i--) { 0723 const int combined = key[i].toCombined(); 0724 auto it = _keys_count.find(combined); 0725 if (it == _keys_count.end()) { 0726 continue; 0727 } 0728 0729 if (it.value() == 1) { 0730 _keys_count.erase(it); 0731 _manager->grabKey(combined, false); 0732 } else { 0733 --(it.value()); 0734 } 0735 } 0736 return false; 0737 } 0738 0739 _active_keys.insert(key, shortcut); 0740 0741 return true; 0742 } 0743 0744 void GlobalShortcutsRegistry::setDBusPath(const QDBusObjectPath &path) 0745 { 0746 _dbusPath = path; 0747 } 0748 0749 void GlobalShortcutsRegistry::ungrabKeys() 0750 { 0751 deactivateShortcuts(); 0752 } 0753 0754 bool GlobalShortcutsRegistry::unregisterKey(const QKeySequence &key, GlobalShortcut *shortcut) 0755 { 0756 if (!_manager) { 0757 return false; 0758 } 0759 if (_active_keys.value(key) != shortcut) { 0760 // The shortcut doesn't own the key or the key isn't grabbed 0761 return false; 0762 } 0763 0764 for (int i = 0; i < key.count(); i++) { 0765 auto iter = _keys_count.find(key[i].toCombined()); 0766 if ((iter == _keys_count.end()) || (iter.value() <= 0)) { 0767 continue; 0768 } 0769 0770 // Unregister if there's only one ref to given key 0771 // We should fail earlier when key is not registered 0772 if (iter.value() == 1) { 0773 qCDebug(KGLOBALACCELD) << "Unregistering key" << QKeySequence(key[i]).toString() << "for" << shortcut->context()->component()->uniqueName() << ":" 0774 << shortcut->uniqueName(); 0775 0776 _manager->grabKey(key[i].toCombined(), false); 0777 _keys_count.erase(iter); 0778 } else { 0779 qCDebug(KGLOBALACCELD) << "Refused to unregister key" << QKeySequence(key[i]).toString() << ": used by another global shortcut"; 0780 --(iter.value()); 0781 } 0782 } 0783 0784 if (shortcut && shortcut == m_lastShortcut) { 0785 m_lastShortcut->context()->component()->emitGlobalShortcutReleased(*m_lastShortcut); 0786 m_lastShortcut = nullptr; 0787 } 0788 0789 _active_keys.remove(key); 0790 return true; 0791 } 0792 0793 void GlobalShortcutsRegistry::writeSettings() 0794 { 0795 auto it = std::remove_if(m_components.begin(), m_components.end(), [this](const ComponentPtr &component) { 0796 bool isService = component->uniqueName().endsWith(QLatin1String(".desktop")); 0797 0798 KConfigGroup configGroup = 0799 isService ? _config.group(QStringLiteral("services")).group(component->uniqueName()) : _config.group(component->uniqueName()); 0800 0801 if (component->allShortcuts().isEmpty()) { 0802 configGroup.deleteGroup(); 0803 return true; 0804 } else { 0805 component->writeSettings(configGroup); 0806 return false; 0807 } 0808 }); 0809 0810 m_components.erase(it, m_components.end()); 0811 _config.sync(); 0812 } 0813 0814 #include "moc_globalshortcutsregistry.cpp"