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