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 }