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"