File indexing completed on 2024-04-28 15:22:25

0001 /*
0002     This file is part of the KDE libraries
0003 
0004     SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com>
0005     SPDX-FileCopyrightText: 2007 Michael Jansen <kde@michael-jansen.biz>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "kglobalacceld.h"
0011 
0012 #include "component.h"
0013 #include "globalshortcut.h"
0014 #include "globalshortcutcontext.h"
0015 #include "globalshortcutsregistry.h"
0016 #include "kglobalaccel.h"
0017 #include "kserviceactioncomponent.h"
0018 #include "logging_p.h"
0019 
0020 #include <QDBusMetaType>
0021 #include <QDBusObjectPath>
0022 #include <QMetaMethod>
0023 #include <QTimer>
0024 
0025 struct KGlobalAccelDPrivate {
0026     KGlobalAccelDPrivate(KGlobalAccelD *qq)
0027         : q(qq)
0028     {
0029     }
0030 
0031     GlobalShortcut *findAction(const QStringList &actionId) const;
0032 
0033     /**
0034      * Find the action @a shortcutUnique in @a componentUnique.
0035      *
0036      * @return the action or @c nullptr if doesn't exist
0037      */
0038     GlobalShortcut *findAction(const QString &componentUnique, const QString &shortcutUnique) const;
0039 
0040     GlobalShortcut *addAction(const QStringList &actionId);
0041     Component *component(const QStringList &actionId) const;
0042 
0043     void splitComponent(QString &component, QString &context) const
0044     {
0045         context = QStringLiteral("default");
0046         const int index = component.indexOf(QLatin1Char('|'));
0047         if (index != -1) {
0048             Q_ASSERT(component.indexOf(QLatin1Char('|'), index + 1) == -1); // Only one '|' character
0049             context = component.mid(index + 1);
0050             component.truncate(index);
0051         }
0052     }
0053 
0054     //! Timer for delayed writing to kglobalshortcutsrc
0055     QTimer writeoutTimer;
0056 
0057     //! Our holder
0058     KGlobalAccelD *q;
0059 
0060     GlobalShortcutsRegistry *m_registry = nullptr;
0061 };
0062 
0063 GlobalShortcut *KGlobalAccelDPrivate::findAction(const QStringList &actionId) const
0064 {
0065     // Check if actionId is valid
0066     if (actionId.size() != 4) {
0067         qCDebug(KGLOBALACCELD) << "Invalid! '" << actionId << "'";
0068         return nullptr;
0069     }
0070 
0071     return findAction(actionId.at(KGlobalAccel::ComponentUnique), actionId.at(KGlobalAccel::ActionUnique));
0072 }
0073 
0074 GlobalShortcut *KGlobalAccelDPrivate::findAction(const QString &_componentUnique, const QString &shortcutUnique) const
0075 {
0076     QString componentUnique = _componentUnique;
0077 
0078     Component *component;
0079     QString contextUnique;
0080     if (componentUnique.indexOf(QLatin1Char('|')) == -1) {
0081         component = m_registry->getComponent(componentUnique);
0082         if (component) {
0083             contextUnique = component->currentContext()->uniqueName();
0084         }
0085     } else {
0086         splitComponent(componentUnique, contextUnique);
0087         component = m_registry->getComponent(componentUnique);
0088     }
0089 
0090     if (!component) {
0091         qCDebug(KGLOBALACCELD) << componentUnique << "not found";
0092         return nullptr;
0093     }
0094 
0095     GlobalShortcut *shortcut = component->getShortcutByName(shortcutUnique, contextUnique);
0096 
0097     if (shortcut) {
0098         qCDebug(KGLOBALACCELD) << componentUnique << contextUnique << shortcut->uniqueName();
0099     } else {
0100         qCDebug(KGLOBALACCELD) << "No match for" << shortcutUnique;
0101     }
0102     return shortcut;
0103 }
0104 
0105 Component *KGlobalAccelDPrivate::component(const QStringList &actionId) const
0106 {
0107     const QString uniqueName = actionId.at(KGlobalAccel::ComponentUnique);
0108 
0109     // If a component for action already exists, use that...
0110     if (Component *c = m_registry->getComponent(uniqueName)) {
0111         return c;
0112     }
0113 
0114     // ... otherwise, create a new one
0115     const QString friendlyName = actionId.at(KGlobalAccel::ComponentFriendly);
0116     if (uniqueName.endsWith(QLatin1String(".desktop"))) {
0117         auto *actionComp = m_registry->createServiceActionComponent(uniqueName, friendlyName);
0118         Q_ASSERT(actionComp);
0119         actionComp->activateGlobalShortcutContext(QStringLiteral("default"));
0120         actionComp->loadFromService();
0121         return actionComp;
0122     } else {
0123         auto *comp = m_registry->createComponent(uniqueName, friendlyName);
0124         Q_ASSERT(comp);
0125         return comp;
0126     }
0127 }
0128 
0129 GlobalShortcut *KGlobalAccelDPrivate::addAction(const QStringList &actionId)
0130 {
0131     Q_ASSERT(actionId.size() >= 4);
0132 
0133     QString componentUnique = actionId.at(KGlobalAccel::ComponentUnique);
0134     QString contextUnique;
0135     splitComponent(componentUnique, contextUnique);
0136 
0137     QStringList actionIdTmp = actionId;
0138     actionIdTmp.replace(KGlobalAccel::ComponentUnique, componentUnique);
0139 
0140     // Create the component if necessary
0141     Component *component = this->component(actionIdTmp);
0142     Q_ASSERT(component);
0143 
0144     // Create the context if necessary
0145     if (component->getShortcutContexts().count(contextUnique) == 0) {
0146         component->createGlobalShortcutContext(contextUnique);
0147     }
0148 
0149     Q_ASSERT(!component->getShortcutByName(componentUnique, contextUnique));
0150 
0151     return new GlobalShortcut(actionId.at(KGlobalAccel::ActionUnique), actionId.at(KGlobalAccel::ActionFriendly), component->shortcutContext(contextUnique));
0152 }
0153 
0154 Q_DECLARE_METATYPE(QStringList)
0155 
0156 KGlobalAccelD::KGlobalAccelD(QObject *parent)
0157     : QObject(parent)
0158     , d(new KGlobalAccelDPrivate(this))
0159 {
0160 }
0161 
0162 bool KGlobalAccelD::init()
0163 {
0164     qDBusRegisterMetaType<QKeySequence>();
0165     qDBusRegisterMetaType<QList<QKeySequence>>();
0166     qDBusRegisterMetaType<QList<QDBusObjectPath>>();
0167     qDBusRegisterMetaType<QList<QStringList>>();
0168     qDBusRegisterMetaType<QStringList>();
0169     qDBusRegisterMetaType<KGlobalShortcutInfo>();
0170     qDBusRegisterMetaType<QList<KGlobalShortcutInfo>>();
0171     qDBusRegisterMetaType<KGlobalAccel::MatchType>();
0172 
0173     d->m_registry = GlobalShortcutsRegistry::self();
0174     Q_ASSERT(d->m_registry);
0175 
0176     d->writeoutTimer.setSingleShot(true);
0177     connect(&d->writeoutTimer, &QTimer::timeout, d->m_registry, &GlobalShortcutsRegistry::writeSettings);
0178 
0179     if (!QDBusConnection::sessionBus().registerService(QLatin1String("org.kde.kglobalaccel"))) {
0180         qCWarning(KGLOBALACCELD) << "Failed to register service org.kde.kglobalaccel";
0181         return false;
0182     }
0183 
0184     if (!QDBusConnection::sessionBus().registerObject(QStringLiteral("/kglobalaccel"), this, QDBusConnection::ExportScriptableContents)) {
0185         qCWarning(KGLOBALACCELD) << "Failed to register object kglobalaccel in org.kde.kglobalaccel";
0186         return false;
0187     }
0188 
0189     d->m_registry->setDBusPath(QDBusObjectPath("/"));
0190     d->m_registry->loadSettings();
0191 
0192     return true;
0193 }
0194 
0195 KGlobalAccelD::~KGlobalAccelD()
0196 {
0197     if (d->writeoutTimer.isActive()) {
0198         d->writeoutTimer.stop();
0199         d->m_registry->writeSettings();
0200     }
0201     d->m_registry->deactivateShortcuts();
0202     delete d;
0203 }
0204 
0205 QList<QStringList> KGlobalAccelD::allMainComponents() const
0206 {
0207     return d->m_registry->allComponentNames();
0208 }
0209 
0210 QList<QStringList> KGlobalAccelD::allActionsForComponent(const QStringList &actionId) const
0211 {
0212     //### Would it be advantageous to sort the actions by unique name?
0213     QList<QStringList> ret;
0214 
0215     Component *const component = d->m_registry->getComponent(actionId[KGlobalAccel::ComponentUnique]);
0216     if (!component) {
0217         return ret;
0218     }
0219 
0220     QStringList partialId(actionId[KGlobalAccel::ComponentUnique]); // ComponentUnique
0221     partialId.append(QString()); // ActionUnique
0222     // Use our internal friendlyName, not the one passed in. We should have the latest data.
0223     partialId.append(component->friendlyName()); // ComponentFriendly
0224     partialId.append(QString()); // ActionFriendly
0225 
0226     const auto listShortcuts = component->allShortcuts();
0227     for (const GlobalShortcut *const shortcut : listShortcuts) {
0228         if (shortcut->isFresh()) {
0229             // isFresh is only an intermediate state, not to be reported outside.
0230             continue;
0231         }
0232         QStringList actionId(partialId);
0233         actionId[KGlobalAccel::ActionUnique] = shortcut->uniqueName();
0234         actionId[KGlobalAccel::ActionFriendly] = shortcut->friendlyName();
0235         ret.append(actionId);
0236     }
0237     return ret;
0238 }
0239 
0240 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(5, 90)
0241 QStringList KGlobalAccelD::action(int key) const
0242 {
0243     return actionList(key);
0244 }
0245 #endif
0246 
0247 QStringList KGlobalAccelD::actionList(const QKeySequence &key) const
0248 {
0249     GlobalShortcut *shortcut = d->m_registry->getShortcutByKey(key);
0250     QStringList ret;
0251     if (shortcut) {
0252         ret.append(shortcut->context()->component()->uniqueName());
0253         ret.append(shortcut->uniqueName());
0254         ret.append(shortcut->context()->component()->friendlyName());
0255         ret.append(shortcut->friendlyName());
0256     }
0257     return ret;
0258 }
0259 
0260 void KGlobalAccelD::activateGlobalShortcutContext(const QString &component, const QString &uniqueName)
0261 {
0262     Component *const comp = d->m_registry->getComponent(component);
0263     if (comp) {
0264         comp->activateGlobalShortcutContext(uniqueName);
0265     }
0266 }
0267 
0268 QList<QDBusObjectPath> KGlobalAccelD::allComponents() const
0269 {
0270     return d->m_registry->componentsDbusPaths();
0271 }
0272 
0273 void KGlobalAccelD::blockGlobalShortcuts(bool block)
0274 {
0275     qCDebug(KGLOBALACCELD) << "Block global shortcuts?" << block;
0276     if (block) {
0277         d->m_registry->deactivateShortcuts(true);
0278     } else {
0279         d->m_registry->activateShortcuts();
0280     }
0281 }
0282 
0283 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(5, 90)
0284 QList<int> KGlobalAccelD::shortcut(const QStringList &action) const
0285 {
0286     GlobalShortcut *shortcut = d->findAction(action);
0287     if (shortcut) {
0288         QList<int> ret;
0289         for (auto i : shortcut->keys()) {
0290             ret << i[0];
0291         }
0292         return ret;
0293     }
0294     return QList<int>();
0295 }
0296 #endif
0297 
0298 QList<QKeySequence> KGlobalAccelD::shortcutKeys(const QStringList &action) const
0299 {
0300     GlobalShortcut *shortcut = d->findAction(action);
0301     if (shortcut) {
0302         return shortcut->keys();
0303     }
0304     return QList<QKeySequence>();
0305 }
0306 
0307 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(5, 90)
0308 QList<int> KGlobalAccelD::defaultShortcut(const QStringList &action) const
0309 {
0310     GlobalShortcut *shortcut = d->findAction(action);
0311     if (shortcut) {
0312         QList<int> ret;
0313         for (auto i : shortcut->keys()) {
0314             ret << i[0];
0315         }
0316         return ret;
0317     }
0318     return QList<int>();
0319 }
0320 #endif
0321 
0322 QList<QKeySequence> KGlobalAccelD::defaultShortcutKeys(const QStringList &action) const
0323 {
0324     GlobalShortcut *shortcut = d->findAction(action);
0325     if (shortcut) {
0326         return shortcut->defaultKeys();
0327     }
0328     return QList<QKeySequence>();
0329 }
0330 
0331 // This method just registers the action. Nothing else. Shortcut has to be set
0332 // later.
0333 void KGlobalAccelD::doRegister(const QStringList &actionId)
0334 {
0335     qCDebug(KGLOBALACCELD) << actionId;
0336 
0337     // Check because we would not want to add a action for an invalid
0338     // actionId. findAction returns nullptr in that case.
0339     if (actionId.size() < 4) {
0340         return;
0341     }
0342 
0343     GlobalShortcut *shortcut = d->findAction(actionId);
0344     if (!shortcut) {
0345         shortcut = d->addAction(actionId);
0346     } else {
0347         // a switch of locales is one common reason for a changing friendlyName
0348         if ((!actionId[KGlobalAccel::ActionFriendly].isEmpty()) && shortcut->friendlyName() != actionId[KGlobalAccel::ActionFriendly]) {
0349             shortcut->setFriendlyName(actionId[KGlobalAccel::ActionFriendly]);
0350             scheduleWriteSettings();
0351         }
0352         if ((!actionId[KGlobalAccel::ComponentFriendly].isEmpty())
0353             && shortcut->context()->component()->friendlyName() != actionId[KGlobalAccel::ComponentFriendly]) {
0354             shortcut->context()->component()->setFriendlyName(actionId[KGlobalAccel::ComponentFriendly]);
0355             scheduleWriteSettings();
0356         }
0357     }
0358 }
0359 
0360 QDBusObjectPath KGlobalAccelD::getComponent(const QString &componentUnique) const
0361 {
0362     qCDebug(KGLOBALACCELD) << componentUnique;
0363 
0364     Component *component = d->m_registry->getComponent(componentUnique);
0365 
0366     if (component) {
0367         return component->dbusPath();
0368     } else {
0369         sendErrorReply(QStringLiteral("org.kde.kglobalaccel.NoSuchComponent"), QStringLiteral("The component '%1' doesn't exist.").arg(componentUnique));
0370         return QDBusObjectPath("/");
0371     }
0372 }
0373 
0374 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(5, 90)
0375 QList<KGlobalShortcutInfo> KGlobalAccelD::getGlobalShortcutsByKey(int key) const
0376 {
0377     return globalShortcutsByKey(key, KGlobalAccel::MatchType::Equal);
0378 }
0379 #endif
0380 
0381 QList<KGlobalShortcutInfo> KGlobalAccelD::globalShortcutsByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const
0382 {
0383     qCDebug(KGLOBALACCELD) << key;
0384     const QList<GlobalShortcut *> shortcuts = d->m_registry->getShortcutsByKey(key, type);
0385 
0386     QList<KGlobalShortcutInfo> rc;
0387     rc.reserve(shortcuts.size());
0388     for (const GlobalShortcut *sc : shortcuts) {
0389         qCDebug(KGLOBALACCELD) << sc->context()->uniqueName() << sc->uniqueName();
0390         rc.append(static_cast<KGlobalShortcutInfo>(*sc));
0391     }
0392 
0393     return rc;
0394 }
0395 
0396 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(5, 90)
0397 bool KGlobalAccelD::isGlobalShortcutAvailable(int shortcut, const QString &component) const
0398 {
0399     return globalShortcutAvailable(shortcut, component);
0400 }
0401 #endif
0402 
0403 bool KGlobalAccelD::globalShortcutAvailable(const QKeySequence &shortcut, const QString &component) const
0404 {
0405     QString realComponent = component;
0406     QString context;
0407     d->splitComponent(realComponent, context);
0408     return d->m_registry->isShortcutAvailable(shortcut, realComponent, context);
0409 }
0410 
0411 void KGlobalAccelD::setInactive(const QStringList &actionId)
0412 {
0413     qCDebug(KGLOBALACCELD) << actionId;
0414 
0415     GlobalShortcut *shortcut = d->findAction(actionId);
0416     if (shortcut) {
0417         shortcut->setIsPresent(false);
0418     }
0419 }
0420 
0421 bool KGlobalAccelD::unregister(const QString &componentUnique, const QString &shortcutUnique)
0422 {
0423     qCDebug(KGLOBALACCELD) << componentUnique << shortcutUnique;
0424 
0425     // Stop grabbing the key
0426     GlobalShortcut *shortcut = d->findAction(componentUnique, shortcutUnique);
0427     if (shortcut) {
0428         shortcut->unRegister();
0429         scheduleWriteSettings();
0430     }
0431 
0432     return shortcut;
0433 }
0434 
0435 #if KGLOBALACCELPRIVATE_BUILD_DEPRECATED_SINCE(4, 3)
0436 void KGlobalAccelD::unRegister(const QStringList &actionId)
0437 {
0438     qCDebug(KGLOBALACCELD) << actionId;
0439 
0440     // Stop grabbing the key
0441     GlobalShortcut *shortcut = d->findAction(actionId);
0442     if (shortcut) {
0443         shortcut->unRegister();
0444         scheduleWriteSettings();
0445     }
0446 }
0447 #endif
0448 
0449 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(5, 90)
0450 QList<int> KGlobalAccelD::setShortcut(const QStringList &actionId, const QList<int> &keys, uint flags)
0451 {
0452     QList<QKeySequence> input;
0453     input.reserve(keys.size());
0454     for (auto i : keys) {
0455         input << i;
0456     }
0457 
0458     const QList<QKeySequence> list = setShortcutKeys(actionId, input, flags);
0459     QList<int> ret;
0460     ret.reserve(list.size());
0461     for (auto i : list) {
0462         ret << i[0];
0463     }
0464     return ret;
0465 }
0466 #endif
0467 
0468 QList<QKeySequence> KGlobalAccelD::setShortcutKeys(const QStringList &actionId, const QList<QKeySequence> &keys, uint flags)
0469 {
0470     // spare the DBus framework some work
0471     const bool setPresent = (flags & SetPresent);
0472     const bool isAutoloading = !(flags & NoAutoloading);
0473     const bool isDefault = (flags & IsDefault);
0474 
0475     GlobalShortcut *shortcut = d->findAction(actionId);
0476     if (!shortcut) {
0477         return QList<QKeySequence>();
0478     }
0479 
0480     // default shortcuts cannot clash because they don't do anything
0481     if (isDefault) {
0482         if (shortcut->defaultKeys() != keys) {
0483             shortcut->setDefaultKeys(keys);
0484             scheduleWriteSettings();
0485         }
0486         return keys; // doesn't matter
0487     }
0488 
0489     if (isAutoloading && !shortcut->isFresh()) {
0490         // the trivial and common case - synchronize the action from our data
0491         // and exit.
0492         if (!shortcut->isPresent() && setPresent) {
0493             shortcut->setIsPresent(true);
0494         }
0495         // We are finished here. Return the list of current active keys.
0496         return shortcut->keys();
0497     }
0498 
0499     // now we are actually changing the shortcut of the action
0500     shortcut->setKeys(keys);
0501 
0502     if (setPresent) {
0503         shortcut->setIsPresent(true);
0504     }
0505 
0506     // maybe isFresh should really only be set if setPresent, but only two things should use !setPresent:
0507     //- the global shortcuts KCM: very unlikely to catch KWin/etc.'s actions in isFresh state
0508     //- KGlobalAccel::stealGlobalShortcutSystemwide(): only applies to actions with shortcuts
0509     //  which can never be fresh if created the usual way
0510     shortcut->setIsFresh(false);
0511 
0512     scheduleWriteSettings();
0513 
0514     return shortcut->keys();
0515 }
0516 
0517 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(5, 90)
0518 void KGlobalAccelD::setForeignShortcut(const QStringList &actionId, const QList<int> &keys)
0519 {
0520     QList<QKeySequence> input;
0521     for (auto i : keys) {
0522         input << i;
0523     }
0524     return setForeignShortcutKeys(actionId, input);
0525 }
0526 #endif
0527 
0528 void KGlobalAccelD::setForeignShortcutKeys(const QStringList &actionId, const QList<QKeySequence> &keys)
0529 {
0530     qCDebug(KGLOBALACCELD) << actionId;
0531 
0532     GlobalShortcut *shortcut = d->findAction(actionId);
0533     if (!shortcut) {
0534         return;
0535     }
0536 
0537     QList<QKeySequence> newKeys = setShortcutKeys(actionId, keys, NoAutoloading);
0538 
0539     Q_EMIT yourShortcutsChanged(actionId, newKeys);
0540 }
0541 
0542 void KGlobalAccelD::scheduleWriteSettings() const
0543 {
0544     if (!d->writeoutTimer.isActive()) {
0545         d->writeoutTimer.start(500);
0546     }
0547 }
0548 
0549 #include "moc_kglobalacceld.cpp"