File indexing completed on 2022-04-28 14:38:41

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