2
KSysGuard, the KDE System Guard
4
Copyright (c) 1999 - 2001 Chris Schlaeger <cs@kde.org>
5
Copyright (c) 2006 - 2007 John Tapsell <john.tapsell@kde.org>
7
This library is free software; you can redistribute it and/or
8
modify it under the terms of the GNU Library General Public
9
License as published by the Free Software Foundation; either
10
version 2 of the License, or (at your option) any later version.
12
This library is distributed in the hope that it will be useful,
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
Library General Public License for more details.
17
You should have received a copy of the GNU Library General Public License
18
along with this library; see the file COPYING.LIB. If not, write to
19
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20
Boston, MA 02110-1301, USA.
24
#include "ksysguardprocesslist.moc"
25
#include "ksysguardprocesslist.h"
27
#include "../config-ksysguard.h"
29
#include <QApplication>
34
#include <QHeaderView>
40
#include <QStyledItemDelegate>
43
#include <QSignalMapper>
45
#include <QAbstractItemModel>
48
#include <signal.h> //For SIGTERM
53
#include <kmessagebox.h>
57
#include <KWindowSystem>
59
#include "ReniceDlg.h"
60
#include "ui_ProcessWidgetUI.h"
61
#include "scripting.h"
63
#include <sys/types.h>
66
//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 in this directory and uncomment the next line
67
//#define DO_MODELCHECK
69
#include "modeltest.h"
71
class ProgressBarItemDelegate : public QStyledItemDelegate
74
ProgressBarItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {
77
virtual void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const
79
QStyleOptionViewItemV4 option = opt;
80
initStyleOption(&option,index);
82
float percentage = index.data(ProcessModel::PercentageRole).toFloat();
84
drawPercentageDisplay(painter,option, percentage);
86
QStyledItemDelegate::paint(painter, option, index);
90
inline void drawPercentageDisplay(QPainter *painter, QStyleOptionViewItemV4 &option, float percentage) const
92
QStyle *style = option.widget ? option.widget->style() : QApplication::style();
94
// draw the background
95
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, option.widget);
97
QPalette::ColorGroup cg = option.state & QStyle::State_Enabled
98
? QPalette::Normal : QPalette::Disabled;
99
if (cg == QPalette::Normal && !(option.state & QStyle::State_Active))
100
cg = QPalette::Inactive;
102
//Now draw our percentage thingy
103
const QRect &rect = option.rect;
104
int size = qMin(percentage,1.0f) * rect.width();
105
if(size > 2 ) { //make sure the line will have a width of more than 1 pixel
106
painter->setPen(Qt::NoPen);
107
QColor color = option.palette.color(cg, QPalette::Link);
110
painter->fillRect( rect.x(), rect.y(), size, rect.height(), color);
114
if (!option.text.isEmpty()) {
115
QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &option, option.widget);
118
if (option.state & QStyle::State_Selected) {
119
painter->setPen(option.palette.color(cg, QPalette::HighlightedText));
121
painter->setPen(option.palette.color(cg, QPalette::Text));
124
painter->setFont(option.font);
125
QTextOption textOption;
126
textOption.setWrapMode(QTextOption::ManualWrap);
127
textOption.setTextDirection(option.direction);
128
textOption.setAlignment(QStyle::visualAlignment(option.direction, option.displayAlignment));
130
painter->drawText(textRect, option.text, textOption);
133
// draw the focus rect
134
if (option.state & QStyle::State_HasFocus) {
135
QStyleOptionFocusRect o;
136
o.QStyleOption::operator=(option);
137
o.rect = style->subElementRect(QStyle::SE_ItemViewItemFocusRect, &option, option.widget);
138
o.state |= QStyle::State_KeyboardFocusChange;
139
o.state |= QStyle::State_Item;
140
QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled)
141
? QPalette::Normal : QPalette::Disabled;
142
o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected)
143
? QPalette::Highlight : QPalette::Window);
144
style->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, option.widget);
149
struct KSysGuardProcessListPrivate {
151
KSysGuardProcessListPrivate(KSysGuardProcessList* q, const QString &hostName)
152
: mModel(q, hostName), mFilterModel(q), mUi(new Ui::ProcessWidget()), mProcessContextMenu(NULL), mUpdateTimer(NULL)
155
mNeedToExpandInit = false;
156
mNumItemsSelected = -1;
157
mResortCountDown = 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
158
renice = new KAction(i18np("Set Priority...", "Set Priority...", 1), q);
159
renice->setShortcut(Qt::Key_F8);
160
selectParent = new KAction(i18n("Jump to Parent Process"), q);
162
selectTracer = new KAction(i18n("Jump to Process Debugging This One"), q);
163
window = new KAction(i18n("Show Application Window"), q);
164
resume = new KAction(i18n("Resume Stopped Process"), q);
165
terminate = new KAction(i18np("End Process", "End Processes", 1), q);
166
terminate->setIcon(KIcon("process-stop"));
167
terminate->setShortcut(Qt::Key_Delete);
168
kill = new KAction(i18np("Forcibly Kill Process", "Forcibly Kill Processes", 1), q);
169
kill->setIcon(KIcon("process-stop"));
170
kill->setShortcut(Qt::SHIFT + Qt::Key_Delete);
172
sigStop = new KAction(i18n("Suspend (STOP)"), q);
173
sigCont = new KAction(i18n("Continue (CONT)"), q);
174
sigHup = new KAction(i18n("Hangup (HUP)"), q);
175
sigInt = new KAction(i18n("Interrupt (INT)"), q);
176
sigTerm = new KAction(i18n("Terminate (TERM)"), q);
177
sigKill = new KAction(i18n("Kill (KILL)"), q);
178
sigUsr1 = new KAction(i18n("User 1 (USR1)"), q);
179
sigUsr2 = new KAction(i18n("User 2 (USR2)"), q);
181
//Set up '/' as a shortcut to jump to the quick search text widget
182
jumpToSearchFilter = new KAction(i18n("Focus on Quick Search"), q);
183
jumpToSearchFilter->setShortcuts(QList<QKeySequence>() << QKeySequence::Find << '/');
186
~KSysGuardProcessListPrivate() { delete mUi; mUi = NULL; }
188
/** The number rows and their children for the given parent in the mFilterModel model */
189
int totalRowCount(const QModelIndex &parent) const;
191
/** Helper function to setup 'action' with the given pids */
192
void setupKAuthAction(KAuth::Action *action, const QList<long long> & pids) const;
194
/** fire a timer event if we are set to use our internal timer*/
195
void fireTimerEvent();
197
/** The process model. This contains all the data on all the processes running on the system */
200
/** The process filter. The mModel is connected to this, and this filter model connects to the view. This lets us
201
* sort the view and filter (by using the combo box or the search line)
203
ProcessFilter mFilterModel;
205
/** The graphical user interface for this process list widget, auto-generated by Qt Designer */
206
Ui::ProcessWidget *mUi;
208
/** The context menu when you right click on a process */
209
QMenu *mProcessContextMenu;
211
/** A timer to call updateList() every mUpdateIntervalMSecs.
212
* NULL is mUpdateIntervalMSecs is <= 0. */
213
QTimer *mUpdateTimer;
215
/** The time to wait, in milliseconds, between updating the process list */
216
int mUpdateIntervalMSecs;
218
/** Number of items that are selected */
219
int mNumItemsSelected;
221
/** Class to deal with the scripting. NULL if scripting is disabled */
222
Scripting *mScripting;
224
/** A counter to mark when to resort, so that we do not resort on every update */
225
int mResortCountDown;
227
bool mNeedToExpandInit;
232
KAction *selectParent;
233
KAction *selectTracer;
234
KAction *jumpToSearchFilter;
247
KSysGuardProcessList::KSysGuardProcessList(QWidget* parent, const QString &hostName)
248
: QWidget(parent), d(new KSysGuardProcessListPrivate(this, hostName))
250
qRegisterMetaType<QList<long long> >();
251
qDBusRegisterMetaType<QList<long long> >();
253
d->mUpdateIntervalMSecs = 0; //Set process to not update manually by default
254
d->mUi->setupUi(this);
255
d->mFilterModel.setSourceModel(&d->mModel);
256
d->mUi->treeView->setModel(&d->mFilterModel);
258
new ModelTest(&d->mModel, this);
260
d->mUi->treeView->setItemDelegate(new ProgressBarItemDelegate(d->mUi->treeView));
262
d->mUi->treeView->header()->setContextMenuPolicy(Qt::CustomContextMenu);
263
connect(d->mUi->treeView->header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showColumnContextMenu(QPoint)));
265
d->mProcessContextMenu = new QMenu(d->mUi->treeView);
266
d->mUi->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
267
connect(d->mUi->treeView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showProcessContextMenu(QPoint)));
269
d->mUi->treeView->header()->setClickable(true);
270
d->mUi->treeView->header()->setSortIndicatorShown(true);
271
d->mUi->treeView->header()->setCascadingSectionResizes(false);
272
connect(d->mUi->btnKillProcess, SIGNAL(clicked()), this, SLOT(killSelectedProcesses()));
273
connect(d->mUi->txtFilter, SIGNAL(textChanged(QString)), this, SLOT(filterTextChanged(QString)));
274
connect(d->mUi->cmbFilter, SIGNAL(currentIndexChanged(int)), this, SLOT(setStateInt(int)));
275
connect(d->mUi->treeView, SIGNAL(expanded(QModelIndex)), this, SLOT(expandAllChildren(QModelIndex)));
276
connect(d->mUi->treeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged()));
277
connect(&d->mFilterModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)));
278
connect(&d->mFilterModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SIGNAL(processListChanged()));
279
setMinimumSize(sizeHint());
281
d->mFilterModel.setFilterKeyColumn(-1);
283
/* Hide various columns by default, to reduce information overload */
284
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingVmSize);
285
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingNiceness);
286
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingTty);
287
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingCommand);
288
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingPid);
289
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingCPUTime);
290
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingIoRead);
291
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingIoWrite);
292
d->mUi->treeView->header()->hideSection(ProcessModel::HeadingXMemory);
293
// NOTE! After this is all setup, the settings for the header are restored
294
// from the user's last run. (in restoreHeaderState)
295
// So making changes here only affects the default settings. To
296
// test changes temporarily, comment out the lines in restoreHeaderState.
297
// When you are happy with the changes and want to commit, increase the
298
// value of PROCESSHEADERVERSION. This will force the header state
299
// to be reset back to the defaults for all users.
300
d->mUi->treeView->header()->resizeSection(ProcessModel::HeadingCPUUsage, d->mUi->treeView->header()->sectionSizeHint(ProcessModel::HeadingCPUUsage));
301
d->mUi->treeView->header()->resizeSection(ProcessModel::HeadingMemory, d->mUi->treeView->header()->sectionSizeHint(ProcessModel::HeadingMemory));
302
d->mUi->treeView->header()->resizeSection(ProcessModel::HeadingSharedMemory, d->mUi->treeView->header()->sectionSizeHint(ProcessModel::HeadingSharedMemory));
303
d->mUi->treeView->header()->setResizeMode(0, QHeaderView::Interactive);
304
d->mUi->treeView->header()->setStretchLastSection(true);
306
//Process names can have mixed case. Make the filter case insensitive.
307
d->mFilterModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
308
d->mFilterModel.setSortCaseSensitivity(Qt::CaseInsensitive);
310
d->mUi->txtFilter->installEventFilter(this);
311
d->mUi->treeView->installEventFilter(this);
313
d->mUi->treeView->setDragEnabled(true);
314
d->mUi->treeView->setDragDropMode(QAbstractItemView::DragOnly);
317
//Sort by username by default
318
d->mUi->treeView->sortByColumn(ProcessModel::HeadingUser, Qt::AscendingOrder);
320
// Add all the actions to the main widget, and get all the actions to call actionTriggered when clicked
321
QSignalMapper *signalMapper = new QSignalMapper(this);
322
QList<QAction *> actions;
323
actions << d->renice << d->kill << d->terminate << d->selectParent << d->selectTracer << d->window << d->jumpToSearchFilter;
324
actions << d->resume << d->sigStop << d->sigCont << d->sigHup << d->sigInt << d->sigTerm << d->sigKill << d->sigUsr1 << d->sigUsr2;
326
foreach(QAction *action, actions) {
328
connect(action, SIGNAL(triggered(bool)), signalMapper, SLOT(map()));
329
signalMapper->setMapping(action, action);
331
connect(signalMapper, SIGNAL(mapped(QObject*)), SLOT(actionTriggered(QObject*)));
335
d->mUi->btnKillProcess->setIcon(KIcon("process-stop"));
336
d->mUi->btnKillProcess->setToolTip(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 technical information.<br>To target a specific window to kill, press Ctrl+Alt+Esc at any time."));
339
KSysGuardProcessList::~KSysGuardProcessList()
344
QTreeView *KSysGuardProcessList::treeView() const {
345
return d->mUi->treeView;
348
QLineEdit *KSysGuardProcessList::filterLineEdit() const {
349
return d->mUi->txtFilter;
352
ProcessFilter::State KSysGuardProcessList::state() const
354
return d->mFilterModel.filter();
356
void KSysGuardProcessList::setStateInt(int state) {
357
setState((ProcessFilter::State) state);
358
d->mUi->treeView->scrollTo( d->mUi->treeView->currentIndex());
360
void KSysGuardProcessList::setState(ProcessFilter::State state)
361
{ //index is the item the user selected in the combo box
362
d->mFilterModel.setFilter(state);
363
d->mModel.setSimpleMode( (state != ProcessFilter::AllProcessesInTreeForm) );
364
d->mUi->cmbFilter->setCurrentIndex( (int)state);
368
void KSysGuardProcessList::filterTextChanged(const QString &newText) {
369
d->mFilterModel.setFilterRegExp(newText.trimmed());
372
d->mUi->btnKillProcess->setEnabled( d->mUi->treeView->selectionModel()->hasSelection() );
373
d->mUi->treeView->scrollTo( d->mUi->treeView->currentIndex());
376
int KSysGuardProcessList::visibleProcessesCount() const {
377
//This assumes that all the visible rows are processes. This is true currently, but might not be
378
//true if we add support for showing threads etc
379
if(d->mModel.isSimpleMode())
380
return d->mFilterModel.rowCount();
381
return d->totalRowCount(QModelIndex());
384
int KSysGuardProcessListPrivate::totalRowCount(const QModelIndex &parent ) const {
385
int numRows = mFilterModel.rowCount(parent);
387
for (int i = 0; i < numRows; ++i) {
388
QModelIndex index = mFilterModel.index(i, 0,parent);
389
//if it has children add the total
390
if (mFilterModel.hasChildren(index))
391
total += totalRowCount(index);
396
void KSysGuardProcessListPrivate::setupKAuthAction(KAuth::Action *action, const QList<long long> & pids) const
398
action->setHelperID("org.kde.ksysguard.processlisthelper");
400
int processCount = pids.count();
401
for(int i = 0; i < processCount; i++) {
402
action->addArgument(QString("pid%1").arg(i), pids[i]);
404
action->addArgument("pidcount", processCount);
406
void KSysGuardProcessList::selectionChanged()
408
int numSelected = d->mUi->treeView->selectionModel()->selectedRows().size();
409
if(numSelected == d->mNumItemsSelected)
411
d->mNumItemsSelected = numSelected;
412
d->mUi->btnKillProcess->setEnabled( numSelected != 0 );
414
d->renice->setText(i18np("Set Priority...", "Set Priority...", numSelected));
415
d->kill->setText(i18np("Forcibly Kill Process", "Forcibly Kill Processes", numSelected));
416
d->terminate->setText(i18ncp("Context menu", "End Process", "End Processes", numSelected));
418
void KSysGuardProcessList::showProcessContextMenu(const QModelIndex &index) {
419
if(!index.isValid()) return;
420
QRect rect = d->mUi->treeView->visualRect(index);
421
QPoint point(rect.x() + rect.width()/4, rect.y() + rect.height()/2 );
422
showProcessContextMenu(point);
424
void KSysGuardProcessList::showProcessContextMenu(const QPoint &point) {
425
d->mProcessContextMenu->clear();
427
QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
428
int numProcesses = selectedIndexes.size();
430
if(numProcesses == 0) return; //No processes selected, so no context menu
432
QModelIndex realIndex = d->mFilterModel.mapToSource(selectedIndexes.at(0));
433
KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *> (realIndex.internalPointer());
437
//If the selected process is a zombie, do not bother offering renice and kill options
438
bool showSignalingEntries = numProcesses != 1 || process->status != KSysGuard::Process::Zombie;
439
if(showSignalingEntries) {
440
d->mProcessContextMenu->addAction(d->renice);
441
QMenu *signalMenu = d->mProcessContextMenu->addMenu(i18n("Send Signal"));
442
signalMenu->addAction(d->sigStop);
443
signalMenu->addAction(d->sigCont);
444
signalMenu->addAction(d->sigHup);
445
signalMenu->addAction(d->sigInt);
446
signalMenu->addAction(d->sigTerm);
447
signalMenu->addAction(d->sigKill);
448
signalMenu->addAction(d->sigUsr1);
449
signalMenu->addAction(d->sigUsr2);
452
if(numProcesses == 1 && process->parent_pid > 1) {
453
//As a design decision, I do not show the 'Jump to parent process' option when the
454
//parent is just 'init'.
456
KSysGuard::Process *parent_process = d->mModel.getProcess(process->parent_pid);
457
if(parent_process) { //it should not be possible for this process to not exist, but check just incase
458
QString parent_name = parent_process->name;
459
if(parent_name.size() > 20) //Elide the text if it is too long
460
parent_name = parent_process->name.left(15) + QString::fromUtf8("…");
461
d->selectParent->setText(i18n("Jump to Parent Process (%1)", parent_name));
462
d->mProcessContextMenu->addAction(d->selectParent);
466
if(numProcesses == 1 && process->tracerpid >= 0) {
467
//If the process is being debugged, offer to select it
468
d->mProcessContextMenu->addAction(d->selectTracer);
471
if (numProcesses == 1 && !d->mModel.data(realIndex, ProcessModel::WindowIdRole).isNull()) {
472
d->mProcessContextMenu->addAction(d->window);
475
if(numProcesses == 1 && process->status == KSysGuard::Process::Stopped) {
476
//If the process is stopped, offer to resume it
477
d->mProcessContextMenu->addAction(d->resume);
480
if(numProcesses == 1 && d->mScripting) {
481
foreach(QAction *action, d->mScripting->actions()) {
482
d->mProcessContextMenu->addAction(action);
485
if (showSignalingEntries) {
486
d->mProcessContextMenu->addSeparator();
487
d->mProcessContextMenu->addAction(d->terminate);
488
if (numProcesses == 1 && !process->timeKillWasSent.isNull())
489
d->mProcessContextMenu->addAction(d->kill);
492
d->mProcessContextMenu->popup(d->mUi->treeView->viewport()->mapToGlobal(point));
494
void KSysGuardProcessList::actionTriggered(QObject *object) {
495
if(!isVisible()) //Ignore triggered actions if we are not visible!
497
//Reset the text back to normal
498
d->selectParent->setText(i18n("Jump to Parent Process"));
499
QAction *result = qobject_cast<QAction *>(object);
501
//Escape was pressed. Do nothing.
502
} else if(result == d->renice) {
503
reniceSelectedProcesses();
504
} else if(result == d->terminate) {
505
sendSignalToSelectedProcesses(SIGTERM, true);
506
} else if(result == d->kill) {
507
sendSignalToSelectedProcesses(SIGKILL, true);
508
} else if(result == d->selectParent) {
509
QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
510
int numProcesses = selectedIndexes.size();
511
if(numProcesses == 0) return; //No processes selected
512
QModelIndex realIndex = d->mFilterModel.mapToSource(selectedIndexes.at(0));
513
KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *> (realIndex.internalPointer());
515
selectAndJumpToProcess(process->parent_pid);
516
} else if(result == d->selectTracer) {
517
QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
518
int numProcesses = selectedIndexes.size();
519
if(numProcesses == 0) return; //No processes selected
520
QModelIndex realIndex = d->mFilterModel.mapToSource(selectedIndexes.at(0));
521
KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *> (realIndex.internalPointer());
523
selectAndJumpToProcess(process->tracerpid);
524
} else if(result == d->window) {
525
QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
526
int numProcesses = selectedIndexes.size();
527
if(numProcesses == 0) return; //No processes selected
528
foreach( const QModelIndex &index, selectedIndexes) {
529
QModelIndex realIndex = d->mFilterModel.mapToSource(index);
530
QVariant widVar= d->mModel.data(realIndex, ProcessModel::WindowIdRole);
531
if( !widVar.isNull() ) {
532
int wid = widVar.toInt();
533
KWindowSystem::activateWindow(wid);
536
} else if(result == d->jumpToSearchFilter) {
537
d->mUi->txtFilter->setFocus();
540
if(result == d->resume || result == d->sigCont)
541
sig = SIGCONT; //Despite the function name, this sends a signal, rather than kill it. Silly unix :)
542
else if(result == d->sigStop)
544
else if(result == d->sigHup)
546
else if(result == d->sigInt)
548
else if(result == d->sigTerm)
550
else if(result == d->sigKill)
552
else if(result == d->sigUsr1)
554
else if(result == d->sigUsr2)
558
sendSignalToSelectedProcesses(sig, false);
562
void KSysGuardProcessList::selectAndJumpToProcess(int pid) {
563
KSysGuard::Process *process = d->mModel.getProcess(pid);
565
QModelIndex sourceIndex = d->mModel.getQModelIndex(process, 0);
566
QModelIndex filterIndex = d->mFilterModel.mapFromSource( sourceIndex );
567
if(!filterIndex.isValid() && !d->mUi->txtFilter->text().isEmpty()) {
568
//The filter is preventing us from finding the parent. Clear the filter
569
//(It could also be the combo box - should we deal with that case as well?)
570
d->mUi->txtFilter->clear();
571
filterIndex = d->mFilterModel.mapFromSource( sourceIndex );
573
d->mUi->treeView->clearSelection();
574
d->mUi->treeView->setCurrentIndex(filterIndex);
575
d->mUi->treeView->scrollTo( filterIndex, QAbstractItemView::PositionAtCenter);
579
void KSysGuardProcessList::showColumnContextMenu(const QPoint &point){
583
int index = d->mUi->treeView->header()->logicalIndexAt(point);
585
//selected a column. Give the option to hide it
586
action = new QAction(&menu);
587
action->setData(-index-1); //We set data to be negative (and minus 1) to hide a column, and positive to show a column
588
action->setText(i18n("Hide Column '%1'", d->mFilterModel.headerData(index, Qt::Horizontal, Qt::DisplayRole).toString()));
589
menu.addAction(action);
590
if(d->mUi->treeView->header()->sectionsHidden()) {
595
if(d->mUi->treeView->header()->sectionsHidden()) {
596
int num_headings = d->mFilterModel.columnCount();
597
for(int i = 0; i < num_headings; ++i) {
598
if(d->mUi->treeView->header()->isSectionHidden(i)) {
600
if(i == ProcessModel::HeadingXMemory)
603
action = new QAction(&menu);
604
action->setText(i18n("Show Column '%1'", d->mFilterModel.headerData(i, Qt::Horizontal, Qt::DisplayRole).toString()));
605
action->setData(i); //We set data to be negative (and minus 1) to hide a column, and positive to show a column
606
menu.addAction(action);
610
QAction *actionAuto = NULL;
611
QAction *actionKB = NULL;
612
QAction *actionMB = NULL;
613
QAction *actionGB = NULL;
614
QAction *actionPercentage = NULL;
615
QAction *actionShowCmdlineOptions = NULL;
616
QAction *actionShowTooltips = NULL;
617
QAction *actionNormalizeCPUUsage = NULL;
619
QAction *actionIoCharacters = NULL;
620
QAction *actionIoSyscalls = NULL;
621
QAction *actionIoActualCharacters = NULL;
622
QAction *actionIoShowRate = NULL;
623
bool showIoRate = false;
624
if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite)
625
showIoRate = d->mModel.ioInformation() == ProcessModel::BytesRate ||
626
d->mModel.ioInformation() == ProcessModel::SyscallsRate ||
627
d->mModel.ioInformation() == ProcessModel::ActualBytesRate;
629
if( index == ProcessModel::HeadingVmSize || index == ProcessModel::HeadingMemory || index == ProcessModel::HeadingXMemory || index == ProcessModel::HeadingSharedMemory || ( (index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) && d->mModel.ioInformation() != ProcessModel::Syscalls)) {
630
//If the user right clicks on a column that contains a memory size, show a toggle option for displaying
631
//the memory in different units. e.g. "2000 k" or "2 m"
632
menu.addSeparator()->setText(i18n("Display Units"));
633
QActionGroup *unitsGroup = new QActionGroup(&menu);
634
/* Automatic (human readable)*/
635
actionAuto = new QAction(&menu);
636
actionAuto->setText(i18n("Mixed"));
637
actionAuto->setCheckable(true);
638
menu.addAction(actionAuto);
639
unitsGroup->addAction(actionAuto);
641
actionKB = new QAction(&menu);
642
actionKB->setText((showIoRate)?i18n("Kilobytes per second"):i18n("Kilobytes"));
643
actionKB->setCheckable(true);
644
menu.addAction(actionKB);
645
unitsGroup->addAction(actionKB);
647
actionMB = new QAction(&menu);
648
actionMB->setText((showIoRate)?i18n("Megabytes per second"):i18n("Megabytes"));
649
actionMB->setCheckable(true);
650
menu.addAction(actionMB);
651
unitsGroup->addAction(actionMB);
653
actionGB = new QAction(&menu);
654
actionGB->setText((showIoRate)?i18n("Gigabytes per second"):i18n("Gigabytes"));
655
actionGB->setCheckable(true);
656
menu.addAction(actionGB);
657
unitsGroup->addAction(actionGB);
658
ProcessModel::Units currentUnit;
659
if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) {
660
currentUnit = d->mModel.ioUnits();
662
actionPercentage = new QAction(&menu);
663
actionPercentage->setText(i18n("Percentage"));
664
actionPercentage->setCheckable(true);
665
menu.addAction(actionPercentage);
666
unitsGroup->addAction(actionPercentage);
667
currentUnit = d->mModel.units();
669
switch(currentUnit) {
670
case ProcessModel::UnitsAuto:
671
actionAuto->setChecked(true);
673
case ProcessModel::UnitsKB:
674
actionKB->setChecked(true);
676
case ProcessModel::UnitsMB:
677
actionMB->setChecked(true);
679
case ProcessModel::UnitsGB:
680
actionGB->setChecked(true);
682
case ProcessModel::UnitsPercentage:
683
actionPercentage->setChecked(true);
688
unitsGroup->setExclusive(true);
689
} else if(index == ProcessModel::HeadingName) {
691
actionShowCmdlineOptions = new QAction(&menu);
692
actionShowCmdlineOptions->setText(i18n("Display command line options"));
693
actionShowCmdlineOptions->setCheckable(true);
694
actionShowCmdlineOptions->setChecked(d->mModel.isShowCommandLineOptions());
695
menu.addAction(actionShowCmdlineOptions);
696
} else if(index == ProcessModel::HeadingCPUUsage) {
698
actionNormalizeCPUUsage = new QAction(&menu);
699
actionNormalizeCPUUsage->setText(i18n("Divide CPU usage by number of CPUs"));
700
actionNormalizeCPUUsage->setCheckable(true);
701
actionNormalizeCPUUsage->setChecked(d->mModel.isNormalizedCPUUsage());
702
menu.addAction(actionNormalizeCPUUsage);
705
if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite) {
706
menu.addSeparator()->setText(i18n("Displayed Information"));
707
QActionGroup *ioInformationGroup = new QActionGroup(&menu);
708
actionIoCharacters = new QAction(&menu);
709
actionIoCharacters->setText(i18n("Characters read/written"));
710
actionIoCharacters->setCheckable(true);
711
menu.addAction(actionIoCharacters);
712
ioInformationGroup->addAction(actionIoCharacters);
713
actionIoSyscalls = new QAction(&menu);
714
actionIoSyscalls->setText(i18n("Number of Read/Write operations"));
715
actionIoSyscalls->setCheckable(true);
716
menu.addAction(actionIoSyscalls);
717
ioInformationGroup->addAction(actionIoSyscalls);
718
actionIoActualCharacters = new QAction(&menu);
719
actionIoActualCharacters->setText(i18n("Bytes actually read/written"));
720
actionIoActualCharacters->setCheckable(true);
721
menu.addAction(actionIoActualCharacters);
722
ioInformationGroup->addAction(actionIoActualCharacters);
724
actionIoShowRate = new QAction(&menu);
725
actionIoShowRate->setText(i18n("Show I/O rate"));
726
actionIoShowRate->setCheckable(true);
727
actionIoShowRate->setChecked(showIoRate);
728
menu.addAction(actionIoShowRate);
730
switch(d->mModel.ioInformation()) {
731
case ProcessModel::Bytes:
732
case ProcessModel::BytesRate:
733
actionIoCharacters->setChecked(true);
735
case ProcessModel::Syscalls:
736
case ProcessModel::SyscallsRate:
737
actionIoSyscalls->setChecked(true);
739
case ProcessModel::ActualBytes:
740
case ProcessModel::ActualBytesRate:
741
actionIoActualCharacters->setChecked(true);
749
actionShowTooltips = new QAction(&menu);
750
actionShowTooltips->setCheckable(true);
751
actionShowTooltips->setChecked(d->mModel.isShowingTooltips());
752
actionShowTooltips->setText(i18n("Show Tooltips"));
753
menu.addAction(actionShowTooltips);
756
QAction *result = menu.exec(d->mUi->treeView->header()->mapToGlobal(point));
757
if(!result) return; //Menu cancelled
758
if(result == actionAuto) {
759
if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite)
760
d->mModel.setIoUnits(ProcessModel::UnitsAuto);
762
d->mModel.setUnits(ProcessModel::UnitsAuto);
764
} else if(result == actionKB) {
765
if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite)
766
d->mModel.setIoUnits(ProcessModel::UnitsKB);
768
d->mModel.setUnits(ProcessModel::UnitsKB);
770
} else if(result == actionMB) {
771
if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite)
772
d->mModel.setIoUnits(ProcessModel::UnitsMB);
774
d->mModel.setUnits(ProcessModel::UnitsMB);
776
} else if(result == actionGB) {
777
if(index == ProcessModel::HeadingIoRead || index == ProcessModel::HeadingIoWrite)
778
d->mModel.setIoUnits(ProcessModel::UnitsGB);
780
d->mModel.setUnits(ProcessModel::UnitsGB);
782
} else if(result == actionPercentage) {
783
d->mModel.setUnits(ProcessModel::UnitsPercentage);
785
} else if(result == actionShowCmdlineOptions) {
786
d->mModel.setShowCommandLineOptions(actionShowCmdlineOptions->isChecked());
788
} else if(result == actionNormalizeCPUUsage) {
789
d->mModel.setNormalizedCPUUsage(actionNormalizeCPUUsage->isChecked());
791
} else if(result == actionShowTooltips) {
792
d->mModel.setShowingTooltips(actionShowTooltips->isChecked());
794
} else if(result == actionIoCharacters) {
795
d->mModel.setIoInformation((showIoRate)?ProcessModel::BytesRate:ProcessModel::Bytes);
797
} else if(result == actionIoSyscalls) {
798
d->mModel.setIoInformation((showIoRate)?ProcessModel::SyscallsRate:ProcessModel::Syscalls);
800
} else if(result == actionIoActualCharacters) {
801
d->mModel.setIoInformation((showIoRate)?ProcessModel::ActualBytesRate:ProcessModel::ActualBytes);
803
} else if(result == actionIoShowRate) {
804
showIoRate = actionIoShowRate->isChecked();
805
switch(d->mModel.ioInformation()) {
806
case ProcessModel::Bytes:
807
case ProcessModel::BytesRate:
808
d->mModel.setIoInformation((showIoRate)?ProcessModel::BytesRate:ProcessModel::Bytes);
810
case ProcessModel::Syscalls:
811
case ProcessModel::SyscallsRate:
812
d->mModel.setIoInformation((showIoRate)?ProcessModel::SyscallsRate:ProcessModel::Syscalls);
814
case ProcessModel::ActualBytes:
815
case ProcessModel::ActualBytesRate:
816
d->mModel.setIoInformation((showIoRate)?ProcessModel::ActualBytesRate:ProcessModel::ActualBytes);
823
int i = result->data().toInt();
824
//We set data to be negative to hide a column, and positive to show a column
826
d->mUi->treeView->hideColumn(-1-i);
828
d->mUi->treeView->showColumn(i);
830
d->mUi->treeView->resizeColumnToContents(i);
831
d->mUi->treeView->resizeColumnToContents(d->mFilterModel.columnCount());
836
void KSysGuardProcessList::expandAllChildren(const QModelIndex &parent)
838
//This is called when the user expands a node. This then expands all of its
839
//children. This will trigger this function again recursively.
840
QModelIndex sourceParent = d->mFilterModel.mapToSource(parent);
841
for(int i = 0; i < d->mModel.rowCount(sourceParent); i++) {
842
d->mUi->treeView->expand(d->mFilterModel.mapFromSource(d->mModel.index(i,0, sourceParent)));
846
void KSysGuardProcessList::rowsInserted(const QModelIndex & parent, int start, int end )
848
if(d->mModel.isSimpleMode() || parent.isValid()) {
849
emit processListChanged();
850
return; //No tree or not a root node - no need to expand init
852
disconnect(&d->mFilterModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)));
853
//It is a root node that we just inserted - expand it
854
bool expanded = false;
855
for(int i = start; i <= end; i++) {
856
QModelIndex index = d->mFilterModel.index(i, 0, QModelIndex());
857
if(!d->mUi->treeView->isExpanded(index)) {
859
disconnect(d->mUi->treeView, SIGNAL(expanded(QModelIndex)), this, SLOT(expandAllChildren(QModelIndex)));
862
d->mUi->treeView->expand(index);
863
d->mNeedToExpandInit = true;
867
connect(d->mUi->treeView, SIGNAL(expanded(QModelIndex)), this, SLOT(expandAllChildren(QModelIndex)));
868
connect(&d->mFilterModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int)));
869
emit processListChanged();
872
void KSysGuardProcessList::expandInit()
874
if(d->mModel.isSimpleMode()) return; //No tree - no need to expand init
876
bool expanded = false;
877
for(int i = 0; i < d->mFilterModel.rowCount(QModelIndex()); i++) {
878
QModelIndex index = d->mFilterModel.index(i, 0, QModelIndex());
879
if(!d->mUi->treeView->isExpanded(index)) {
881
disconnect(d->mUi->treeView, SIGNAL(expanded(QModelIndex)), this, SLOT(expandAllChildren(QModelIndex)));
885
d->mUi->treeView->expand(index);
889
connect(d->mUi->treeView, SIGNAL(expanded(QModelIndex)), this, SLOT(expandAllChildren(QModelIndex)));
892
void KSysGuardProcessList::hideEvent ( QHideEvent * event ) //virtual protected from QWidget
894
//Stop updating the process list if we are hidden
896
d->mUpdateTimer->stop();
897
//stop any scripts running, to save on memory
899
d->mScripting->stopAllScripts();
900
QWidget::hideEvent(event);
903
void KSysGuardProcessList::showEvent ( QShowEvent * event ) //virtual protected from QWidget
905
//Start updating the process list again if we are shown again
907
QWidget::showEvent(event);
910
void KSysGuardProcessList::changeEvent( QEvent * event )
912
if (event->type() == QEvent::LanguageChange) {
913
d->mModel.retranslateUi();
914
d->mUi->retranslateUi(this);
917
QWidget::changeEvent(event);
919
void KSysGuardProcessList::retranslateUi()
921
d->mUi->cmbFilter->setItemIcon(ProcessFilter::AllProcesses, KIcon("view-process-all"));
922
d->mUi->cmbFilter->setItemIcon(ProcessFilter::AllProcessesInTreeForm, KIcon("view-process-all-tree"));
923
d->mUi->cmbFilter->setItemIcon(ProcessFilter::SystemProcesses, KIcon("view-process-system"));
924
d->mUi->cmbFilter->setItemIcon(ProcessFilter::UserProcesses, KIcon("view-process-users"));
925
d->mUi->cmbFilter->setItemIcon(ProcessFilter::OwnProcesses, KIcon("view-process-own"));
926
d->mUi->cmbFilter->setItemIcon(ProcessFilter::ProgramsOnly, KIcon("view-process-all"));
929
void KSysGuardProcessList::updateList()
932
KSysGuard::Processes::UpdateFlags updateFlags = KSysGuard::Processes::StandardInformation;
933
if(!d->mUi->treeView->isColumnHidden(ProcessModel::HeadingIoRead) || !d->mUi->treeView->isColumnHidden(ProcessModel::HeadingIoWrite))
934
updateFlags |= KSysGuard::Processes::IOStatistics;
935
if(!d->mUi->treeView->isColumnHidden(ProcessModel::HeadingXMemory))
936
updateFlags |= KSysGuard::Processes::XMemory;
937
d->mModel.update(d->mUpdateIntervalMSecs, updateFlags);
939
d->mUpdateTimer->start(d->mUpdateIntervalMSecs);
941
if (QToolTip::isVisible() && qApp->topLevelAt(QCursor::pos()) == window()) {
942
QWidget *w = d->mUi->treeView->viewport();
943
if(w->geometry().contains(d->mUi->treeView->mapFromGlobal( QCursor::pos() ))) {
944
QHelpEvent event(QEvent::ToolTip, w->mapFromGlobal( QCursor::pos() ), QCursor::pos());
945
qApp->notify(w, &event);
948
if(--d->mResortCountDown <= 0) {
949
d->mResortCountDown = 2; //resort every second time
951
QHeaderView *header= d->mUi->treeView->header();
952
d->mUi->treeView->sortByColumn(header->sortIndicatorSection(), header->sortIndicatorOrder());
954
if( d->mNeedToExpandInit ) {
956
d->mNeedToExpandInit = false;
961
int KSysGuardProcessList::updateIntervalMSecs() const
963
return d->mUpdateIntervalMSecs;
966
void KSysGuardProcessList::setUpdateIntervalMSecs(int intervalMSecs)
968
if(intervalMSecs == d->mUpdateIntervalMSecs)
971
d->mUpdateIntervalMSecs = intervalMSecs;
972
if(intervalMSecs <= 0) { //no point keep the timer around if we aren't updating automatically
973
delete d->mUpdateTimer;
974
d->mUpdateTimer = NULL;
978
if(!d->mUpdateTimer) {
979
//intervalMSecs is a valid time, so set up a timer
980
d->mUpdateTimer = new QTimer(this);
981
d->mUpdateTimer->setSingleShot(true);
982
connect(d->mUpdateTimer, SIGNAL(timeout()), SLOT(updateList()));
984
d->mUpdateTimer->start(d->mUpdateIntervalMSecs);
986
d->mUpdateTimer->setInterval(d->mUpdateIntervalMSecs);
989
bool KSysGuardProcessList::reniceProcesses(const QList<long long> &pids, int niceValue)
991
QList< long long> unreniced_pids;
992
for (int i = 0; i < pids.size(); ++i) {
993
bool success = d->mModel.processController()->setNiceness(pids.at(i), niceValue);
995
unreniced_pids << pids.at(i);
998
if(unreniced_pids.isEmpty()) return true; //All processes were reniced successfully
999
if(!d->mModel.isLocalhost()) return false; //We can't use kauth to renice non-localhost processes
1002
KAuth::Action *action = new KAuth::Action("org.kde.ksysguard.processlisthelper.renice");
1003
action->setParentWidget(window());
1004
d->setupKAuthAction( action, unreniced_pids);
1005
action->addArgument("nicevalue", niceValue);
1006
KAuth::ActionReply reply = action->execute();
1008
if (reply == KAuth::ActionReply::SuccessReply) {
1011
} else if (reply != KAuth::ActionReply::UserCancelled && reply != KAuth::ActionReply::AuthorizationDenied) {
1012
KMessageBox::sorry(this, i18n("You do not have the permission to renice the process and there "
1013
"was a problem trying to run as root. Error %1 %2", reply.errorCode(), reply.errorDescription()));
1020
QList<KSysGuard::Process *> KSysGuardProcessList::selectedProcesses() const
1022
QList<KSysGuard::Process *> processes;
1023
QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
1024
for(int i = 0; i < selectedIndexes.size(); ++i) {
1025
KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *> (d->mFilterModel.mapToSource(selectedIndexes.at(i)).internalPointer());
1026
processes << process;
1032
void KSysGuardProcessList::reniceSelectedProcesses()
1034
QList<long long> pids;
1035
QPointer<ReniceDlg> reniceDlg;
1037
QList<KSysGuard::Process *> processes = selectedProcesses();
1038
QStringList selectedAsStrings;
1040
if (processes.isEmpty()) {
1041
KMessageBox::sorry(this, i18n("You must select a process first."));
1047
foreach(KSysGuard::Process *process, processes) {
1048
pids << process->pid;
1049
selectedAsStrings << d->mModel.getStringForProcess(process);
1050
if(sched == -2) sched = (int)process->scheduler;
1051
else if(sched != -1 && sched != (int)process->scheduler) sched = -1; //If two processes have different schedulers, disable the cpu scheduler stuff
1052
if(iosched == -2) iosched = (int)process->ioPriorityClass;
1053
else if(iosched != -1 && iosched != (int)process->ioPriorityClass) iosched = -1; //If two processes have different schedulers, disable the cpu scheduler stuff
1056
int firstPriority = processes.first()->niceLevel;
1057
int firstIOPriority = processes.first()->ioniceLevel;
1059
bool supportsIoNice = d->mModel.processController()->supportsIoNiceness();
1060
if(!supportsIoNice) { iosched = -2; firstIOPriority = -2; }
1061
reniceDlg = new ReniceDlg(d->mUi->treeView, selectedAsStrings, firstPriority, sched, firstIOPriority, iosched);
1062
if(reniceDlg->exec() == QDialog::Rejected) {
1068
//Because we've done into ReniceDlg, which calls processEvents etc, our processes list is no
1071
QList<long long> renicePids;
1072
QList<long long> changeCPUSchedulerPids;
1073
QList<long long> changeIOSchedulerPids;
1074
foreach (long long pid, pids) {
1075
KSysGuard::Process *process = d->mModel.getProcess(pid);
1079
switch(reniceDlg->newCPUSched) {
1081
case -1: //Invalid, not changed etc.
1082
break; //So do nothing
1083
case KSysGuard::Process::Other:
1084
case KSysGuard::Process::Fifo:
1085
if(reniceDlg->newCPUSched != (int)process->scheduler) {
1086
changeCPUSchedulerPids << pid;
1088
} else if(reniceDlg->newCPUPriority != process->niceLevel)
1092
case KSysGuard::Process::RoundRobin:
1093
case KSysGuard::Process::Batch:
1094
if(reniceDlg->newCPUSched != (int)process->scheduler || reniceDlg->newCPUPriority != process->niceLevel) {
1095
changeCPUSchedulerPids << pid;
1099
switch(reniceDlg->newIOSched) {
1101
case -1: //Invalid, not changed etc.
1102
break; //So do nothing
1103
case KSysGuard::Process::None:
1104
if(reniceDlg->newIOSched != (int)process->ioPriorityClass) {
1105
// Unfortunately linux doesn't actually let us set the ioniceness back to none after being set to something else
1106
if(process->ioPriorityClass != KSysGuard::Process::BestEffort || reniceDlg->newIOPriority != process->ioniceLevel)
1107
changeIOSchedulerPids << pid;
1110
case KSysGuard::Process::Idle:
1111
if(reniceDlg->newIOSched != (int)process->ioPriorityClass) {
1112
changeIOSchedulerPids << pid;
1115
case KSysGuard::Process::BestEffort:
1116
if(process->ioPriorityClass == KSysGuard::Process::None && reniceDlg->newIOPriority == (process->niceLevel + 20)/5)
1117
break; //Don't set to BestEffort if it's on None and the nicelevel wouldn't change
1118
case KSysGuard::Process::RealTime:
1119
if(reniceDlg->newIOSched != (int)process->ioPriorityClass || reniceDlg->newIOPriority != process->ioniceLevel) {
1120
changeIOSchedulerPids << pid;
1126
if(!changeCPUSchedulerPids.isEmpty()) {
1127
Q_ASSERT(reniceDlg->newCPUSched >= 0);
1128
if(!changeCpuScheduler(changeCPUSchedulerPids, (KSysGuard::Process::Scheduler) reniceDlg->newCPUSched, reniceDlg->newCPUPriority)) {
1134
if(!renicePids.isEmpty()) {
1135
Q_ASSERT(reniceDlg->newCPUPriority <= 20 && reniceDlg->newCPUPriority >= -20);
1136
if(!reniceProcesses(renicePids, reniceDlg->newCPUPriority)) {
1141
if(!changeIOSchedulerPids.isEmpty()) {
1142
if(!changeIoScheduler(changeIOSchedulerPids, (KSysGuard::Process::IoPriorityClass) reniceDlg->newIOSched, reniceDlg->newIOPriority)) {
1151
bool KSysGuardProcessList::changeIoScheduler(const QList< long long> &pids, KSysGuard::Process::IoPriorityClass newIoSched, int newIoSchedPriority)
1153
if(newIoSched == KSysGuard::Process::None) newIoSched = KSysGuard::Process::BestEffort;
1154
if(newIoSched == KSysGuard::Process::Idle) newIoSchedPriority = 0;
1155
QList< long long> unchanged_pids;
1156
for (int i = 0; i < pids.size(); ++i) {
1157
bool success = d->mModel.processController()->setIoNiceness(pids.at(i), newIoSched, newIoSchedPriority);
1159
unchanged_pids << pids.at(i);
1162
if(unchanged_pids.isEmpty()) return true;
1163
if(!d->mModel.isLocalhost()) return false; //We can't use kauth to affect non-localhost processes
1165
KAuth::Action *action = new KAuth::Action("org.kde.ksysguard.processlisthelper.changeioscheduler");
1166
action->setParentWidget(window());
1168
d->setupKAuthAction( action, unchanged_pids);
1169
action->addArgument("ioScheduler", (int)newIoSched);
1170
action->addArgument("ioSchedulerPriority", newIoSchedPriority);
1172
KAuth::ActionReply reply = action->execute();
1174
if (reply == KAuth::ActionReply::SuccessReply) {
1177
} else if (reply != KAuth::ActionReply::UserCancelled && reply != KAuth::ActionReply::AuthorizationDenied) {
1178
KMessageBox::sorry(this, i18n("You do not have the permission to change the I/O priority of the process and there "
1179
"was a problem trying to run as root. Error %1 %2", reply.errorCode(), reply.errorDescription()));
1186
bool KSysGuardProcessList::changeCpuScheduler(const QList< long long> &pids, KSysGuard::Process::Scheduler newCpuSched, int newCpuSchedPriority)
1188
if(newCpuSched == KSysGuard::Process::Other || newCpuSched == KSysGuard::Process::Batch) newCpuSchedPriority = 0;
1189
QList< long long> unchanged_pids;
1190
for (int i = 0; i < pids.size(); ++i) {
1191
bool success = d->mModel.processController()->setScheduler(pids.at(i), newCpuSched, newCpuSchedPriority);
1193
unchanged_pids << pids.at(i);
1196
if(unchanged_pids.isEmpty()) return true;
1197
if(!d->mModel.isLocalhost()) return false; //We can't use KAuth to affect non-localhost processes
1199
KAuth::Action *action = new KAuth::Action("org.kde.ksysguard.processlisthelper.changecpuscheduler");
1200
action->setParentWidget(window());
1201
d->setupKAuthAction( action, unchanged_pids);
1202
action->addArgument("cpuScheduler", (int)newCpuSched);
1203
action->addArgument("cpuSchedulerPriority", newCpuSchedPriority);
1204
KAuth::ActionReply reply = action->execute();
1206
if (reply == KAuth::ActionReply::SuccessReply) {
1209
} else if (reply != KAuth::ActionReply::UserCancelled && reply != KAuth::ActionReply::AuthorizationDenied) {
1210
KMessageBox::sorry(this, i18n("You do not have the permission to change the CPU Scheduler for the process and there "
1211
"was a problem trying to run as root. Error %1 %2", reply.errorCode(), reply.errorDescription()));
1218
bool KSysGuardProcessList::killProcesses(const QList< long long> &pids, int sig)
1220
QList< long long> unkilled_pids;
1221
for (int i = 0; i < pids.size(); ++i) {
1222
bool success = d->mModel.processController()->sendSignal(pids.at(i), sig);
1223
// If we failed due to a reason other than insufficient permissions, elevating to root can't
1225
if(!success && (d->mModel.processController()->lastError() == KSysGuard::Processes::InsufficientPermissions || d->mModel.processController()->lastError() == KSysGuard::Processes::Unknown)) {
1226
unkilled_pids << pids.at(i);
1229
if(unkilled_pids.isEmpty()) return true;
1230
if(!d->mModel.isLocalhost()) return false; //We can't elevate privileges to kill non-localhost processes
1232
KAuth::Action action("org.kde.ksysguard.processlisthelper.sendsignal");
1233
action.setParentWidget(window());
1234
d->setupKAuthAction( &action, unkilled_pids);
1235
action.addArgument("signal", sig);
1236
KAuth::ActionReply reply = action.execute();
1238
if (reply == KAuth::ActionReply::SuccessReply) {
1240
} else if (reply.type() == KAuth::ActionReply::HelperError) {
1241
if (reply.errorCode() > 0)
1242
KMessageBox::sorry(this, i18n("You do not have the permission to kill the process and there "
1243
"was a problem trying to run as root. %1", reply.errorDescription()));
1245
} else if (reply != KAuth::ActionReply::UserCancelled && reply != KAuth::ActionReply::AuthorizationDenied) {
1246
KMessageBox::sorry(this, i18n("You do not have the permission to kill the process and there "
1247
"was a problem trying to run as root. Error %1 %2", reply.errorCode(), reply.errorDescription()));
1253
void KSysGuardProcessList::killSelectedProcesses()
1255
sendSignalToSelectedProcesses(SIGTERM, true);
1258
void KSysGuardProcessList::sendSignalToSelectedProcesses(int sig, bool confirm)
1260
QModelIndexList selectedIndexes = d->mUi->treeView->selectionModel()->selectedRows();
1261
QStringList selectedAsStrings;
1262
QList< long long> selectedPids;
1264
QList<KSysGuard::Process *> processes = selectedProcesses();
1265
foreach(KSysGuard::Process *process, processes) {
1266
selectedPids << process->pid;
1269
QString name = d->mModel.getStringForProcess(process);
1270
if(name.size() > 100)
1271
name = name.left(95) + QString::fromUtf8("…");
1272
selectedAsStrings << name;
1275
if (selectedPids.isEmpty()) {
1277
KMessageBox::sorry(this, i18n("You must select a process first."));
1279
} else if (confirm && (sig == SIGTERM || sig == SIGKILL)) {
1280
int count = selectedAsStrings.count();
1283
QString dontAskAgainKey;
1284
QString closeButton;
1285
if (sig == SIGTERM) {
1286
msg = i18np("Are you sure you want to end this process? Any unsaved work may be lost.",
1287
"Are you sure you want to end these %1 processes? Any unsaved work may be lost",
1289
title = i18ncp("Dialog title", "End Process", "End %1 Processes", count);
1290
dontAskAgainKey = "endconfirmation";
1291
closeButton = i18n("End");
1292
} else if (sig == SIGKILL) {
1293
msg = i18np("<qt>Are you sure you want to <b>immediately and forcibly kill</b> this process? Any unsaved work may be lost.",
1294
"<qt>Are you sure you want to <b>immediately and forcibly kill</b> these %1 processes? Any unsaved work may be lost",
1296
title = i18ncp("Dialog title", "Forcibly Kill Process", "Forcibly Kill %1 Processes", count);
1297
dontAskAgainKey = "killconfirmation";
1298
closeButton = i18n("Kill");
1301
int res = KMessageBox::warningContinueCancelList(this, msg, selectedAsStrings,
1303
KGuiItem(closeButton, "process-stop"),
1304
KStandardGuiItem::cancel(),
1306
if (res != KMessageBox::Continue)
1310
//We have shown a GUI dialog box, which processes events etc.
1311
//So processes is NO LONGER VALID
1313
if (!killProcesses(selectedPids, sig))
1315
if (sig == SIGTERM || sig == SIGKILL) {
1316
foreach (long long pid, selectedPids) {
1317
KSysGuard::Process *process = d->mModel.getProcess(pid);
1319
process->timeKillWasSent.start();
1320
d->mUi->treeView->selectionModel()->clearSelection();
1326
bool KSysGuardProcessList::showTotals() const {
1327
return d->mModel.showTotals();
1330
void KSysGuardProcessList::setShowTotals(bool showTotals) //slot
1332
d->mModel.setShowTotals(showTotals);
1335
ProcessModel::Units KSysGuardProcessList::units() const {
1336
return d->mModel.units();
1339
void KSysGuardProcessList::setUnits(ProcessModel::Units unit) {
1340
d->mModel.setUnits(unit);
1343
void KSysGuardProcessList::saveSettings(KConfigGroup &cg) {
1344
/* Note that the ksysguard program does not use these functions. It saves the settings itself to an xml file instead */
1345
cg.writeEntry("units", (int)(units()));
1346
cg.writeEntry("ioUnits", (int)(d->mModel.ioUnits()));
1347
cg.writeEntry("ioInformation", (int)(d->mModel.ioInformation()));
1348
cg.writeEntry("showCommandLineOptions", d->mModel.isShowCommandLineOptions());
1349
cg.writeEntry("normalizeCPUUsage", d->mModel.isNormalizedCPUUsage());
1350
cg.writeEntry("showTooltips", d->mModel.isShowingTooltips());
1351
cg.writeEntry("showTotals", showTotals());
1352
cg.writeEntry("filterState", (int)(state()));
1353
cg.writeEntry("updateIntervalMSecs", updateIntervalMSecs());
1354
cg.writeEntry("headerState", d->mUi->treeView->header()->saveState());
1355
//If we change, say, the header between versions of ksysguard, then the old headerState settings will not be valid.
1356
//The version property lets us keep track of which version we are
1357
cg.writeEntry("version", PROCESSHEADERVERSION);
1360
void KSysGuardProcessList::loadSettings(const KConfigGroup &cg) {
1361
/* Note that the ksysguard program does not use these functions. It saves the settings itself to an xml file instead */
1362
setUnits((ProcessModel::Units) cg.readEntry("units", (int)ProcessModel::UnitsKB));
1363
d->mModel.setIoUnits((ProcessModel::Units) cg.readEntry("ioUnits", (int)ProcessModel::UnitsKB));
1364
d->mModel.setIoInformation((ProcessModel::IoInformation) cg.readEntry("ioInformation", (int)ProcessModel::ActualBytesRate));
1365
d->mModel.setShowCommandLineOptions(cg.readEntry("showCommandLineOptions", false));
1366
d->mModel.setNormalizedCPUUsage(cg.readEntry("normalizeCPUUsage", true));
1367
d->mModel.setShowingTooltips(cg.readEntry("showTooltips", true));
1368
setShowTotals(cg.readEntry("showTotals", true));
1369
setStateInt(cg.readEntry("filterState", (int)ProcessFilter::AllProcesses));
1370
setUpdateIntervalMSecs(cg.readEntry("updateIntervalMSecs", 2000));
1371
int version = cg.readEntry("version", 0);
1372
if(version == PROCESSHEADERVERSION) { //If the header has changed, the old settings are no longer valid. Only restore if version is the same
1373
restoreHeaderState(cg.readEntry("headerState", QByteArray()));
1377
void KSysGuardProcessList::restoreHeaderState(const QByteArray & state) {
1378
d->mUi->treeView->header()->restoreState(state);
1381
bool KSysGuardProcessList::eventFilter(QObject *obj, QEvent *event) {
1382
if (event->type() == QEvent::KeyPress) {
1383
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
1384
if(obj == d->mUi->treeView) {
1385
if( keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
1386
d->mUi->treeView->selectionModel()->select(d->mUi->treeView->currentIndex(), QItemSelectionModel::Select | QItemSelectionModel::Rows);
1387
showProcessContextMenu(d->mUi->treeView->currentIndex());
1390
// obj must be txtFilter
1391
if(keyEvent->matches(QKeySequence::MoveToNextLine) || keyEvent->matches(QKeySequence::SelectNextLine) ||
1392
keyEvent->matches(QKeySequence::MoveToPreviousLine) || keyEvent->matches(QKeySequence::SelectPreviousLine) ||
1393
keyEvent->matches(QKeySequence::MoveToNextPage) || keyEvent->matches(QKeySequence::SelectNextPage) ||
1394
keyEvent->matches(QKeySequence::MoveToPreviousPage) || keyEvent->matches(QKeySequence::SelectPreviousPage) ||
1395
keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return)
1397
QApplication::sendEvent(d->mUi->treeView, event);
1405
ProcessModel *KSysGuardProcessList::processModel() {
1409
void KSysGuardProcessList::setKillButtonVisible(bool visible)
1411
d->mUi->btnKillProcess->setVisible(visible);
1414
bool KSysGuardProcessList::isKillButtonVisible() const
1416
return d->mUi->btnKillProcess->isVisible();
1418
bool KSysGuardProcessList::scriptingEnabled() const
1420
return !!d->mScripting;
1422
void KSysGuardProcessList::setScriptingEnabled(bool enabled)
1424
if(!!d->mScripting == enabled)
1425
return; //Nothing changed
1427
delete d->mScripting;
1428
d->mScripting = NULL;
1430
d->mScripting = new Scripting(this);
1431
d->mScripting->hide();