File indexing completed on 2024-05-05 17:45:01

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, const QVariantList &args)
0025     : Plasma::AbstractRunner(parent, metaData, args)
0026     , m_processes(nullptr)
0027 {
0028     setObjectName(QStringLiteral("Kill Runner"));
0029 
0030     auto *sigterm = new QAction(QIcon::fromTheme(QStringLiteral("application-exit")), i18n("Send SIGTERM"), this);
0031     sigterm->setData(15);
0032     auto *sigkill = new QAction(QIcon::fromTheme(QStringLiteral("process-stop")), i18n("Send SIGKILL"), this);
0033     sigkill->setData(9);
0034     m_actionList = {sigterm, sigkill};
0035 
0036     connect(this, &Plasma::AbstractRunner::prepare, this, &KillRunner::prep);
0037     connect(this, &Plasma::AbstractRunner::teardown, this, &KillRunner::cleanup);
0038 
0039     m_delayedCleanupTimer.setInterval(50);
0040     m_delayedCleanupTimer.setSingleShot(true);
0041     connect(&m_delayedCleanupTimer, &QTimer::timeout, this, &KillRunner::cleanup);
0042 }
0043 
0044 KillRunner::~KillRunner() = default;
0045 
0046 void KillRunner::reloadConfiguration()
0047 {
0048     KConfigGroup grp = config();
0049     m_triggerWord.clear();
0050     if (grp.readEntry(CONFIG_USE_TRIGGERWORD, true)) {
0051         m_triggerWord = grp.readEntry(CONFIG_TRIGGERWORD, i18n("kill")) + QLatin1Char(' ');
0052     }
0053     m_hasTrigger = !m_triggerWord.isEmpty();
0054 
0055     m_sorting = (Sort)grp.readEntry(CONFIG_SORTING, static_cast<int>(Sort::NONE));
0056     QList<Plasma::RunnerSyntax> syntaxes;
0057     syntaxes << Plasma::RunnerSyntax(m_triggerWord + QStringLiteral(":q:"), i18n("Terminate running applications whose names match the query."));
0058     setSyntaxes(syntaxes);
0059     if (m_hasTrigger) {
0060         setTriggerWords({m_triggerWord});
0061         setMinLetterCount(minLetterCount() + 2); // Requires two characters after trigger word
0062     } else {
0063         setMinLetterCount(2);
0064         setMatchRegex(QRegularExpression());
0065     }
0066 }
0067 
0068 void KillRunner::prep()
0069 {
0070     m_delayedCleanupTimer.stop();
0071 }
0072 
0073 void KillRunner::cleanup()
0074 {
0075     if (!m_processes) {
0076         return;
0077     }
0078 
0079     if (m_prepLock.tryLockForWrite()) {
0080         delete m_processes;
0081         m_processes = nullptr;
0082 
0083         m_prepLock.unlock();
0084     } else {
0085         m_delayedCleanupTimer.stop();
0086     }
0087 }
0088 
0089 void KillRunner::match(Plasma::RunnerContext &context)
0090 {
0091     QString term = context.query();
0092     m_prepLock.lockForRead();
0093     if (!m_processes) {
0094         m_prepLock.unlock();
0095         m_prepLock.lockForWrite();
0096         if (!m_processes) {
0097             suspendMatching(true);
0098             m_processes = new KSysGuard::Processes();
0099             m_processes->updateAllProcesses();
0100             suspendMatching(false);
0101         }
0102     }
0103     m_prepLock.unlock();
0104 
0105     term = term.right(term.length() - m_triggerWord.length());
0106 
0107     QList<Plasma::QueryMatch> matches;
0108     const QList<KSysGuard::Process *> processlist = m_processes->getAllProcesses();
0109     for (const KSysGuard::Process *process : processlist) {
0110         if (!context.isValid()) {
0111             return;
0112         }
0113         const QString name = process->name();
0114         if (!name.contains(term, Qt::CaseInsensitive)) {
0115             continue;
0116         }
0117 
0118         const quint64 pid = process->pid();
0119         Plasma::QueryMatch match(this);
0120         match.setText(i18n("Terminate %1", name));
0121         match.setSubtext(i18n("Process ID: %1", QString::number(pid)));
0122         match.setIconName(QStringLiteral("application-exit"));
0123         match.setData(pid);
0124         match.setId(name);
0125         match.setActions(m_actionList);
0126 
0127         // Set the relevance
0128         switch (m_sorting) {
0129         case Sort::CPU:
0130             match.setRelevance((process->userUsage() + process->sysUsage()) / 100);
0131             break;
0132         case Sort::CPUI:
0133             match.setRelevance(1 - (process->userUsage() + process->sysUsage()) / 100);
0134             break;
0135         case Sort::NONE:
0136             match.setRelevance(name.compare(term, Qt::CaseInsensitive) == 0 ? 1 : 9);
0137             break;
0138         }
0139 
0140         matches << match;
0141     }
0142 
0143     context.addMatches(matches);
0144 }
0145 
0146 void KillRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match)
0147 {
0148     Q_UNUSED(context)
0149 
0150     const quint64 pid = match.data().toUInt();
0151 
0152     int signal;
0153     if (match.selectedAction()) {
0154         signal = match.selectedAction()->data().toInt();
0155     } else {
0156         signal = 9; // default: SIGKILL
0157     }
0158 
0159     const QStringList args = {QStringLiteral("-%1").arg(signal), QString::number(pid)};
0160     int returnCode = KProcess::execute(QStringLiteral("kill"), args);
0161     if (returnCode == 0) {
0162         return;
0163     }
0164 
0165     KAuth::Action killAction = QStringLiteral("org.kde.ksysguard.processlisthelper.sendsignal");
0166     killAction.setHelperId(QStringLiteral("org.kde.ksysguard.processlisthelper"));
0167     killAction.addArgument(QStringLiteral("pid0"), pid);
0168     killAction.addArgument(QStringLiteral("pidcount"), 1);
0169     killAction.addArgument(QStringLiteral("signal"), signal);
0170     killAction.execute();
0171 }
0172 
0173 #include "killrunner.moc"