SPDX-FileCopyrightText: 2025 Fushan Wen <qydwhotmail@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "keysrunner.h"
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusMetaType>
#include <algorithm>
#include <KGlobalShortcutInfo>
#include <KLocalizedString>
#include "filteredmodel.h"
#include "globalaccelmodel.h"
using namespace Qt::StringLiterals;
static const QMap<QString, QStringList> ignoredComponentActions = {
{ u"org_kde_powerdevil"_s , {
u"Hibernate"_s,
u"PowerDown"_s,
u"PowerOff"_s,
u"Sleep"_s
}},
{ u"ksmserver"_s , {
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<QKeySequence>();
qDBusRegisterMetaType<QList<QKeySequence>>();
QDBusConnection::sessionBus().connect(u"org.kde.kglobalaccel"_s,
u"/kglobalaccel"_s,
u"org.kde.KGlobalAccel"_s,
u"yourShortcutsChanged"_s,
this,
SLOT(onShortcutsChanged(QStringList, QList<QKeySequence>)));
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<QSet<QKeySequence>>();
if (shortcuts.empty()) {
shortcuts = actionIdx.data(BaseModel::DefaultShortcutsRole).value<QSet<QKeySequence>>();
}
if (shortcuts.empty()) {
shortcuts = actionIdx.data(BaseModel::CustomShortcutsRole).value<QSet<QKeySequence>>();
}
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 & , const KRunner::QueryMatch &match)
{
const QStringList data = match.data().toStringList();
Q_ASSERT(data.length() == 2);
QString dbusPath = data[0];
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<QKeySequence> &)
{
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"