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"