File indexing completed on 2018-07-10 11:36:23

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     foreach (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     foreach (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     Q_FOREACH (GlobalShortcut *shortcut, _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     Q_FOREACH (GlobalShortcut *shortcut, _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     Q_FOREACH (GlobalShortcut *shortcut, _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     Q_FOREACH(GlobalShortcutContext *context, _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     Q_FOREACH (GlobalShortcut *shortcut, _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         Q_FOREACH(GlobalShortcut *sc, shortcutContext(context)->_actions)
0334             {
0335             if (sc->keys().contains(key)) return false;
0336             }
0337         }
0338     else
0339         {
0340         Q_FOREACH(GlobalShortcutContext *ctx, _contexts)
0341             {
0342             Q_FOREACH(GlobalShortcut *sc, 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     QList<int> keys = keysFromString(shortcutString);
0360     shortcut->setDefaultKeys(keysFromString(defaultShortcutString));
0361     shortcut->setIsFresh(false);
0362 
0363     Q_FOREACH (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                 keys.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     Q_FOREACH (const QString &confKey, configGroup.keyList())
0385         {
0386         const QStringList entry = configGroup.readEntry(confKey, QStringList());
0387         if (entry.size() != 3)
0388             {
0389             continue;
0390             }
0391 
0392         GlobalShortcut *shortcut = registerShortcut(confKey, entry[2], entry[0], entry[1]);
0393         if (configGroup.name().endsWith(QLatin1String(".desktop"))) {
0394             shortcut->setIsPresent(true);
0395         }
0396         }
0397     }
0398 
0399 
0400 void Component::setFriendlyName(const QString &name)
0401     {
0402     _friendlyName = name;
0403     }
0404 
0405 
0406 GlobalShortcutContext *Component::shortcutContext( const QString &contextName )
0407     {
0408     return _contexts.value(contextName);
0409     }
0410 
0411 
0412 GlobalShortcutContext const *Component::shortcutContext( const QString &contextName ) const
0413     {
0414     return _contexts.value(contextName);
0415     }
0416 
0417 
0418 QStringList Component::shortcutNames( const QString &contextName) const
0419     {
0420     GlobalShortcutContext *context = _contexts.value(contextName);
0421     if (!context)
0422         {
0423         return QStringList();
0424         }
0425 
0426     return context->_actions.keys();
0427     }
0428 
0429 
0430 QString Component::uniqueName() const
0431     {
0432     return _uniqueName;
0433     }
0434 
0435 
0436 void Component::unregisterShortcut(const QString &uniqueName)
0437     {
0438     // Now wrote all contexts
0439     Q_FOREACH( GlobalShortcutContext *context, _contexts)
0440         {
0441         if (context->_actions.value(uniqueName))
0442             {
0443             delete context->takeShortcut(context->_actions.value(uniqueName));
0444             }
0445         }
0446     }
0447 
0448 
0449 void Component::writeSettings(KConfigGroup& configGroup) const
0450     {
0451     // If we don't delete the current content global shortcut
0452     // registrations will never not deleted after forgetGlobalShortcut()
0453     configGroup.deleteGroup();
0454 
0455 
0456     // Now write all contexts
0457     Q_FOREACH( GlobalShortcutContext *context, _contexts)
0458         {
0459         KConfigGroup contextGroup;
0460 
0461         if (context->uniqueName() == QLatin1String("default"))
0462             {
0463             contextGroup = configGroup;
0464             // Write the friendly name
0465             contextGroup.writeEntry("_k_friendly_name", friendlyName());
0466             }
0467         else
0468             {
0469             contextGroup = KConfigGroup(&configGroup, context->uniqueName());
0470             // Write the friendly name
0471             contextGroup.writeEntry("_k_friendly_name", context->friendlyName());
0472             }
0473 
0474         // qCDebug(KGLOBALACCELD) << "writing group " << _uniqueName << ":" << context->uniqueName();
0475 
0476         Q_FOREACH(const GlobalShortcut *shortcut, context->_actions)
0477             {
0478             // qCDebug(KGLOBALACCELD) << "writing" << shortcut->uniqueName();
0479 
0480             // We do not write fresh shortcuts.
0481             // We do not write session shortcuts
0482             if (shortcut->isFresh() || shortcut->isSessionShortcut())
0483                 {
0484                 continue;
0485                 }
0486             // qCDebug(KGLOBALACCELD) << "really writing" << shortcut->uniqueName();
0487 
0488             QStringList entry(stringFromKeys(shortcut->keys()));
0489             entry.append(stringFromKeys(shortcut->defaultKeys()));
0490             entry.append(shortcut->friendlyName());
0491 
0492             contextGroup.writeEntry(shortcut->uniqueName(), entry);
0493             }
0494         }
0495     }
0496 
0497 #include "moc_component.cpp"
0498 
0499 } // namespace KdeDGlobalAccel
0500