File indexing completed on 2024-04-28 11:37:13
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"