File indexing completed on 2024-04-21 14:56:34

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"