Warning, file /plasma/libksysguard/processui/ksysguardprocesslist.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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