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