Warning, file /frameworks/kglobalaccel/src/runtime/component.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2008 Michael Jansen <kde@michael-jansen.biz>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "component.h"
0008 
0009 #include "globalshortcutcontext.h"
0010 #include "globalshortcutsregistry.h"
0011 #include "kglobalaccel_interface.h"
0012 #include "logging_p.h"
0013 #include "sequencehelpers_p.h"
0014 #include <config-kglobalaccel.h>
0015 
0016 #include <QKeySequence>
0017 #include <QStringList>
0018 
0019 #if HAVE_X11
0020 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0021 #include <private/qtx11extras_p.h>
0022 #else
0023 #include <QX11Info>
0024 #endif
0025 #endif
0026 
0027 static QList<QKeySequence> keysFromString(const QString &str)
0028 {
0029     QList<QKeySequence> ret;
0030     if (str == QLatin1String("none")) {
0031         return ret;
0032     }
0033     const QStringList strList = str.split('\t');
0034     for (const QString &s : strList) {
0035         QKeySequence key = QKeySequence(s);
0036         if (!key.isEmpty()) { // sanity check just in case
0037             ret.append(key);
0038         }
0039     }
0040     return ret;
0041 }
0042 
0043 static QString stringFromKeys(const QList<QKeySequence> &keys)
0044 {
0045     if (keys.isEmpty()) {
0046         return QStringLiteral("none");
0047     }
0048     QString ret;
0049     for (const QKeySequence &key : keys) {
0050         ret.append(key.toString());
0051         ret.append('\t');
0052     }
0053     ret.chop(1);
0054     return ret;
0055 }
0056 
0057 namespace KdeDGlobalAccel
0058 {
0059 Component::Component(const QString &uniqueName, const QString &friendlyName, GlobalShortcutsRegistry *registry)
0060     : _uniqueName(uniqueName)
0061     , _friendlyName(friendlyName)
0062     , _registry(registry)
0063 {
0064     // Make sure we do no get uniquenames still containing the context
0065     Q_ASSERT(uniqueName.indexOf("|") == -1);
0066 
0067     // Register ourselves with the registry
0068     if (_registry) {
0069         _registry->addComponent(this);
0070     }
0071 
0072     QString DEFAULT = QStringLiteral("default");
0073     createGlobalShortcutContext(DEFAULT, QStringLiteral("Default Context"));
0074     _current = _contexts.value(DEFAULT);
0075 }
0076 
0077 Component::~Component()
0078 {
0079     // Remove ourselves from the registry
0080     if (_registry) {
0081         _registry->takeComponent(this);
0082     }
0083 
0084     // We delete all shortcuts from all contexts
0085     qDeleteAll(_contexts);
0086 }
0087 
0088 bool Component::activateGlobalShortcutContext(const QString &uniqueName)
0089 {
0090     if (!_contexts.value(uniqueName)) {
0091         createGlobalShortcutContext(uniqueName, "TODO4");
0092         return false;
0093     }
0094 
0095     // Deactivate the current contexts shortcuts
0096     deactivateShortcuts();
0097 
0098     // Switch the context
0099     _current = _contexts.value(uniqueName);
0100 
0101     return true;
0102 }
0103 
0104 void Component::activateShortcuts()
0105 {
0106     for (GlobalShortcut *shortcut : std::as_const(_current->_actions)) {
0107         shortcut->setActive();
0108     }
0109 }
0110 
0111 QList<GlobalShortcut *> Component::allShortcuts(const QString &contextName) const
0112 {
0113     GlobalShortcutContext *context = _contexts.value(contextName);
0114     return context ? context->_actions.values() : QList<GlobalShortcut *>{};
0115 }
0116 
0117 QList<KGlobalShortcutInfo> Component::allShortcutInfos(const QString &contextName) const
0118 {
0119     GlobalShortcutContext *context = _contexts.value(contextName);
0120     return context ? context->allShortcutInfos() : QList<KGlobalShortcutInfo>{};
0121 }
0122 
0123 bool Component::cleanUp()
0124 {
0125     bool changed = false;
0126 
0127     const auto actions = _current->_actions;
0128     for (GlobalShortcut *shortcut : actions) {
0129         qCDebug(KGLOBALACCELD) << _current->_actions.size();
0130         if (!shortcut->isPresent()) {
0131             changed = true;
0132             shortcut->unRegister();
0133         }
0134     }
0135 
0136     if (changed) {
0137         _registry->writeSettings();
0138         // We could be destroyed after this call!
0139     }
0140 
0141     return changed;
0142 }
0143 
0144 bool Component::createGlobalShortcutContext(const QString &uniqueName, const QString &friendlyName)
0145 {
0146     if (_contexts.value(uniqueName)) {
0147         qCDebug(KGLOBALACCELD) << "Shortcut Context " << uniqueName << "already exists for component " << _uniqueName;
0148         return false;
0149     }
0150     _contexts.insert(uniqueName, new GlobalShortcutContext(uniqueName, friendlyName, this));
0151     return true;
0152 }
0153 
0154 GlobalShortcutContext *Component::currentContext()
0155 {
0156     return _current;
0157 }
0158 
0159 QDBusObjectPath Component::dbusPath() const
0160 {
0161     QString dbusPath = _uniqueName;
0162     // Clean up for dbus usage: any non-alphanumeric char should be turned into '_'
0163     const int len = dbusPath.length();
0164     for (int i = 0; i < len; ++i) {
0165         if (!dbusPath[i].isLetterOrNumber() || dbusPath[i].unicode() >= 0x7F) {
0166             // DBus path can only contain ASCII characters
0167             dbusPath[i] = QLatin1Char('_');
0168         }
0169     }
0170     // QDBusObjectPath could be a little bit easier to handle :-)
0171     return QDBusObjectPath(_registry->dbusPath().path() + QLatin1String("component/") + dbusPath);
0172 }
0173 
0174 void Component::deactivateShortcuts(bool temporarily)
0175 {
0176     for (GlobalShortcut *shortcut : std::as_const(_current->_actions)) {
0177         if (temporarily //
0178             && _uniqueName == QLatin1String("kwin") //
0179             && shortcut->uniqueName() == QLatin1String("Block Global Shortcuts")) {
0180             continue;
0181         }
0182         shortcut->setInactive();
0183     }
0184 }
0185 
0186 void Component::emitGlobalShortcutPressed(const GlobalShortcut &shortcut)
0187 {
0188 #if HAVE_X11
0189     // pass X11 timestamp
0190     const long timestamp = QX11Info::appTime();
0191     // Make sure kglobalacceld has ungrabbed the keyboard after receiving the
0192     // keypress, otherwise actions in application that try to grab the
0193     // keyboard (e.g. in kwin) may fail to do so. There is still a small race
0194     // condition with this being out-of-process.
0195     if (_registry->_manager) {
0196         _registry->_manager->syncWindowingSystem();
0197     }
0198 #else
0199     const long timestamp = 0;
0200 #endif
0201 
0202     if (shortcut.context()->component() != this) {
0203         return;
0204     }
0205 
0206     Q_EMIT globalShortcutPressed(shortcut.context()->component()->uniqueName(), shortcut.uniqueName(), timestamp);
0207 }
0208 
0209 void Component::emitGlobalShortcutReleased(const GlobalShortcut &shortcut)
0210 {
0211 #if HAVE_X11
0212     // pass X11 timestamp
0213     const long timestamp = QX11Info::appTime();
0214     // Make sure kglobalacceld has ungrabbed the keyboard after receiving the
0215     // keypress, otherwise actions in application that try to grab the
0216     // keyboard (e.g. in kwin) may fail to do so. There is still a small race
0217     // condition with this being out-of-process.
0218     if (_registry->_manager) {
0219         _registry->_manager->syncWindowingSystem();
0220     }
0221 #else
0222     const long timestamp = 0;
0223 #endif
0224 
0225     if (shortcut.context()->component() != this) {
0226         return;
0227     }
0228 
0229     Q_EMIT globalShortcutReleased(shortcut.context()->component()->uniqueName(), shortcut.uniqueName(), timestamp);
0230 }
0231 
0232 void Component::invokeShortcut(const QString &shortcutName, const QString &context)
0233 {
0234     GlobalShortcut *shortcut = getShortcutByName(shortcutName, context);
0235     if (shortcut) {
0236         emitGlobalShortcutPressed(*shortcut);
0237     }
0238 }
0239 
0240 QString Component::friendlyName() const
0241 {
0242     return !_friendlyName.isEmpty() ? _friendlyName : _uniqueName;
0243 }
0244 
0245 GlobalShortcut *Component::getShortcutByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const
0246 {
0247     return _current->getShortcutByKey(key, type);
0248 }
0249 
0250 QList<GlobalShortcut *> Component::getShortcutsByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const
0251 {
0252     QList<GlobalShortcut *> rc;
0253     for (GlobalShortcutContext *context : std::as_const(_contexts)) {
0254         GlobalShortcut *sc = context->getShortcutByKey(key, type);
0255         if (sc) {
0256             rc.append(sc);
0257         }
0258     }
0259     return rc;
0260 }
0261 
0262 GlobalShortcut *Component::getShortcutByName(const QString &uniqueName, const QString &context) const
0263 {
0264     const GlobalShortcutContext *shortcutContext = _contexts.value(context);
0265     return shortcutContext ? shortcutContext->_actions.value(uniqueName) : nullptr;
0266 }
0267 
0268 QStringList Component::getShortcutContexts() const
0269 {
0270     return _contexts.keys();
0271 }
0272 
0273 bool Component::isActive() const
0274 {
0275     // The component is active if at least one of it's global shortcuts is
0276     // present.
0277     for (GlobalShortcut *shortcut : std::as_const(_current->_actions)) {
0278         if (shortcut->isPresent()) {
0279             return true;
0280         }
0281     }
0282     return false;
0283 }
0284 
0285 bool Component::isShortcutAvailable(const QKeySequence &key, const QString &component, const QString &context) const
0286 {
0287     qCDebug(KGLOBALACCELD) << key.toString() << component;
0288 
0289     // if this component asks for the key. only check the keys in the same
0290     // context
0291     if (component == uniqueName()) {
0292         const auto actions = shortcutContext(context)->_actions;
0293         for (GlobalShortcut *sc : actions) {
0294             if (matchSequences(key, sc->keys())) {
0295                 return false;
0296             }
0297         }
0298     } else {
0299         for (GlobalShortcutContext *ctx : std::as_const(_contexts)) {
0300             for (GlobalShortcut *sc : std::as_const(ctx->_actions)) {
0301                 if (matchSequences(key, sc->keys())) {
0302                     return false;
0303                 }
0304             }
0305         }
0306     }
0307     return true;
0308 }
0309 
0310 GlobalShortcut *
0311 Component::registerShortcut(const QString &uniqueName, const QString &friendlyName, const QString &shortcutString, const QString &defaultShortcutString)
0312 {
0313     // The shortcut will register itself with us
0314     GlobalShortcut *shortcut = new GlobalShortcut(uniqueName, friendlyName, currentContext());
0315 
0316     const QList<QKeySequence> keys = keysFromString(shortcutString);
0317     shortcut->setDefaultKeys(keysFromString(defaultShortcutString));
0318     shortcut->setIsFresh(false);
0319     QList<QKeySequence> newKeys = keys;
0320     for (const QKeySequence &key : keys) {
0321         if (!key.isEmpty()) {
0322             if (GlobalShortcutsRegistry::self()->getShortcutByKey(key)) {
0323                 // The shortcut is already used. The config file is
0324                 // broken. Ignore the request.
0325                 newKeys.removeAll(key);
0326                 qCWarning(KGLOBALACCELD) << "Shortcut found twice in kglobalshortcutsrc." << key;
0327             }
0328         }
0329     }
0330     shortcut->setKeys(keys);
0331     return shortcut;
0332 }
0333 
0334 void Component::loadSettings(KConfigGroup &configGroup)
0335 {
0336     // GlobalShortcutsRegistry::loadSettings handles contexts.
0337     const auto listKeys = configGroup.keyList();
0338     for (const QString &confKey : listKeys) {
0339         const QStringList entry = configGroup.readEntry(confKey, QStringList());
0340         if (entry.size() != 3) {
0341             continue;
0342         }
0343 
0344         GlobalShortcut *shortcut = registerShortcut(confKey, entry[2], entry[0], entry[1]);
0345         if (configGroup.name().endsWith(QLatin1String(".desktop"))) {
0346             shortcut->setIsPresent(true);
0347         }
0348     }
0349 }
0350 
0351 void Component::setFriendlyName(const QString &name)
0352 {
0353     _friendlyName = name;
0354 }
0355 
0356 GlobalShortcutContext *Component::shortcutContext(const QString &contextName)
0357 {
0358     return _contexts.value(contextName);
0359 }
0360 
0361 GlobalShortcutContext const *Component::shortcutContext(const QString &contextName) const
0362 {
0363     return _contexts.value(contextName);
0364 }
0365 
0366 QStringList Component::shortcutNames(const QString &contextName) const
0367 {
0368     const GlobalShortcutContext *context = _contexts.value(contextName);
0369     return context ? context->_actions.keys() : QStringList{};
0370 }
0371 
0372 QString Component::uniqueName() const
0373 {
0374     return _uniqueName;
0375 }
0376 
0377 void Component::unregisterShortcut(const QString &uniqueName)
0378 {
0379     // Now wrote all contexts
0380     for (GlobalShortcutContext *context : std::as_const(_contexts)) {
0381         if (context->_actions.value(uniqueName)) {
0382             delete context->takeShortcut(context->_actions.value(uniqueName));
0383         }
0384     }
0385 }
0386 
0387 void Component::writeSettings(KConfigGroup &configGroup) const
0388 {
0389     // If we don't delete the current content global shortcut
0390     // registrations will never not deleted after forgetGlobalShortcut()
0391     configGroup.deleteGroup();
0392 
0393     // Now write all contexts
0394     for (GlobalShortcutContext *context : std::as_const(_contexts)) {
0395         KConfigGroup contextGroup;
0396 
0397         if (context->uniqueName() == QLatin1String("default")) {
0398             contextGroup = configGroup;
0399             // Write the friendly name
0400             contextGroup.writeEntry("_k_friendly_name", friendlyName());
0401         } else {
0402             contextGroup = KConfigGroup(&configGroup, context->uniqueName());
0403             // Write the friendly name
0404             contextGroup.writeEntry("_k_friendly_name", context->friendlyName());
0405         }
0406 
0407         // qCDebug(KGLOBALACCELD) << "writing group " << _uniqueName << ":" << context->uniqueName();
0408 
0409         for (const GlobalShortcut *shortcut : std::as_const(context->_actions)) {
0410             // qCDebug(KGLOBALACCELD) << "writing" << shortcut->uniqueName();
0411 
0412             // We do not write fresh shortcuts.
0413             // We do not write session shortcuts
0414             if (shortcut->isFresh() || shortcut->isSessionShortcut()) {
0415                 continue;
0416             }
0417             // qCDebug(KGLOBALACCELD) << "really writing" << shortcut->uniqueName();
0418 
0419             QStringList entry(stringFromKeys(shortcut->keys()));
0420             entry.append(stringFromKeys(shortcut->defaultKeys()));
0421             entry.append(shortcut->friendlyName());
0422 
0423             contextGroup.writeEntry(shortcut->uniqueName(), entry);
0424         }
0425     }
0426 }
0427 
0428 } // namespace KdeDGlobalAccel