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 }