File indexing completed on 2019-03-26 12:08:28

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