File indexing completed on 2024-04-28 05:31:41

0001 /*
0002     KSysGuard, the KDE System Guard
0003 
0004     SPDX-FileCopyrightText: 1999-2001 Chris Schlaeger <cs@kde.org>
0005     SPDX-FileCopyrightText: 2006-2007 John Tapsell <john.tapsell@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 
0009 */
0010 
0011 #include "ksysguardprocesslist.h"
0012 
0013 #include "../config-ksysguard.h"
0014 
0015 #include <QAbstractItemModel>
0016 #include <QAction>
0017 #include <QActionGroup>
0018 #include <QApplication>
0019 #include <QComboBox>
0020 #include <QDBusMetaType>
0021 #include <QDialog>
0022 #include <QHeaderView>
0023 #include <QHideEvent>
0024 #include <QIcon>
0025 #include <QLineEdit>
0026 #include <QList>
0027 #include <QMenu>
0028 #include <QPainter>
0029 #include <QPainterPath>
0030 #include <QPointer>
0031 #include <QSet>
0032 #include <QShowEvent>
0033 #include <QStyle>
0034 #include <QStyledItemDelegate>
0035 #include <QTimer>
0036 #include <QToolTip>
0037 #include <QtDBus>
0038 
0039 #include <signal.h> //For SIGTERM
0040 
0041 #include <KAuth/Action>
0042 #include <KDialogJobUiDelegate>
0043 #include <KGlobalAccel>
0044 #include <KIO/ApplicationLauncherJob>
0045 #include <KService>
0046 #include <KWindowSystem>
0047 #include <KX11Extras>
0048 #include <klocalizedstring.h>
0049 #include <kmessagebox.h>
0050 
0051 #include "processdetails/ProcessDetailsDialog.h"
0052 #include "ReniceDlg.h"
0053 #include "process_attribute.h"
0054 #include "process_controller.h"
0055 #include "scripting.h"
0056 #include "ui_ProcessWidgetUI.h"
0057 
0058 #include <sys/types.h>
0059 #include <unistd.h>
0060 
0061 // Trolltech have a testing class for classes that inherit QAbstractItemModel.  If you want to run with this run-time testing enabled, put the modeltest.* files
0062 // in this directory and uncomment the next line #define DO_MODELCHECK
0063 #ifdef DO_MODELCHECK
0064 #include "modeltest.h"
0065 #endif
0066 class ProgressBarItemDelegate : public QStyledItemDelegate
0067 {
0068 public:
0069     ProgressBarItemDelegate(QObject *parent)
0070         : QStyledItemDelegate(parent)
0071     {
0072     }
0073 
0074     void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const override
0075     {
0076         QStyleOptionViewItem option = opt;
0077         initStyleOption(&option, index);
0078 
0079         float percentage = index.data(ProcessModel::PercentageRole).toFloat();
0080         auto history = index.data(ProcessModel::PercentageHistoryRole).value<QList<ProcessModel::PercentageHistoryEntry>>();
0081         if (percentage >= 0 || history.size() > 1) {
0082             drawPercentageDisplay(painter, option, percentage, history);
0083         } else {
0084             QStyledItemDelegate::paint(painter, option, index);
0085         }
0086     }
0087 
0088 private:
0089     inline void
0090     drawPercentageDisplay(QPainter *painter, QStyleOptionViewItem &option, float percentage, const QList<ProcessModel::PercentageHistoryEntry> &history) const
0091     {
0092         QStyle *style = option.widget ? option.widget->style() : QApplication::style();
0093         const QRect &rect = option.rect;
0094 
0095         const int HIST_MS_PER_PX = 100; // 100 ms = 1 px -> 1 s = 10 px
0096         bool hasHistory = history.size() > 1;
0097         // Make sure that more than one entry is visible
0098         if (hasHistory) {
0099             int width = (history.crbegin()->timestamp - (history.crbegin() + 1)->timestamp) / HIST_MS_PER_PX;
0100             hasHistory = width < rect.width();
0101         }
0102 
0103         // draw the background
0104         style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, option.widget);
0105 
0106         QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled;
0107         if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) {
0108             cg = QPalette::Inactive;
0109         }
0110 
0111         // Now draw our percentage thingy
0112         int size = qMin(int(percentage * rect.height()), rect.height());
0113         if (size > 2) { // make sure the line will have a width of more than 1 pixel
0114             painter->setPen(Qt::NoPen);
0115             QColor color = option.palette.color(cg, QPalette::Link);
0116             color.setAlpha(33);
0117 
0118             painter->fillRect(rect.x(), rect.y() + rect.height() - size, rect.width(), size, color);
0119         }
0120 
0121         // Draw the history graph
0122         if (hasHistory) {
0123             QColor color = option.palette.color(cg, QPalette::Link);
0124             color.setAlpha(66);
0125             painter->setPen(Qt::NoPen);
0126 
0127             QPainterPath path;
0128             // From right to left
0129             path.moveTo(rect.right(), rect.bottom());
0130 
0131             int xNow = rect.right();
0132             auto now = history.constLast();
0133             int height = qMin(int(rect.height() * now.value), rect.height());
0134             path.lineTo(xNow, rect.bottom() - height);
0135 
0136             for (int index = history.size() - 2; index >= 0 && xNow > rect.left(); --index) {
0137                 auto next = history.at(index);
0138                 int width = (now.timestamp - next.timestamp) / HIST_MS_PER_PX;
0139                 int xNext = qMax(xNow - width, rect.left());
0140 
0141                 now = next;
0142                 xNow = xNext;
0143                 int height = qMin(int(rect.height() * now.value), rect.height());
0144 
0145                 path.lineTo(xNow, rect.bottom() - height);
0146             }
0147 
0148             path.lineTo(xNow, rect.bottom());
0149             path.lineTo(rect.right(), rect.bottom());
0150 
0151             painter->fillPath(path, color);
0152         }
0153 
0154         // draw the text
0155         if (!option.text.isEmpty()) {
0156             QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &option, option.widget);
0157 
0158             if (option.state & QStyle::State_Selected) {
0159                 painter->setPen(option.palette.color(cg, QPalette::HighlightedText));
0160             } else {
0161                 painter->setPen(option.palette.color(cg, QPalette::Text));
0162             }
0163 
0164             painter->setFont(option.font);
0165             QTextOption textOption;
0166             textOption.setWrapMode(QTextOption::ManualWrap);
0167             textOption.setTextDirection(option.direction);
0168             textOption.setAlignment(QStyle::visualAlignment(option.direction, option.displayAlignment));
0169 
0170             painter->drawText(textRect, option.text, textOption);
0171         }
0172 
0173         // draw the focus rect
0174         if (option.state & QStyle::State_HasFocus) {
0175             QStyleOptionFocusRect o;
0176             o.QStyleOption::operator=(option);
0177             o.rect = style->subElementRect(QStyle::SE_ItemViewItemFocusRect, &option, option.widget);
0178             o.state |= QStyle::State_KeyboardFocusChange;
0179             o.state |= QStyle::State_Item;
0180             QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;
0181             o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected) ? QPalette::Highlight : QPalette::Window);
0182             style->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, option.widget);
0183         }
0184     }
0185 };
0186 
0187 struct KSysGuardProcessListPrivate {
0188     KSysGuardProcessListPrivate(KSysGuardProcessList *q, const QString &hostName)
0189         : mModel(q, hostName)
0190         , mFilterModel(q)
0191         , mUi(new Ui::ProcessWidget())
0192         , mProcessContextMenu(nullptr)
0193         , mUpdateTimer(nullptr)
0194         , mToolsMenu(new QMenu(q))
0195     {
0196         mScripting = nullptr;
0197         mNeedToExpandInit = false;
0198         mNumItemsSelected = -1;
0199         // The items added initially will be already sorted, but without CPU info.  On the second refresh we will have CPU usage, so /then/ we can resort
0200         mResortCountDown = 2;
0201         renice = new QAction(i18np("Set Priority...", "Set Priority...", 1), q);
0202         renice->setShortcut(Qt::Key_F8);
0203         selectParent = new QAction(i18n("Jump to Parent Process"), q);
0204 
0205         selectTracer = new QAction(i18n("Jump to Process Debugging This One"), q);
0206         window = new QAction(i18n("Show Application Window"), q);
0207         processDetails = new QAction(i18nc("@action:inmenu", "Detailed Information..."), q);
0208         resume = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-start")), i18n("Resume Stopped Process"), q);
0209         terminate = new QAction(i18np("End Process", "End Processes", 1), q);
0210         terminate->setIcon(QIcon::fromTheme(QStringLiteral("process-stop")));
0211         terminate->setShortcut(Qt::Key_Delete);
0212         kill = new QAction(i18np("Forcibly Kill Process", "Forcibly Kill Processes", 1), q);
0213         kill->setIcon(QIcon::fromTheme(QStringLiteral("process-stop")));
0214         kill->setShortcut(Qt::SHIFT | Qt::Key_Delete);
0215 
0216         sigStop = new QAction(i18n("Suspend (STOP)"), q);
0217         sigCont = new QAction(i18n("Continue (CONT)"), q);
0218         sigHup = new QAction(i18n("Hangup (HUP)"), q);
0219         sigInt = new QAction(i18n("Interrupt (INT)"), q);
0220         sigTerm = new QAction(i18n("Terminate (TERM)"), q);
0221         sigKill = new QAction(i18n("Kill (KILL)"), q);
0222         sigUsr1 = new QAction(i18n("User 1 (USR1)"), q);
0223         sigUsr2 = new QAction(i18n("User 2 (USR2)"), q);
0224 
0225         // Set up '/' as a shortcut to jump to the quick search text widget
0226         jumpToSearchFilter = new QAction(i18n("Focus on Quick Search"), q);
0227         jumpToSearchFilter->setShortcuts(QList<QKeySequence>() << QKeySequence::Find << '/');
0228     }
0229 
0230     ~KSysGuardProcessListPrivate()
0231     {
0232         delete mUi;
0233         mUi = nullptr;
0234     }
0235 
0236     /** The number rows and their children for the given parent in the mFilterModel model */
0237     int totalRowCount(const QModelIndex &parent) const;
0238 
0239     /** Helper function to setup 'action' with the given pids */
0240     void setupKAuthAction(KAuth::Action &action, const QList<long long> &pids) const;
0241 
0242     /** fire a timer event if we are set to use our internal timer*/
0243     void fireTimerEvent();
0244 
0245     /** The process model.  This contains all the data on all the processes running on the system */
0246     ProcessModel mModel;
0247 
0248     /** The process filter.  The mModel is connected to this, and this filter model connects to the view.  This lets us
0249      *  sort the view and filter (by using the combo box or the search line)
0250      */
0251     ProcessFilter mFilterModel;
0252 
0253     KSysGuard::ProcessController *mProcessController = nullptr;
0254 
0255     /** The graphical user interface for this process list widget, auto-generated by Qt Designer */
0256     Ui::ProcessWidget *mUi;
0257 
0258     /** The context menu when you right click on a process */
0259     QMenu *mProcessContextMenu;
0260 
0261     /** A timer to call updateList() every mUpdateIntervalMSecs.
0262      *  NULL is mUpdateIntervalMSecs is <= 0. */
0263     QTimer *mUpdateTimer;
0264 
0265     /** The time to wait, in milliseconds, between updating the process list */
0266     int mUpdateIntervalMSecs;
0267 
0268     /** Number of items that are selected */
0269     int mNumItemsSelected;
0270 
0271     /** Class to deal with the scripting. NULL if scripting is disabled */
0272     Scripting *mScripting;
0273 
0274     /** A counter to mark when to resort, so that we do not resort on every update */
0275     int mResortCountDown;
0276 
0277     bool mNeedToExpandInit;
0278 
0279     QAction *renice;
0280     QAction *terminate;
0281     QAction *kill;
0282     QAction *selectParent;
0283     QAction *selectTracer;
0284     QAction *jumpToSearchFilter;
0285     QAction *window;
0286     QAction *processDetails;
0287     QAction *resume;
0288     QAction *sigStop;
0289     QAction *sigCont;
0290     QAction *sigHup;
0291     QAction *sigInt;
0292     QAction *sigTerm;
0293     QAction *sigKill;
0294     QAction *sigUsr1;
0295     QAction *sigUsr2;
0296 
0297     QMenu *mToolsMenu;
0298 
0299     QPointer<ProcessDetailsDialog> processDetailsDialog;
0300 };
0301 
0302 KSysGuardProcessList::KSysGuardProcessList(QWidget *parent, const QString &hostName)
0303     : QWidget(parent)
0304     , d(new KSysGuardProcessListPrivate(this, hostName))
0305 {
0306     qRegisterMetaType<QList<long long>>();
0307     qDBusRegisterMetaType<QList<long long>>();
0308 
0309     d->mProcessController = new KSysGuard::ProcessController(this);
0310     window()->winId();
0311     d->mProcessController->setWindow(window()->windowHandle());
0312 
0313     d->mUpdateIntervalMSecs = 0; // Set process to not update manually by default
0314     d->mUi->setupUi(this);
0315     d->mFilterModel.setSourceModel(&d->mModel);
0316     d->mUi->treeView->setModel(&d->mFilterModel);
0317 #ifdef DO_MODELCHECK
0318     new ModelTest(&d->mModel, this);
0319 #endif
0320     d->mUi->treeView->setItemDelegate(new ProgressBarItemDelegate(d->mUi->treeView));
0321 
0322     d->mUi->treeView->header()->setContextMenuPolicy(Qt::CustomContextMenu);
0323     connect(d->mUi->treeView->header(), &QWidget::customContextMenuRequested, this, &KSysGuardProcessList::showColumnContextMenu);
0324 
0325     d->mProcessContextMenu = new QMenu(d->mUi->treeView);
0326     d->mUi->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
0327     connect(d->mUi->treeView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showProcessContextMenu(QPoint)));
0328 
0329     d->mUi->treeView->header()->setSectionsClickable(true);
0330     d->mUi->treeView->header()->setSortIndicatorShown(true);
0331     d->mUi->treeView->header()->setCascadingSectionResizes(false);
0332     connect(d->mUi->btnKillProcess, &QAbstractButton::clicked, this, &KSysGuardProcessList::killSelectedProcesses);
0333     connect(d->mUi->txtFilter, &QLineEdit::textChanged, this, &KSysGuardProcessList::filterTextChanged);
0334     connect(d->mUi->cmbFilter, SIGNAL(currentIndexChanged(int)), this, SLOT(setStateInt(int)));
0335     connect(d->mUi->treeView, &QTreeView::expanded, this, &KSysGuardProcessList::expandAllChildren);
0336     connect(d->mUi->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KSysGuardProcessList::selectionChanged);
0337     connect(&d->mFilterModel, &QAbstractItemModel::rowsInserted, this, &KSysGuardProcessList::rowsInserted);
0338     connect(&d->mFilterModel, &QAbstractItemModel::rowsRemoved, this, &KSysGuardProcessList::processListChanged);
0339     setMinimumSize(sizeHint());
0340 
0341     d->mFilterModel.setFilterKeyColumn(-1);
0342 
0343     /*  Hide various columns by default, to reduce information overload */
0344     d->mUi->treeView->header()->hideSection(ProcessModel::HeadingVmSize);
0345     d->mUi->treeView->header()->hideSection(ProcessModel::HeadingNiceness);
0346     d->mUi->treeView->header()->hideSection(ProcessModel::HeadingTty);
0347     d->mUi->treeView->header()->hideSection(ProcessModel::HeadingStartTime);
0348     d->mUi->treeView->header()->hideSection(ProcessModel::HeadingNoNewPrivileges);
0349     d->mUi->treeView->header()->hideSection(ProcessModel::HeadingCommand);
0350     d->mUi->treeView->header()->hideSection(ProcessModel::HeadingPid);
0351     d->mUi->treeView->header()->hideSection(ProcessModel::HeadingCPUTime);
0352     d->mUi->treeView->header()->hideSection(ProcessModel::HeadingIoRead);
0353     d->mUi->treeView->header()->hideSection(ProcessModel::HeadingIoWrite);
0354     d->mUi->treeView->header()->hideSection(ProcessModel::HeadingXMemory);
0355     d->mUi->treeView->header()->hideSection(ProcessModel::HeadingCGroup);
0356     d->mUi->treeView->header()->hideSection(ProcessModel::HeadingMACContext);
0357     d->mUi->treeView->header()->hideSection(ProcessModel::HeadingVmPSS);
0358     // NOTE!  After this is all setup, the settings for the header are restored
0359     // from the user's last run.  (in restoreHeaderState)
0360     // So making changes here only affects the default settings.  To
0361     // test changes temporarily, comment out the lines in restoreHeaderState.
0362     // When you are happy with the changes and want to commit, increase the
0363     // value of PROCESSHEADERVERSION.  This will force the header state
0364     // to be reset back to the defaults for all users.
0365     d->mUi->treeView->header()->resizeSection(ProcessModel::HeadingCPUUsage, d->mUi->treeView->header()->sectionSizeHint(ProcessModel::HeadingCPUUsage));
0366     d->mUi->treeView->header()->resizeSection(ProcessModel::HeadingMemory, d->mUi->treeView->header()->sectionSizeHint(ProcessModel::HeadingMemory));
0367     d->mUi->treeView->header()->resizeSection(ProcessModel::HeadingSharedMemory,
0368                                               d->mUi->treeView->header()->sectionSizeHint(ProcessModel::HeadingSharedMemory));
0369     d->mUi->treeView->header()->setSectionResizeMode(0, QHeaderView::Interactive);
0370     d->mUi->treeView->header()->setStretchLastSection(true);
0371 
0372     // Process names can have mixed case. Make the filter case insensitive.
0373     d->mFilterModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
0374     d->mFilterModel.setSortCaseSensitivity(Qt::CaseInsensitive);
0375 
0376     d->mUi->txtFilter->installEventFilter(this);
0377     d->mUi->treeView->installEventFilter(this);
0378 
0379     d->mUi->treeView->setDragEnabled(true);
0380     d->mUi->treeView->setDragDropMode(QAbstractItemView::DragOnly);
0381 
0382     auto extraAttributes = d->mModel.extraAttributes();
0383     for (int i = 0; i < extraAttributes.count(); ++i) {
0384         auto attribute = extraAttributes.at(i);
0385         if (!attribute->isVisibleByDefault()) {
0386             d->mUi->treeView->header()->hideSection(ProcessModel::HeadingPluginStart + i);
0387         }
0388     }
0389 
0390     // Sort by username by default
0391     d->mUi->treeView->sortByColumn(ProcessModel::HeadingUser, Qt::AscendingOrder);
0392 
0393     // Add all the actions to the main widget, and get all the actions to call actionTriggered when clicked
0394     QList<QAction *> actions;
0395     actions << d->renice << d->kill << d->terminate << d->selectParent << d->selectTracer << d->window;
0396     actions << d->processDetails << d->jumpToSearchFilter << d->resume << d->sigStop << d->sigCont;
0397     actions << d->sigHup << d->sigInt << d->sigTerm << d->sigKill << d->sigUsr1 << d->sigUsr2;
0398 
0399     foreach (QAction *action, actions) {
0400         addAction(action);
0401         connect(action, &QAction::triggered, this, [this, action]() {
0402             actionTriggered(action);
0403         });
0404     }
0405 
0406     retranslateUi();
0407 
0408     d->mUi->btnKillProcess->setIcon(QIcon::fromTheme(QStringLiteral("process-stop")));
0409     d->mUi->btnKillProcess->setToolTip(
0410         i18n("<qt>End the selected process. Warning - you may lose unsaved work.<br>Right click on a process to send other signals.<br>See What's This for "
0411              "technical information."));
0412 
0413     auto addByDesktopName = [this](const QString &desktopName) {
0414         auto kService = KService::serviceByDesktopName(desktopName);
0415         if (kService) {
0416             auto action = new QAction(QIcon::fromTheme(kService->icon()), kService->name(), this);
0417 
0418             connect(action, &QAction::triggered, this, [this, kService](bool) {
0419                 auto *job = new KIO::ApplicationLauncherJob(kService);
0420                 job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, window()));
0421                 job->start();
0422             });
0423             d->mToolsMenu->addAction(action);
0424         }
0425     };
0426 
0427     addByDesktopName(QStringLiteral("org.kde.konsole"));
0428 
0429     const QString ksysguardDesktopName = QStringLiteral("org.kde.ksysguard");
0430     // The following expression is true when the libksysguard process list is _not_ embedded in KSysGuard.
0431     // Only then we add KSysGuard to the menu
0432     if (qApp->desktopFileName() != ksysguardDesktopName) {
0433         addByDesktopName(ksysguardDesktopName);
0434     }
0435 
0436     addByDesktopName(QStringLiteral("org.kde.plasma-systemmonitor"));
0437     addByDesktopName(QStringLiteral("org.kde.ksystemlog"));
0438     addByDesktopName(QStringLiteral("org.kde.kinfocenter"));
0439     addByDesktopName(QStringLiteral("org.kde.filelight"));
0440     addByDesktopName(QStringLiteral("org.kde.sweeper"));
0441     addByDesktopName(QStringLiteral("org.kde.kmag"));
0442     addByDesktopName(QStringLiteral("htop"));
0443 
0444     // Add the xkill functionality...
0445     auto killWindowAction = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18nc("@action:inmenu", "Kill a Window"), this);
0446     // Find shortcut of xkill functionality which is defined in KWin
0447     const auto killWindowShortcutList = KGlobalAccel::self()->globalShortcut(QStringLiteral("kwin"), QStringLiteral("Kill Window"));
0448     killWindowAction->setShortcuts(killWindowShortcutList);
0449     // We don't use xkill directly but the method in KWin which allows to press Esc to abort.
0450     auto killWindowKwinMethod = new QDBusInterface(QStringLiteral("org.kde.KWin"), QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin"));
0451     // If KWin is not the window manager, then we disable the entry:
0452     if (!killWindowKwinMethod->isValid()) {
0453         killWindowAction->setEnabled(false);
0454     }
0455     connect(killWindowAction, &QAction::triggered, this, [this, killWindowKwinMethod](bool) {
0456         // with DBus call, always use the async method.
0457         // Otherwise it could wait up to 30 seconds in certain situations.
0458         killWindowKwinMethod->asyncCall(QStringLiteral("killWindow"));
0459     });
0460     d->mToolsMenu->addAction(killWindowAction);
0461 
0462     d->mUi->btnTools->setMenu(d->mToolsMenu);
0463 }
0464 
0465 KSysGuardProcessList::~KSysGuardProcessList()
0466 {
0467     delete d;
0468 }
0469 
0470 QTreeView *KSysGuardProcessList::treeView() const
0471 {
0472     return d->mUi->treeView;
0473 }
0474 
0475 QLineEdit *KSysGuardProcessList::filterLineEdit() const
0476 {
0477     return d->mUi->txtFilter;
0478 }
0479 
0480 ProcessFilter::State KSysGuardProcessList::state() const
0481 {
0482     return d->mFilterModel.filter();
0483 }
0484 
0485 void KSysGuardProcessList::setStateInt(int state)
0486 {
0487     setState((ProcessFilter::State)state);
0488     d->mUi->treeView->scrollTo(d->mUi->treeView->currentIndex());
0489 }
0490 
0491 void KSysGuardProcessList::setState(ProcessFilter::State state)
0492 { // index is the item the user selected in the combo box
0493     d->mFilterModel.setFilter(state);
0494     d->mModel.setSimpleMode((state != ProcessFilter::AllProcessesInTreeForm));
0495     d->mUi->cmbFilter->setCurrentIndex((int)state);
0496     if (isVisible()) {
0497         expandInit();
0498     }
0499 }
0500 
0501 void KSysGuardProcessList::filterTextChanged(const QString &newText)
0502 {
0503     d->mFilterModel.setFilterRegularExpression(newText.trimmed());
0504     if (isVisible()) {
0505         expandInit();
0506     }
0507     d->mUi->btnKillProcess->setEnabled(d->mUi->treeView->selectionModel()->hasSelection());
0508     d->mUi->treeView->scrollTo(d->mUi->treeView->currentIndex());
0509 }
0510 
0511 int KSysGuardProcessList::visibleProcessesCount() const
0512 {
0513     // This assumes that all the visible rows are processes.  This is true currently, but might not be
0514     // true if we add support for showing threads etc
0515     if (d->mModel.isSimpleMode()) {
0516         return d->mFilterModel.rowCount();
0517     }
0518     return d->totalRowCount(QModelIndex());
0519 }
0520 
0521 int KSysGuardProcessListPrivate::totalRowCount(const QModelIndex &parent) const
0522 {
0523     int numRows = mFilterModel.rowCount(parent);
0524     int total = numRows;
0525     for (int i = 0; i < numRows; ++i) {
0526         QModelIndex index = mFilterModel.index(i, 0, parent);
0527         // if it has children add the total
0528         if (mFilterModel.hasChildren(index)) {
0529             total += totalRowCount(index);
0530         }
0531     }
0532     return total;
0533 }
0534 
0535 void KSysGuardProcessListPrivate::setupKAuthAction(KAuth::Action &action, const QList<long long> &pids) const
0536 {
0537     action.setHelperId(QStringLiteral("org.kde.ksysguard.processlisthelper"));
0538 
0539     const int processCount = pids.count();
0540     for (int i = 0; i < processCount; i++) {
0541         action.addArgument(QStringLiteral("pid%1").arg(i), pids[i]);
0542     }
0543     action.addArgument(QStringLiteral("pidcount"), processCount);
0544 }
0545 void KSysGuardProcessList::selectionChanged()
0546 {
0547     int numSelected = d->mUi->treeView->selectionModel()->selectedRows().size();
0548     if (numSelected == d->mNumItemsSelected) {
0549         return;
0550     }
0551     d->mNumItemsSelected = numSelected;
0552     d->mUi->btnKillProcess->setEnabled(numSelected != 0);
0553 
0554     d->renice->setText(i18np("Set Priority...", "Set Priority...", numSelected));
0555     d->kill->setText(i18np("Forcibly Kill Process", "Forcibly Kill Processes", numSelected));
0556     d->terminate->setText(i18ncp("Context menu", "End Process", "End Processes", numSelected));
0557 }
0558 void KSysGuardProcessList::showProcessContextMenu(const QModelIndex &index)
0559 {
0560     if (!index.isValid()) {
0561         return;
0562     }
0563     QRect rect = d->mUi->treeView->visualRect(index);
0564     QPoint point(rect.x() + rect.width() / 4, rect.y() + rect.height() / 2);
0565     showProcessContextMenu(point);
0566 }
0567 void KSysGuardProcessList::showProcessContextMenu(const QPoint &point)
0568 {
0569     d->mProcessContextMenu->clear();
0570 
0571     const QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
0572     const int numProcesses = selectedIndexes.size();
0573 
0574     if (numProcesses == 0) {
0575         // No processes selected, so no process context menu
0576 
0577         // Check just incase we have no columns visible.  In which case show the column context menu
0578         // so that users can unhide columns if there are no columns visible
0579         for (int i = 0; i < d->mFilterModel.columnCount(); ++i) {
0580             if (!d->mUi->treeView->header()->isSectionHidden(i)) {
0581                 return;
0582             }
0583         }
0584         showColumnContextMenu(point);
0585         return;
0586     }
0587 
0588     QModelIndex realIndex = d->mFilterModel.mapToSource(selectedIndexes.at(0));
0589     KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(realIndex.internalPointer());
0590 
0591     d->mProcessContextMenu->addAction(d->renice);
0592     QMenu *signalMenu = d->mProcessContextMenu->addMenu(i18n("Send Signal"));
0593     signalMenu->addAction(d->sigStop);
0594     signalMenu->addAction(d->sigCont);
0595     signalMenu->addAction(d->sigHup);
0596     signalMenu->addAction(d->sigInt);
0597     signalMenu->addAction(d->sigTerm);
0598     signalMenu->addAction(d->sigKill);
0599     signalMenu->addAction(d->sigUsr1);
0600     signalMenu->addAction(d->sigUsr2);
0601 
0602     if (numProcesses == 1 && process->parentPid() > 1) {
0603         // As a design decision, I do not show the 'Jump to parent process' option when the
0604         // parent is just 'init'.
0605 
0606         KSysGuard::Process *parent_process = d->mModel.getProcess(process->parentPid());
0607         // it should not be possible for this process to not exist, but check just in case
0608         if (parent_process) {
0609             QString parent_name = parent_process->name();
0610             d->selectParent->setText(i18n("Jump to Parent Process (%1)", parent_name));
0611             d->mProcessContextMenu->addAction(d->selectParent);
0612         }
0613     }
0614 
0615     if (numProcesses == 1 && process->tracerpid() >= 0) {
0616         // If the process is being debugged, offer to select it
0617         d->mProcessContextMenu->addAction(d->selectTracer);
0618     }
0619 
0620     if (numProcesses == 1 && !d->mModel.data(realIndex, ProcessModel::WindowIdRole).isNull()) {
0621         d->mProcessContextMenu->addAction(d->window);
0622     }
0623 
0624     if (numProcesses == 1) {
0625         const QFileInfo procDirFileInfo(QStringLiteral("/proc/%1").arg(process->pid()));
0626         if (procDirFileInfo.exists() && procDirFileInfo.isReadable() && procDirFileInfo.isDir()) {
0627             d->mProcessContextMenu->addAction(d->processDetails);
0628         }
0629     }
0630 
0631     if (numProcesses == 1 && process->status() == KSysGuard::Process::Stopped) {
0632         // If the process is stopped, offer to resume it
0633         d->mProcessContextMenu->addAction(d->resume);
0634     }
0635 
0636     if (numProcesses == 1 && d->mScripting) {
0637         foreach (QAction *action, d->mScripting->actions()) {
0638             d->mProcessContextMenu->addAction(action);
0639         }
0640     }
0641     d->mProcessContextMenu->addSeparator();
0642     d->mProcessContextMenu->addAction(d->terminate);
0643     if (numProcesses == 1 && process->timeKillWasSent().isValid()) {
0644         d->mProcessContextMenu->addAction(d->kill);
0645     }
0646 
0647     d->mProcessContextMenu->popup(d->mUi->treeView->viewport()->mapToGlobal(point));
0648 }
0649 void KSysGuardProcessList::actionTriggered(QObject *object)
0650 {
0651     if (!isVisible()) {
0652         // Ignore triggered actions if we are not visible!
0653         return;
0654     }
0655     // Reset the text back to normal
0656     d->selectParent->setText(i18n("Jump to Parent Process"));
0657     QAction *result = qobject_cast<QAction *>(object);
0658     if (result == nullptr) {
0659         // Escape was pressed. Do nothing.
0660     } else if (result == d->renice) {
0661         reniceSelectedProcesses();
0662     } else if (result == d->terminate) {
0663         sendSignalToSelectedProcesses(SIGTERM, true);
0664     } else if (result == d->kill) {
0665         sendSignalToSelectedProcesses(SIGKILL, true);
0666     } else if (result == d->selectParent) {
0667         QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
0668         int numProcesses = selectedIndexes.size();
0669         if (numProcesses == 0) {
0670             return; // No processes selected
0671         }
0672         QModelIndex realIndex = d->mFilterModel.mapToSource(selectedIndexes.at(0));
0673         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(realIndex.internalPointer());
0674         if (process) {
0675             selectAndJumpToProcess(process->parentPid());
0676         }
0677     } else if (result == d->selectTracer) {
0678         QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
0679         int numProcesses = selectedIndexes.size();
0680         if (numProcesses == 0) {
0681             return; // No processes selected
0682         }
0683         QModelIndex realIndex = d->mFilterModel.mapToSource(selectedIndexes.at(0));
0684         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(realIndex.internalPointer());
0685         if (process) {
0686             selectAndJumpToProcess(process->tracerpid());
0687         }
0688     } else if (result == d->window) {
0689         QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
0690         int numProcesses = selectedIndexes.size();
0691         if (numProcesses == 0) {
0692             return; // No processes selected
0693         }
0694         foreach (const QModelIndex &index, selectedIndexes) {
0695             QModelIndex realIndex = d->mFilterModel.mapToSource(index);
0696             QVariant widVar = d->mModel.data(realIndex, ProcessModel::WindowIdRole);
0697             if (!widVar.isNull()) {
0698                 int wid = widVar.toInt();
0699                 KX11Extras::activateWindow(wid);
0700             }
0701         }
0702     } else if (result == d->processDetails) {
0703         const QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
0704         if (selectedIndexes.size() != 1) {
0705             return;
0706         }
0707         const QModelIndex mappedIndex = d->mFilterModel.mapToSource(selectedIndexes.first());
0708         Q_ASSERT(mappedIndex.isValid());
0709         if (!d->processDetailsDialog) {
0710             d->processDetailsDialog = new ProcessDetailsDialog(this);
0711         }
0712         d->processDetailsDialog->setModelIndex(mappedIndex);
0713         d->processDetailsDialog->show();
0714         d->processDetailsDialog->raise();
0715     } else if (result == d->jumpToSearchFilter) {
0716         d->mUi->txtFilter->setFocus();
0717     } else {
0718         int sig;
0719         if (result == d->resume || result == d->sigCont) {
0720             sig = SIGCONT; // Despite the function name, this sends a signal, rather than kill it.  Silly unix :)
0721         } else if (result == d->sigStop) {
0722             sig = SIGSTOP;
0723         } else if (result == d->sigHup) {
0724             sig = SIGHUP;
0725         } else if (result == d->sigInt) {
0726             sig = SIGINT;
0727         } else if (result == d->sigTerm) {
0728             sig = SIGTERM;
0729         } else if (result == d->sigKill) {
0730             sig = SIGKILL;
0731         } else if (result == d->sigUsr1) {
0732             sig = SIGUSR1;
0733         } else if (result == d->sigUsr2) {
0734             sig = SIGUSR2;
0735         } else {
0736             return;
0737         }
0738         sendSignalToSelectedProcesses(sig, false);
0739     }
0740 }
0741 
0742 void KSysGuardProcessList::selectAndJumpToProcess(int pid)
0743 {
0744     KSysGuard::Process *process = d->mModel.getProcess(pid);
0745     if (!process) {
0746         return;
0747     }
0748     QModelIndex sourceIndex = d->mModel.getQModelIndex(process, 0);
0749     QModelIndex filterIndex = d->mFilterModel.mapFromSource(sourceIndex);
0750     if (!filterIndex.isValid() && !d->mUi->txtFilter->text().isEmpty()) {
0751         // The filter is preventing us from finding the parent.  Clear the filter
0752         //(It could also be the combo box - should we deal with that case as well?)
0753         d->mUi->txtFilter->clear();
0754         filterIndex = d->mFilterModel.mapFromSource(sourceIndex);
0755     }
0756     d->mUi->treeView->clearSelection();
0757     d->mUi->treeView->setCurrentIndex(filterIndex);
0758     d->mUi->treeView->scrollTo(filterIndex, QAbstractItemView::PositionAtCenter);
0759 }
0760 
0761 void KSysGuardProcessList::showColumnContextMenu(const QPoint &point)
0762 {
0763     QMenu menu;
0764 
0765     QAction *action;
0766 
0767     int num_headings = d->mFilterModel.columnCount();
0768 
0769     int index = d->mUi->treeView->header()->logicalIndexAt(point);
0770     if (index >= 0) {
0771         bool anyOtherVisibleColumns = false;
0772         for (int i = 0; i < num_headings; ++i) {
0773             if (i != index && !d->mUi->treeView->header()->isSectionHidden(i)) {
0774                 anyOtherVisibleColumns = true;
0775                 break;
0776             }
0777         }
0778         if (anyOtherVisibleColumns) {
0779             // selected a column.  Give the option to hide it
0780             action = new QAction(&menu);
0781             action->setData(-index - 1); // We set data to be negative (and minus 1) to hide a column, and positive to show a column
0782             action->setText(i18n("Hide Column '%1'", d->mFilterModel.headerData(index, Qt::Horizontal, Qt::DisplayRole).toString()));
0783             menu.addAction(action);
0784             if (d->mUi->treeView->header()->sectionsHidden()) {
0785                 menu.addSeparator();
0786             }
0787         }
0788     }
0789 
0790     if (d->mUi->treeView->header()->sectionsHidden()) {
0791         for (int i = 0; i < num_headings; ++i) {
0792             if (d->mUi->treeView->header()->isSectionHidden(i)) {
0793 #if !HAVE_XRES
0794                 if (i == ProcessModel::HeadingXMemory) {
0795                     continue;
0796                 }
0797 #endif
0798                 action = new QAction(&menu);
0799                 action->setText(i18n("Show Column '%1'", d->mFilterModel.headerData(i, Qt::Horizontal, Qt::DisplayRole).toString()));
0800                 action->setData(i); // We set data to be negative (and minus 1) to hide a column, and positive to show a column
0801                 menu.addAction(action);
0802             }
0803         }
0804     }
0805     QAction *actionAuto = nullptr;
0806     QAction *actionKB = nullptr;
0807     QAction *actionMB = nullptr;
0808     QAction *actionGB = nullptr;
0809     QAction *actionPercentage = nullptr;
0810     QAction *actionShowCmdlineOptions = nullptr;
0811     QAction *actionShowTooltips = nullptr;
0812     QAction *actionNormalizeCPUUsage = nullptr;
0813 
0814     QAction *actionIoCharacters = nullptr;
0815     QAction *actionIoSyscalls = nullptr;
0816     QAction *actionIoActualCharacters = nullptr;
0817     QAction *actionIoShowRate = nullptr;
0818     bool showIoRate = false;
0819     if (index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) {
0820         showIoRate = d->mModel.ioInformation() == ProcessModel::BytesRate || d->mModel.ioInformation() == ProcessModel::SyscallsRate
0821             || d->mModel.ioInformation() == ProcessModel::ActualBytesRate;
0822     }
0823 
0824     if (index == ProcessModel::HeadingVmSize || index == ProcessModel::HeadingMemory || index == ProcessModel::HeadingXMemory
0825         || index == ProcessModel::HeadingSharedMemory || index == ProcessModel::HeadingVmPSS
0826         || ((index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) && d->mModel.ioInformation() != ProcessModel::Syscalls)) {
0827         // If the user right clicks on a column that contains a memory size, show a toggle option for displaying
0828         // the memory in different units.  e.g.  "2000 k" or "2 m"
0829         menu.addSeparator()->setText(i18n("Display Units"));
0830         QActionGroup *unitsGroup = new QActionGroup(&menu);
0831         /* Automatic (human readable)*/
0832         actionAuto = new QAction(&menu);
0833         actionAuto->setText(i18n("Mixed"));
0834         actionAuto->setCheckable(true);
0835         menu.addAction(actionAuto);
0836         unitsGroup->addAction(actionAuto);
0837         /* Kilobytes */
0838         actionKB = new QAction(&menu);
0839         actionKB->setText((showIoRate) ? i18n("Kilobytes per second") : i18n("Kilobytes"));
0840         actionKB->setCheckable(true);
0841         menu.addAction(actionKB);
0842         unitsGroup->addAction(actionKB);
0843         /* Megabytes */
0844         actionMB = new QAction(&menu);
0845         actionMB->setText((showIoRate) ? i18n("Megabytes per second") : i18n("Megabytes"));
0846         actionMB->setCheckable(true);
0847         menu.addAction(actionMB);
0848         unitsGroup->addAction(actionMB);
0849         /* Gigabytes */
0850         actionGB = new QAction(&menu);
0851         actionGB->setText((showIoRate) ? i18n("Gigabytes per second") : i18n("Gigabytes"));
0852         actionGB->setCheckable(true);
0853         menu.addAction(actionGB);
0854         unitsGroup->addAction(actionGB);
0855         ProcessModel::Units currentUnit;
0856         if (index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) {
0857             currentUnit = d->mModel.ioUnits();
0858         } else {
0859             actionPercentage = new QAction(&menu);
0860             actionPercentage->setText(i18n("Percentage"));
0861             actionPercentage->setCheckable(true);
0862             menu.addAction(actionPercentage);
0863             unitsGroup->addAction(actionPercentage);
0864             currentUnit = d->mModel.units();
0865         }
0866         switch (currentUnit) {
0867         case ProcessModel::UnitsAuto:
0868             actionAuto->setChecked(true);
0869             break;
0870         case ProcessModel::UnitsKB:
0871             actionKB->setChecked(true);
0872             break;
0873         case ProcessModel::UnitsMB:
0874             actionMB->setChecked(true);
0875             break;
0876         case ProcessModel::UnitsGB:
0877             actionGB->setChecked(true);
0878             break;
0879         case ProcessModel::UnitsPercentage:
0880             actionPercentage->setChecked(true);
0881             break;
0882         default:
0883             break;
0884         }
0885         unitsGroup->setExclusive(true);
0886     } else if (index == ProcessModel::HeadingName) {
0887         menu.addSeparator();
0888         actionShowCmdlineOptions = new QAction(&menu);
0889         actionShowCmdlineOptions->setText(i18n("Display command line options"));
0890         actionShowCmdlineOptions->setCheckable(true);
0891         actionShowCmdlineOptions->setChecked(d->mModel.isShowCommandLineOptions());
0892         menu.addAction(actionShowCmdlineOptions);
0893     } else if (index == ProcessModel::HeadingCPUUsage) {
0894         menu.addSeparator();
0895         actionNormalizeCPUUsage = new QAction(&menu);
0896         actionNormalizeCPUUsage->setText(i18n("Divide CPU usage by number of CPUs"));
0897         actionNormalizeCPUUsage->setCheckable(true);
0898         actionNormalizeCPUUsage->setChecked(d->mModel.isNormalizedCPUUsage());
0899         menu.addAction(actionNormalizeCPUUsage);
0900     }
0901 
0902     if (index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) {
0903         menu.addSeparator()->setText(i18n("Displayed Information"));
0904         QActionGroup *ioInformationGroup = new QActionGroup(&menu);
0905         actionIoCharacters = new QAction(&menu);
0906         actionIoCharacters->setText(i18n("Characters read/written"));
0907         actionIoCharacters->setCheckable(true);
0908         menu.addAction(actionIoCharacters);
0909         ioInformationGroup->addAction(actionIoCharacters);
0910         actionIoSyscalls = new QAction(&menu);
0911         actionIoSyscalls->setText(i18n("Number of Read/Write operations"));
0912         actionIoSyscalls->setCheckable(true);
0913         menu.addAction(actionIoSyscalls);
0914         ioInformationGroup->addAction(actionIoSyscalls);
0915         actionIoActualCharacters = new QAction(&menu);
0916         actionIoActualCharacters->setText(i18n("Bytes actually read/written"));
0917         actionIoActualCharacters->setCheckable(true);
0918         menu.addAction(actionIoActualCharacters);
0919         ioInformationGroup->addAction(actionIoActualCharacters);
0920 
0921         actionIoShowRate = new QAction(&menu);
0922         actionIoShowRate->setText(i18n("Show I/O rate"));
0923         actionIoShowRate->setCheckable(true);
0924         actionIoShowRate->setChecked(showIoRate);
0925         menu.addAction(actionIoShowRate);
0926 
0927         switch (d->mModel.ioInformation()) {
0928         case ProcessModel::Bytes:
0929         case ProcessModel::BytesRate:
0930             actionIoCharacters->setChecked(true);
0931             break;
0932         case ProcessModel::Syscalls:
0933         case ProcessModel::SyscallsRate:
0934             actionIoSyscalls->setChecked(true);
0935             break;
0936         case ProcessModel::ActualBytes:
0937         case ProcessModel::ActualBytesRate:
0938             actionIoActualCharacters->setChecked(true);
0939             break;
0940         default:
0941             break;
0942         }
0943     }
0944 
0945     menu.addSeparator();
0946     actionShowTooltips = new QAction(&menu);
0947     actionShowTooltips->setCheckable(true);
0948     actionShowTooltips->setChecked(d->mModel.isShowingTooltips());
0949     actionShowTooltips->setText(i18n("Show Tooltips"));
0950     menu.addAction(actionShowTooltips);
0951 
0952     QAction *result = menu.exec(d->mUi->treeView->header()->mapToGlobal(point));
0953     if (!result) {
0954         return; // Menu cancelled
0955     }
0956     if (result == actionAuto) {
0957         if (index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) {
0958             d->mModel.setIoUnits(ProcessModel::UnitsAuto);
0959         } else {
0960             d->mModel.setUnits(ProcessModel::UnitsAuto);
0961         }
0962         return;
0963     } else if (result == actionKB) {
0964         if (index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) {
0965             d->mModel.setIoUnits(ProcessModel::UnitsKB);
0966         } else {
0967             d->mModel.setUnits(ProcessModel::UnitsKB);
0968         }
0969         return;
0970     } else if (result == actionMB) {
0971         if (index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) {
0972             d->mModel.setIoUnits(ProcessModel::UnitsMB);
0973         } else {
0974             d->mModel.setUnits(ProcessModel::UnitsMB);
0975         }
0976         return;
0977     } else if (result == actionGB) {
0978         if (index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) {
0979             d->mModel.setIoUnits(ProcessModel::UnitsGB);
0980         } else {
0981             d->mModel.setUnits(ProcessModel::UnitsGB);
0982         }
0983         return;
0984     } else if (result == actionPercentage) {
0985         d->mModel.setUnits(ProcessModel::UnitsPercentage);
0986         return;
0987     } else if (result == actionShowCmdlineOptions) {
0988         d->mModel.setShowCommandLineOptions(actionShowCmdlineOptions->isChecked());
0989         return;
0990     } else if (result == actionNormalizeCPUUsage) {
0991         d->mModel.setNormalizedCPUUsage(actionNormalizeCPUUsage->isChecked());
0992         return;
0993     } else if (result == actionShowTooltips) {
0994         d->mModel.setShowingTooltips(actionShowTooltips->isChecked());
0995         return;
0996     } else if (result == actionIoCharacters) {
0997         d->mModel.setIoInformation((showIoRate) ? ProcessModel::BytesRate : ProcessModel::Bytes);
0998         return;
0999     } else if (result == actionIoSyscalls) {
1000         d->mModel.setIoInformation((showIoRate) ? ProcessModel::SyscallsRate : ProcessModel::Syscalls);
1001         return;
1002     } else if (result == actionIoActualCharacters) {
1003         d->mModel.setIoInformation((showIoRate) ? ProcessModel::ActualBytesRate : ProcessModel::ActualBytes);
1004         return;
1005     } else if (result == actionIoShowRate) {
1006         showIoRate = actionIoShowRate->isChecked();
1007         switch (d->mModel.ioInformation()) {
1008         case ProcessModel::Bytes:
1009         case ProcessModel::BytesRate:
1010             d->mModel.setIoInformation((showIoRate) ? ProcessModel::BytesRate : ProcessModel::Bytes);
1011             break;
1012         case ProcessModel::Syscalls:
1013         case ProcessModel::SyscallsRate:
1014             d->mModel.setIoInformation((showIoRate) ? ProcessModel::SyscallsRate : ProcessModel::Syscalls);
1015             break;
1016         case ProcessModel::ActualBytes:
1017         case ProcessModel::ActualBytesRate:
1018             d->mModel.setIoInformation((showIoRate) ? ProcessModel::ActualBytesRate : ProcessModel::ActualBytes);
1019             break;
1020         default:
1021             break;
1022         }
1023     }
1024 
1025     int i = result->data().toInt();
1026     // We set data to be negative to hide a column, and positive to show a column
1027     if (i < 0) {
1028         auto index = -1 - i;
1029         d->mUi->treeView->hideColumn(index);
1030     } else {
1031         d->mUi->treeView->showColumn(i);
1032         updateList();
1033         d->mUi->treeView->resizeColumnToContents(i);
1034         d->mUi->treeView->resizeColumnToContents(d->mFilterModel.columnCount());
1035     }
1036     menu.deleteLater();
1037 }
1038 
1039 void KSysGuardProcessList::expandAllChildren(const QModelIndex &parent)
1040 {
1041     // This is called when the user expands a node.  This then expands all of its
1042     // children.  This will trigger this function again recursively.
1043     QModelIndex sourceParent = d->mFilterModel.mapToSource(parent);
1044     for (int i = 0; i < d->mModel.rowCount(sourceParent); i++) {
1045         d->mUi->treeView->expand(d->mFilterModel.mapFromSource(d->mModel.index(i, 0, sourceParent)));
1046     }
1047 }
1048 
1049 void KSysGuardProcessList::rowsInserted(const QModelIndex &parent, int start, int end)
1050 {
1051     if (d->mModel.isSimpleMode() || parent.isValid()) {
1052         Q_EMIT processListChanged();
1053         return; // No tree or not a root node - no need to expand init
1054     }
1055     disconnect(&d->mFilterModel, &QAbstractItemModel::rowsInserted, this, &KSysGuardProcessList::rowsInserted);
1056     // It is a root node that we just inserted - expand it
1057     bool expanded = false;
1058     for (int i = start; i <= end; i++) {
1059         QModelIndex index = d->mFilterModel.index(i, 0, QModelIndex());
1060         if (!d->mUi->treeView->isExpanded(index)) {
1061             if (!expanded) {
1062                 disconnect(d->mUi->treeView, &QTreeView::expanded, this, &KSysGuardProcessList::expandAllChildren);
1063                 expanded = true;
1064             }
1065             d->mUi->treeView->expand(index);
1066             d->mNeedToExpandInit = true;
1067         }
1068     }
1069     if (expanded) {
1070         connect(d->mUi->treeView, &QTreeView::expanded, this, &KSysGuardProcessList::expandAllChildren);
1071     }
1072     connect(&d->mFilterModel, &QAbstractItemModel::rowsInserted, this, &KSysGuardProcessList::rowsInserted);
1073     Q_EMIT processListChanged();
1074 }
1075 
1076 void KSysGuardProcessList::expandInit()
1077 {
1078     if (d->mModel.isSimpleMode()) {
1079         return; // No tree - no need to expand init
1080     }
1081 
1082     bool expanded = false;
1083     for (int i = 0; i < d->mFilterModel.rowCount(QModelIndex()); i++) {
1084         QModelIndex index = d->mFilterModel.index(i, 0, QModelIndex());
1085         if (!d->mUi->treeView->isExpanded(index)) {
1086             if (!expanded) {
1087                 disconnect(d->mUi->treeView, &QTreeView::expanded, this, &KSysGuardProcessList::expandAllChildren);
1088                 expanded = true;
1089             }
1090 
1091             d->mUi->treeView->expand(index);
1092         }
1093     }
1094     if (expanded) {
1095         connect(d->mUi->treeView, &QTreeView::expanded, this, &KSysGuardProcessList::expandAllChildren);
1096     }
1097 }
1098 
1099 void KSysGuardProcessList::hideEvent(QHideEvent *event) // virtual protected from QWidget
1100 {
1101     // Stop updating the process list if we are hidden
1102     if (d->mUpdateTimer) {
1103         d->mUpdateTimer->stop();
1104     }
1105     // stop any scripts running, to save on memory
1106     if (d->mScripting) {
1107         d->mScripting->stopAllScripts();
1108     }
1109 
1110     QWidget::hideEvent(event);
1111 }
1112 
1113 void KSysGuardProcessList::showEvent(QShowEvent *event) // virtual protected from QWidget
1114 {
1115     // Start updating the process list again if we are shown again
1116     updateList();
1117     QHeaderView *header = d->mUi->treeView->header();
1118     d->mUi->treeView->sortByColumn(header->sortIndicatorSection(), header->sortIndicatorOrder());
1119 
1120     QWidget::showEvent(event);
1121 }
1122 
1123 void KSysGuardProcessList::changeEvent(QEvent *event)
1124 {
1125     if (event->type() == QEvent::LanguageChange) {
1126         d->mModel.retranslateUi();
1127         d->mUi->retranslateUi(this);
1128         retranslateUi();
1129     }
1130     QWidget::changeEvent(event);
1131 }
1132 void KSysGuardProcessList::retranslateUi()
1133 {
1134     d->mUi->cmbFilter->setItemIcon(ProcessFilter::AllProcesses, QIcon::fromTheme(QStringLiteral("view-process-all")));
1135     d->mUi->cmbFilter->setItemIcon(ProcessFilter::AllProcessesInTreeForm, QIcon::fromTheme(QStringLiteral("view-process-all-tree")));
1136     d->mUi->cmbFilter->setItemIcon(ProcessFilter::SystemProcesses, QIcon::fromTheme(QStringLiteral("view-process-system")));
1137     d->mUi->cmbFilter->setItemIcon(ProcessFilter::UserProcesses, QIcon::fromTheme(QStringLiteral("view-process-users")));
1138     d->mUi->cmbFilter->setItemIcon(ProcessFilter::OwnProcesses, QIcon::fromTheme(QStringLiteral("view-process-own")));
1139     d->mUi->cmbFilter->setItemIcon(ProcessFilter::ProgramsOnly, QIcon::fromTheme(QStringLiteral("view-process-all")));
1140 }
1141 
1142 void KSysGuardProcessList::updateList()
1143 {
1144     if (isVisible()) {
1145         KSysGuard::Processes::UpdateFlags updateFlags = KSysGuard::Processes::StandardInformation;
1146         if (!d->mUi->treeView->isColumnHidden(ProcessModel::HeadingIoRead) || !d->mUi->treeView->isColumnHidden(ProcessModel::HeadingIoWrite)) {
1147             updateFlags |= KSysGuard::Processes::IOStatistics;
1148         }
1149         if (!d->mUi->treeView->isColumnHidden(ProcessModel::HeadingXMemory)) {
1150             updateFlags |= KSysGuard::Processes::XMemory;
1151         }
1152         // Updating VmPSS every call results in ~4x CPU load on my machine, so do it less often
1153         if (!d->mUi->treeView->isColumnHidden(ProcessModel::HeadingVmPSS) && d->mResortCountDown <= 1) {
1154             updateFlags |= KSysGuard::Processes::Smaps;
1155         }
1156         d->mModel.update(d->mUpdateIntervalMSecs, updateFlags);
1157         if (d->mUpdateTimer) {
1158             d->mUpdateTimer->start(d->mUpdateIntervalMSecs);
1159         }
1160         Q_EMIT updated();
1161         if (QToolTip::isVisible() && qApp->topLevelAt(QCursor::pos()) == window()) {
1162             QWidget *w = d->mUi->treeView->viewport();
1163             if (w->geometry().contains(d->mUi->treeView->mapFromGlobal(QCursor::pos()))) {
1164                 QHelpEvent event(QEvent::ToolTip, w->mapFromGlobal(QCursor::pos()), QCursor::pos());
1165                 qApp->notify(w, &event);
1166             }
1167         }
1168         if (--d->mResortCountDown <= 0) {
1169             d->mResortCountDown = 2; // resort every second time
1170             // resort now
1171             QHeaderView *header = d->mUi->treeView->header();
1172             d->mUi->treeView->sortByColumn(header->sortIndicatorSection(), header->sortIndicatorOrder());
1173         }
1174         if (d->mNeedToExpandInit) {
1175             expandInit();
1176             d->mNeedToExpandInit = false;
1177         }
1178     }
1179 }
1180 
1181 int KSysGuardProcessList::updateIntervalMSecs() const
1182 {
1183     return d->mUpdateIntervalMSecs;
1184 }
1185 
1186 void KSysGuardProcessList::setUpdateIntervalMSecs(int intervalMSecs)
1187 {
1188     if (intervalMSecs == d->mUpdateIntervalMSecs) {
1189         return;
1190     }
1191 
1192     d->mUpdateIntervalMSecs = intervalMSecs;
1193     if (intervalMSecs <= 0) { // no point keep the timer around if we aren't updating automatically
1194         delete d->mUpdateTimer;
1195         d->mUpdateTimer = nullptr;
1196         return;
1197     }
1198 
1199     if (!d->mUpdateTimer) {
1200         // intervalMSecs is a valid time, so set up a timer
1201         d->mUpdateTimer = new QTimer(this);
1202         d->mUpdateTimer->setSingleShot(true);
1203         connect(d->mUpdateTimer, &QTimer::timeout, this, &KSysGuardProcessList::updateList);
1204         if (isVisible()) {
1205             d->mUpdateTimer->start(d->mUpdateIntervalMSecs);
1206         }
1207     } else {
1208         d->mUpdateTimer->setInterval(d->mUpdateIntervalMSecs);
1209     }
1210 }
1211 
1212 bool KSysGuardProcessList::reniceProcesses(const QList<long long> &pids, int niceValue)
1213 {
1214     auto result = d->mProcessController->setPriority(pids, niceValue);
1215     if (result == KSysGuard::ProcessController::Result::Success) {
1216         updateList();
1217         return true;
1218     } else if (result == KSysGuard::ProcessController::Result::Error) {
1219         KMessageBox::error(this,
1220                            i18n("You do not have the permission to renice the process and there "
1221                                 "was a problem trying to run as root."));
1222     }
1223     return true;
1224 }
1225 
1226 QList<KSysGuard::Process *> KSysGuardProcessList::selectedProcesses() const
1227 {
1228     QList<KSysGuard::Process *> processes;
1229     QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
1230     for (int i = 0; i < selectedIndexes.size(); ++i) {
1231         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(d->mFilterModel.mapToSource(selectedIndexes.at(i)).internalPointer());
1232         processes << process;
1233     }
1234     return processes;
1235 }
1236 
1237 void KSysGuardProcessList::reniceSelectedProcesses()
1238 {
1239     QList<long long> pids;
1240     QPointer<ReniceDlg> reniceDlg;
1241     {
1242         QList<KSysGuard::Process *> processes = selectedProcesses();
1243         QStringList selectedAsStrings;
1244 
1245         if (processes.isEmpty()) {
1246             KMessageBox::error(this, i18n("You must select a process first."));
1247             return;
1248         }
1249 
1250         int sched = -2;
1251         int iosched = -2;
1252         foreach (KSysGuard::Process *process, processes) {
1253             pids << process->pid();
1254             selectedAsStrings << d->mModel.getStringForProcess(process);
1255             if (sched == -2) {
1256                 sched = (int)process->scheduler();
1257             } else if (sched != -1 && sched != (int)process->scheduler()) {
1258                 sched = -1; // If two processes have different schedulers, disable the cpu scheduler stuff
1259             }
1260             if (iosched == -2) {
1261                 iosched = (int)process->ioPriorityClass();
1262             } else if (iosched != -1 && iosched != (int)process->ioPriorityClass()) {
1263                 iosched = -1; // If two processes have different schedulers, disable the cpu scheduler stuff
1264             }
1265         }
1266         int firstPriority = processes.first()->niceLevel();
1267         int firstIOPriority = processes.first()->ioniceLevel();
1268 
1269         bool supportsIoNice = d->mModel.processController()->supportsIoNiceness();
1270         if (!supportsIoNice) {
1271             iosched = -2;
1272             firstIOPriority = -2;
1273         }
1274         reniceDlg = new ReniceDlg(d->mUi->treeView, selectedAsStrings, firstPriority, sched, firstIOPriority, iosched);
1275         if (reniceDlg->exec() == QDialog::Rejected) {
1276             delete reniceDlg;
1277             return;
1278         }
1279     }
1280 
1281     // Because we've done into ReniceDlg, which calls processEvents etc, our processes list is no
1282     // longer valid
1283 
1284     QList<long long> renicePids;
1285     QList<long long> changeCPUSchedulerPids;
1286     QList<long long> changeIOSchedulerPids;
1287     foreach (long long pid, pids) {
1288         KSysGuard::Process *process = d->mModel.getProcess(pid);
1289         if (!process) {
1290             continue;
1291         }
1292 
1293         switch (reniceDlg->newCPUSched) {
1294         case -2:
1295         case -1: // Invalid, not changed etc.
1296             break; // So do nothing
1297         case KSysGuard::Process::Other:
1298         case KSysGuard::Process::Fifo: // Don't know if some other
1299                                        // system uses SCHED_FIFO
1300                                        // with niceness. Linux
1301                                        // doesn't
1302         case KSysGuard::Process::Batch:
1303             if (reniceDlg->newCPUSched != (int)process->scheduler()) {
1304                 changeCPUSchedulerPids << pid;
1305                 renicePids << pid;
1306             } else if (reniceDlg->newCPUPriority != process->niceLevel()) {
1307                 renicePids << pid;
1308             }
1309             break;
1310 
1311         case KSysGuard::Process::RoundRobin:
1312             if (reniceDlg->newCPUSched != (int)process->scheduler() || reniceDlg->newCPUPriority != process->niceLevel()) {
1313                 changeCPUSchedulerPids << pid;
1314             }
1315             break;
1316         }
1317         switch (reniceDlg->newIOSched) {
1318         case -2:
1319         case -1: // Invalid, not changed etc.
1320             break; // So do nothing
1321         case KSysGuard::Process::None:
1322             if (reniceDlg->newIOSched != (int)process->ioPriorityClass()) {
1323                 // Unfortunately linux doesn't actually let us set the ioniceness back to none after being set to something else
1324                 if (process->ioPriorityClass() != KSysGuard::Process::BestEffort || reniceDlg->newIOPriority != process->ioniceLevel()) {
1325                     changeIOSchedulerPids << pid;
1326                 }
1327             }
1328             break;
1329         case KSysGuard::Process::Idle:
1330             if (reniceDlg->newIOSched != (int)process->ioPriorityClass()) {
1331                 changeIOSchedulerPids << pid;
1332             }
1333             break;
1334         case KSysGuard::Process::BestEffort:
1335             if (process->ioPriorityClass() == KSysGuard::Process::None && reniceDlg->newIOPriority == (process->niceLevel() + 20) / 5) {
1336                 // Don't set to BestEffort if it's on None and the nicelevel wouldn't change
1337             }
1338             break;
1339         case KSysGuard::Process::RealTime:
1340             if (reniceDlg->newIOSched != (int)process->ioPriorityClass() || reniceDlg->newIOPriority != process->ioniceLevel()) {
1341                 changeIOSchedulerPids << pid;
1342             }
1343             break;
1344         }
1345     }
1346     if (!changeCPUSchedulerPids.isEmpty()) {
1347         Q_ASSERT(reniceDlg->newCPUSched >= 0);
1348         if (!changeCpuScheduler(changeCPUSchedulerPids, (KSysGuard::Process::Scheduler)reniceDlg->newCPUSched, reniceDlg->newCPUPriority)) {
1349             delete reniceDlg;
1350             return;
1351         }
1352     }
1353     if (!renicePids.isEmpty()) {
1354         Q_ASSERT(reniceDlg->newCPUPriority <= 20 && reniceDlg->newCPUPriority >= -20);
1355         if (!reniceProcesses(renicePids, reniceDlg->newCPUPriority)) {
1356             delete reniceDlg;
1357             return;
1358         }
1359     }
1360     if (!changeIOSchedulerPids.isEmpty()) {
1361         if (!changeIoScheduler(changeIOSchedulerPids, (KSysGuard::Process::IoPriorityClass)reniceDlg->newIOSched, reniceDlg->newIOPriority)) {
1362             delete reniceDlg;
1363             return;
1364         }
1365     }
1366     delete reniceDlg;
1367     updateList();
1368 }
1369 
1370 bool KSysGuardProcessList::changeIoScheduler(const QList<long long> &pids, KSysGuard::Process::IoPriorityClass newIoSched, int newIoSchedPriority)
1371 {
1372     auto result = d->mProcessController->setIOScheduler(pids, newIoSched, newIoSchedPriority);
1373     if (result == KSysGuard::ProcessController::Result::Success) {
1374         updateList();
1375         return true;
1376     } else if (result == KSysGuard::ProcessController::Result::Error) {
1377         KMessageBox::error(this,
1378                            i18n("You do not have the permission to change the I/O priority of the process and there "
1379                                 "was a problem trying to run as root."));
1380     }
1381 
1382     return false;
1383 }
1384 
1385 bool KSysGuardProcessList::changeCpuScheduler(const QList<long long> &pids, KSysGuard::Process::Scheduler newCpuSched, int newCpuSchedPriority)
1386 {
1387     auto result = d->mProcessController->setCPUScheduler(pids, newCpuSched, newCpuSchedPriority);
1388 
1389     if (result == KSysGuard::ProcessController::Result::Success) {
1390         updateList();
1391         return true;
1392     } else if (result == KSysGuard::ProcessController::Result::Error) {
1393         KMessageBox::error(this,
1394                            i18n("You do not have the permission to change the CPU Scheduler for the process and there "
1395                                 "was a problem trying to run as root."));
1396     }
1397     return false;
1398 }
1399 
1400 bool KSysGuardProcessList::killProcesses(const QList<long long> &pids, int sig)
1401 {
1402     auto result = d->mProcessController->sendSignal(pids, sig);
1403 
1404     if (result == KSysGuard::ProcessController::Result::Success) {
1405         updateList();
1406         return true;
1407     } else if (result == KSysGuard::ProcessController::Result::Error) {
1408         KMessageBox::error(this,
1409                            i18n("You do not have the permission to kill the process and there "
1410                                 "was a problem trying to run as root."));
1411     }
1412     return false;
1413 }
1414 
1415 void KSysGuardProcessList::killSelectedProcesses()
1416 {
1417     sendSignalToSelectedProcesses(SIGTERM, true);
1418 }
1419 
1420 void KSysGuardProcessList::sendSignalToSelectedProcesses(int sig, bool confirm)
1421 {
1422     QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
1423     QStringList selectedAsStrings;
1424     QList<long long> selectedPids;
1425 
1426     QList<KSysGuard::Process *> processes = selectedProcesses();
1427     foreach (KSysGuard::Process *process, processes) {
1428         selectedPids << process->pid();
1429         if (!confirm) {
1430             continue;
1431         }
1432         QString name = d->mModel.getStringForProcess(process);
1433         selectedAsStrings << name;
1434     }
1435 
1436     if (selectedPids.isEmpty()) {
1437         if (confirm) {
1438             KMessageBox::error(this, i18n("You must select a process first."));
1439         }
1440         return;
1441     } else if (confirm && (sig == SIGTERM || sig == SIGKILL)) {
1442         int count = selectedAsStrings.count();
1443         QString msg;
1444         QString title;
1445         QString dontAskAgainKey;
1446         QString closeButton;
1447         if (sig == SIGTERM) {
1448             msg = i18np("Are you sure you want to end this process?  Any unsaved work may be lost.",
1449                         "Are you sure you want to end these %1 processes?  Any unsaved work may be lost",
1450                         count);
1451             title = i18ncp("Dialog title", "End Process", "End %1 Processes", count);
1452             dontAskAgainKey = QStringLiteral("endconfirmation");
1453             closeButton = i18n("End");
1454         } else if (sig == SIGKILL) {
1455             msg = i18np("<qt>Are you sure you want to <b>immediately and forcibly kill</b> this process?  Any unsaved work may be lost.",
1456                         "<qt>Are you sure you want to <b>immediately and forcibly kill</b> these %1 processes?  Any unsaved work may be lost",
1457                         count);
1458             title = i18ncp("Dialog title", "Forcibly Kill Process", "Forcibly Kill %1 Processes", count);
1459             dontAskAgainKey = QStringLiteral("killconfirmation");
1460             closeButton = i18n("Kill");
1461         }
1462 
1463         int res = KMessageBox::warningContinueCancelList(this,
1464                                                          msg,
1465                                                          selectedAsStrings,
1466                                                          title,
1467                                                          KGuiItem(closeButton, QStringLiteral("process-stop")),
1468                                                          KStandardGuiItem::cancel(),
1469                                                          dontAskAgainKey);
1470         if (res != KMessageBox::Continue) {
1471             return;
1472         }
1473     }
1474 
1475     // We have shown a GUI dialog box, which processes events etc.
1476     // So processes is NO LONGER VALID
1477 
1478     if (!killProcesses(selectedPids, sig)) {
1479         return;
1480     }
1481     if (sig == SIGTERM || sig == SIGKILL) {
1482         foreach (long long pid, selectedPids) {
1483             KSysGuard::Process *process = d->mModel.getProcess(pid);
1484             if (process) {
1485                 process->timeKillWasSent().start();
1486             }
1487             d->mUi->treeView->selectionModel()->clearSelection();
1488         }
1489     }
1490     updateList();
1491 }
1492 
1493 bool KSysGuardProcessList::showTotals() const
1494 {
1495     return d->mModel.showTotals();
1496 }
1497 
1498 void KSysGuardProcessList::setShowTotals(bool showTotals) // slot
1499 {
1500     d->mModel.setShowTotals(showTotals);
1501 }
1502 
1503 ProcessModel::Units KSysGuardProcessList::units() const
1504 {
1505     return d->mModel.units();
1506 }
1507 
1508 void KSysGuardProcessList::setUnits(ProcessModel::Units unit)
1509 {
1510     d->mModel.setUnits(unit);
1511 }
1512 
1513 void KSysGuardProcessList::saveSettings(KConfigGroup &cg)
1514 {
1515     /* Note that the ksysguard program does not use these functions.  It saves the settings itself to an xml file instead */
1516     cg.writeEntry("units", (int)(units()));
1517     cg.writeEntry("ioUnits", (int)(d->mModel.ioUnits()));
1518     cg.writeEntry("ioInformation", (int)(d->mModel.ioInformation()));
1519     cg.writeEntry("showCommandLineOptions", d->mModel.isShowCommandLineOptions());
1520     cg.writeEntry("normalizeCPUUsage", d->mModel.isNormalizedCPUUsage());
1521     cg.writeEntry("showTooltips", d->mModel.isShowingTooltips());
1522     cg.writeEntry("showTotals", showTotals());
1523     cg.writeEntry("filterState", (int)(state()));
1524     cg.writeEntry("updateIntervalMSecs", updateIntervalMSecs());
1525     cg.writeEntry("headerState", d->mUi->treeView->header()->saveState());
1526     // If we change, say, the header between versions of ksysguard, then the old headerState settings will not be valid.
1527     // The version property lets us keep track of which version we are
1528     cg.writeEntry("version", PROCESSHEADERVERSION);
1529 }
1530 
1531 void KSysGuardProcessList::loadSettings(const KConfigGroup &cg)
1532 {
1533     /* Note that the ksysguard program does not use these functions.  It saves the settings itself to an xml file instead */
1534     setUnits((ProcessModel::Units)cg.readEntry("units", (int)ProcessModel::UnitsKB));
1535     d->mModel.setIoUnits((ProcessModel::Units)cg.readEntry("ioUnits", (int)ProcessModel::UnitsKB));
1536     d->mModel.setIoInformation((ProcessModel::IoInformation)cg.readEntry("ioInformation", (int)ProcessModel::ActualBytesRate));
1537     d->mModel.setShowCommandLineOptions(cg.readEntry("showCommandLineOptions", false));
1538     d->mModel.setNormalizedCPUUsage(cg.readEntry("normalizeCPUUsage", true));
1539     d->mModel.setShowingTooltips(cg.readEntry("showTooltips", true));
1540     setShowTotals(cg.readEntry("showTotals", true));
1541     setStateInt(cg.readEntry("filterState", (int)ProcessFilter::AllProcesses));
1542     setUpdateIntervalMSecs(cg.readEntry("updateIntervalMSecs", 2000));
1543     int version = cg.readEntry("version", 0);
1544     if (version == PROCESSHEADERVERSION) { // If the header has changed, the old settings are no longer valid.  Only restore if version is the same
1545         restoreHeaderState(cg.readEntry("headerState", QByteArray()));
1546     }
1547 }
1548 
1549 void KSysGuardProcessList::restoreHeaderState(const QByteArray &state)
1550 {
1551     d->mUi->treeView->header()->restoreState(state);
1552 }
1553 
1554 bool KSysGuardProcessList::eventFilter(QObject *obj, QEvent *event)
1555 {
1556     if (event->type() == QEvent::KeyPress) {
1557         QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
1558         if (obj == d->mUi->treeView) {
1559             if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
1560                 d->mUi->treeView->selectionModel()->select(d->mUi->treeView->currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Rows);
1561                 showProcessContextMenu(d->mUi->treeView->currentIndex());
1562                 return true;
1563 
1564             } else if (keyEvent->matches(QKeySequence::MoveToPreviousLine) || keyEvent->matches(QKeySequence::SelectPreviousLine)
1565                        || keyEvent->matches(QKeySequence::MoveToPreviousPage) || keyEvent->matches(QKeySequence::SelectPreviousPage)) {
1566                 if (d->mUi->treeView->selectionModel()->selectedRows().size() == 1 && d->mUi->treeView->selectionModel()->selectedRows().first().row() == 0) {
1567                     // when first row is selected, pressing up or pgup moves to the textfield
1568                     d->mUi->txtFilter->setFocus();
1569                     return true;
1570                 }
1571             } else if (!keyEvent->text().isEmpty() && keyEvent->key() != Qt::Key_Tab
1572                        && (!keyEvent->modifiers() || keyEvent->modifiers() == Qt::ShiftModifier)) {
1573                 // move to textfield and forward keyevent if user starts typing from treeview
1574                 d->mUi->txtFilter->setFocus();
1575                 QApplication::sendEvent(d->mUi->txtFilter, event);
1576                 return true;
1577             }
1578         } else {
1579             Q_ASSERT(obj == d->mUi->txtFilter);
1580             if (d->mUi->treeView->model()->rowCount() == 0) {
1581                 // treeview is empty, do nothing
1582                 return false;
1583             }
1584 
1585             if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
1586                 // pressing enter will send enter to the first row in the list
1587                 // the focusin eventfilter will make sure the first row is selected if there was
1588                 // no previous selection
1589                 d->mUi->treeView->setFocus();
1590                 QApplication::sendEvent(d->mUi->treeView, event);
1591                 return true;
1592 
1593             } else if (keyEvent->matches(QKeySequence::MoveToNextLine) || keyEvent->matches(QKeySequence::SelectNextLine)
1594                        || keyEvent->matches(QKeySequence::MoveToNextPage) || keyEvent->matches(QKeySequence::SelectNextPage)) {
1595                 // attempting to move down by down-key or pgdown, or pressing enter will move focus
1596                 // to the treeview
1597                 d->mUi->treeView->setFocus();
1598                 return true;
1599             }
1600         }
1601     }
1602     return false;
1603 }
1604 
1605 ProcessModel *KSysGuardProcessList::processModel()
1606 {
1607     return &d->mModel;
1608 }
1609 
1610 void KSysGuardProcessList::setKillButtonVisible(bool visible)
1611 {
1612     d->mUi->btnKillProcess->setVisible(visible);
1613 }
1614 
1615 bool KSysGuardProcessList::isKillButtonVisible() const
1616 {
1617     return d->mUi->btnKillProcess->isVisible();
1618 }
1619 
1620 bool KSysGuardProcessList::scriptingEnabled() const
1621 {
1622     return !!d->mScripting;
1623 }
1624 
1625 void KSysGuardProcessList::setScriptingEnabled(bool enabled)
1626 {
1627     if (!!d->mScripting == enabled) {
1628         return; // Nothing changed
1629     }
1630     if (!enabled) {
1631         delete d->mScripting;
1632         d->mScripting = nullptr;
1633     } else {
1634         d->mScripting = new Scripting(this);
1635         d->mScripting->hide();
1636     }
1637 }