File indexing completed on 2021-10-26 12:23:29

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