File indexing completed on 2020-08-09 11:37:06

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