Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / workspace / libs / ksysguard / processui / ProcessModel.cc
blob0346b070791cf77ea684b978188da45d79a6f0a6
1 /*
2 KSysGuard, the KDE System Guard
4 Copyright (c) 1999, 2000 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.
26 #include <kapplication.h>
27 #include <kiconloader.h>
28 #include <kdebug.h>
29 #include <klocale.h>
30 #include <QBitmap>
31 #include <QFont>
32 #include <QIcon>
33 #include <QPixmap>
34 #include <QList>
36 #define HEADING_X_ICON_SIZE 16
38 #define GET_OWN_ID
40 #ifdef GET_OWN_ID
41 /* For getuid*/
42 #include <unistd.h>
43 #include <sys/types.h>
44 #endif
46 #include "ProcessModel.moc"
47 #include "ProcessModel_p.moc"
48 #include "ProcessModel.h"
49 #include "ProcessModel_p.h"
51 #include "processcore/processes.h"
52 #include "processcore/process.h"
54 extern KApplication* Kapp;
56 ProcessModelPrivate::ProcessModelPrivate() : mBlankPixmap(HEADING_X_ICON_SIZE,1)
58 mBlankPixmap.fill(QColor(0,0,0,0));
59 mSimple = true;
60 mIsLocalhost = true;
61 mMemTotal = -1;
62 mNumProcessorCores = 1;
63 mProcesses = NULL;
64 mShowChildTotals = true;
65 mIsChangingLayout = false;
68 ProcessModelPrivate::~ProcessModelPrivate()
70 if(mProcesses)
71 KSysGuard::Processes::returnInstance(mHostName);
72 foreach(WindowInfo wininfo, mPidToWindowInfo) {
73 delete wininfo.netWinInfo;
75 mProcesses = NULL;
77 ProcessModel::ProcessModel(QObject* parent, const QString &host)
78 : QAbstractItemModel(parent), d(new ProcessModelPrivate)
80 KGlobal::locale()->insertCatalog("processui"); //Make sure we include the translation stuff. This needs to be run before any i18n call here
81 d->q=this;
82 if(host.isEmpty() || host == "localhost") {
83 d->mHostName = QString();
84 d->mIsLocalhost = true;
85 } else {
86 d->mHostName = host;
87 d->mIsLocalhost = false;
89 setupHeader();
90 d->setupProcesses();
91 d->setupWindows();
92 d->mUnits = UnitsKB;
95 ProcessModel::~ProcessModel()
97 delete d;
100 KSysGuard::Processes *ProcessModel::processController()
102 return d->mProcesses;
105 void ProcessModelPrivate::windowRemoved(WId wid) {
106 #ifdef Q_WS_X11
107 long long pid = mWIdToPid.value(wid, 0);
108 if(pid <= 0) return;
110 int count = mPidToWindowInfo.count(pid);
111 QMultiHash<long long, WindowInfo>::iterator i = mPidToWindowInfo.find(pid);
112 while (i != mPidToWindowInfo.end() && i.key() == pid) {
113 if(i.value().wid == wid) {
114 delete i.value().netWinInfo;
115 i = mPidToWindowInfo.erase(i);
116 // break;
117 } else
118 i++;
120 Q_ASSERT(count-1 == mPidToWindowInfo.count(pid) || count == mPidToWindowInfo.count(pid));
121 KSysGuard::Process *process = mProcesses->getProcess(pid);
122 if(!process) return;
124 int row;
125 if(mSimple)
126 row = process->index;
127 else
128 row = process->parent->children.indexOf(process);
129 QModelIndex index1 = q->createIndex(row, ProcessModel::HeadingName, process);
130 emit q->dataChanged(index1, index1);
131 QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process);
132 emit q->dataChanged(index2, index2);
133 #endif
136 void ProcessModelPrivate::setupWindows() {
137 #ifdef Q_WS_X11
138 QList<WId>::ConstIterator it;
139 for ( it = KWindowSystem::windows().begin(); it != KWindowSystem::windows().end(); ++it ) {
140 windowAdded(*it);
143 connect( KWindowSystem::self(), SIGNAL( windowChanged (WId, unsigned int )), this, SLOT(windowChanged(WId, unsigned int)));
144 connect( KWindowSystem::self(), SIGNAL( windowAdded (WId )), this, SLOT(windowAdded(WId)));
145 connect( KWindowSystem::self(), SIGNAL( windowRemoved (WId )), this, SLOT(windowRemoved(WId)));
146 #endif
149 void ProcessModelPrivate::setupProcesses() {
150 if(mProcesses) {
151 mWIdToPid.clear();
152 mPidToWindowInfo.clear();
153 KSysGuard::Processes::returnInstance(mHostName);
154 q->reset();
157 mProcesses = KSysGuard::Processes::getInstance(mHostName);
159 connect( mProcesses, SIGNAL( processChanged(KSysGuard::Process *, bool)), this, SLOT(processChanged(KSysGuard::Process *, bool)));
160 connect( mProcesses, SIGNAL( beginAddProcess(KSysGuard::Process *)), this, SLOT(beginInsertRow( KSysGuard::Process *)));
161 connect( mProcesses, SIGNAL( endAddProcess()), this, SLOT(endInsertRow()));
162 connect( mProcesses, SIGNAL( beginRemoveProcess(KSysGuard::Process *)), this, SLOT(beginRemoveRow( KSysGuard::Process *)));
163 connect( mProcesses, SIGNAL( endRemoveProcess()), this, SLOT(endRemoveRow()));
164 connect( mProcesses, SIGNAL( beginMoveProcess(KSysGuard::Process *, KSysGuard::Process *)), this,
165 SLOT( beginMoveProcess(KSysGuard::Process *, KSysGuard::Process *)));
166 connect( mProcesses, SIGNAL( endMoveProcess()), this, SLOT(endMoveRow()));
167 mNumProcessorCores = mProcesses->numberProcessorCores();
168 if(mNumProcessorCores < 1) mNumProcessorCores=1; //Default to 1 if there was an error getting the number
171 #ifdef Q_WS_X11
172 void ProcessModelPrivate::windowChanged(WId wid, unsigned int properties)
174 if(! (properties & NET::WMVisibleName || properties & NET::WMName || properties & NET::WMIcon || properties & NET::WMState)) return;
175 windowAdded(wid);
179 void ProcessModelPrivate::windowAdded(WId wid)
181 foreach(WindowInfo w, mPidToWindowInfo.values()) {
182 if(w.wid == wid) return; //already added
184 //The name changed
185 KXErrorHandler handler;
186 NETWinInfo *info = new NETWinInfo( QX11Info::display(), wid, QX11Info::appRootWindow(),
187 NET::WMPid | NET::WMVisibleName | NET::WMName | NET::WMState );
188 if (handler.error( false ) ) {
189 delete info;
190 return; //info is invalid - window just closed or something probably
192 long long pid = info->pid();
193 if(pid <= 0) {
194 delete info;
195 return;
198 WindowInfo w;
199 w.icon = KWindowSystem::icon(wid, HEADING_X_ICON_SIZE, HEADING_X_ICON_SIZE, true);
200 w.wid = wid;
201 w.netWinInfo = info;
202 mPidToWindowInfo.insertMulti(pid, w);
203 mWIdToPid[wid] = pid;
205 KSysGuard::Process *process = mProcesses->getProcess(pid);
206 if(!process) return; //shouldn't really happen.. maybe race condition etc
207 int row;
208 if(mSimple)
209 row = process->index;
210 else
211 row = process->parent->children.indexOf(process);
212 QModelIndex index1 = q->createIndex(row, ProcessModel::HeadingName, process);
213 emit q->dataChanged(index1, index1);
214 QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process);
215 emit q->dataChanged(index2, index2);
217 #endif
219 void ProcessModel::update(int updateDurationMS) {
220 // kDebug() << "update all processes: " << QTime::currentTime().toString("hh:mm:ss.zzz");
221 d->mProcesses->updateAllProcesses(updateDurationMS);
222 if(d->mMemTotal <= 0)
223 d->mMemTotal = d->mProcesses->totalPhysicalMemory();
224 if(d->mIsChangingLayout) {
225 d->mIsChangingLayout = false;
226 emit layoutChanged();
228 // kDebug() << "finished: " << QTime::currentTime().toString("hh:mm:ss.zzz");
231 QString ProcessModelPrivate::getStatusDescription(KSysGuard::Process::ProcessStatus status) const
233 switch( status) {
234 case KSysGuard::Process::Running:
235 return i18n("- Process is doing some work");
236 case KSysGuard::Process::Sleeping:
237 return i18n("- Process is waiting for something to happen");
238 case KSysGuard::Process::Stopped:
239 return i18n("- Process has been stopped. It will not respond to user input at the moment");
240 case KSysGuard::Process::Zombie:
241 return i18n("- Process has finished and is now dead, but the parent process has not cleaned up");
242 default:
243 return QString();
247 KSysGuard::Process *ProcessModel::getProcessAtIndex(int index) const
249 Q_ASSERT(d->mSimple);
250 return d->mProcesses->getAllProcesses().at(index);
252 int ProcessModel::rowCount(const QModelIndex &parent) const
254 if(d->mSimple) {
255 if(parent.isValid()) return 0; //In flat mode, none of the processes have children
256 return d->mProcesses->getAllProcesses().count();
259 //Deal with the case that we are showing it as a tree
260 KSysGuard::Process *process;
261 if(parent.isValid()) {
262 if(parent.column() != 0) return 0; //For a treeview we say that only the first column has children
263 process = reinterpret_cast< KSysGuard::Process * > (parent.internalPointer()); //when parent is invalid, it must be the root level which we set as 0
264 } else {
265 process = d->mProcesses->getProcess(0);
267 Q_ASSERT(process);
268 int num_rows = process->children.count();
269 return num_rows;
272 int ProcessModel::columnCount ( const QModelIndex & ) const
274 return d->mHeadings.count();
277 bool ProcessModel::hasChildren ( const QModelIndex & parent = QModelIndex() ) const
280 if(d->mSimple) {
281 if(parent.isValid()) return 0; //In flat mode, none of the processes have children
282 return !d->mProcesses->getAllProcesses().isEmpty();
285 //Deal with the case that we are showing it as a tree
286 KSysGuard::Process *process;
287 if(parent.isValid()) {
288 if(parent.column() != 0) return false; //For a treeview we say that only the first column has children
289 process = reinterpret_cast< KSysGuard::Process * > (parent.internalPointer()); //when parent is invalid, it must be the root level which we set as 0
290 } else {
291 process = d->mProcesses->getProcess(0);
293 Q_ASSERT(process);
294 bool has_children = !process->children.isEmpty();
296 Q_ASSERT((rowCount(parent) > 0) == has_children);
297 return has_children;
300 QModelIndex ProcessModel::index ( int row, int column, const QModelIndex & parent ) const
302 if(row<0) return QModelIndex();
303 if(column<0 || column >= d->mHeadings.count() ) return QModelIndex();
305 if(d->mSimple) {
306 if( parent.isValid()) return QModelIndex();
307 if( d->mProcesses->getAllProcesses().count() <= row) return QModelIndex();
308 return createIndex( row, column, d->mProcesses->getAllProcesses().at(row));
311 //Deal with the case that we are showing it as a tree
312 KSysGuard::Process *parent_process = 0;
314 if(parent.isValid()) //not valid for init, and init has ppid of 0
315 parent_process = reinterpret_cast< KSysGuard::Process * > (parent.internalPointer());
316 else
317 parent_process = d->mProcesses->getProcess(0);
318 Q_ASSERT(parent_process);
320 if(parent_process->children.count() > row)
321 return createIndex(row,column, parent_process->children[row]);
322 else
324 return QModelIndex();
328 bool ProcessModel::isSimpleMode() const
330 return d->mSimple;
333 void ProcessModelPrivate::processChanged(KSysGuard::Process *process, bool onlyTotalCpu)
335 int row;
336 if(mSimple)
337 row = process->index;
338 else
339 row = process->parent->children.indexOf(process);
341 int totalUpdated = 0;
342 Q_ASSERT(row != -1); //Something has gone very wrong
343 if(onlyTotalCpu) {
344 if(mShowChildTotals) {
345 //Only the total cpu usage changed, so only update that
346 QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process);
347 emit q->dataChanged(index, index);
349 return;
350 } else {
351 if(process->changes == KSysGuard::Process::Nothing) {
352 return; //Nothing changed
354 if(process->changes & KSysGuard::Process::Uids) {
355 totalUpdated++;
356 QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process);
357 emit q->dataChanged(index, index);
359 if(process->changes & KSysGuard::Process::Tty) {
360 totalUpdated++;
361 QModelIndex index = q->createIndex(row, ProcessModel::HeadingTty, process);
362 emit q->dataChanged(index, index);
364 if(process->changes & (KSysGuard::Process::Usage | KSysGuard::Process::Status) || (process->changes & KSysGuard::Process::TotalUsage && mShowChildTotals)) {
365 totalUpdated++;
366 QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process);
367 emit q->dataChanged(index, index);
369 if(process->changes & KSysGuard::Process::NiceLevels) {
370 totalUpdated++;
371 QModelIndex index = q->createIndex(row, ProcessModel::HeadingNiceness, process);
372 emit q->dataChanged(index, index);
374 if(process->changes & KSysGuard::Process::VmSize) {
375 totalUpdated++;
376 QModelIndex index = q->createIndex(row, ProcessModel::HeadingVmSize, process);
377 emit q->dataChanged(index, index);
379 if(process->changes & (KSysGuard::Process::VmSize | KSysGuard::Process::VmRSS | KSysGuard::Process::VmURSS)) {
380 totalUpdated+=2;
381 QModelIndex index = q->createIndex(row, ProcessModel::HeadingMemory, process);
382 emit q->dataChanged(index, index);
383 QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingSharedMemory, process);
384 emit q->dataChanged(index2, index2);
387 if(process->changes & KSysGuard::Process::Name) {
388 totalUpdated++;
389 QModelIndex index = q->createIndex(row, ProcessModel::HeadingName, process);
390 emit q->dataChanged(index, index);
392 if(process->changes & KSysGuard::Process::Command) {
393 totalUpdated++;
394 QModelIndex index = q->createIndex(row, ProcessModel::HeadingCommand, process);
395 emit q->dataChanged(index, index);
397 if(process->changes & KSysGuard::Process::Login) {
398 totalUpdated++;
399 QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process);
400 emit q->dataChanged(index, index);
405 void ProcessModelPrivate::beginInsertRow( KSysGuard::Process *process)
407 Q_ASSERT(process);
408 if(mIsChangingLayout) {
409 mIsChangingLayout = false;
410 emit q->layoutChanged();
413 if(mSimple) {
414 int row = mProcesses->getAllProcesses().count();
415 q->beginInsertRows( QModelIndex(), row, row );
416 return;
419 //Deal with the case that we are showing it as a tree
420 int row = process->parent->children.count();
421 QModelIndex parentModelIndex = q->getQModelIndex(process->parent, 0);
423 //Only here can we actually change the model. First notify the view/proxy models then modify
424 q->beginInsertRows(parentModelIndex, row, row);
426 void ProcessModelPrivate::endInsertRow() {
427 q->endInsertRows();
429 void ProcessModelPrivate::beginRemoveRow( KSysGuard::Process *process )
431 if(mIsChangingLayout) {
432 mIsChangingLayout = false;
433 emit q->layoutChanged();
436 Q_ASSERT(process);
437 Q_ASSERT(process->pid > 0);
439 if(mSimple) {
440 return q->beginRemoveRows(QModelIndex(), process->index, process->index);
441 } else {
442 int row = process->parent->children.indexOf(process);
443 if(row == -1) {
444 kDebug(1215) << "A serious problem occurred in remove row.";
445 return;
448 return q->beginRemoveRows(q->getQModelIndex(process->parent,0), row, row);
451 void ProcessModelPrivate::endRemoveRow()
453 q->endRemoveRows();
457 void ProcessModelPrivate::beginMoveProcess(KSysGuard::Process *process, KSysGuard::Process *new_parent)
459 if(mSimple) return; //We don't need to move processes when in simple mode
460 if(!mIsChangingLayout) {
461 emit q->layoutAboutToBeChanged ();
462 mIsChangingLayout = true;
465 //FIXME
466 int current_row = process->parent->children.indexOf(process);
467 int new_row = new_parent->children.count();
468 Q_ASSERT(current_row != -1);
470 QList<QModelIndex> fromIndexes;
471 QList<QModelIndex> toIndexes;
472 for(int i=0; i < q->columnCount(); i++) {
473 fromIndexes << q->createIndex(current_row, i, process);
474 toIndexes << q->createIndex(new_row, i, process);
476 q->changePersistentIndexList(fromIndexes, toIndexes);
478 void ProcessModelPrivate::endMoveRow()
483 QModelIndex ProcessModel::getQModelIndex( KSysGuard::Process *process, int column) const
485 Q_ASSERT(process);
486 int pid = process->pid;
487 if(pid == 0) return QModelIndex(); //pid 0 is our fake process meaning the very root (never drawn). To represent that, we return QModelIndex() which also means the top element
488 int row = 0;
489 if(d->mSimple) {
490 row = process->index;
491 } else {
492 row = process->parent->children.indexOf(process);
494 Q_ASSERT(row != -1);
495 return createIndex(row, column, process);
498 QModelIndex ProcessModel::parent ( const QModelIndex & index ) const
500 if(!index.isValid()) return QModelIndex();
501 KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer());
502 Q_ASSERT(process);
504 if(d->mSimple)
505 return QModelIndex();
506 else
507 return getQModelIndex(process->parent,0);
510 QVariant ProcessModel::headerData(int section, Qt::Orientation orientation,
511 int role) const
513 if(orientation != Qt::Horizontal)
514 return QVariant();
515 if(section < 0 || section >= d->mHeadings.count())
516 return QVariant(); //is this needed?
517 switch( role ) {
518 case Qt::TextAlignmentRole:
520 switch(section) {
521 case HeadingPid:
522 case HeadingMemory:
523 case HeadingSharedMemory:
524 case HeadingVmSize:
525 // return QVariant(Qt::AlignRight);
526 case HeadingUser:
527 case HeadingCPUUsage:
528 return QVariant(Qt::AlignCenter);
531 return QVariant();
533 case Qt::ToolTipRole:
535 switch(section) {
536 case HeadingName:
537 return i18n("The process name");
538 case HeadingUser:
539 return i18n("The user that owns this process");
540 case HeadingTty:
541 return i18n("The controlling terminal that this process is running on.");
542 case HeadingNiceness:
543 return i18n("The priority that this process is being run with. Ranges from 19 (very nice, least priority) to -19 (top priority)");
544 case HeadingCPUUsage:
545 if(d->mNumProcessorCores == 1)
546 return i18n("The current CPU usage of the process.");
547 else
548 return i18np("The current CPU usage of the process, divided by the %1 process core in the machine.", "The current total CPU usage of the process, divided by the %1 processor cores in the machine.", d->mNumProcessorCores);
549 case HeadingVmSize:
550 return i18n("<qt>This is the amount of virtual memory space that the process is using, included shared libraries, graphics memory, files on disk, and so on. This number is almost meaningless.</qt>");
551 case HeadingMemory:
552 return i18n("<qt>This is the amount of real physical memory that this process is using by itself. It does not include any swapped out memory, nor the code size of its shared libraries. This is often the most useful figure to judge the memory use of a program.</qt>");
553 case HeadingSharedMemory:
554 return i18n("<qt>This is the amount of real physical memory that this process's shared libraries are using. This memory is shared among all processes that use this library</qt>");
555 case HeadingCommand:
556 return i18n("<qt>The command that this process was launched with</qt>");
557 case HeadingXTitle:
558 return i18n("<qt>The title of any windows that this process is showing</qt>");
559 case HeadingPid:
560 return i18n("The unique Process ID that identifies this process");
561 default:
562 return QVariant();
565 case Qt::DisplayRole:
566 return d->mHeadings[section];
567 default:
568 return QVariant();
571 void ProcessModel::setSimpleMode(bool simple)
573 if(d->mSimple == simple) return;
575 if(!d->mIsChangingLayout) {
576 emit layoutAboutToBeChanged ();
579 d->mSimple = simple;
580 d->mIsChangingLayout = false;
582 int flatrow;
583 int treerow;
584 QList<QModelIndex> flatIndexes;
585 QList<QModelIndex> treeIndexes;
586 foreach( KSysGuard::Process *process, d->mProcesses->getAllProcesses()) {
587 flatrow = process->index;
588 treerow = process->parent->children.indexOf(process);
589 flatIndexes.clear();
590 treeIndexes.clear();
592 for(int i=0; i < columnCount(); i++) {
593 flatIndexes << createIndex(flatrow, i, process);
594 treeIndexes << createIndex(treerow, i, process);
596 if(d->mSimple) //change from tree mode to flat mode
597 changePersistentIndexList(treeIndexes, flatIndexes);
598 else // change from flat mode to tree mode
599 changePersistentIndexList(flatIndexes, treeIndexes);
601 emit layoutChanged();
605 bool ProcessModel::canUserLogin(long long uid ) const
607 if(uid == 65534) {
608 //nobody user
609 return false;
612 if(!d->mIsLocalhost) return true; //We only deal with localhost. Just always return true for non localhost
614 int canLogin = d->mUidCanLogin.value(uid, -1); //Returns 0 if we cannot login, 1 if we can, and the default is -1 meaning we don't know
615 if(canLogin != -1) return canLogin; //We know whether they can log in
617 //We got the default, -1, so we don't know. Look it up
619 KUser user(uid);
620 if(!user.isValid()) {
621 //for some reason the user isn't recognised. This might happen under certain security situations.
622 //Just return true to be safe
623 d->mUidCanLogin[uid] = 1;
624 return true;
626 QString shell = user.shell();
627 if(shell == "/bin/false" ) //FIXME - add in any other shells it could be for false
629 d->mUidCanLogin[uid] = 0;
630 return false;
632 d->mUidCanLogin[uid] = 1;
633 return true;
636 QString ProcessModelPrivate::getTooltipForUser(const KSysGuard::Process *ps) const
638 QString userTooltip;
639 if(!mIsLocalhost) {
640 userTooltip = "<qt>";
641 userTooltip += i18n("Login Name: %1<br/>", getUsernameForUser(ps->uid, true));
642 } else {
643 KUser user(ps->uid);
644 if(!user.isValid())
645 userTooltip = i18n("This user is not recognised for some reason");
646 else {
647 userTooltip = "<qt>";
648 if(!user.property(KUser::FullName).isValid()) userTooltip += i18n("<b>%1</b><br/>", user.property(KUser::FullName).toString());
649 userTooltip += i18n("Login Name: %1 (uid: %2)<br/>", user.loginName(), ps->uid);
650 if(!user.property(KUser::RoomNumber).isValid()) userTooltip += i18n(" Room Number: %1<br/>", user.property(KUser::RoomNumber).toString());
651 if(!user.property(KUser::WorkPhone).isValid()) userTooltip += i18n(" Work Phone: %1<br/>", user.property(KUser::WorkPhone).toString());
654 if( (ps->uid != ps->euid && ps->euid != -1) ||
655 (ps->uid != ps->suid && ps->suid != -1) ||
656 (ps->uid != ps->fsuid && ps->fsuid != -1)) {
657 if(ps->euid != -1)
658 userTooltip += i18n("Effective User: %1<br/>", getUsernameForUser(ps->euid, true));
659 if(ps->suid != -1)
660 userTooltip += i18n("Setuid User: %1<br/>", getUsernameForUser(ps->suid, true));
661 if(ps->fsuid != -1)
662 userTooltip += i18n("File System User: %1<br/>", getUsernameForUser(ps->fsuid, true));
663 userTooltip += "<br/>";
665 if(ps->gid != -1) {
666 userTooltip += i18n("Group: %1", getGroupnameForGroup(ps->gid));
667 if( (ps->gid != ps->egid && ps->egid != -1) ||
668 (ps->gid != ps->sgid && ps->sgid != -1) ||
669 (ps->gid != ps->fsgid && ps->fsgid != -1)) {
670 if(ps->egid != -1)
671 userTooltip += i18n("<br/>Effective Group: %1", getGroupnameForGroup(ps->egid));
672 if(ps->sgid != -1)
673 userTooltip += i18n("<br/>Setuid Group: %1", getGroupnameForGroup(ps->sgid));
674 if(ps->fsgid != -1)
675 userTooltip += i18n("<br/>File System Group: %1", getGroupnameForGroup(ps->fsgid));
678 return userTooltip;
681 QString ProcessModel::getStringForProcess(KSysGuard::Process *process) const {
682 return i18nc("Short description of a process. PID, name, user", "<numid>%1</numid>: %2, owned by user %3", (long)(process->pid), process->name, d->getUsernameForUser(process->uid, false));
685 QString ProcessModelPrivate::getGroupnameForGroup(long long gid) const {
686 if(mIsLocalhost) {
687 QString groupname = KUserGroup(gid).name();
688 if(!groupname.isEmpty())
689 return i18n("%1 (gid: <numid>%2</numid>)", groupname, gid);
691 return QString::number(gid);
694 QString ProcessModelPrivate::getUsernameForUser(long long uid, bool withuid) const {
695 QString &username = mUserUsername[uid];
696 if(username.isNull()) {
697 if(!mIsLocalhost) {
698 username = ""; //empty, but not null
699 } else {
700 KUser user(uid);
701 if(!user.isValid())
702 username = "";
703 else
704 username = user.loginName();
707 if(username.isEmpty())
708 return QString::number(uid);
709 if(withuid)
710 return i18n("%1 (uid: %2)", username, (long int)uid);
711 return username;
714 QVariant ProcessModel::data(const QModelIndex &index, int role) const
716 //This function must be super duper ultra fast because it's called thousands of times every few second :(
717 //I think it should be optomised for role first, hence the switch statement (fastest possible case)
719 if (!index.isValid()) {
720 return QVariant();
722 if (index.column() >= d->mHeadings.count()) {
723 return QVariant();
726 switch (role){
727 case Qt::DisplayRole: {
728 KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer());
729 switch(index.column()) {
730 case HeadingName:
731 return process->name;
732 case HeadingPid:
733 return (qlonglong)process->pid;
734 case HeadingUser:
735 if(!process->login.isEmpty()) return process->login;
736 if(process->uid == process->euid)
737 return d->getUsernameForUser(process->uid, false);
738 else
739 return d->getUsernameForUser(process->uid, false) + ", " + d->getUsernameForUser(process->euid, false);
740 case HeadingNiceness:
741 return process->niceLevel;
742 case HeadingTty:
743 return process->tty;
744 case HeadingCPUUsage:
746 double total;
747 if(d->mShowChildTotals && !d->mSimple) total = process->totalUserUsage + process->totalSysUsage;
748 else total = process->userUsage + process->sysUsage;
749 total = total / d->mNumProcessorCores;
751 if(total < 1 && process->status != KSysGuard::Process::Sleeping && process->status != KSysGuard::Process::Running)
752 return process->translatedStatus(); //tell the user when the process is a zombie or stopped
753 if(total < 0.5)
754 return "";
756 return QString::number((int)(total+0.5)) + '%';
758 case HeadingMemory:
759 if(process->vmRSS == 0) return QVariant(QVariant::String);
760 if(process->vmURSS == -1) {
761 //If we don't have the URSS (the memory used by only the process, not the shared libraries)
762 //then return the RSS (physical memory used by the process + shared library) as the next best thing
763 return formatMemoryInfo(process->vmRSS);
764 } else {
765 return formatMemoryInfo(process->vmURSS);
767 case HeadingVmSize:
768 if(process->vmSize == 0) return QVariant(QVariant::String);
769 return formatMemoryInfo(process->vmSize);
770 case HeadingSharedMemory:
771 if(process->vmRSS - process->vmURSS <= 0 || process->vmURSS == -1) return QVariant(QVariant::String);
772 return formatMemoryInfo(process->vmRSS - process->vmURSS);
773 case HeadingCommand:
775 return process->command;
776 // It would be nice to embolden the process name in command, but this requires that the itemdelegate to support html text
777 // QString command = process->command;
778 // command.replace(process->name, "<b>" + process->name + "</b>");
779 // return "<qt>" + command;
781 #ifdef Q_WS_X11
782 case HeadingXTitle:
784 if(!d->mPidToWindowInfo.contains(process->pid)) return QVariant(QVariant::String);
785 WindowInfo w = d->mPidToWindowInfo.value(process->pid);
786 if(!w.netWinInfo) return QVariant(QVariant::String);
787 const char *name = w.netWinInfo->visibleName();
788 if( !name || name[0] == 0 )
789 name = w.netWinInfo->name();
790 if(name && name[0] != 0)
791 return QString::fromUtf8(name);
792 return QVariant(QVariant::String);
794 #endif
795 default:
796 return QVariant();
798 break;
800 case Qt::ToolTipRole: {
801 KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer());
802 QString tracer;
803 if(process->tracerpid > 0) {
804 KSysGuard::Process *process_tracer = d->mProcesses->getProcess(process->tracerpid);
805 if(process_tracer) { //it is possible for this to be not the case in certain race conditions
806 KSysGuard::Process *process_tracer = d->mProcesses->getProcess(process->tracerpid);
807 tracer = i18nc("tooltip. name,pid ","This process is being debugged by %1 (<numid>%2</numid>)", process_tracer->name, (long int)process->tracerpid);
810 switch(index.column()) {
811 case HeadingName: {
812 QString tooltip = "<qt>";
813 /*** It would be nice to be able to show the icon in the tooltip, but Qt4 won't let us put
814 * a picture in a tooltip :(
816 QIcon icon;
817 if(mPidToWindowInfo.contains(process->pid)) {
818 WId wid;
819 wid = mPidToWindowInfo[process->pid].wid;
820 icon = KWindowSystem::icon(wid);
822 if(icon.isValid()) {
823 tooltip = i18n("<qt><table><tr><td>%1", icon);
826 if(process->parent_pid == 0) {
827 //Give a quick explanation of init and kthreadd
828 if(process->name == "init") {
829 tooltip += i18n("<b>Init</b> is the parent of all other processes and cannot be killed.<br/>");
830 } else if(process->name == "kthreadd") {
831 tooltip += i18n("<b>KThreadd</b> manages kernel threads. The children processes run in the kernel, controlling hard disk access etc.<br/>");
833 tooltip += i18nc("name column tooltip. first item is the name","<b>%1</b><br />Process ID: <numid>%2</numid>", process->name, (long int)process->pid);
835 else {
836 KSysGuard::Process *parent_process = d->mProcesses->getProcess(process->parent_pid);
837 if(parent_process) { //it should not be possible for this process to not exist, but check just incase
838 tooltip = i18nc("name column tooltip. first item is the name","<b>%1</b><br />Process ID: <numid>%2</numid><br />Parent: %3<br />Parent's ID: <numid>%4</numid>", process->name, (long int)process->pid, parent_process->name, (long int)process->parent_pid);
839 } else {
840 tooltip = i18nc("name column tooltip. first item is the name","<b>%1</b><br />Process ID: <numid>%2</numid><br />Parent's ID: <numid>%3</numid>", process->name, (long int)process->pid, (long int)process->parent_pid);
843 if(!process->command.isEmpty()) {
844 tooltip+= i18n("<br/>Command: %1", process->command);
846 if(!process->tty.isEmpty())
847 tooltip += i18n( "<br />Running on: %1", QString(process->tty));
848 if(!tracer.isEmpty())
849 return tooltip + "<br />" + tracer;
850 return tooltip;
853 case HeadingCommand: {
854 QString tooltip =
855 i18n("<qt>This process was run with the following command:<br />%1", process->command);
856 if(!process->tty.isEmpty())
857 tooltip += i18n( "<br /><br />Running on: %1", QString(process->tty));
858 if(tracer.isEmpty()) return tooltip;
859 return tooltip + "<br />" + tracer;
861 case HeadingUser: {
862 if(!tracer.isEmpty())
863 return d->getTooltipForUser(process) + "<br />" + tracer;
864 return d->getTooltipForUser(process);
866 case HeadingNiceness: {
867 QString tooltip;
868 switch(process->scheduler) {
869 case KSysGuard::Process::Other:
870 case KSysGuard::Process::Batch:
871 tooltip = i18n("<qt>Nice level: %1 (%2)", process->niceLevel, process->niceLevelAsString() );
872 break;
873 case KSysGuard::Process::RoundRobin:
874 case KSysGuard::Process::Fifo:
875 tooltip = i18n("<qt>Scheduler priority: %1", process->niceLevel);
876 break;
878 if(process->scheduler != KSysGuard::Process::Other)
879 tooltip += i18n("<br/>Scheduler: %1", process->schedulerAsString());
881 if(process->ioPriorityClass != KSysGuard::Process::None) {
882 if((process->ioPriorityClass == KSysGuard::Process::RealTime || process->ioPriorityClass == KSysGuard::Process::BestEffort) && process->ioniceLevel != -1)
883 tooltip += i18n("<br/>I/O Nice level: %1 (%2)", process->ioniceLevel, process->ioniceLevelAsString() );
884 tooltip += i18n("<br/>I/O Class: %1", process->ioPriorityClassAsString() );
886 if(tracer.isEmpty()) return tooltip;
887 return tooltip + "<br />" + tracer;
889 case HeadingCPUUsage: {
890 QString tooltip = ki18n("<qt>Process status: %1 %2<br />"
891 "User CPU usage: %3%<br />System CPU usage: %4%</qt>")
892 .subs(process->translatedStatus())
893 .subs(d->getStatusDescription(process->status))
894 .subs((float)(process->userUsage) / d->mNumProcessorCores)
895 .subs((float)(process->sysUsage) / d->mNumProcessorCores)
896 .toString();
898 if(process->numChildren > 0) {
899 tooltip += ki18n("<br />Number of children: %1<br />Total User CPU usage: %2%<br />"
900 "Total System CPU usage: %3%<br />Total CPU usage: %4%")
901 .subs(process->numChildren)
902 .subs((float)(process->totalUserUsage)/ d->mNumProcessorCores)
903 .subs((float)(process->totalSysUsage) / d->mNumProcessorCores)
904 .subs((float)(process->totalUserUsage + process->totalSysUsage) / d->mNumProcessorCores)
905 .toString();
907 if(process->userTime > 0)
908 tooltip += ki18n("<br /><br />CPU time spent running as user: %1 seconds")
909 .subs(process->userTime / 100.0, 0, 'f', 1)
910 .toString();
911 if(process->sysTime > 0)
912 tooltip += ki18n("<br />CPU time spent running in kernel: %1 seconds")
913 .subs(process->sysTime / 100.0, 0, 'f', 1)
914 .toString();
915 if(process->niceLevel != 0)
916 tooltip += i18n("<br />Nice level: %1 (%2)", process->niceLevel, process->niceLevelAsString() );
917 if(process->ioPriorityClass != KSysGuard::Process::None) {
918 if((process->ioPriorityClass == KSysGuard::Process::RealTime || process->ioPriorityClass == KSysGuard::Process::BestEffort) && process->ioniceLevel != -1)
919 tooltip += i18n("<br/>I/O Nice level: %1 (%2)", process->ioniceLevel, process->ioniceLevelAsString() );
920 tooltip += i18n("<br/>I/O Class: %1", process->ioPriorityClassAsString() );
923 if(!tracer.isEmpty())
924 return tooltip + "<br />" + tracer;
925 return tooltip;
927 case HeadingVmSize: {
928 return QVariant();
930 case HeadingMemory: {
931 QString tooltip = "<qt>";
932 if(process->vmURSS != -1) {
933 //We don't have information about the URSS, so just fallback to RSS
934 if(d->mMemTotal > 0)
935 tooltip += i18n("Memory usage: %1 out of %2 (%3 %)<br />", KGlobal::locale()->formatByteSize(process->vmURSS * 1024), KGlobal::locale()->formatByteSize(d->mMemTotal*1024), process->vmURSS*100/d->mMemTotal);
936 else
937 tooltip += i18n("Memory usage: %1<br />", KGlobal::locale()->formatByteSize(process->vmURSS * 1024));
939 if(d->mMemTotal > 0)
940 tooltip += i18n("RSS Memory usage: %1 out of %2 (%3 %)", KGlobal::locale()->formatByteSize(process->vmRSS * 1024), KGlobal::locale()->formatByteSize(d->mMemTotal*1024), process->vmRSS*100/d->mMemTotal);
941 else
942 tooltip += i18n("RSS Memory usage: %1", KGlobal::locale()->formatByteSize(process->vmRSS * 1024));
943 return tooltip;
945 case HeadingSharedMemory: {
946 if(process->vmURSS == -1)
947 return i18n("<qt>Your system does not seem to have this information for us to read, sorry.</qt>");
948 QString tooltip = "<qt>";
949 if(d->mMemTotal >0)
950 tooltip += i18n("Shared library memory usage: %1 out of %2 (%3 %)", KGlobal::locale()->formatByteSize((process->vmRSS - process->vmURSS) * 1024), KGlobal::locale()->formatByteSize(d->mMemTotal*1024), (process->vmRSS-process->vmURSS)*100/d->mMemTotal);
951 else
952 tooltip += i18n("Shared library memory usage: %1", KGlobal::locale()->formatByteSize((process->vmRSS - process->vmURSS) * 1024));
954 return tooltip;
956 case HeadingXTitle: {
957 QString tooltip;
958 QList<WindowInfo> values = d->mPidToWindowInfo.values(process->pid);
959 if(values.isEmpty()) return QVariant(QVariant::String);
960 for(int i = 0; i< values.size(); i++) {
961 WindowInfo w = values[i];
962 if(w.netWinInfo) {
963 const char *name = w.netWinInfo->visibleName();
964 if( !name || name[0] == 0 )
965 name = w.netWinInfo->name();
966 if(name && name[0] != 0) {
967 if( i==0 && values.size()==1)
968 return QString::fromUtf8(name);
969 tooltip += "<li>" + QString::fromUtf8(name) + "</li>";
973 if(!tooltip.isEmpty())
974 return "<qt><ul>" + tooltip + "</ul>";
975 return QVariant(QVariant::String);
978 default:
979 return QVariant(QVariant::String);
982 case Qt::TextAlignmentRole:
983 switch(index.column() ) {
984 case HeadingUser:
985 case HeadingCPUUsage:
986 return QVariant(Qt::AlignCenter);
987 case HeadingPid:
988 case HeadingMemory:
989 case HeadingSharedMemory:
990 case HeadingVmSize:
991 return QVariant(Qt::AlignRight);
993 return QVariant();
994 case UidRole: {
995 if(index.column() != 0) return QVariant(); //If we query with this role, then we want the raw UID for this.
996 KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer());
997 return process->uid;
999 case SortingValueRole: {
1000 //We have a special understanding with the filter sort. This returns an int (in a qvariant) that can be sorted by
1001 KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer());
1002 Q_ASSERT(process);
1003 switch(index.column()) {
1004 case HeadingUser: {
1005 //Sorting by user will be the default and the most common.
1006 //We want to sort in the most useful way that we can. We need to return a number though.
1007 //This code is based on that sorting ascendingly should put the current user at the top
1009 //First the user we are running as should be at the top. We add 0 for this
1010 //Then any other users in the system. We add 100,000,000 for this (remember it's ascendingly sorted)
1011 //Then at the bottom the 'system' processes. We add 200,000,000 for this
1013 //We subtract the uid to sort ascendingly by that, then subtract the cpu usage to sort by that, then finally subtract the memory
1015 long long base = 0;
1016 long long memory = 0;
1017 if(process->vmURSS != -1) memory = process->vmURSS;
1018 else memory = process->vmRSS;
1019 if(d->mIsLocalhost && process->uid == getuid())
1020 base = 0; //own user
1021 else if(process->uid < 100 || !canUserLogin(process->uid))
1022 base = 200000000 - process->uid * 10000; //system user
1023 else
1024 base = 100000000 - process->uid * 10000;
1025 //One special exception is a traced process since that's probably important. We should put that at the top
1026 if(process->tracerpid >0) return base - 9999 ;
1027 int cpu;
1028 if(d->mSimple || !d->mShowChildTotals)
1029 cpu = process->userUsage + process->sysUsage;
1030 else
1031 cpu = process->totalUserUsage + process->totalSysUsage;
1032 if(cpu == 0 && process->status != KSysGuard::Process::Running && process->status != KSysGuard::Process::Sleeping)
1033 cpu = 1; //stopped or zombied processes should be near the top of the list
1034 bool hasWindow = d->mPidToWindowInfo.contains(process->pid);
1035 //However we can of course have lots of processes with the same user. Next we sort by CPU.
1036 if(d->mMemTotal>0)
1037 return (double)(base - (cpu*100) -(hasWindow?50:0) - memory*100.0/d->mMemTotal);
1038 else
1039 return (double)(base - (cpu*100) -(hasWindow?50:0));
1041 case HeadingCPUUsage: {
1042 int cpu;
1043 if(d->mSimple || !d->mShowChildTotals)
1044 cpu = process->userUsage + process->sysUsage;
1045 else
1046 cpu = process->totalUserUsage + process->totalSysUsage;
1047 if(cpu == 0 && process->status != KSysGuard::Process::Running && process->status != KSysGuard::Process::Sleeping)
1048 cpu = 1; //stopped or zombied processes should be near the top of the list
1049 return -cpu;
1051 case HeadingMemory:
1052 if(process->vmURSS == -1)
1053 return (long long)-process->vmRSS;
1054 else
1055 return (long long)-process->vmURSS;
1056 case HeadingVmSize:
1057 return (long long)-process->vmSize;
1058 case HeadingSharedMemory:
1059 if(process->vmURSS == -1) return (long long)0;
1060 return (long long)-(process->vmRSS - process->vmURSS);
1062 return QVariant();
1064 #ifdef Q_WS_X11
1065 case WindowIdRole: {
1066 KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer());
1067 if(!d->mPidToWindowInfo.contains(process->pid)) return QVariant();
1068 WindowInfo w = d->mPidToWindowInfo.value(process->pid);
1069 return (int)w.wid;
1071 #endif
1072 case TotalMemoryRole: {
1073 return d->mMemTotal;
1075 case NumberOfProcessorsRole: {
1076 return d->mNumProcessorCores;
1078 case Qt::DecorationRole: {
1079 if(index.column() == HeadingName) {
1080 KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer());
1081 if(!d->mPidToWindowInfo.contains(process->pid)) {
1082 if(d->mSimple) //When not in tree mode, we need to pad the name column where we do not have an icon
1083 return QIcon(d->mBlankPixmap);
1084 else //When in tree mode, the padding looks pad, so do not pad in this case
1085 return QVariant();
1088 WindowInfo w = d->mPidToWindowInfo.value(process->pid);
1089 if(w.icon.isNull())
1090 return QIcon(d->mBlankPixmap);
1091 return w.icon;
1093 } else if (index.column() == HeadingCPUUsage) {
1094 KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer());
1095 if(process->status == KSysGuard::Process::Stopped || process->status == KSysGuard::Process::Zombie) {
1096 QPixmap pix = KIconLoader::global()->loadIcon("button_cancel", KIconLoader::Small,
1097 KIconLoader::SizeSmall, KIconLoader::DefaultState, QStringList(),
1098 0L, true);
1102 return QVariant();
1104 case Qt::BackgroundRole: {
1105 if(index.column() != HeadingUser) return QVariant();
1106 KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer());
1107 if(process->tracerpid >0) {
1108 //It's being debugged, so probably important. Let's mark it as such
1109 return QColor("yellow");
1111 if(d->mIsLocalhost && process->uid == getuid()) { //own user
1112 return QColor(0, 208, 214, 50);
1114 if(process->uid < 100 || !canUserLogin(process->uid))
1115 return QColor(218, 220,215, 50); //no color for system tasks
1116 //other users
1117 return QColor(2, 154, 54, 50);
1119 case Qt::FontRole: {
1120 if(index.column() == HeadingCPUUsage) {
1121 KSysGuard::Process *process = reinterpret_cast< KSysGuard::Process * > (index.internalPointer());
1122 if(process->userUsage == 0) {
1123 QFont font;
1124 font.setItalic(true);
1125 return font;
1128 return QVariant();
1130 default: //This is a very very common case, so the route to this must be very minimal
1131 return QVariant();
1134 return QVariant(); //never get here, but make compilier happy
1137 bool ProcessModel::hasGUIWindow(long long pid) const
1139 return d->mPidToWindowInfo.contains(pid);
1142 bool ProcessModel::isLocalhost() const
1144 return d->mIsLocalhost;
1148 void ProcessModel::setupHeader() {
1149 QStringList headings;
1150 headings << i18nc("process heading", "Name");
1151 headings << i18nc("process heading", "User Name");
1152 headings << i18nc("process heading", "Pid");
1153 headings << i18nc("process heading", "Tty");
1154 headings << i18nc("process heading", "Niceness");
1155 // xgettext: no-c-format
1156 headings << i18nc("process heading", "CPU %");
1157 headings << i18nc("process heading", "Virtual Size");
1158 headings << i18nc("process heading", "Memory");
1159 headings << i18nc("process heading", "Shared Mem");
1160 headings << i18nc("process heading", "Command");
1161 #ifdef Q_WS_X11
1162 headings << i18nc("process heading", "Window Title");
1163 #endif
1165 if(d->mHeadings.isEmpty()) { // If it's empty, this is the first time this has been called, so insert the headings
1166 beginInsertColumns(QModelIndex(), 0, headings.count()-1);
1167 d->mHeadings = headings;
1168 endInsertColumns();
1169 } else {
1170 // This was called to retranslate the headings. Just use the new translations and call headerDataChanged
1171 Q_ASSERT(d->mHeadings.count() == headings.count());
1172 d->mHeadings = headings;
1173 headerDataChanged(Qt::Horizontal, 0 , headings.count()-1);
1178 void ProcessModel::retranslateUi()
1180 setupHeader();
1183 KSysGuard::Process *ProcessModel::getProcess(long long pid) {
1184 return d->mProcesses->getProcess(pid);
1187 bool ProcessModel::showTotals() const {
1188 return d->mShowChildTotals;
1191 void ProcessModel::setShowTotals(bool showTotals) //slot
1193 if(showTotals == d->mShowChildTotals) return;
1194 d->mShowChildTotals = showTotals;
1196 QModelIndex index;
1197 foreach( KSysGuard::Process *process, d->mProcesses->getAllProcesses()) {
1198 if(process->numChildren > 0) {
1199 int row;
1200 if(d->mSimple)
1201 row = process->index;
1202 else
1203 row = process->parent->children.indexOf(process);
1204 index = createIndex(row, HeadingCPUUsage, process);
1205 emit dataChanged(index, index);
1210 long long ProcessModel::totalMemory() const
1212 return d->mMemTotal;
1214 void ProcessModel::setUnits(Units units)
1216 d->mUnits = units;
1218 ProcessModel::Units ProcessModel::units() const
1220 return (Units) d->mUnits;
1223 QString ProcessModel::formatMemoryInfo(long amountInKB) const
1225 switch(d->mUnits) {
1226 case UnitsKB:
1227 return i18n("%1 k", amountInKB);
1228 case UnitsMB:
1229 return i18n("%1 m", (amountInKB+512)/1024); //Round to nearest megabyte
1230 case UnitsGB:
1231 return i18n("%1 g", (amountInKB+512*1024)/(1024*1024)); //Round to nearest gigabyte
1233 return ""; //error
1236 QString ProcessModel::hostName() const {
1237 return d->mHostName;