/* SPDX-FileCopyrightText: 2008 Michael Jansen SPDX-License-Identifier: LGPL-2.0-or-later */ #include "component.h" #include "globalshortcutcontext.h" #include "globalshortcutsregistry.h" #include "kglobalaccel_interface.h" #include "logging_p.h" #include #include #include #if HAVE_X11 #include #endif QList Component::keysFromString(const QString &str) { QList ret; if (str == QLatin1String("none")) { return ret; } const QStringList strList = str.split(QLatin1Char('\t')); for (const QString &s : strList) { QKeySequence key = QKeySequence::fromString(s, QKeySequence::PortableText); if (!key.isEmpty()) { // sanity check just in case ret.append(key); } } return ret; } QString Component::stringFromKeys(const QList &keys) { if (keys.isEmpty()) { return QStringLiteral("none"); } QString ret; for (const QKeySequence &key : keys) { ret.append(key.toString(QKeySequence::PortableText)); ret.append(QLatin1Char('\t')); } ret.chop(1); return ret; } Component::Component(const QString &uniqueName, const QString &friendlyName) : _uniqueName(uniqueName) , _friendlyName(friendlyName) , _registry(GlobalShortcutsRegistry::self()) { // Make sure we do no get uniquenames still containing the context Q_ASSERT(uniqueName.indexOf(QLatin1Char('|')) == -1); const QString DEFAULT(QStringLiteral("default")); createGlobalShortcutContext(DEFAULT, QStringLiteral("Default Context")); _current = _contexts.value(DEFAULT); } Component::~Component() { // We delete all shortcuts from all contexts qDeleteAll(_contexts); } bool Component::activateGlobalShortcutContext(const QString &uniqueName) { if (!_contexts.value(uniqueName)) { createGlobalShortcutContext(uniqueName, QStringLiteral("TODO4")); return false; } // Deactivate the current contexts shortcuts deactivateShortcuts(); // Switch the context _current = _contexts.value(uniqueName); return true; } void Component::activateShortcuts() { for (GlobalShortcut *shortcut : std::as_const(_current->_actionsMap)) { shortcut->setActive(); } } QList Component::allShortcuts(const QString &contextName) const { GlobalShortcutContext *context = _contexts.value(contextName); return context ? context->_actionsMap.values() : QList{}; } QList Component::allShortcutInfos(const QString &contextName) const { GlobalShortcutContext *context = _contexts.value(contextName); return context ? context->allShortcutInfos() : QList{}; } bool Component::cleanUp() { bool changed = false; const auto actions = _current->_actionsMap; for (GlobalShortcut *shortcut : actions) { qCDebug(KGLOBALACCELD) << _current->_actionsMap.size(); if (!shortcut->isPresent()) { changed = true; shortcut->unRegister(); } } if (changed) { _registry->writeSettings(); // We could be destroyed after this call! } return changed; } bool Component::createGlobalShortcutContext(const QString &uniqueName, const QString &friendlyName) { if (_contexts.value(uniqueName)) { qCDebug(KGLOBALACCELD) << "Shortcut Context " << uniqueName << "already exists for component " << _uniqueName; return false; } _contexts.insert(uniqueName, new GlobalShortcutContext(uniqueName, friendlyName, this)); return true; } GlobalShortcutContext *Component::currentContext() { return _current; } QDBusObjectPath Component::dbusPath() const { auto isNonAscii = [](QChar ch) { const char c = ch.unicode(); const bool isAscii = c == '_' // || (c >= 'A' && c <= 'Z') // || (c >= 'a' && c <= 'z') // || (c >= '0' && c <= '9'); return !isAscii; }; QString dbusPath = _uniqueName; // DBus path can only contain ASCII characters, any non-alphanumeric char should // be turned into '_' std::replace_if(dbusPath.begin(), dbusPath.end(), isNonAscii, QLatin1Char('_')); // QDBusObjectPath could be a little bit easier to handle :-) return QDBusObjectPath(_registry->dbusPath().path() + QLatin1String("component/") + dbusPath); } void Component::deactivateShortcuts(bool temporarily) { for (GlobalShortcut *shortcut : std::as_const(_current->_actionsMap)) { if (temporarily // && _uniqueName == QLatin1String("kwin") // && shortcut->uniqueName() == QLatin1String("Block Global Shortcuts")) { continue; } shortcut->setInactive(); } } void Component::emitGlobalShortcutPressed(const GlobalShortcut &shortcut) { #if HAVE_X11 // pass X11 timestamp const long timestamp = QX11Info::appTime(); #else const long timestamp = 0; #endif if (shortcut.context()->component() != this) { return; } Q_EMIT globalShortcutPressed(shortcut.context()->component()->uniqueName(), shortcut.uniqueName(), timestamp); } void Component::emitGlobalShortcutReleased(const GlobalShortcut &shortcut) { #if HAVE_X11 // pass X11 timestamp const long timestamp = QX11Info::appTime(); #else const long timestamp = 0; #endif if (shortcut.context()->component() != this) { return; } Q_EMIT globalShortcutReleased(shortcut.context()->component()->uniqueName(), shortcut.uniqueName(), timestamp); } void Component::invokeShortcut(const QString &shortcutName, const QString &context) { GlobalShortcut *shortcut = getShortcutByName(shortcutName, context); if (shortcut) { emitGlobalShortcutPressed(*shortcut); } } QString Component::friendlyName() const { return !_friendlyName.isEmpty() ? _friendlyName : _uniqueName; } GlobalShortcut *Component::getShortcutByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const { return _current->getShortcutByKey(key, type); } QList Component::getShortcutsByKey(const QKeySequence &key, KGlobalAccel::MatchType type) const { QList rc; for (GlobalShortcutContext *context : std::as_const(_contexts)) { GlobalShortcut *sc = context->getShortcutByKey(key, type); if (sc) { rc.append(sc); } } return rc; } GlobalShortcut *Component::getShortcutByName(const QString &uniqueName, const QString &context) const { const GlobalShortcutContext *shortcutContext = _contexts.value(context); return shortcutContext ? shortcutContext->_actionsMap.value(uniqueName) : nullptr; } QStringList Component::getShortcutContexts() const { return _contexts.keys(); } bool Component::isActive() const { // The component is active if at least one of it's global shortcuts is // present. for (GlobalShortcut *shortcut : std::as_const(_current->_actionsMap)) { if (shortcut->isPresent()) { return true; } } return false; } bool Component::isShortcutAvailable(const QKeySequence &key, const QString &component, const QString &context) const { qCDebug(KGLOBALACCELD) << key.toString() << component; // if this component asks for the key. only check the keys in the same // context if (component == uniqueName()) { return shortcutContext(context)->isShortcutAvailable(key); } else { for (auto it = _contexts.cbegin(), endIt = _contexts.cend(); it != endIt; ++it) { const GlobalShortcutContext *ctx = it.value(); if (!ctx->isShortcutAvailable(key)) { return false; } } } return true; } GlobalShortcut * Component::registerShortcut(const QString &uniqueName, const QString &friendlyName, const QString &shortcutString, const QString &defaultShortcutString) { // The shortcut will register itself with us GlobalShortcut *shortcut = new GlobalShortcut(uniqueName, friendlyName, currentContext()); const QList keys = keysFromString(shortcutString); shortcut->setDefaultKeys(keysFromString(defaultShortcutString)); shortcut->setIsFresh(false); QList newKeys = keys; for (const QKeySequence &key : keys) { if (!key.isEmpty()) { if (_registry->getShortcutByKey(key)) { // The shortcut is already used. The config file is // broken. Ignore the request. newKeys.removeAll(key); qCWarning(KGLOBALACCELD) << "Shortcut found twice in kglobalshortcutsrc." << key; } } } shortcut->setKeys(keys); return shortcut; } void Component::loadSettings(KConfigGroup &configGroup) { // GlobalShortcutsRegistry::loadSettings handles contexts. const auto listKeys = configGroup.keyList(); for (const QString &confKey : listKeys) { const QStringList entry = configGroup.readEntry(confKey, QStringList()); if (entry.size() != 3) { continue; } registerShortcut(confKey, entry[2], entry[0], entry[1]); } } void Component::setFriendlyName(const QString &name) { _friendlyName = name; } GlobalShortcutContext *Component::shortcutContext(const QString &contextName) { return _contexts.value(contextName); } GlobalShortcutContext const *Component::shortcutContext(const QString &contextName) const { return _contexts.value(contextName); } QStringList Component::shortcutNames(const QString &contextName) const { const GlobalShortcutContext *context = _contexts.value(contextName); return context ? context->_actionsMap.keys() : QStringList{}; } QString Component::uniqueName() const { return _uniqueName; } void Component::unregisterShortcut(const QString &uniqueName) { // Now wrote all contexts for (GlobalShortcutContext *context : std::as_const(_contexts)) { if (context->_actionsMap.value(uniqueName)) { delete context->takeShortcut(context->_actionsMap.value(uniqueName)); } } } void Component::writeSettings(KConfigGroup &configGroup) const { // If we don't delete the current content global shortcut // registrations will never not deleted after forgetGlobalShortcut() configGroup.deleteGroup(); // Now write all contexts for (GlobalShortcutContext *context : std::as_const(_contexts)) { KConfigGroup contextGroup; if (context->uniqueName() == QLatin1String("default")) { contextGroup = configGroup; // Write the friendly name contextGroup.writeEntry("_k_friendly_name", friendlyName()); } else { contextGroup = KConfigGroup(&configGroup, context->uniqueName()); // Write the friendly name contextGroup.writeEntry("_k_friendly_name", context->friendlyName()); } // qCDebug(KGLOBALACCELD) << "writing group " << _uniqueName << ":" << context->uniqueName(); for (const GlobalShortcut *shortcut : std::as_const(context->_actionsMap)) { // qCDebug(KGLOBALACCELD) << "writing" << shortcut->uniqueName(); // We do not write fresh shortcuts. // We do not write session shortcuts if (shortcut->isFresh() || shortcut->isSessionShortcut()) { continue; } // qCDebug(KGLOBALACCELD) << "really writing" << shortcut->uniqueName(); QStringList entry(stringFromKeys(shortcut->keys())); entry.append(stringFromKeys(shortcut->defaultKeys())); entry.append(shortcut->friendlyName()); contextGroup.writeEntry(shortcut->uniqueName(), entry); } } }