File indexing completed on 2024-05-19 05:29:56

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