/* SPDX-FileCopyrightText: 2025 Fushan Wen SPDX-License-Identifier: GPL-2.0-or-later */ #include "keysrunner.h" #include #include #include #include #include #include #include "filteredmodel.h" #include "globalaccelmodel.h" using namespace Qt::StringLiterals; // Don't show actions that are handled by other runners (e.g. Desktop Sessions / Power) static const QMap ignoredComponentActions = { { u"org_kde_powerdevil"_s /* "Power Managment" */, { u"Hibernate"_s, u"PowerDown"_s, u"PowerOff"_s, u"Sleep"_s }}, { u"ksmserver"_s /* Session Management */, { u"Lock Session"_s, u"LogOut"_s, u"Log Out Without Confirmation"_s, u"Reboot"_s, u"Reboot Without Confirmation"_s, u"Shut Down"_s, u"Halt Without Confirmation"_s }} }; KeysRunner::KeysRunner(QObject *parent, const KPluginMetaData &metaData) : KRunner::AbstractRunner(parent, metaData) , m_isKRunner(KLocalizedString::applicationDomain() == QByteArrayView("krunner")) , m_isPlasmashell(KLocalizedString::applicationDomain() == QByteArrayView("plasmashell")) { addSyntax(u":q:"_s, i18nc("@info:usagetip", "Finds shortcuts with names or descriptions matching :q: or gets the name of a shortcut")); } void KeysRunner::init() { m_globalAccelModel = new GlobalAccelModel(this); m_filteredModel = new FilteredShortcutsModel(this); m_filteredModel->setShowsLaunchAction(false); m_filteredModel->setSourceModel(m_globalAccelModel); if (m_globalAccelModel->isValid()) { m_globalAccelModel->load(); connect( m_globalAccelModel, &QAbstractItemModel::modelReset, this, [this] { qDBusRegisterMetaType(); qDBusRegisterMetaType>(); QDBusConnection::sessionBus().connect(u"org.kde.kglobalaccel"_s, u"/kglobalaccel"_s, u"org.kde.KGlobalAccel"_s, u"yourShortcutsChanged"_s, this, SLOT(onShortcutsChanged(QStringList, QList))); m_needsUpdate = false; }, Qt::SingleShotConnection); } connect(this, &KRunner::AbstractRunner::prepare, this, &KeysRunner::onPrepare); } void KeysRunner::match(KRunner::RunnerContext &context) { m_filteredModel->setFilter(context.query()); for (int i = 0, count = m_filteredModel->rowCount(); i < count; ++i) { const QModelIndex componentIdx = m_filteredModel->index(i, 0); Q_ASSERT(componentIdx.isValid() && !componentIdx.parent().isValid()); const QString componentUniqueName = componentIdx.data(BaseModel::ComponentRole).toString(); if (m_isKRunner && componentUniqueName == u"org.kde.krunner.desktop") { continue; } else if (m_isPlasmashell && componentUniqueName == u"plasmashell") { continue; } const QString componentFriendlyName = componentIdx.data(Qt::DisplayRole).toString(); const QString iconName = componentIdx.data(Qt::DecorationRole).toString(); for (int j = 0, childCount = m_filteredModel->rowCount(componentIdx); j < childCount; ++j) { const QModelIndex actionIdx = m_filteredModel->index(j, 0, componentIdx); const QString actionName = actionIdx.data(Qt::DisplayRole).toString(); const QString action = actionIdx.data(BaseModel::ActionRole).toString(); if (actionName.isEmpty()) { continue; } auto it = ignoredComponentActions.constFind(componentUniqueName); if (it != ignoredComponentActions.cend() && it->contains(action)) { continue; }; KRunner::QueryMatch match(this); match.setText(i18nc("@label action - app", "%1 - %2", actionName, componentFriendlyName)); auto shortcuts = actionIdx.data(BaseModel::ActiveShortcutsRole).value>(); if (shortcuts.empty()) { shortcuts = actionIdx.data(BaseModel::DefaultShortcutsRole).value>(); } if (shortcuts.empty()) { shortcuts = actionIdx.data(BaseModel::CustomShortcutsRole).value>(); } if (!shortcuts.empty()) { QString subText = shortcuts.cbegin()->toString(QKeySequence::NativeText); subText.reserve(shortcuts.size() * 5); for (auto it = std::next(shortcuts.cbegin()); it != shortcuts.cend(); it = std::next(it)) { subText += u", " + it->toString(QKeySequence::NativeText); } match.setSubtext(subText); } match.setIconName(iconName); match.setData(QStringList{componentUniqueName, action}); context.addMatch(match); } } } void KeysRunner::run(const KRunner::RunnerContext & /*context*/, const KRunner::QueryMatch &match) { const QStringList data = match.data().toStringList(); Q_ASSERT(data.length() == 2); QString dbusPath = data[0]; // DBus path can only contain ASCII characters, any non-alphanumeric char should // be turned into '_' 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; }; std::replace_if(dbusPath.begin(), dbusPath.end(), isNonAscii, QLatin1Char('_')); QDBusMessage msg = QDBusMessage::createMethodCall(u"org.kde.kglobalaccel"_s, u"/component/" + dbusPath, u"org.kde.kglobalaccel.Component"_s, u"invokeShortcut"_s); msg << data[1]; QDBusConnection::sessionBus().call(msg, QDBus::NoBlock); } void KeysRunner::onShortcutsChanged(const QStringList &, const QList &) { m_needsUpdate = true; } void KeysRunner::onPrepare() { if (!m_needsUpdate) { return; } connect( m_globalAccelModel, &QAbstractItemModel::modelReset, this, [this] { m_needsUpdate = false; }, Qt::SingleShotConnection); m_globalAccelModel->load(); } K_PLUGIN_CLASS_WITH_JSON(KeysRunner, "plasma-runner-keys.json") #include "keysrunner.moc" #include "moc_keysrunner.cpp"