File indexing completed on 2019-08-13 13:47:06

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 <QStringList>
0029 #include <QKeySequence>
0030 
0031 #if HAVE_X11
0032 #include <QX11Info>
0033 #endif
0034 
0035 static QList<int> keysFromString(const QString &str)
0036 {
0037     QList<int> ret;
0038     if (str == QLatin1String("none")) {
0039         return ret;
0040     }
0041     const QStringList strList = str.split('\t');
0042     for (const QString &s : strList) {
0043         int key = QKeySequence(s)[0];
0044         if (key != -1) {     //sanity check just in case
0045             ret.append(key);
0046         }
0047     }
0048     return ret;
0049 }
0050 
0051 
0052 static QString stringFromKeys(const QList<int> &keys)
0053 {
0054     if (keys.isEmpty()) {
0055         return QStringLiteral("none");
0056     }
0057     QString ret;
0058     for (int key : keys) {
0059         ret.append(QKeySequence(key).toString());
0060         ret.append('\t');
0061     }
0062     ret.chop(1);
0063     return ret;
0064 }
0065 
0066 namespace KdeDGlobalAccel {
0067 
0068 Component::Component(
0069             const QString &uniqueName,
0070             const QString &friendlyName,
0071             GlobalShortcutsRegistry *registry)
0072     :   _uniqueName(uniqueName)
0073         ,_friendlyName(friendlyName)
0074         ,_registry(registry)
0075     {
0076     // Make sure we do no get uniquenames still containing the context
0077     Q_ASSERT(uniqueName.indexOf("|")==-1);
0078 
0079     // Register ourselve with the registry
0080     if (_registry)
0081         {
0082         _registry->addComponent(this);
0083         }
0084 
0085     QString DEFAULT=QStringLiteral("default");
0086     createGlobalShortcutContext(DEFAULT, QStringLiteral("Default Context"));
0087     _current = _contexts.value(DEFAULT);
0088     }
0089 
0090 
0091 Component::~Component()
0092     {
0093     // Remove ourselve from the registry
0094     if (_registry)
0095         {
0096         _registry->takeComponent(this);
0097         }
0098 
0099     // We delete all shortcuts from all contexts
0100     qDeleteAll(_contexts);
0101     }
0102 
0103 
0104 bool Component::activateGlobalShortcutContext(
0105         const QString &uniqueName)
0106     {
0107     if (!_contexts.value(uniqueName))
0108         {
0109         createGlobalShortcutContext(uniqueName, "TODO4");
0110         return false;
0111         }
0112 
0113     // Deactivate the current contexts shortcuts
0114     deactivateShortcuts();
0115 
0116     // Switch the context
0117     _current = _contexts.value(uniqueName);
0118 
0119     return true;
0120     }
0121 
0122 
0123 void Component::activateShortcuts()
0124     {
0125     for (GlobalShortcut *shortcut : qAsConst(_current->_actions))
0126         {
0127         shortcut->setActive();
0128         }
0129     }
0130 
0131 
0132 QList<GlobalShortcut*> Component::allShortcuts(const QString &contextName) const
0133     {
0134     GlobalShortcutContext *context = _contexts.value(contextName);
0135     if (context)
0136         {
0137         return context->_actions.values();
0138         }
0139     else
0140         {
0141         return QList<GlobalShortcut*> ();
0142         }
0143     }
0144 
0145 
0146 QList<KGlobalShortcutInfo> Component::allShortcutInfos(const QString &contextName) const
0147     {
0148     GlobalShortcutContext *context = _contexts.value(contextName);
0149     if (!context)
0150         {
0151         return QList<KGlobalShortcutInfo>();
0152         }
0153 
0154     return context->allShortcutInfos();
0155     }
0156 
0157 
0158 bool Component::cleanUp()
0159     {
0160     bool changed = false;
0161 
0162     const auto actions = _current->_actions;
0163     for (GlobalShortcut *shortcut : 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         const auto actions = shortcutContext(context)->_actions;
0334         for (GlobalShortcut *sc : actions)
0335             {
0336             if (sc->keys().contains(key)) return false;
0337             }
0338         }
0339     else
0340         {
0341         for (GlobalShortcutContext *ctx : qAsConst(_contexts))
0342             {
0343             for (GlobalShortcut *sc : qAsConst(ctx->_actions))
0344                 {
0345                 if (sc->keys().contains(key)) return false;
0346                 }
0347             }
0348         }
0349     return true;
0350     }
0351 
0352 GlobalShortcut *Component::registerShortcut(const QString &uniqueName, const QString &friendlyName, const QString &shortcutString, const QString &defaultShortcutString)
0353     {
0354     // The shortcut will register itself with us
0355     GlobalShortcut *shortcut = new GlobalShortcut(
0356             uniqueName,
0357             friendlyName,
0358             currentContext());
0359 
0360     const QList<int> keys = keysFromString(shortcutString);
0361     shortcut->setDefaultKeys(keysFromString(defaultShortcutString));
0362     shortcut->setIsFresh(false);
0363     QList<int> newKeys = keys;
0364     for (int key : keys)
0365         {
0366         if (key != 0)
0367             {
0368             if (GlobalShortcutsRegistry::self()->getShortcutByKey(key))
0369                 {
0370                 // The shortcut is already used. The config file is
0371                 // broken. Ignore the request.
0372                 newKeys.removeAll(key);
0373                 qCWarning(KGLOBALACCELD) << "Shortcut found twice in kglobalshortcutsrc."<<key;
0374                 }
0375             }
0376         }
0377     shortcut->setKeys(keys);
0378     return shortcut;
0379     }
0380 
0381 
0382 void Component::loadSettings(KConfigGroup &configGroup)
0383     {
0384     // GlobalShortcutsRegistry::loadSettings handles contexts.
0385     const auto listKeys = configGroup.keyList();
0386     for (const QString &confKey : listKeys)
0387         {
0388         const QStringList entry = configGroup.readEntry(confKey, QStringList());
0389         if (entry.size() != 3)
0390             {
0391             continue;
0392             }
0393 
0394         GlobalShortcut *shortcut = registerShortcut(confKey, entry[2], entry[0], entry[1]);
0395         if (configGroup.name().endsWith(QLatin1String(".desktop"))) {
0396             shortcut->setIsPresent(true);
0397         }
0398         }
0399     }
0400 
0401 
0402 void Component::setFriendlyName(const QString &name)
0403     {
0404     _friendlyName = name;
0405     }
0406 
0407 
0408 GlobalShortcutContext *Component::shortcutContext( const QString &contextName )
0409     {
0410     return _contexts.value(contextName);
0411     }
0412 
0413 
0414 GlobalShortcutContext const *Component::shortcutContext( const QString &contextName ) const
0415     {
0416     return _contexts.value(contextName);
0417     }
0418 
0419 
0420 QStringList Component::shortcutNames( const QString &contextName) const
0421     {
0422     GlobalShortcutContext *context = _contexts.value(contextName);
0423     if (!context)
0424         {
0425         return QStringList();
0426         }
0427 
0428     return context->_actions.keys();
0429     }
0430 
0431 
0432 QString Component::uniqueName() const
0433     {
0434     return _uniqueName;
0435     }
0436 
0437 
0438 void Component::unregisterShortcut(const QString &uniqueName)
0439     {
0440     // Now wrote all contexts
0441     for( GlobalShortcutContext *context : qAsConst(_contexts))
0442         {
0443         if (context->_actions.value(uniqueName))
0444             {
0445             delete context->takeShortcut(context->_actions.value(uniqueName));
0446             }
0447         }
0448     }
0449 
0450 
0451 void Component::writeSettings(KConfigGroup& configGroup) const
0452     {
0453     // If we don't delete the current content global shortcut
0454     // registrations will never not deleted after forgetGlobalShortcut()
0455     configGroup.deleteGroup();
0456 
0457 
0458     // Now write all contexts
0459     for( GlobalShortcutContext *context : qAsConst(_contexts))
0460         {
0461         KConfigGroup contextGroup;
0462 
0463         if (context->uniqueName() == QLatin1String("default"))
0464             {
0465             contextGroup = configGroup;
0466             // Write the friendly name
0467             contextGroup.writeEntry("_k_friendly_name", friendlyName());
0468             }
0469         else
0470             {
0471             contextGroup = KConfigGroup(&configGroup, context->uniqueName());
0472             // Write the friendly name
0473             contextGroup.writeEntry("_k_friendly_name", context->friendlyName());
0474             }
0475 
0476         // qCDebug(KGLOBALACCELD) << "writing group " << _uniqueName << ":" << context->uniqueName();
0477 
0478         for (const GlobalShortcut *shortcut : qAsConst(context->_actions))
0479             {
0480             // qCDebug(KGLOBALACCELD) << "writing" << shortcut->uniqueName();
0481 
0482             // We do not write fresh shortcuts.
0483             // We do not write session shortcuts
0484             if (shortcut->isFresh() || shortcut->isSessionShortcut())
0485                 {
0486                 continue;
0487                 }
0488             // qCDebug(KGLOBALACCELD) << "really writing" << shortcut->uniqueName();
0489 
0490             QStringList entry(stringFromKeys(shortcut->keys()));
0491             entry.append(stringFromKeys(shortcut->defaultKeys()));
0492             entry.append(shortcut->friendlyName());
0493 
0494             contextGroup.writeEntry(shortcut->uniqueName(), entry);
0495             }
0496         }
0497     }
0498 
0499 } // namespace KdeDGlobalAccel