Warning, file /frameworks/kglobalaccel/src/kglobalaccel.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2001, 2002 Ellis Whitehead <ellis@kde.org> 0004 SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org> 0005 SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com> 0006 SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz> 0007 0008 SPDX-License-Identifier: LGPL-2.0-or-later 0009 */ 0010 0011 #include "kglobalaccel.h" 0012 #include "kglobalaccel_debug.h" 0013 #include "kglobalaccel_p.h" 0014 0015 #include <memory> 0016 0017 #include <QAction> 0018 #include <QDBusMessage> 0019 #include <QDBusMetaType> 0020 #include <QGuiApplication> 0021 #include <QMessageBox> 0022 #include <QPushButton> 0023 #include <config-kglobalaccel.h> 0024 0025 #if HAVE_X11 0026 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0027 #include <private/qtx11extras_p.h> 0028 #else 0029 #include <QX11Info> 0030 #endif 0031 #endif 0032 0033 org::kde::kglobalaccel::Component *KGlobalAccelPrivate::getComponent(const QString &componentUnique, bool remember = false) 0034 { 0035 // Check if we already have this component 0036 { 0037 auto component = components.value(componentUnique); 0038 if (component) { 0039 return component; 0040 } 0041 } 0042 0043 // Get the path for our component. We have to do that because 0044 // componentUnique is probably not a valid dbus object path 0045 QDBusReply<QDBusObjectPath> reply = iface()->getComponent(componentUnique); 0046 if (!reply.isValid()) { 0047 if (reply.error().name() == QLatin1String("org.kde.kglobalaccel.NoSuchComponent")) { 0048 // No problem. The component doesn't exists. That's normal 0049 return nullptr; 0050 } 0051 0052 // An unknown error. 0053 qCDebug(KGLOBALACCEL_LOG) << "Failed to get dbus path for component " << componentUnique << reply.error(); 0054 return nullptr; 0055 } 0056 0057 // Now get the component 0058 org::kde::kglobalaccel::Component *component = 0059 new org::kde::kglobalaccel::Component(QStringLiteral("org.kde.kglobalaccel"), reply.value().path(), QDBusConnection::sessionBus(), q); 0060 0061 // No component no cleaning 0062 if (!component->isValid()) { 0063 qCDebug(KGLOBALACCEL_LOG) << "Failed to get component" << componentUnique << QDBusConnection::sessionBus().lastError(); 0064 return nullptr; 0065 } 0066 0067 if (remember) { 0068 // Connect to the signals we are interested in. 0069 q->connect(component, 0070 &org::kde::kglobalaccel::Component::globalShortcutPressed, 0071 q, 0072 [this](const QString &componentUnique, const QString &shortcutUnique, qlonglong timestamp) { 0073 invokeAction(componentUnique, shortcutUnique, timestamp); 0074 }); 0075 0076 q->connect(component, 0077 &org::kde::kglobalaccel::Component::globalShortcutReleased, 0078 q, 0079 [this](const QString &componentUnique, const QString &shortcutUnique, qlonglong) { 0080 invokeDeactivate(componentUnique, shortcutUnique); 0081 }); 0082 0083 components[componentUnique] = component; 0084 } 0085 0086 return component; 0087 } 0088 0089 namespace 0090 { 0091 QString serviceName() 0092 { 0093 return QStringLiteral("org.kde.kglobalaccel"); 0094 } 0095 } 0096 0097 void KGlobalAccelPrivate::cleanup() 0098 { 0099 qDeleteAll(components); 0100 delete m_iface; 0101 m_iface = nullptr; 0102 delete m_watcher; 0103 m_watcher = nullptr; 0104 } 0105 0106 KGlobalAccelPrivate::KGlobalAccelPrivate(KGlobalAccel *qq) 0107 : q(qq) 0108 { 0109 m_watcher = new QDBusServiceWatcher(serviceName(), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, q); 0110 q->connect(m_watcher, &QDBusServiceWatcher::serviceOwnerChanged, q, [this](const QString &serviceName, const QString &oldOwner, const QString &newOwner) { 0111 serviceOwnerChanged(serviceName, oldOwner, newOwner); 0112 }); 0113 } 0114 0115 org::kde::KGlobalAccel *KGlobalAccelPrivate::iface() 0116 { 0117 if (!m_iface) { 0118 m_iface = new org::kde::KGlobalAccel(serviceName(), QStringLiteral("/kglobalaccel"), QDBusConnection::sessionBus()); 0119 // Make sure kglobalaccel is running. The iface declaration above somehow works anyway. 0120 QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface(); 0121 if (bus && !bus->isServiceRegistered(serviceName())) { 0122 QDBusReply<void> reply = bus->startService(serviceName()); 0123 if (!reply.isValid()) { 0124 qCritical() << "Couldn't start kglobalaccel from org.kde.kglobalaccel.service:" << reply.error(); 0125 } 0126 } 0127 0128 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(5, 90) 0129 q->connect(m_iface, &org::kde::KGlobalAccel::yourShortcutGotChanged, q, [this](const QStringList &actionId, const QList<int> &newKeys) { 0130 shortcutGotChanged(actionId, newKeys); 0131 }); 0132 #endif 0133 0134 q->connect(m_iface, &org::kde::KGlobalAccel::yourShortcutsChanged, q, [this](const QStringList &actionId, const QList<QKeySequence> &newKeys) { 0135 shortcutsChanged(actionId, newKeys); 0136 }); 0137 } 0138 return m_iface; 0139 } 0140 0141 KGlobalAccel::KGlobalAccel() 0142 : d(new KGlobalAccelPrivate(this)) 0143 { 0144 qDBusRegisterMetaType<QList<int>>(); 0145 qDBusRegisterMetaType<QKeySequence>(); 0146 qDBusRegisterMetaType<QList<QKeySequence>>(); 0147 qDBusRegisterMetaType<QList<QStringList>>(); 0148 qDBusRegisterMetaType<KGlobalShortcutInfo>(); 0149 qDBusRegisterMetaType<QList<KGlobalShortcutInfo>>(); 0150 qDBusRegisterMetaType<KGlobalAccel::MatchType>(); 0151 } 0152 0153 KGlobalAccel::~KGlobalAccel() 0154 { 0155 delete d; 0156 } 0157 0158 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(5, 102) 0159 void KGlobalAccel::activateGlobalShortcutContext(const QString &contextUnique, const QString &contextFriendly, const QString &programName) 0160 { 0161 Q_UNUSED(contextFriendly); 0162 // TODO: provide contextFriendly 0163 self()->d->iface()->activateGlobalShortcutContext(programName, contextUnique); 0164 } 0165 #endif 0166 0167 // static 0168 bool KGlobalAccel::cleanComponent(const QString &componentUnique) 0169 { 0170 org::kde::kglobalaccel::Component *component = self()->getComponent(componentUnique); 0171 if (!component) { 0172 return false; 0173 } 0174 0175 return component->cleanUp(); 0176 } 0177 0178 // static 0179 bool KGlobalAccel::isComponentActive(const QString &componentUnique) 0180 { 0181 org::kde::kglobalaccel::Component *component = self()->getComponent(componentUnique); 0182 if (!component) { 0183 return false; 0184 } 0185 0186 return component->isActive(); 0187 } 0188 0189 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(4, 4) 0190 bool KGlobalAccel::isEnabled() const 0191 { 0192 return d->enabled; 0193 } 0194 #endif 0195 0196 org::kde::kglobalaccel::Component *KGlobalAccel::getComponent(const QString &componentUnique) 0197 { 0198 return d->getComponent(componentUnique); 0199 } 0200 0201 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(4, 4) 0202 void KGlobalAccel::setEnabled(bool enabled) 0203 { 0204 d->enabled = enabled; 0205 } 0206 #endif 0207 0208 class KGlobalAccelSingleton 0209 { 0210 public: 0211 KGlobalAccelSingleton(); 0212 0213 KGlobalAccel instance; 0214 }; 0215 0216 Q_GLOBAL_STATIC(KGlobalAccelSingleton, s_instance) 0217 0218 KGlobalAccelSingleton::KGlobalAccelSingleton() 0219 { 0220 qAddPostRoutine([]() { 0221 s_instance->instance.d->cleanup(); 0222 }); 0223 } 0224 0225 KGlobalAccel *KGlobalAccel::self() 0226 { 0227 return &s_instance()->instance; 0228 } 0229 0230 bool KGlobalAccelPrivate::doRegister(QAction *action) 0231 { 0232 if (!action || action->objectName().isEmpty() || action->objectName().startsWith(QLatin1String("unnamed-"))) { 0233 qWarning() << "Attempt to set global shortcut for action without objectName()." 0234 " Read the setGlobalShortcut() documentation."; 0235 return false; 0236 } 0237 0238 const bool isRegistered = actions.contains(action); 0239 if (isRegistered) { 0240 return true; 0241 } 0242 0243 QStringList actionId = makeActionId(action); 0244 0245 nameToAction.insert(actionId.at(KGlobalAccel::ActionUnique), action); 0246 actions.insert(action); 0247 iface()->doRegister(actionId); 0248 0249 QObject::connect(action, &QObject::destroyed, [this, action](QObject *) { 0250 if (actions.contains(action) && (actionShortcuts.contains(action) || actionDefaultShortcuts.contains(action))) { 0251 remove(action, KGlobalAccelPrivate::SetInactive); 0252 } 0253 }); 0254 0255 return true; 0256 } 0257 0258 void KGlobalAccelPrivate::remove(QAction *action, Removal removal) 0259 { 0260 if (!action || action->objectName().isEmpty()) { 0261 return; 0262 } 0263 0264 const bool isRegistered = actions.contains(action); 0265 if (!isRegistered) { 0266 return; 0267 } 0268 0269 QStringList actionId = makeActionId(action); 0270 0271 nameToAction.remove(actionId.at(KGlobalAccel::ActionUnique), action); 0272 actions.remove(action); 0273 0274 if (removal == UnRegister) { 0275 // Complete removal of the shortcut is requested 0276 // (forgetGlobalShortcut) 0277 unregister(actionId); 0278 } else { 0279 // If the action is a configurationAction wen only remove it from our 0280 // internal registry. That happened above. 0281 0282 // If we are merely marking a callback as inactive there is nothing for kglobalaccel to do if kglobalaccel is not running 0283 // this can happen on shutdown where all apps and kglobalaccel are all torn down at once 0284 // For this reason we turn off the autostart flag in the DBus message call 0285 0286 if (!action->property("isConfigurationAction").toBool()) { 0287 // If it's a session shortcut unregister it. 0288 if (action->objectName().startsWith(QLatin1String("_k_session:"))) { 0289 unregister(actionId); 0290 } else { 0291 setInactive(actionId); 0292 } 0293 } 0294 } 0295 0296 actionDefaultShortcuts.remove(action); 0297 actionShortcuts.remove(action); 0298 } 0299 0300 void KGlobalAccelPrivate::unregister(const QStringList &actionId) 0301 { 0302 const auto component = actionId.at(KGlobalAccel::ComponentUnique); 0303 const auto action = actionId.at(KGlobalAccel::ActionUnique); 0304 0305 auto message = QDBusMessage::createMethodCall(iface()->service(), iface()->path(), iface()->interface(), QStringLiteral("unregister")); 0306 message.setArguments({component, action}); 0307 message.setAutoStartService(false); 0308 QDBusConnection::sessionBus().call(message); 0309 } 0310 0311 void KGlobalAccelPrivate::setInactive(const QStringList &actionId) 0312 { 0313 auto message = QDBusMessage::createMethodCall(iface()->service(), iface()->path(), iface()->interface(), QStringLiteral("setInactive")); 0314 message.setArguments({actionId}); 0315 message.setAutoStartService(false); 0316 QDBusConnection::sessionBus().call(message); 0317 } 0318 0319 void KGlobalAccelPrivate::updateGlobalShortcut(/*const would be better*/ QAction *action, 0320 ShortcutTypes actionFlags, 0321 KGlobalAccel::GlobalShortcutLoading globalFlags) 0322 { 0323 // No action or no objectname -> Do nothing 0324 if (!action || action->objectName().isEmpty()) { 0325 return; 0326 } 0327 0328 QStringList actionId = makeActionId(action); 0329 0330 uint setterFlags = 0; 0331 if (globalFlags & NoAutoloading) { 0332 setterFlags |= NoAutoloading; 0333 } 0334 0335 if (actionFlags & ActiveShortcut) { 0336 const QList<QKeySequence> activeShortcut = actionShortcuts.value(action); 0337 bool isConfigurationAction = action->property("isConfigurationAction").toBool(); 0338 uint activeSetterFlags = setterFlags; 0339 0340 // setPresent tells kglobalaccel that the shortcut is active 0341 if (!isConfigurationAction) { 0342 activeSetterFlags |= SetPresent; 0343 } 0344 0345 // Sets the shortcut, returns the active/real keys 0346 const auto result = iface()->setShortcutKeys(actionId, activeShortcut, activeSetterFlags); 0347 0348 // Make sure we get informed about changes in the component by kglobalaccel 0349 getComponent(componentUniqueForAction(action), true); 0350 0351 // Create a shortcut from the result 0352 const QList<QKeySequence> scResult(result); 0353 0354 if (isConfigurationAction && (globalFlags & NoAutoloading)) { 0355 // If this is a configuration action and we have set the shortcut, 0356 // inform the real owner of the change. 0357 // Note that setForeignShortcut will cause a signal to be sent to applications 0358 // even if it did not "see" that the shortcut has changed. This is Good because 0359 // at the time of comparison (now) the action *already has* the new shortcut. 0360 // We called setShortcut(), remember? 0361 // Also note that we will see our own signal so we may not need to call 0362 // setActiveGlobalShortcutNoEnable - shortcutGotChanged() does it. 0363 // In practice it's probably better to get the change propagated here without 0364 // DBus delay as we do below. 0365 iface()->setForeignShortcutKeys(actionId, result); 0366 } 0367 if (scResult != activeShortcut) { 0368 // If kglobalaccel returned a shortcut that differs from the one we 0369 // sent, use that one. There must have been clashes or some other problem. 0370 actionShortcuts.insert(action, scResult); 0371 Q_EMIT q->globalShortcutChanged(action, scResult.isEmpty() ? QKeySequence() : scResult.first()); 0372 } 0373 } 0374 0375 if (actionFlags & DefaultShortcut) { 0376 const QList<QKeySequence> defaultShortcut = actionDefaultShortcuts.value(action); 0377 iface()->setShortcutKeys(actionId, defaultShortcut, setterFlags | IsDefault); 0378 } 0379 } 0380 0381 QStringList KGlobalAccelPrivate::makeActionId(const QAction *action) 0382 { 0383 QStringList ret(componentUniqueForAction(action)); // Component Unique Id ( see actionIdFields ) 0384 Q_ASSERT(!ret.at(KGlobalAccel::ComponentUnique).isEmpty()); 0385 Q_ASSERT(!action->objectName().isEmpty()); 0386 ret.append(action->objectName()); // Action Unique Name 0387 ret.append(componentFriendlyForAction(action)); // Component Friendly name 0388 const QString actionText = action->text().replace(QLatin1Char('&'), QStringLiteral("")); 0389 ret.append(actionText); // Action Friendly Name 0390 return ret; 0391 } 0392 0393 QList<int> KGlobalAccelPrivate::intListFromShortcut(const QList<QKeySequence> &cut) 0394 { 0395 QList<int> ret; 0396 for (const QKeySequence &sequence : cut) { 0397 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0398 ret.append(sequence[0].toCombined()); 0399 #else 0400 ret.append(sequence[0]); 0401 #endif 0402 } 0403 while (!ret.isEmpty() && ret.last() == 0) { 0404 ret.removeLast(); 0405 } 0406 return ret; 0407 } 0408 0409 QList<QKeySequence> KGlobalAccelPrivate::shortcutFromIntList(const QList<int> &list) 0410 { 0411 QList<QKeySequence> ret; 0412 ret.reserve(list.size()); 0413 std::transform(list.begin(), list.end(), std::back_inserter(ret), [](int i) { 0414 return QKeySequence(i); 0415 }); 0416 return ret; 0417 } 0418 0419 QString KGlobalAccelPrivate::componentUniqueForAction(const QAction *action) 0420 { 0421 if (!action->property("componentName").isValid()) { 0422 return QCoreApplication::applicationName(); 0423 } else { 0424 return action->property("componentName").toString(); 0425 } 0426 } 0427 0428 QString KGlobalAccelPrivate::componentFriendlyForAction(const QAction *action) 0429 { 0430 QString property = action->property("componentDisplayName").toString(); 0431 if (!property.isEmpty()) { 0432 return property; 0433 } 0434 if (!QGuiApplication::applicationDisplayName().isEmpty()) { 0435 return QGuiApplication::applicationDisplayName(); 0436 } 0437 return QCoreApplication::applicationName(); 0438 } 0439 0440 #if HAVE_X11 0441 int timestampCompare(unsigned long time1_, unsigned long time2_) // like strcmp() 0442 { 0443 quint32 time1 = time1_; 0444 quint32 time2 = time2_; 0445 if (time1 == time2) { 0446 return 0; 0447 } 0448 return quint32(time1 - time2) < 0x7fffffffU ? 1 : -1; // time1 > time2 -> 1, handle wrapping 0449 } 0450 #endif 0451 0452 QAction *KGlobalAccelPrivate::findAction(const QString &componentUnique, const QString &actionUnique) 0453 { 0454 QAction *action = nullptr; 0455 const QList<QAction *> candidates = nameToAction.values(actionUnique); 0456 for (QAction *const a : candidates) { 0457 if (componentUniqueForAction(a) == componentUnique) { 0458 action = a; 0459 } 0460 } 0461 0462 // We do not trigger if 0463 // - there is no action 0464 // - the action is not enabled 0465 // - the action is an configuration action 0466 if (!action || !action->isEnabled() || action->property("isConfigurationAction").toBool()) { 0467 return nullptr; 0468 } 0469 return action; 0470 } 0471 0472 void KGlobalAccelPrivate::invokeAction(const QString &componentUnique, const QString &actionUnique, qlonglong timestamp) 0473 { 0474 QAction *action = findAction(componentUnique, actionUnique); 0475 if (!action) { 0476 return; 0477 } 0478 0479 #if HAVE_X11 0480 // Update this application's X timestamp if needed. 0481 // TODO The 100%-correct solution should probably be handling this action 0482 // in the proper place in relation to the X events queue in order to avoid 0483 // the possibility of wrong ordering of user events. 0484 if (QX11Info::isPlatformX11()) { 0485 if (timestampCompare(timestamp, QX11Info::appTime()) > 0) { 0486 QX11Info::setAppTime(timestamp); 0487 } 0488 if (timestampCompare(timestamp, QX11Info::appUserTime()) > 0) { 0489 QX11Info::setAppUserTime(timestamp); 0490 } 0491 } 0492 #endif 0493 action->setProperty("org.kde.kglobalaccel.activationTimestamp", timestamp); 0494 0495 if (m_lastActivatedAction != action) { 0496 Q_EMIT q->globalShortcutActiveChanged(action, true); 0497 m_lastActivatedAction = action; 0498 } 0499 action->trigger(); 0500 } 0501 0502 void KGlobalAccelPrivate::invokeDeactivate(const QString &componentUnique, const QString &actionUnique) 0503 { 0504 QAction *action = findAction(componentUnique, actionUnique); 0505 if (!action) { 0506 return; 0507 } 0508 0509 m_lastActivatedAction.clear(); 0510 0511 Q_EMIT q->globalShortcutActiveChanged(action, false); 0512 } 0513 0514 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(5, 90) 0515 void KGlobalAccelPrivate::shortcutGotChanged(const QStringList &actionId, const QList<int> &keys) 0516 { 0517 QAction *action = nameToAction.value(actionId.at(KGlobalAccel::ActionUnique)); 0518 if (!action) { 0519 return; 0520 } 0521 0522 const QList<QKeySequence> shortcuts = shortcutFromIntList(keys); 0523 actionShortcuts.insert(action, shortcuts); 0524 Q_EMIT q->globalShortcutChanged(action, keys.isEmpty() ? QKeySequence() : shortcuts.first()); 0525 } 0526 #endif 0527 0528 void KGlobalAccelPrivate::shortcutsChanged(const QStringList &actionId, const QList<QKeySequence> &keys) 0529 { 0530 QAction *action = nameToAction.value(actionId.at(KGlobalAccel::ActionUnique)); 0531 if (!action) { 0532 return; 0533 } 0534 0535 actionShortcuts.insert(action, keys); 0536 Q_EMIT q->globalShortcutChanged(action, keys.isEmpty() ? QKeySequence() : keys.first()); 0537 } 0538 0539 void KGlobalAccelPrivate::serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) 0540 { 0541 Q_UNUSED(oldOwner); 0542 if (name == QLatin1String("org.kde.kglobalaccel") && !newOwner.isEmpty()) { 0543 // kglobalaccel was restarted 0544 qCDebug(KGLOBALACCEL_LOG) << "detected kglobalaccel restarting, re-registering all shortcut keys"; 0545 reRegisterAll(); 0546 } 0547 } 0548 0549 void KGlobalAccelPrivate::reRegisterAll() 0550 { 0551 // We clear all our data, assume that all data on the other side is clear too, 0552 // and register each action as if it just was allowed to have global shortcuts. 0553 // If the kded side still has the data it doesn't matter because of the 0554 // autoloading mechanism. The worst case I can imagine is that an action's 0555 // shortcut was changed but the kded side died before it got the message so 0556 // autoloading will now assign an old shortcut to the action. Particularly 0557 // picky apps might assert or misbehave. 0558 const QSet<QAction *> allActions = actions; 0559 nameToAction.clear(); 0560 actions.clear(); 0561 for (QAction *const action : allActions) { 0562 if (doRegister(action)) { 0563 updateGlobalShortcut(action, ActiveShortcut, KGlobalAccel::Autoloading); 0564 } 0565 } 0566 } 0567 0568 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(4, 2) 0569 QList<QStringList> KGlobalAccel::allMainComponents() 0570 { 0571 return d->iface()->allMainComponents(); 0572 } 0573 #endif 0574 0575 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(4, 2) 0576 QList<QStringList> KGlobalAccel::allActionsForComponent(const QStringList &actionId) 0577 { 0578 return d->iface()->allActionsForComponent(actionId); 0579 } 0580 #endif 0581 0582 // static 0583 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(4, 2) 0584 QStringList KGlobalAccel::findActionNameSystemwide(const QKeySequence &seq) 0585 { 0586 return self()->d->iface()->actionList(seq); 0587 } 0588 #endif 0589 0590 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(5, 90) 0591 QList<KGlobalShortcutInfo> KGlobalAccel::getGlobalShortcutsByKey(const QKeySequence &seq) 0592 { 0593 return globalShortcutsByKey(seq); 0594 } 0595 #endif 0596 0597 QList<KGlobalShortcutInfo> KGlobalAccel::globalShortcutsByKey(const QKeySequence &seq, MatchType type) 0598 { 0599 return self()->d->iface()->globalShortcutsByKey(seq, type); 0600 } 0601 0602 bool KGlobalAccel::isGlobalShortcutAvailable(const QKeySequence &seq, const QString &comp) 0603 { 0604 return self()->d->iface()->globalShortcutAvailable(seq, comp); 0605 } 0606 0607 // static 0608 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(4, 2) 0609 bool KGlobalAccel::promptStealShortcutSystemwide(QWidget *parent, const QStringList &actionIdentifier, const QKeySequence &seq) 0610 { 0611 if (actionIdentifier.size() < 4) { 0612 return false; 0613 } 0614 QString title = tr("Conflict with Global Shortcut"); 0615 QString message = tr("The '%1' key combination has already been allocated " 0616 "to the global action \"%2\" in %3.\n" 0617 "Do you want to reassign it from that action to the current one?") 0618 .arg(seq.toString(), actionIdentifier.at(KGlobalAccel::ActionFriendly), actionIdentifier.at(KGlobalAccel::ComponentFriendly)); 0619 0620 QMessageBox box(parent); 0621 box.setWindowTitle(title); 0622 box.setText(message); 0623 box.addButton(QMessageBox::Ok)->setText(tr("Reassign")); 0624 box.addButton(QMessageBox::Cancel); 0625 0626 return box.exec() == QMessageBox::Ok; 0627 } 0628 #endif 0629 0630 // static 0631 bool KGlobalAccel::promptStealShortcutSystemwide(QWidget *parent, const QList<KGlobalShortcutInfo> &shortcuts, const QKeySequence &seq) 0632 { 0633 if (shortcuts.isEmpty()) { 0634 // Usage error. Just say no 0635 return false; 0636 } 0637 0638 QString component = shortcuts[0].componentFriendlyName(); 0639 0640 QString message; 0641 if (shortcuts.size() == 1) { 0642 message = tr("The '%1' key combination is registered by application %2 for action %3.").arg(seq.toString(), component, shortcuts[0].friendlyName()); 0643 } else { 0644 QString actionList; 0645 for (const KGlobalShortcutInfo &info : shortcuts) { 0646 actionList += tr("In context '%1' for action '%2'\n").arg(info.contextFriendlyName(), info.friendlyName()); 0647 } 0648 message = tr("The '%1' key combination is registered by application %2.\n%3").arg(seq.toString(), component, actionList); 0649 } 0650 0651 QString title = tr("Conflict With Registered Global Shortcut"); 0652 0653 QMessageBox box(parent); 0654 box.setWindowTitle(title); 0655 box.setText(message); 0656 box.addButton(QMessageBox::Ok)->setText(tr("Reassign")); 0657 box.addButton(QMessageBox::Cancel); 0658 0659 return box.exec() == QMessageBox::Ok; 0660 } 0661 0662 // static 0663 void KGlobalAccel::stealShortcutSystemwide(const QKeySequence &seq) 0664 { 0665 // get the shortcut, remove seq, and set the new shortcut 0666 const QStringList actionId = self()->d->iface()->actionList(seq); 0667 if (actionId.size() < 4) { // not a global shortcut 0668 return; 0669 } 0670 QList<QKeySequence> sc = self()->d->iface()->shortcutKeys(actionId); 0671 0672 for (int i = 0; i < sc.count(); i++) { 0673 if (sc[i] == seq) { 0674 sc[i] = QKeySequence(); 0675 } 0676 } 0677 0678 self()->d->iface()->setForeignShortcutKeys(actionId, sc); 0679 } 0680 0681 bool checkGarbageKeycode(const QList<QKeySequence> &shortcut) 0682 { 0683 // protect against garbage keycode -1 that Qt sometimes produces for exotic keys; 0684 // at the moment (~mid 2008) Multimedia PlayPause is one of those keys. 0685 for (const QKeySequence &sequence : shortcut) { 0686 for (int i = 0; i < 4; i++) { 0687 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0688 if (sequence[i].toCombined() == -1) { 0689 #else 0690 if (sequence[i] == -1) { 0691 #endif 0692 qWarning() << "Encountered garbage keycode (keycode = -1) in input, not doing anything."; 0693 return true; 0694 } 0695 } 0696 } 0697 return false; 0698 } 0699 0700 bool KGlobalAccel::setDefaultShortcut(QAction *action, const QList<QKeySequence> &shortcut, GlobalShortcutLoading loadFlag) 0701 { 0702 if (checkGarbageKeycode(shortcut)) { 0703 return false; 0704 } 0705 0706 if (!d->doRegister(action)) { 0707 return false; 0708 } 0709 0710 d->actionDefaultShortcuts.insert(action, shortcut); 0711 d->updateGlobalShortcut(action, KGlobalAccelPrivate::DefaultShortcut, loadFlag); 0712 return true; 0713 } 0714 0715 bool KGlobalAccel::setShortcut(QAction *action, const QList<QKeySequence> &shortcut, GlobalShortcutLoading loadFlag) 0716 { 0717 if (checkGarbageKeycode(shortcut)) { 0718 return false; 0719 } 0720 0721 if (!d->doRegister(action)) { 0722 return false; 0723 } 0724 0725 d->actionShortcuts.insert(action, shortcut); 0726 d->updateGlobalShortcut(action, KGlobalAccelPrivate::ActiveShortcut, loadFlag); 0727 return true; 0728 } 0729 0730 QList<QKeySequence> KGlobalAccel::defaultShortcut(const QAction *action) const 0731 { 0732 return d->actionDefaultShortcuts.value(action); 0733 } 0734 0735 QList<QKeySequence> KGlobalAccel::shortcut(const QAction *action) const 0736 { 0737 return d->actionShortcuts.value(action); 0738 } 0739 0740 QList<QKeySequence> KGlobalAccel::globalShortcut(const QString &componentName, const QString &actionId) const 0741 { 0742 // see also d->updateGlobalShortcut(action, KGlobalAccelPrivate::ActiveShortcut, KGlobalAccel::Autoloading); 0743 0744 // how componentName and actionId map to QAction, e.g: 0745 // action->setProperty("componentName", "kwin"); 0746 // action->setObjectName("Kill Window"); 0747 0748 const QList<QKeySequence> scResult = self()->d->iface()->shortcutKeys({componentName, actionId, QString(), QString()}); 0749 return scResult; 0750 } 0751 0752 void KGlobalAccel::removeAllShortcuts(QAction *action) 0753 { 0754 d->remove(action, KGlobalAccelPrivate::UnRegister); 0755 } 0756 0757 bool KGlobalAccel::hasShortcut(const QAction *action) const 0758 { 0759 return d->actionShortcuts.contains(action) || d->actionDefaultShortcuts.contains(action); 0760 } 0761 0762 #if KGLOBALACCEL_BUILD_DEPRECATED_SINCE(5, 9) 0763 bool KGlobalAccel::eventFilter(QObject *watched, QEvent *event) 0764 { 0765 return QObject::eventFilter(watched, event); 0766 } 0767 #endif 0768 0769 bool KGlobalAccel::setGlobalShortcut(QAction *action, const QList<QKeySequence> &shortcut) 0770 { 0771 KGlobalAccel *g = KGlobalAccel::self(); 0772 return g->d->setShortcutWithDefault(action, shortcut, Autoloading); 0773 } 0774 0775 bool KGlobalAccel::setGlobalShortcut(QAction *action, const QKeySequence &shortcut) 0776 { 0777 return KGlobalAccel::setGlobalShortcut(action, QList<QKeySequence>() << shortcut); 0778 } 0779 0780 bool KGlobalAccelPrivate::setShortcutWithDefault(QAction *action, const QList<QKeySequence> &shortcut, KGlobalAccel::GlobalShortcutLoading loadFlag) 0781 { 0782 if (checkGarbageKeycode(shortcut)) { 0783 return false; 0784 } 0785 0786 if (!doRegister(action)) { 0787 return false; 0788 } 0789 0790 actionDefaultShortcuts.insert(action, shortcut); 0791 actionShortcuts.insert(action, shortcut); 0792 updateGlobalShortcut(action, KGlobalAccelPrivate::DefaultShortcut | KGlobalAccelPrivate::ActiveShortcut, loadFlag); 0793 return true; 0794 } 0795 0796 QDBusArgument &operator<<(QDBusArgument &argument, const KGlobalAccel::MatchType &type) 0797 { 0798 argument.beginStructure(); 0799 argument << static_cast<int>(type); 0800 argument.endStructure(); 0801 return argument; 0802 } 0803 0804 const QDBusArgument &operator>>(const QDBusArgument &argument, KGlobalAccel::MatchType &type) 0805 { 0806 argument.beginStructure(); 0807 int arg; 0808 argument >> arg; 0809 type = static_cast<KGlobalAccel::MatchType>(arg); 0810 argument.endStructure(); 0811 return argument; 0812 } 0813 0814 #include "moc_kglobalaccel.cpp"