File indexing completed on 2019-04-19 10:18:43

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     const auto actions = _current->_actions;
0164     for (GlobalShortcut *shortcut : actions)
0165         {
0166         qCDebug(KGLOBALACCELD) << _current->_actions.size();
0167         if (!shortcut->isPresent())
0168             {
0169             changed = true;
0170             shortcut->unRegister();
0171             }
0172         }
0173 
0174     if (changed) {
0175         _registry->writeSettings();
0176         // We could be destroyed after this call!
0177     }
0178 
0179     return changed;
0180     }
0181 
0182 
0183 bool Component::createGlobalShortcutContext(
0184         const QString &uniqueName,
0185         const QString &friendlyName)
0186     {
0187     if (_contexts.value(uniqueName))
0188         {
0189         qCDebug(KGLOBALACCELD) << "Shortcut Context " << uniqueName << "already exists for component " << _uniqueName;
0190         return false;
0191         }
0192     _contexts.insert(uniqueName, new GlobalShortcutContext(uniqueName, friendlyName, this));
0193     return true;
0194     }
0195 
0196 
0197 GlobalShortcutContext *Component::currentContext()
0198     {
0199     return _current;
0200     }
0201 
0202 
0203 QDBusObjectPath Component::dbusPath() const
0204     {
0205     QString dbusPath = _uniqueName;
0206     // Clean up for dbus usage: any non-alphanumeric char should be turned into '_'
0207     const int len = dbusPath.length();
0208     for ( int i = 0; i < len; ++i )
0209         {
0210         if ( !dbusPath[i].isLetterOrNumber() )
0211             dbusPath[i] = QLatin1Char('_');
0212         }
0213     // QDBusObjectPath could be a little bit easier to handle :-)
0214     return QDBusObjectPath( _registry->dbusPath().path() + "component/" + dbusPath);
0215     }
0216 
0217 
0218 void Component::deactivateShortcuts(bool temporarily)
0219     {
0220     for (GlobalShortcut *shortcut : qAsConst(_current->_actions))
0221         {
0222         if (temporarily
0223                 && uniqueName() == QLatin1String("kwin")
0224                 && shortcut->uniqueName() == QLatin1String("Block Global Shortcuts"))
0225             {
0226             continue;
0227             }
0228         shortcut->setInactive();
0229         }
0230     }
0231 
0232 
0233 void Component::emitGlobalShortcutPressed( const GlobalShortcut &shortcut )
0234     {
0235 #if HAVE_X11
0236     // pass X11 timestamp
0237     long timestamp = QX11Info::appTime();
0238     // Make sure kglobalacceld has ungrabbed the keyboard after receiving the
0239     // keypress, otherwise actions in application that try to grab the
0240     // keyboard (e.g. in kwin) may fail to do so. There is still a small race
0241     // condition with this being out-of-process.
0242     if (_registry->_manager) {
0243         _registry->_manager->syncWindowingSystem();
0244     }
0245 #else
0246     long timestamp = 0;
0247 #endif
0248 
0249     // Make sure it is one of ours
0250     if (shortcut.context()->component() != this)
0251         {
0252         // In production mode do nothing
0253         return;
0254         }
0255 
0256     emit globalShortcutPressed(
0257             shortcut.context()->component()->uniqueName(),
0258             shortcut.uniqueName(),
0259             timestamp);
0260     }
0261 
0262 void Component::invokeShortcut(const QString &shortcutName, const QString &context)
0263     {
0264         GlobalShortcut *shortcut = getShortcutByName(shortcutName, context);
0265         if (shortcut) emitGlobalShortcutPressed(*shortcut);
0266     }
0267 
0268 QString Component::friendlyName() const
0269     {
0270     if (_friendlyName.isEmpty())
0271         return _uniqueName;
0272     return _friendlyName;
0273     }
0274 
0275 
0276 GlobalShortcut *Component::getShortcutByKey(int key) const
0277     {
0278     return _current->getShortcutByKey(key);
0279     }
0280 
0281 
0282 QList<GlobalShortcut *> Component::getShortcutsByKey(int key) const
0283     {
0284     QList <GlobalShortcut *> rc;
0285     for (GlobalShortcutContext *context : qAsConst(_contexts))
0286         {
0287         GlobalShortcut *sc = context->getShortcutByKey(key);
0288         if (sc) rc.append(sc);
0289         }
0290     return rc;
0291     }
0292 
0293 
0294 GlobalShortcut *Component::getShortcutByName(const QString &uniqueName, const QString &context) const
0295     {
0296     if (!_contexts.value(context))
0297         {
0298         return nullptr;
0299         }
0300 
0301     return _contexts.value(context)->_actions.value(uniqueName);
0302     }
0303 
0304 
0305 QStringList Component::getShortcutContexts() const
0306     {
0307     return _contexts.keys();
0308     }
0309 
0310 
0311 bool Component::isActive() const
0312     {
0313     // The component is active if at least one of it's global shortcuts is
0314     // present.
0315     for (GlobalShortcut *shortcut : qAsConst(_current->_actions))
0316         {
0317         if (shortcut->isPresent()) return true;
0318         }
0319     return false;
0320     }
0321 
0322 
0323 bool Component::isShortcutAvailable(
0324         int key,
0325         const QString &component,
0326         const QString &context) const
0327     {
0328     qCDebug(KGLOBALACCELD) << QKeySequence(key).toString() << component;
0329 
0330     // if this component asks for the key. only check the keys in the same
0331     // context
0332     if (component==uniqueName())
0333         {
0334         const auto actions = shortcutContext(context)->_actions;
0335         for (GlobalShortcut *sc : actions)
0336             {
0337             if (sc->keys().contains(key)) return false;
0338             }
0339         }
0340     else
0341         {
0342         for (GlobalShortcutContext *ctx : qAsConst(_contexts))
0343             {
0344             for (GlobalShortcut *sc : qAsConst(ctx->_actions))
0345                 {
0346                 if (sc->keys().contains(key)) return false;
0347                 }
0348             }
0349         }
0350     return true;
0351     }
0352 
0353 GlobalShortcut *Component::registerShortcut(const QString &uniqueName, const QString &friendlyName, const QString &shortcutString, const QString &defaultShortcutString)
0354     {
0355     // The shortcut will register itself with us
0356     GlobalShortcut *shortcut = new GlobalShortcut(
0357             uniqueName,
0358             friendlyName,
0359             currentContext());
0360 
0361     const QList<int> keys = keysFromString(shortcutString);
0362     shortcut->setDefaultKeys(keysFromString(defaultShortcutString));
0363     shortcut->setIsFresh(false);
0364     QList<int> newKeys = keys;
0365     for (int key : keys)
0366         {
0367         if (key != 0)
0368             {
0369             if (GlobalShortcutsRegistry::self()->getShortcutByKey(key))
0370                 {
0371                 // The shortcut is already used. The config file is
0372                 // broken. Ignore the request.
0373                 newKeys.removeAll(key);
0374                 qCWarning(KGLOBALACCELD) << "Shortcut found twice in kglobalshortcutsrc."<<key;
0375                 }
0376             }
0377         }
0378     shortcut->setKeys(keys);
0379     return shortcut;
0380     }
0381 
0382 
0383 void Component::loadSettings(KConfigGroup &configGroup)
0384     {
0385     // GlobalShortcutsRegistry::loadSettings handles contexts.
0386     const auto listKeys = configGroup.keyList();
0387     for (const QString &confKey : listKeys)
0388         {
0389         const QStringList entry = configGroup.readEntry(confKey, QStringList());
0390         if (entry.size() != 3)
0391             {
0392             continue;
0393             }
0394 
0395         GlobalShortcut *shortcut = registerShortcut(confKey, entry[2], entry[0], entry[1]);
0396         if (configGroup.name().endsWith(QLatin1String(".desktop"))) {
0397             shortcut->setIsPresent(true);
0398         }
0399         }
0400     }
0401 
0402 
0403 void Component::setFriendlyName(const QString &name)
0404     {
0405     _friendlyName = name;
0406     }
0407 
0408 
0409 GlobalShortcutContext *Component::shortcutContext( const QString &contextName )
0410     {
0411     return _contexts.value(contextName);
0412     }
0413 
0414 
0415 GlobalShortcutContext const *Component::shortcutContext( const QString &contextName ) const
0416     {
0417     return _contexts.value(contextName);
0418     }
0419 
0420 
0421 QStringList Component::shortcutNames( const QString &contextName) const
0422     {
0423     GlobalShortcutContext *context = _contexts.value(contextName);
0424     if (!context)
0425         {
0426         return QStringList();
0427         }
0428 
0429     return context->_actions.keys();
0430     }
0431 
0432 
0433 QString Component::uniqueName() const
0434     {
0435     return _uniqueName;
0436     }
0437 
0438 
0439 void Component::unregisterShortcut(const QString &uniqueName)
0440     {
0441     // Now wrote all contexts
0442     for( GlobalShortcutContext *context : qAsConst(_contexts))
0443         {
0444         if (context->_actions.value(uniqueName))
0445             {
0446             delete context->takeShortcut(context->_actions.value(uniqueName));
0447             }
0448         }
0449     }
0450 
0451 
0452 void Component::writeSettings(KConfigGroup& configGroup) const
0453     {
0454     // If we don't delete the current content global shortcut
0455     // registrations will never not deleted after forgetGlobalShortcut()
0456     configGroup.deleteGroup();
0457 
0458 
0459     // Now write all contexts
0460     for( GlobalShortcutContext *context : qAsConst(_contexts))
0461         {
0462         KConfigGroup contextGroup;
0463 
0464         if (context->uniqueName() == QLatin1String("default"))
0465             {
0466             contextGroup = configGroup;
0467             // Write the friendly name
0468             contextGroup.writeEntry("_k_friendly_name", friendlyName());
0469             }
0470         else
0471             {
0472             contextGroup = KConfigGroup(&configGroup, context->uniqueName());
0473             // Write the friendly name
0474             contextGroup.writeEntry("_k_friendly_name", context->friendlyName());
0475             }
0476 
0477         // qCDebug(KGLOBALACCELD) << "writing group " << _uniqueName << ":" << context->uniqueName();
0478 
0479         for (const GlobalShortcut *shortcut : qAsConst(context->_actions))
0480             {
0481             // qCDebug(KGLOBALACCELD) << "writing" << shortcut->uniqueName();
0482 
0483             // We do not write fresh shortcuts.
0484             // We do not write session shortcuts
0485             if (shortcut->isFresh() || shortcut->isSessionShortcut())
0486                 {
0487                 continue;
0488                 }
0489             // qCDebug(KGLOBALACCELD) << "really writing" << shortcut->uniqueName();
0490 
0491             QStringList entry(stringFromKeys(shortcut->keys()));
0492             entry.append(stringFromKeys(shortcut->defaultKeys()));
0493             entry.append(shortcut->friendlyName());
0494 
0495             contextGroup.writeEntry(shortcut->uniqueName(), entry);
0496             }
0497         }
0498     }
0499 
0500 } // namespace KdeDGlobalAccel