File indexing completed on 2024-05-19 05:29:57

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 <QDBusConnection>
0021 #include <QDBusMetaType>
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);
0118         if (!actionComp) {
0119             return nullptr;
0120         }
0121         actionComp->activateGlobalShortcutContext(QStringLiteral("default"));
0122         actionComp->loadFromService();
0123         return actionComp;
0124     } else {
0125         return m_registry->createComponent(uniqueName, friendlyName);
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     if (!component) {
0143         return nullptr;
0144     }
0145 
0146     // Create the context if necessary
0147     if (component->getShortcutContexts().count(contextUnique) == 0) {
0148         component->createGlobalShortcutContext(contextUnique);
0149     }
0150 
0151     Q_ASSERT(!component->getShortcutByName(componentUnique, contextUnique));
0152 
0153     return new GlobalShortcut(actionId.at(KGlobalAccel::ActionUnique), actionId.at(KGlobalAccel::ActionFriendly), component->shortcutContext(contextUnique));
0154 }
0155 
0156 Q_DECLARE_METATYPE(QStringList)
0157 
0158 KGlobalAccelD::KGlobalAccelD(QObject *parent)
0159     : QObject(parent)
0160     , d(new KGlobalAccelDPrivate(this))
0161 {
0162 }
0163 
0164 bool KGlobalAccelD::init()
0165 {
0166     qDBusRegisterMetaType<QKeySequence>();
0167     qDBusRegisterMetaType<QList<QKeySequence>>();
0168     qDBusRegisterMetaType<QList<QDBusObjectPath>>();
0169     qDBusRegisterMetaType<QList<QStringList>>();
0170     qDBusRegisterMetaType<QStringList>();
0171     qDBusRegisterMetaType<KGlobalShortcutInfo>();
0172     qDBusRegisterMetaType<QList<KGlobalShortcutInfo>>();
0173     qDBusRegisterMetaType<KGlobalAccel::MatchType>();
0174 
0175     d->m_registry = GlobalShortcutsRegistry::self();
0176     Q_ASSERT(d->m_registry);
0177 
0178     d->writeoutTimer.setSingleShot(true);
0179     connect(&d->writeoutTimer, &QTimer::timeout, d->m_registry, &GlobalShortcutsRegistry::writeSettings);
0180 
0181     if (!QDBusConnection::sessionBus().registerService(QLatin1String("org.kde.kglobalaccel"))) {
0182         qCWarning(KGLOBALACCELD) << "Failed to register service org.kde.kglobalaccel";
0183         return false;
0184     }
0185 
0186     if (!QDBusConnection::sessionBus().registerObject(QStringLiteral("/kglobalaccel"), this, QDBusConnection::ExportScriptableContents)) {
0187         qCWarning(KGLOBALACCELD) << "Failed to register object kglobalaccel in org.kde.kglobalaccel";
0188         return false;
0189     }
0190 
0191     d->m_registry->setDBusPath(QDBusObjectPath("/"));
0192     d->m_registry->loadSettings();
0193 
0194     return true;
0195 }
0196 
0197 KGlobalAccelD::~KGlobalAccelD()
0198 {
0199     if (d->writeoutTimer.isActive()) {
0200         d->writeoutTimer.stop();
0201         d->m_registry->writeSettings();
0202     }
0203     d->m_registry->deactivateShortcuts();
0204     delete d;
0205 }
0206 
0207 QList<QStringList> KGlobalAccelD::allMainComponents() const
0208 {
0209     return d->m_registry->allComponentNames();
0210 }
0211 
0212 QList<QStringList> KGlobalAccelD::allActionsForComponent(const QStringList &actionId) const
0213 {
0214     // ### Would it be advantageous to sort the actions by unique name?
0215     QList<QStringList> ret;
0216 
0217     Component *const component = d->m_registry->getComponent(actionId[KGlobalAccel::ComponentUnique]);
0218     if (!component) {
0219         return ret;
0220     }
0221 
0222     QStringList partialId(actionId[KGlobalAccel::ComponentUnique]); // ComponentUnique
0223     partialId.append(QString()); // ActionUnique
0224     // Use our internal friendlyName, not the one passed in. We should have the latest data.
0225     partialId.append(component->friendlyName()); // ComponentFriendly
0226     partialId.append(QString()); // ActionFriendly
0227 
0228     const auto listShortcuts = component->allShortcuts();
0229     for (const GlobalShortcut *const shortcut : listShortcuts) {
0230         if (shortcut->isFresh()) {
0231             // isFresh is only an intermediate state, not to be reported outside.
0232             continue;
0233         }
0234         QStringList actionId(partialId);
0235         actionId[KGlobalAccel::ActionUnique] = shortcut->uniqueName();
0236         actionId[KGlobalAccel::ActionFriendly] = shortcut->friendlyName();
0237         ret.append(actionId);
0238     }
0239     return ret;
0240 }
0241 
0242 #if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(5, 90)
0243 QStringList KGlobalAccelD::action(int key) const
0244 {
0245     return actionList(key);
0246 }
0247 #endif
0248 
0249 QStringList KGlobalAccelD::actionList(const QKeySequence &key) const
0250 {
0251     GlobalShortcut *shortcut = d->m_registry->getShortcutByKey(key);
0252     QStringList ret;
0253     if (shortcut) {
0254         ret.append(shortcut->context()->component()->uniqueName());
0255         ret.append(shortcut->uniqueName());
0256         ret.append(shortcut->context()->component()->friendlyName());
0257         ret.append(shortcut->friendlyName());
0258     }
0259     return ret;
0260 }
0261 
0262 void KGlobalAccelD::activateGlobalShortcutContext(const QString &component, const QString &uniqueName)
0263 {
0264     Component *const comp = d->m_registry->getComponent(component);
0265     if (comp) {
0266         comp->activateGlobalShortcutContext(uniqueName);
0267     }
0268 }
0269 
0270 QList<QDBusObjectPath> KGlobalAccelD::allComponents() const
0271 {
0272     return d->m_registry->componentsDbusPaths();
0273 }
0274 
0275 void KGlobalAccelD::blockGlobalShortcuts(bool block)
0276 {
0277     qCDebug(KGLOBALACCELD) << "Block global shortcuts?" << block;
0278     if (block) {
0279         d->m_registry->deactivateShortcuts(true);
0280     } else {
0281         d->m_registry->activateShortcuts();
0282     }
0283 }
0284 
0285 #if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(5, 90)
0286 QList<int> KGlobalAccelD::shortcut(const QStringList &action) const
0287 {
0288     GlobalShortcut *shortcut = d->findAction(action);
0289     if (shortcut) {
0290         QList<int> ret;
0291         for (auto i : shortcut->keys()) {
0292             ret << i[0].toCombined();
0293         }
0294         return ret;
0295     }
0296     return QList<int>();
0297 }
0298 #endif
0299 
0300 QList<QKeySequence> KGlobalAccelD::shortcutKeys(const QStringList &action) const
0301 {
0302     GlobalShortcut *shortcut = d->findAction(action);
0303     if (shortcut) {
0304         return shortcut->keys();
0305     }
0306     return QList<QKeySequence>();
0307 }
0308 
0309 #if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(5, 90)
0310 QList<int> KGlobalAccelD::defaultShortcut(const QStringList &action) const
0311 {
0312     GlobalShortcut *shortcut = d->findAction(action);
0313     if (shortcut) {
0314         QList<int> ret;
0315         for (auto i : shortcut->keys()) {
0316             ret << i[0].toCombined();
0317         }
0318         return ret;
0319     }
0320     return QList<int>();
0321 }
0322 #endif
0323 
0324 QList<QKeySequence> KGlobalAccelD::defaultShortcutKeys(const QStringList &action) const
0325 {
0326     GlobalShortcut *shortcut = d->findAction(action);
0327     if (shortcut) {
0328         return shortcut->defaultKeys();
0329     }
0330     return QList<QKeySequence>();
0331 }
0332 
0333 // This method just registers the action. Nothing else. Shortcut has to be set
0334 // later.
0335 void KGlobalAccelD::doRegister(const QStringList &actionId)
0336 {
0337     qCDebug(KGLOBALACCELD) << actionId;
0338 
0339     // Check because we would not want to add a action for an invalid
0340     // actionId. findAction returns nullptr in that case.
0341     if (actionId.size() < 4) {
0342         return;
0343     }
0344 
0345     GlobalShortcut *shortcut = d->findAction(actionId);
0346     if (!shortcut) {
0347         shortcut = d->addAction(actionId);
0348     } else {
0349         // a switch of locales is one common reason for a changing friendlyName
0350         if ((!actionId[KGlobalAccel::ActionFriendly].isEmpty()) && shortcut->friendlyName() != actionId[KGlobalAccel::ActionFriendly]) {
0351             shortcut->setFriendlyName(actionId[KGlobalAccel::ActionFriendly]);
0352             scheduleWriteSettings();
0353         }
0354         if ((!actionId[KGlobalAccel::ComponentFriendly].isEmpty())
0355             && shortcut->context()->component()->friendlyName() != actionId[KGlobalAccel::ComponentFriendly]) {
0356             shortcut->context()->component()->setFriendlyName(actionId[KGlobalAccel::ComponentFriendly]);
0357             scheduleWriteSettings();
0358         }
0359     }
0360 }
0361 
0362 QDBusObjectPath KGlobalAccelD::getComponent(const QString &componentUnique) const
0363 {
0364     qCDebug(KGLOBALACCELD) << componentUnique;
0365 
0366     Component *component = d->m_registry->getComponent(componentUnique);
0367 
0368     if (component) {
0369         return component->dbusPath();
0370     } else {
0371         sendErrorReply(QStringLiteral("org.kde.kglobalaccel.NoSuchComponent"), QStringLiteral("The component '%1' doesn't exist.").arg(componentUnique));
0372         return QDBusObjectPath("/");
0373     }
0374 }
0375 
0376 #if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(5, 90)
0377 QList<KGlobalShortcutInfo> KGlobalAccelD::getGlobalShortcutsByKey(int key) const
0378 {
0379     return globalShortcutsByKey(key, KGlobalAccel::MatchType::Equal);
0380 }
0381 #endif
0382 
0383 QList<KGlobalShortcutInfo> KGlobalAccelD::globalShortcutsByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const
0384 {
0385     qCDebug(KGLOBALACCELD) << key;
0386     const QList<GlobalShortcut *> shortcuts = d->m_registry->getShortcutsByKey(key, type);
0387 
0388     QList<KGlobalShortcutInfo> rc;
0389     rc.reserve(shortcuts.size());
0390     for (const GlobalShortcut *sc : shortcuts) {
0391         qCDebug(KGLOBALACCELD) << sc->context()->uniqueName() << sc->uniqueName();
0392         rc.append(static_cast<KGlobalShortcutInfo>(*sc));
0393     }
0394 
0395     return rc;
0396 }
0397 
0398 #if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(5, 90)
0399 bool KGlobalAccelD::isGlobalShortcutAvailable(int shortcut, const QString &component) const
0400 {
0401     return globalShortcutAvailable(shortcut, component);
0402 }
0403 #endif
0404 
0405 bool KGlobalAccelD::globalShortcutAvailable(const QKeySequence &shortcut, const QString &component) const
0406 {
0407     QString realComponent = component;
0408     QString context;
0409     d->splitComponent(realComponent, context);
0410     return d->m_registry->isShortcutAvailable(shortcut, realComponent, context);
0411 }
0412 
0413 void KGlobalAccelD::setInactive(const QStringList &actionId)
0414 {
0415     qCDebug(KGLOBALACCELD) << actionId;
0416 
0417     GlobalShortcut *shortcut = d->findAction(actionId);
0418     if (shortcut) {
0419         shortcut->setIsPresent(false);
0420     }
0421 }
0422 
0423 bool KGlobalAccelD::unregister(const QString &componentUnique, const QString &shortcutUnique)
0424 {
0425     qCDebug(KGLOBALACCELD) << componentUnique << shortcutUnique;
0426 
0427     // Stop grabbing the key
0428     GlobalShortcut *shortcut = d->findAction(componentUnique, shortcutUnique);
0429     if (shortcut) {
0430         shortcut->unRegister();
0431         scheduleWriteSettings();
0432     }
0433 
0434     return shortcut;
0435 }
0436 
0437 #if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(4, 3)
0438 void KGlobalAccelD::unRegister(const QStringList &actionId)
0439 {
0440     qCDebug(KGLOBALACCELD) << actionId;
0441 
0442     // Stop grabbing the key
0443     GlobalShortcut *shortcut = d->findAction(actionId);
0444     if (shortcut) {
0445         shortcut->unRegister();
0446         scheduleWriteSettings();
0447     }
0448 }
0449 #endif
0450 
0451 #if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(5, 90)
0452 QList<int> KGlobalAccelD::setShortcut(const QStringList &actionId, const QList<int> &keys, uint flags)
0453 {
0454     QList<QKeySequence> input;
0455     input.reserve(keys.size());
0456     for (auto i : keys) {
0457         input << i;
0458     }
0459 
0460     const QList<QKeySequence> list = setShortcutKeys(actionId, input, flags);
0461     QList<int> ret;
0462     ret.reserve(list.size());
0463     for (auto i : list) {
0464         ret << i[0].toCombined();
0465     }
0466     return ret;
0467 }
0468 #endif
0469 
0470 QList<QKeySequence> KGlobalAccelD::setShortcutKeys(const QStringList &actionId, const QList<QKeySequence> &keys, uint flags)
0471 {
0472     // spare the DBus framework some work
0473     const bool setPresent = (flags & SetPresent);
0474     const bool isAutoloading = !(flags & NoAutoloading);
0475     const bool isDefault = (flags & IsDefault);
0476 
0477     GlobalShortcut *shortcut = d->findAction(actionId);
0478     if (!shortcut) {
0479         return QList<QKeySequence>();
0480     }
0481 
0482     // default shortcuts cannot clash because they don't do anything
0483     if (isDefault) {
0484         if (shortcut->defaultKeys() != keys) {
0485             shortcut->setDefaultKeys(keys);
0486             scheduleWriteSettings();
0487         }
0488         return keys; // doesn't matter
0489     }
0490 
0491     if (isAutoloading && !shortcut->isFresh()) {
0492         // the trivial and common case - synchronize the action from our data
0493         // and exit.
0494         if (!shortcut->isPresent() && setPresent) {
0495             shortcut->setIsPresent(true);
0496         }
0497         // We are finished here. Return the list of current active keys.
0498         return shortcut->keys();
0499     }
0500 
0501     // now we are actually changing the shortcut of the action
0502     shortcut->setKeys(keys);
0503 
0504     if (setPresent) {
0505         shortcut->setIsPresent(true);
0506     }
0507 
0508     // maybe isFresh should really only be set if setPresent, but only two things should use !setPresent:
0509     //- the global shortcuts KCM: very unlikely to catch KWin/etc.'s actions in isFresh state
0510     //- KGlobalAccel::stealGlobalShortcutSystemwide(): only applies to actions with shortcuts
0511     //  which can never be fresh if created the usual way
0512     shortcut->setIsFresh(false);
0513 
0514     scheduleWriteSettings();
0515 
0516     return shortcut->keys();
0517 }
0518 
0519 #if KGLOBALACCELD_BUILD_DEPRECATED_SINCE(5, 90)
0520 void KGlobalAccelD::setForeignShortcut(const QStringList &actionId, const QList<int> &keys)
0521 {
0522     QList<QKeySequence> input;
0523     for (auto i : keys) {
0524         input << i;
0525     }
0526     return setForeignShortcutKeys(actionId, input);
0527 }
0528 #endif
0529 
0530 void KGlobalAccelD::setForeignShortcutKeys(const QStringList &actionId, const QList<QKeySequence> &keys)
0531 {
0532     qCDebug(KGLOBALACCELD) << actionId;
0533 
0534     GlobalShortcut *shortcut = d->findAction(actionId);
0535     if (!shortcut) {
0536         return;
0537     }
0538 
0539     QList<QKeySequence> newKeys = setShortcutKeys(actionId, keys, NoAutoloading);
0540 
0541     Q_EMIT yourShortcutsChanged(actionId, newKeys);
0542 }
0543 
0544 void KGlobalAccelD::scheduleWriteSettings() const
0545 {
0546     if (!d->writeoutTimer.isActive()) {
0547         d->writeoutTimer.start(500);
0548     }
0549 }
0550 
0551 #include "moc_kglobalacceld.cpp"