File indexing completed on 2024-05-12 05:38:19

0001 /*
0002     SPDX-FileCopyrightText: 2009 Jan Gerrit Marker <jangerrit@weiler-marker.com>
0003     SPDX-FileCopyrightText: 2020 Alexander Lohnau <alexander.lohnau@gmx.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #include "killrunner.h"
0009 
0010 #include <QAction>
0011 #include <QDebug>
0012 #include <QIcon>
0013 
0014 #include <KAuth/Action>
0015 #include <KConfigGroup>
0016 #include <KLocalizedString>
0017 #include <KProcess>
0018 
0019 #include <processcore/process.h>
0020 #include <processcore/processes.h>
0021 
0022 K_PLUGIN_CLASS_WITH_JSON(KillRunner, "plasma-runner-kill.json")
0023 
0024 KillRunner::KillRunner(QObject *parent, const KPluginMetaData &metaData)
0025     : KRunner::AbstractRunner(parent, metaData)
0026     , m_actionList({
0027           KRunner::Action(QString::number(15), QStringLiteral("application-exit"), i18n("Send SIGTERM")),
0028           KRunner::Action(QString::number(9), QStringLiteral("process-stop"), i18n("Send SIGKILL")),
0029       })
0030     , m_processes(new KSysGuard::Processes(QString(), this))
0031 {
0032     connect(this, &KRunner::AbstractRunner::prepare, m_processes, [this]() {
0033         m_needsRefresh = true;
0034     });
0035 }
0036 
0037 void KillRunner::reloadConfiguration()
0038 {
0039     KConfigGroup grp = config();
0040     m_triggerWord.clear();
0041     if (grp.readEntry(CONFIG_USE_TRIGGERWORD, true)) {
0042         m_triggerWord = grp.readEntry(CONFIG_TRIGGERWORD, i18n("kill")) + QLatin1Char(' ');
0043     }
0044     m_hasTrigger = !m_triggerWord.isEmpty();
0045 
0046     m_sorting = (Sort)grp.readEntry(CONFIG_SORTING, static_cast<int>(Sort::NONE));
0047     QList<KRunner::RunnerSyntax> syntaxes;
0048     syntaxes << KRunner::RunnerSyntax(m_triggerWord + QStringLiteral(":q:"), i18n("Terminate running applications whose names match the query."));
0049     setSyntaxes(syntaxes);
0050     if (m_hasTrigger) {
0051         setTriggerWords({m_triggerWord});
0052         setMinLetterCount(minLetterCount() + 2); // Requires two characters after trigger word
0053     } else {
0054         setMinLetterCount(2);
0055         setMatchRegex(QRegularExpression());
0056     }
0057 }
0058 
0059 void KillRunner::match(KRunner::RunnerContext &context)
0060 {
0061     // Only refresh the matches when we are matching. If we were to call it in the prepare slot, we would waste resources
0062     // because very likely the runner will not be used during the current match session
0063     if (m_needsRefresh) {
0064         m_processes->updateAllProcesses();
0065         if (!context.isValid()) {
0066             return;
0067         }
0068     }
0069     QString term = context.query();
0070     term = term.right(term.length() - m_triggerWord.length());
0071 
0072     QList<KRunner::QueryMatch> matches;
0073     const QList<KSysGuard::Process *> processlist = m_processes->getAllProcesses();
0074     for (const KSysGuard::Process *process : processlist) {
0075         if (!context.isValid()) {
0076             return;
0077         }
0078         const QString name = process->name();
0079         if (!name.contains(term, Qt::CaseInsensitive)) {
0080             continue;
0081         }
0082 
0083         const quint64 pid = process->pid();
0084         KRunner::QueryMatch match(this);
0085         match.setText(i18n("Terminate %1", name));
0086         match.setSubtext(i18n("Process ID: %1", QString::number(pid)));
0087         match.setIconName(QStringLiteral("application-exit"));
0088         match.setData(pid);
0089         match.setId(name);
0090         match.setActions(m_actionList);
0091 
0092         // Set the relevance
0093         switch (m_sorting) {
0094         case Sort::CPU:
0095             match.setRelevance((process->userUsage() + process->sysUsage()) / 100.0);
0096             break;
0097         case Sort::CPUI:
0098             match.setRelevance(1.0 - (process->userUsage() + process->sysUsage()) / 100.0);
0099             break;
0100         case Sort::NONE:
0101             match.setRelevance(name.compare(term, Qt::CaseInsensitive) == 0 ? 1 : 9);
0102             break;
0103         }
0104 
0105         matches << match;
0106     }
0107 
0108     context.addMatches(matches);
0109 }
0110 
0111 void KillRunner::run(const KRunner::RunnerContext & /*context*/, const KRunner::QueryMatch &match)
0112 {
0113     const quint64 pid = match.data().toUInt();
0114     const int signal = match.selectedAction() ? match.selectedAction().id().toInt() : 9; // default: SIGKILL
0115 
0116     int returnCode = KProcess::execute(QStringLiteral("kill"), {QStringLiteral("-%1").arg(signal), QString::number(pid)});
0117     if (returnCode == 0) {
0118         return;
0119     }
0120 
0121     KAuth::Action killAction = QStringLiteral("org.kde.ksysguard.processlisthelper.sendsignal");
0122     killAction.setHelperId(QStringLiteral("org.kde.ksysguard.processlisthelper"));
0123     killAction.addArgument(QStringLiteral("pid0"), pid);
0124     killAction.addArgument(QStringLiteral("pidcount"), 1);
0125     killAction.addArgument(QStringLiteral("signal"), signal);
0126     killAction.execute();
0127 }
0128 
0129 #include "killrunner.moc"