1 /*
2     KSysGuard, the KDE System Guard
3 
4     SPDX-FileCopyrightText: 1999, 2000 Chris Schlaeger <cs@kde.org>
5     SPDX-FileCopyrightText: 2006-2007 John Tapsell <john.tapsell@kde.org>
6 
7     SPDX-License-Identifier: LGPL-2.0-or-later
8 
9 */
10 
11 #include "ProcessModel.h"
12 #include "ProcessModel_p.h"
13 #include "timeutil.h"
14 
15 #include "processcore/extended_process_list.h"
16 #include "processcore/formatter.h"
17 #include "processcore/process.h"
18 #include "processcore/process_attribute.h"
19 #include "processcore/process_data_provider.h"
20 
21 #include "processui_debug.h"
22 
23 #include <KFormat>
24 #include <KLocalizedString>
25 #include <QApplication>
26 #include <QBitmap>
27 #include <QDebug>
28 #include <QFont>
29 #include <QIcon>
30 #include <QList>
31 #include <QLocale>
32 #include <QMimeData>
33 #include <QPixmap>
34 #include <QTextDocument>
35 #include <kcolorscheme.h>
36 #include <kiconloader.h>
37 
38 #define HEADING_X_ICON_SIZE 16
39 #define MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS 2000
40 #define GET_OWN_ID
41 
42 #ifdef GET_OWN_ID
43 /* For getuid*/
44 #include <sys/types.h>
45 #include <unistd.h>
46 #endif
47 
48 #ifdef HAVE_XRES
49 #include <X11/extensions/XRes.h>
50 #endif
51 
52 extern QApplication *Qapp;
53 
formatByteSize(qlonglong amountInKB,int units)54 static QString formatByteSize(qlonglong amountInKB, int units)
55 {
56     enum { UnitsAuto, UnitsKB, UnitsMB, UnitsGB, UnitsTB, UnitsPB };
57     static QString kString = i18n("%1 K", QString::fromLatin1("%1"));
58     static QString mString = i18n("%1 M", QString::fromLatin1("%1"));
59     static QString gString = i18n("%1 G", QString::fromLatin1("%1"));
60     static QString tString = i18n("%1 T", QString::fromLatin1("%1"));
61     static QString pString = i18n("%1 P", QString::fromLatin1("%1"));
62     double amount;
63 
64     if (units == UnitsAuto) {
65         if (amountInKB < 1024.0 * 0.9)
66             units = UnitsKB; // amount < 0.9 MiB == KiB
67         else if (amountInKB < 1024.0 * 1024.0 * 0.9)
68             units = UnitsMB; // amount < 0.9 GiB == MiB
69         else if (amountInKB < 1024.0 * 1024.0 * 1024.0 * 0.9)
70             units = UnitsGB; // amount < 0.9 TiB == GiB
71         else if (amountInKB < 1024.0 * 1024.0 * 1024.0 * 1024.0 * 0.9)
72             units = UnitsTB; // amount < 0.9 PiB == TiB
73         else
74             units = UnitsPB;
75     }
76 
77     switch (units) {
78     case UnitsKB:
79         return kString.arg(QLocale().toString(amountInKB));
80     case UnitsMB:
81         amount = amountInKB / 1024.0;
82         return mString.arg(QLocale().toString(amount, 'f', 1));
83     case UnitsGB:
84         amount = amountInKB / (1024.0 * 1024.0);
85         if (amount < 0.1 && amount > 0.05)
86             amount = 0.1;
87         return gString.arg(QLocale().toString(amount, 'f', 1));
88     case UnitsTB:
89         amount = amountInKB / (1024.0 * 1024.0 * 1024.0);
90         if (amount < 0.1 && amount > 0.05)
91             amount = 0.1;
92         return tString.arg(QLocale().toString(amount, 'f', 1));
93     case UnitsPB:
94         amount = amountInKB / (1024.0 * 1024.0 * 1024.0 * 1024.0);
95         if (amount < 0.1 && amount > 0.05)
96             amount = 0.1;
97         return pString.arg(QLocale().toString(amount, 'f', 1));
98     default:
99         return QLatin1String(""); // error
100     }
101 }
102 
ProcessModelPrivate()103 ProcessModelPrivate::ProcessModelPrivate()
104     : mBlankPixmap(HEADING_X_ICON_SIZE, 1)
105 {
106     mBlankPixmap.fill(QColor(0, 0, 0, 0));
107     mSimple = true;
108     mIsLocalhost = true;
109     mMemTotal = -1;
110     mNumProcessorCores = 1;
111     mProcesses = nullptr;
112     mShowChildTotals = true;
113     mShowCommandLineOptions = false;
114     mShowingTooltips = true;
115     mNormalizeCPUUsage = true;
116     mIoInformation = ProcessModel::ActualBytes;
117 #ifdef HAVE_XRES
118     mHaveXRes = false;
119 #endif
120     mHaveTimer = false, mTimerId = -1, mMovingRow = false;
121     mRemovingRow = false;
122     mInsertingRow = false;
123 #if HAVE_X11
124     mIsX11 = QX11Info::isPlatformX11();
125 #else
126     mIsX11 = false;
127 #endif
128 }
129 
~ProcessModelPrivate()130 ProcessModelPrivate::~ProcessModelPrivate()
131 {
132 #if HAVE_X11
133     qDeleteAll(mPidToWindowInfo);
134 #endif
135     mProcesses.clear();
136 }
137 
ProcessModel(QObject * parent,const QString & host)138 ProcessModel::ProcessModel(QObject *parent, const QString &host)
139     : QAbstractItemModel(parent)
140     , d(new ProcessModelPrivate)
141 {
142     d->q = this;
143 #ifdef HAVE_XRES
144     if (d->mIsX11) {
145         int event, error, major, minor;
146         d->mHaveXRes = XResQueryExtension(QX11Info::display(), &event, &error) && XResQueryVersion(QX11Info::display(), &major, &minor);
147     }
148 #endif
149 
150     if (host.isEmpty() || host == QLatin1String("localhost")) {
151         d->mHostName = QString();
152         d->mIsLocalhost = true;
153     } else {
154         d->mHostName = host;
155         d->mIsLocalhost = false;
156     }
157     setupHeader();
158     d->setupProcesses();
159 #if HAVE_X11
160     d->setupWindows();
161 #endif
162     d->mUnits = UnitsKB;
163     d->mIoUnits = UnitsKB;
164 }
165 
lessThan(const QModelIndex & left,const QModelIndex & right) const166 bool ProcessModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
167 {
168     // Because we need to sort Descendingly by default for most of the headings, we often return left > right
169     KSysGuard::Process *processLeft = reinterpret_cast<KSysGuard::Process *>(left.internalPointer());
170     KSysGuard::Process *processRight = reinterpret_cast<KSysGuard::Process *>(right.internalPointer());
171     Q_ASSERT(processLeft);
172     Q_ASSERT(processRight);
173     Q_ASSERT(left.column() == right.column());
174     switch (left.column()) {
175     case HeadingUser: {
176         /* Sorting by user will be the default and the most common.
177            We want to sort in the most useful way that we can. We need to return a number though.
178            This code is based on that sorting ascendingly should put the current user at the top
179            First the user we are running as should be at the top.
180            Then any other users in the system.
181            Then at the bottom the 'system' processes.
182            We then sort by cpu usage to sort by that, then finally sort by memory usage */
183 
184         /* First, place traced processes at the very top, ignoring any other sorting criteria */
185         if (processLeft->tracerpid() >= 0)
186             return true;
187         if (processRight->tracerpid() >= 0)
188             return false;
189 
190         /* Sort by username.  First group into own user, normal users, system users */
191         if (processLeft->uid() != processRight->uid()) {
192             // We primarily sort by username
193             if (d->mIsLocalhost) {
194                 int ownUid = getuid();
195                 if (processLeft->uid() == ownUid)
196                     return true; // Left is our user, right is not.  So left is above right
197                 if (processRight->uid() == ownUid)
198                     return false; // Left is not our user, right is.  So right is above left
199             }
200             bool isLeftSystemUser = processLeft->uid() < 100 || !canUserLogin(processLeft->uid());
201             bool isRightSystemUser = processRight->uid() < 100 || !canUserLogin(processRight->uid());
202             if (isLeftSystemUser && !isRightSystemUser)
203                 return false; // System users are less than non-system users
204             if (!isLeftSystemUser && isRightSystemUser)
205                 return true;
206             // They are either both system users, or both non-system users.
207             // So now sort by username
208             return d->getUsernameForUser(processLeft->uid(), false) < d->getUsernameForUser(processRight->uid(), false);
209         }
210 
211         /* 2nd sort order - Graphics Windows */
212         // Both columns have the same user.  Place processes with windows at the top
213         if (processLeft->hasManagedGuiWindow() && !processRight->hasManagedGuiWindow())
214             return true;
215         if (!processLeft->hasManagedGuiWindow() && processRight->hasManagedGuiWindow())
216             return false;
217 
218         /* 3rd sort order - CPU Usage */
219         int leftCpu, rightCpu;
220         if (d->mSimple || !d->mShowChildTotals) {
221             leftCpu = processLeft->userUsage() + processLeft->sysUsage();
222             rightCpu = processRight->userUsage() + processRight->sysUsage();
223         } else {
224             leftCpu = processLeft->totalUserUsage() + processLeft->totalSysUsage();
225             rightCpu = processRight->totalUserUsage() + processRight->totalSysUsage();
226         }
227         if (leftCpu != rightCpu)
228             return leftCpu > rightCpu;
229 
230         /* 4th sort order - Memory Usage */
231         qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmURSS() : processLeft->vmRSS();
232         qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmURSS() : processRight->vmRSS();
233         return memoryLeft > memoryRight;
234     }
235     case HeadingCPUUsage: {
236         int leftCpu, rightCpu;
237         if (d->mSimple || !d->mShowChildTotals) {
238             leftCpu = processLeft->userUsage() + processLeft->sysUsage();
239             rightCpu = processRight->userUsage() + processRight->sysUsage();
240         } else {
241             leftCpu = processLeft->totalUserUsage() + processLeft->totalSysUsage();
242             rightCpu = processRight->totalUserUsage() + processRight->totalSysUsage();
243         }
244         return leftCpu > rightCpu;
245     }
246     case HeadingCPUTime: {
247         return (processLeft->userTime() + processLeft->sysTime()) > (processRight->userTime() + processRight->sysTime());
248     }
249     case HeadingMemory: {
250         qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmURSS() : processLeft->vmRSS();
251         qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmURSS() : processRight->vmRSS();
252         return memoryLeft > memoryRight;
253     }
254     case HeadingVmPSS: {
255         return processLeft->vmPSS() > processRight->vmPSS();
256     }
257     case HeadingStartTime: {
258         return processLeft->startTime() > processRight->startTime();
259     }
260     case HeadingNoNewPrivileges:
261         return processLeft->noNewPrivileges() > processRight->noNewPrivileges();
262     case HeadingXMemory:
263         return processLeft->pixmapBytes() > processRight->pixmapBytes();
264     case HeadingVmSize:
265         return processLeft->vmSize() > processRight->vmSize();
266     case HeadingSharedMemory: {
267         qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmRSS() - processLeft->vmURSS() : 0;
268         qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmRSS() - processRight->vmURSS() : 0;
269         return memoryLeft > memoryRight;
270     }
271     case HeadingPid:
272         return processLeft->pid() > processRight->pid();
273     case HeadingNiceness:
274         // Sort by scheduler first
275         if (processLeft->scheduler() != processRight->scheduler()) {
276             if (processLeft->scheduler() == KSysGuard::Process::RoundRobin || processLeft->scheduler() == KSysGuard::Process::Fifo)
277                 return true;
278             if (processRight->scheduler() == KSysGuard::Process::RoundRobin || processRight->scheduler() == KSysGuard::Process::Fifo)
279                 return false;
280             if (processLeft->scheduler() == KSysGuard::Process::Other)
281                 return true;
282             if (processRight->scheduler() == KSysGuard::Process::Other)
283                 return false;
284             if (processLeft->scheduler() == KSysGuard::Process::Batch)
285                 return true;
286         }
287         if (processLeft->niceLevel() == processRight->niceLevel())
288             return processLeft->pid() < processRight->pid(); // Subsort by pid if the niceLevel is the same
289         return processLeft->niceLevel() < processRight->niceLevel();
290     case HeadingTty: {
291         if (processLeft->tty() == processRight->tty())
292             return processLeft->pid() < processRight->pid(); // Both ttys are the same.  Sort by pid
293         if (processLeft->tty().isEmpty())
294             return false; // Only left is empty (since they aren't the same)
295         else if (processRight->tty().isEmpty())
296             return true; // Only right is empty
297 
298         // Neither left or right is empty. The tty string is like  "tty10"  so split this into "tty" and "10"
299         // and sort by the string first, then sort by the number
300         QRegExp regexpLeft(QStringLiteral("^(\\D*)(\\d*)$"));
301         QRegExp regexpRight(regexpLeft);
302         if (regexpLeft.indexIn(QString::fromUtf8(processLeft->tty())) == -1 || regexpRight.indexIn(QString::fromUtf8(processRight->tty())) == -1)
303             return processLeft->tty() < processRight->tty();
304         int nameMatch = regexpLeft.cap(1).compare(regexpRight.cap(1));
305         if (nameMatch < 0)
306             return true;
307         if (nameMatch > 0)
308             return false;
309         return regexpLeft.cap(2).toInt() < regexpRight.cap(2).toInt();
310     }
311     case HeadingIoRead:
312         switch (d->mIoInformation) {
313         case ProcessModel::Bytes:
314             return processLeft->ioCharactersRead() > processRight->ioCharactersRead();
315         case ProcessModel::Syscalls:
316             return processLeft->ioReadSyscalls() > processRight->ioReadSyscalls();
317         case ProcessModel::ActualBytes:
318             return processLeft->ioCharactersActuallyRead() > processRight->ioCharactersActuallyRead();
319         case ProcessModel::BytesRate:
320             return processLeft->ioCharactersReadRate() > processRight->ioCharactersReadRate();
321         case ProcessModel::SyscallsRate:
322             return processLeft->ioReadSyscallsRate() > processRight->ioReadSyscallsRate();
323         case ProcessModel::ActualBytesRate:
324             return processLeft->ioCharactersActuallyReadRate() > processRight->ioCharactersActuallyReadRate();
325         }
326         return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through
327     case HeadingIoWrite:
328         switch (d->mIoInformation) {
329         case ProcessModel::Bytes:
330             return processLeft->ioCharactersWritten() > processRight->ioCharactersWritten();
331         case ProcessModel::Syscalls:
332             return processLeft->ioWriteSyscalls() > processRight->ioWriteSyscalls();
333         case ProcessModel::ActualBytes:
334             return processLeft->ioCharactersActuallyWritten() > processRight->ioCharactersActuallyWritten();
335         case ProcessModel::BytesRate:
336             return processLeft->ioCharactersWrittenRate() > processRight->ioCharactersWrittenRate();
337         case ProcessModel::SyscallsRate:
338             return processLeft->ioWriteSyscallsRate() > processRight->ioWriteSyscallsRate();
339         case ProcessModel::ActualBytesRate:
340             return processLeft->ioCharactersActuallyWrittenRate() > processRight->ioCharactersActuallyWrittenRate();
341         }
342     }
343     // Sort by the display string if we do not have an explicit sorting here
344 
345     if (data(left, ProcessModel::PlainValueRole).toInt() == data(right, ProcessModel::PlainValueRole).toInt()) {
346         return data(left, Qt::DisplayRole).toString() < data(right, Qt::DisplayRole).toString();
347     }
348     return data(left, ProcessModel::PlainValueRole).toInt() < data(right, ProcessModel::PlainValueRole).toInt();
349 }
350 
~ProcessModel()351 ProcessModel::~ProcessModel()
352 {
353     delete d;
354 }
355 
processController() const356 KSysGuard::Processes *ProcessModel::processController() const
357 {
358     return d->mProcesses.get();
359 }
360 
extraAttributes() const361 const QVector<KSysGuard::ProcessAttribute *> ProcessModel::extraAttributes() const
362 {
363     return d->mExtraAttributes;
364 }
365 
366 #if HAVE_X11
windowRemoved(WId wid)367 void ProcessModelPrivate::windowRemoved(WId wid)
368 {
369     WindowInfo *window = mWIdToWindowInfo.take(wid);
370     if (!window)
371         return;
372     qlonglong pid = window->pid;
373 
374     QMultiHash<qlonglong, WindowInfo *>::iterator i = mPidToWindowInfo.find(pid);
375     while (i != mPidToWindowInfo.end() && i.key() == pid) {
376         if (i.value()->wid == wid) {
377             i = mPidToWindowInfo.erase(i);
378             break;
379         } else
380             i++;
381     }
382     delete window;
383 
384     // Update the model so that it redraws and resorts
385     KSysGuard::Process *process = mProcesses->getProcess(pid);
386     if (!process)
387         return;
388 
389     int row;
390     if (mSimple)
391         row = process->index();
392     else
393         row = process->parent()->children().indexOf(process);
394     QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process);
395     emit q->dataChanged(index2, index2);
396 }
397 #endif
398 
399 #if HAVE_X11
setupWindows()400 void ProcessModelPrivate::setupWindows()
401 {
402     if (!mIsX11) {
403         return;
404     }
405     connect(KWindowSystem::self(),
406             QOverload<WId, NET::Properties, NET::Properties2>::of(&KWindowSystem::windowChanged),
407             this,
408             &ProcessModelPrivate::windowChanged);
409     connect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, &ProcessModelPrivate::windowAdded);
410     connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &ProcessModelPrivate::windowRemoved);
411 
412     // Add all the windows that KWin is managing - i.e. windows that the user can see
413     const QList<WId> windows = KWindowSystem::windows();
414     for (auto it = windows.begin(); it != windows.end(); ++it) {
415         updateWindowInfo(*it, NET::Properties{}, NET::Properties2{}, true);
416     }
417 }
418 #endif
419 
420 #ifdef HAVE_XRES
updateXResClientData()421 bool ProcessModelPrivate::updateXResClientData()
422 {
423     if (!mIsX11) {
424         return false;
425     }
426     XResClient *clients;
427     int count;
428 
429     XResQueryClients(QX11Info::display(), &count, &clients);
430 
431     mXResClientResources.clear();
432     for (int i = 0; i < count; i++)
433         mXResClientResources.insert(-(qlonglong)(clients[i].resource_base), clients[i].resource_mask);
434 
435     if (clients)
436         XFree(clients);
437     return true;
438 }
439 
queryForAndUpdateAllXWindows()440 void ProcessModelPrivate::queryForAndUpdateAllXWindows()
441 {
442     if (!mIsX11) {
443         return;
444     }
445     updateXResClientData();
446     Window *children, dummy;
447     unsigned int count;
448     Status result = XQueryTree(QX11Info::display(), QX11Info::appRootWindow(), &dummy, &dummy, &children, &count);
449     if (!result)
450         return;
451     if (!updateXResClientData())
452         return;
453     for (uint i = 0; i < count; ++i) {
454         WId wid = children[i];
455         QMap<qlonglong, XID>::iterator iter = mXResClientResources.lowerBound(-(qlonglong)(wid));
456         if (iter == mXResClientResources.end())
457             continue; // We couldn't find it this time :-/
458 
459         if (-iter.key() != (qlonglong)(wid & ~iter.value()))
460             continue; // Already added this window
461 
462         // Get the PID for this window if we do not know it
463         NETWinInfo info(QX11Info::connection(), wid, QX11Info::appRootWindow(), NET::WMPid, NET::Properties2());
464 
465         qlonglong pid = info.pid();
466         if (!pid)
467             continue;
468         // We found a window with this client
469         mXResClientResources.erase(iter);
470         KSysGuard::Process *process = mProcesses->getProcess(pid);
471         if (!process)
472             return; // shouldn't really happen.. maybe race condition etc
473         unsigned long previousPixmapBytes = process->pixmapBytes();
474         // Now update the pixmap bytes for this window
475         bool success = XResQueryClientPixmapBytes(QX11Info::display(), wid, &process->pixmapBytes());
476         if (!success)
477             process->pixmapBytes() = 0;
478 
479         if (previousPixmapBytes != process->pixmapBytes()) {
480             int row;
481             if (mSimple)
482                 row = process->index();
483             else
484                 row = process->parent()->children().indexOf(process);
485             QModelIndex index = q->createIndex(row, ProcessModel::HeadingXMemory, process);
486             emit q->dataChanged(index, index);
487         }
488     }
489     if (children)
490         XFree((char *)children);
491 }
492 #endif
493 
setupProcesses()494 void ProcessModelPrivate::setupProcesses()
495 {
496     if (mProcesses) {
497 #ifdef Q_WS_X11_DISABLE
498         mWIdToWindowInfo.clear();
499         mPidToWindowInfo.clear();
500 #endif
501         mProcesses.clear();
502         q->beginResetModel();
503         q->endResetModel();
504     }
505 
506     mProcesses = KSysGuard::ExtendedProcesses::instance();
507 
508     connect(mProcesses.get(), &KSysGuard::Processes::processChanged, this, &ProcessModelPrivate::processChanged);
509     connect(mProcesses.get(), &KSysGuard::Processes::beginAddProcess, this, &ProcessModelPrivate::beginInsertRow);
510     connect(mProcesses.get(), &KSysGuard::Processes::endAddProcess, this, &ProcessModelPrivate::endInsertRow);
511     connect(mProcesses.get(), &KSysGuard::Processes::beginRemoveProcess, this, &ProcessModelPrivate::beginRemoveRow);
512     connect(mProcesses.get(), &KSysGuard::Processes::endRemoveProcess, this, &ProcessModelPrivate::endRemoveRow);
513     connect(mProcesses.get(), &KSysGuard::Processes::beginMoveProcess, this, &ProcessModelPrivate::beginMoveProcess);
514     connect(mProcesses.get(), &KSysGuard::Processes::endMoveProcess, this, &ProcessModelPrivate::endMoveRow);
515     mNumProcessorCores = mProcesses->numberProcessorCores();
516     if (mNumProcessorCores < 1)
517         mNumProcessorCores = 1; // Default to 1 if there was an error getting the number
518 
519     mExtraAttributes = mProcesses->extendedAttributes();
520     for (int i = 0; i < mExtraAttributes.count(); i++) {
521         connect(mExtraAttributes[i], &KSysGuard::ProcessAttribute::dataChanged, this, [this, i](KSysGuard::Process *process) {
522             const QModelIndex index = q->getQModelIndex(process, mHeadings.count() + i);
523             emit q->dataChanged(index, index);
524         });
525     }
526 }
527 
528 #if HAVE_X11
windowChanged(WId wid,NET::Properties properties,NET::Properties2 properties2)529 void ProcessModelPrivate::windowChanged(WId wid, NET::Properties properties, NET::Properties2 properties2)
530 {
531     updateWindowInfo(wid, properties, properties2, false);
532 }
533 
windowAdded(WId wid)534 void ProcessModelPrivate::windowAdded(WId wid)
535 {
536     updateWindowInfo(wid, NET::Properties{}, NET::Properties2{}, true);
537 }
538 
updateWindowInfo(WId wid,NET::Properties properties,NET::Properties2,bool newWindow)539 void ProcessModelPrivate::updateWindowInfo(WId wid, NET::Properties properties, NET::Properties2 /*properties2*/, bool newWindow)
540 {
541     if (!mIsX11) {
542         return;
543     }
544     properties &= (NET::WMPid | NET::WMVisibleName | NET::WMName | NET::WMIcon);
545 
546     if (!properties) {
547         return; // Nothing interesting changed
548     }
549 
550     WindowInfo *w = mWIdToWindowInfo.value(wid);
551     const qreal dpr = qApp->devicePixelRatio();
552 
553     if (!w && !newWindow)
554         return; // We do not have a record of this window and this is not a new window
555 
556     if (properties == NET::WMIcon) {
557         if (w) {
558             w->icon = KWindowSystem::icon(wid, HEADING_X_ICON_SIZE * dpr, HEADING_X_ICON_SIZE * dpr, true);
559             w->icon.setDevicePixelRatio(dpr);
560         }
561         return;
562     }
563     /* Get PID for window */
564     NETWinInfo info(QX11Info::connection(), wid, QX11Info::appRootWindow(), properties & ~NET::WMIcon, NET::Properties2{});
565 
566     if (!w) {
567         // We know that this must be a newWindow
568         qlonglong pid = info.pid();
569         if (!(properties & NET::WMPid && pid))
570             return; // No PID for the window - this happens if the process did not set _NET_WM_PID
571 
572         // If we are to get the PID only, we are only interested in the XRes info for this,
573         // so don't bother if we already have this info
574         if (properties == NET::WMPid && mPidToWindowInfo.contains(pid))
575             return;
576 
577         w = new WindowInfo(wid, pid);
578         mPidToWindowInfo.insert(pid, w);
579         mWIdToWindowInfo.insert(wid, w);
580     }
581 
582     if (w && (properties & NET::WMIcon)) {
583         w->icon = KWindowSystem::icon(wid, HEADING_X_ICON_SIZE * dpr, HEADING_X_ICON_SIZE * dpr, true);
584         w->icon.setDevicePixelRatio(dpr);
585     }
586     if (properties & NET::WMVisibleName && info.visibleName())
587         w->name = QString::fromUtf8(info.visibleName());
588     else if (properties & NET::WMName)
589         w->name = QString::fromUtf8(info.name());
590     else if (properties & (NET::WMName | NET::WMVisibleName))
591         w->name.clear();
592 
593     KSysGuard::Process *process = mProcesses->getProcess(w->pid);
594     if (!process) {
595         return; // This happens when a new window is detected before we've read in the process
596     }
597 
598     int row;
599     if (mSimple)
600         row = process->index();
601     else
602         row = process->parent()->children().indexOf(process);
603     if (!process->hasManagedGuiWindow()) {
604         process->hasManagedGuiWindow() = true;
605         // Since this is the first window for a process, invalidate HeadingName so that
606         // if we are sorting by name this gets taken into account
607         QModelIndex index1 = q->createIndex(row, ProcessModel::HeadingName, process);
608         emit q->dataChanged(index1, index1);
609     }
610     QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process);
611     emit q->dataChanged(index2, index2);
612 }
613 #endif
614 
update(long updateDurationMSecs,KSysGuard::Processes::UpdateFlags updateFlags)615 void ProcessModel::update(long updateDurationMSecs, KSysGuard::Processes::UpdateFlags updateFlags)
616 {
617     if (updateFlags != KSysGuard::Processes::XMemory) {
618         d->mProcesses->updateAllProcesses(updateDurationMSecs, updateFlags);
619         if (d->mMemTotal <= 0)
620             d->mMemTotal = d->mProcesses->totalPhysicalMemory();
621     }
622 
623 #ifdef HAVE_XRES
624     // Add all the rest of the windows
625     if (d->mHaveXRes && updateFlags.testFlag(KSysGuard::Processes::XMemory))
626         d->queryForAndUpdateAllXWindows();
627 #endif
628 }
629 
getStatusDescription(KSysGuard::Process::ProcessStatus status) const630 QString ProcessModelPrivate::getStatusDescription(KSysGuard::Process::ProcessStatus status) const
631 {
632     switch (status) {
633     case KSysGuard::Process::Running:
634         return i18n("- Process is doing some work.");
635     case KSysGuard::Process::Sleeping:
636         return i18n("- Process is waiting for something to happen.");
637     case KSysGuard::Process::Stopped:
638         return i18n("- Process has been stopped. It will not respond to user input at the moment.");
639     case KSysGuard::Process::Zombie:
640         return i18n("- Process has finished and is now dead, but the parent process has not cleaned up.");
641     case KSysGuard::Process::Ended:
642         //            return i18n("- Process has finished and no longer exists.");
643     default:
644         return QString();
645     }
646 }
647 
getProcessAtIndex(int index) const648 KSysGuard::Process *ProcessModel::getProcessAtIndex(int index) const
649 {
650     Q_ASSERT(d->mSimple);
651     return d->mProcesses->getAllProcesses().at(index);
652 }
653 
rowCount(const QModelIndex & parent) const654 int ProcessModel::rowCount(const QModelIndex &parent) const
655 {
656     if (d->mSimple) {
657         if (parent.isValid())
658             return 0; // In flat mode, none of the processes have children
659         return d->mProcesses->processCount();
660     }
661 
662     // Deal with the case that we are showing it as a tree
663     KSysGuard::Process *process;
664     if (parent.isValid()) {
665         if (parent.column() != 0)
666             return 0; // For a treeview we say that only the first column has children
667         process = reinterpret_cast<KSysGuard::Process *>(parent.internalPointer()); // when parent is invalid, it must be the root level which we set as 0
668     } else {
669         process = d->mProcesses->getProcess(-1);
670     }
671     Q_ASSERT(process);
672     int num_rows = process->children().count();
673     return num_rows;
674 }
675 
columnCount(const QModelIndex &) const676 int ProcessModel::columnCount(const QModelIndex &) const
677 {
678     return d->mHeadings.count() + d->mExtraAttributes.count();
679 }
680 
hasChildren(const QModelIndex & parent=QModelIndex ()) const681 bool ProcessModel::hasChildren(const QModelIndex &parent = QModelIndex()) const
682 {
683     if (d->mSimple) {
684         if (parent.isValid())
685             return 0; // In flat mode, none of the processes have children
686         return !d->mProcesses->getAllProcesses().isEmpty();
687     }
688 
689     // Deal with the case that we are showing it as a tree
690     KSysGuard::Process *process;
691     if (parent.isValid()) {
692         if (parent.column() != 0)
693             return false; // For a treeview we say that only the first column has children
694         process = reinterpret_cast<KSysGuard::Process *>(parent.internalPointer()); // when parent is invalid, it must be the root level which we set as 0
695     } else {
696         process = d->mProcesses->getProcess(-1);
697     }
698     Q_ASSERT(process);
699     bool has_children = !process->children().isEmpty();
700 
701     Q_ASSERT((rowCount(parent) > 0) == has_children);
702     return has_children;
703 }
704 
index(int row,int column,const QModelIndex & parent) const705 QModelIndex ProcessModel::index(int row, int column, const QModelIndex &parent) const
706 {
707     if (row < 0)
708         return QModelIndex();
709     if (column < 0 || column >= columnCount())
710         return QModelIndex();
711 
712     if (d->mSimple) {
713         if (parent.isValid())
714             return QModelIndex();
715         if (d->mProcesses->processCount() <= row)
716             return QModelIndex();
717         return createIndex(row, column, d->mProcesses->getAllProcesses().at(row));
718     }
719 
720     // Deal with the case that we are showing it as a tree
721     KSysGuard::Process *parent_process = nullptr;
722 
723     if (parent.isValid()) // not valid for init or children without parents, so use our special item with pid of 0
724         parent_process = reinterpret_cast<KSysGuard::Process *>(parent.internalPointer());
725     else
726         parent_process = d->mProcesses->getProcess(-1);
727     Q_ASSERT(parent_process);
728 
729     if (parent_process->children().count() > row)
730         return createIndex(row, column, parent_process->children()[row]);
731     else {
732         return QModelIndex();
733     }
734 }
735 
isSimpleMode() const736 bool ProcessModel::isSimpleMode() const
737 {
738     return d->mSimple;
739 }
740 
processChanged(KSysGuard::Process * process,bool onlyTotalCpu)741 void ProcessModelPrivate::processChanged(KSysGuard::Process *process, bool onlyTotalCpu)
742 {
743     int row;
744     if (mSimple)
745         row = process->index();
746     else
747         row = process->parent()->children().indexOf(process);
748 
749     if (process->timeKillWasSent().isValid()) {
750         int elapsed = process->timeKillWasSent().elapsed();
751         if (elapsed < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) {
752             if (!mPidsToUpdate.contains(process->pid()))
753                 mPidsToUpdate.append(process->pid());
754             QModelIndex index1 = q->createIndex(row, 0, process);
755             QModelIndex index2 = q->createIndex(row, mHeadings.count() - 1, process);
756             emit q->dataChanged(index1, index2);
757             if (!mHaveTimer) {
758                 mHaveTimer = true;
759                 mTimerId = startTimer(100);
760             }
761         }
762     }
763     int totalUpdated = 0;
764     Q_ASSERT(row != -1); // Something has gone very wrong
765     if (onlyTotalCpu) {
766         if (mShowChildTotals) {
767             // Only the total cpu usage changed, so only update that
768             QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process);
769             emit q->dataChanged(index, index);
770         }
771         return;
772     } else {
773         if (process->changes() & KSysGuard::Process::Uids) {
774             totalUpdated++;
775             QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process);
776             emit q->dataChanged(index, index);
777         }
778         if (process->changes() & KSysGuard::Process::Tty) {
779             totalUpdated++;
780             QModelIndex index = q->createIndex(row, ProcessModel::HeadingTty, process);
781             emit q->dataChanged(index, index);
782         }
783         if (process->changes() & (KSysGuard::Process::Usage | KSysGuard::Process::Status)
784             || (process->changes() & KSysGuard::Process::TotalUsage && mShowChildTotals)) {
785             totalUpdated += 2;
786             QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process);
787             emit q->dataChanged(index, index);
788             index = q->createIndex(row, ProcessModel::HeadingCPUTime, process);
789             emit q->dataChanged(index, index);
790             // Because of our sorting, changing usage needs to also invalidate the User column
791             index = q->createIndex(row, ProcessModel::HeadingUser, process);
792             emit q->dataChanged(index, index);
793         }
794         if (process->changes() & KSysGuard::Process::Status) {
795             totalUpdated += 2;
796             QModelIndex index = q->createIndex(row, ProcessModel::HeadingNoNewPrivileges, process);
797             emit q->dataChanged(index, index);
798             index = q->createIndex(row, ProcessModel::HeadingCGroup, process);
799             emit q->dataChanged(index, index);
800             index = q->createIndex(row, ProcessModel::HeadingMACContext, process);
801             emit q->dataChanged(index, index);
802         }
803         if (process->changes() & KSysGuard::Process::NiceLevels) {
804             totalUpdated++;
805             QModelIndex index = q->createIndex(row, ProcessModel::HeadingNiceness, process);
806             emit q->dataChanged(index, index);
807         }
808         if (process->changes() & KSysGuard::Process::VmSize) {
809             totalUpdated++;
810             QModelIndex index = q->createIndex(row, ProcessModel::HeadingVmSize, process);
811             emit q->dataChanged(index, index);
812         }
813         if (process->changes() & (KSysGuard::Process::VmSize | KSysGuard::Process::VmRSS | KSysGuard::Process::VmURSS)) {
814             totalUpdated += 2;
815             QModelIndex index = q->createIndex(row, ProcessModel::HeadingMemory, process);
816             emit q->dataChanged(index, index);
817             QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingSharedMemory, process);
818             emit q->dataChanged(index2, index2);
819             // Because of our sorting, changing usage needs to also invalidate the User column
820             index = q->createIndex(row, ProcessModel::HeadingUser, process);
821             emit q->dataChanged(index, index);
822         }
823         if (process->changes() & KSysGuard::Process::VmPSS) {
824             totalUpdated++;
825             auto index = q->createIndex(row, ProcessModel::HeadingVmPSS, process);
826             emit q->dataChanged(index, index);
827         }
828         if (process->changes() & KSysGuard::Process::Name) {
829             totalUpdated++;
830             QModelIndex index = q->createIndex(row, ProcessModel::HeadingName, process);
831             emit q->dataChanged(index, index);
832         }
833         if (process->changes() & KSysGuard::Process::Command) {
834             totalUpdated++;
835             QModelIndex index = q->createIndex(row, ProcessModel::HeadingCommand, process);
836             emit q->dataChanged(index, index);
837         }
838         if (process->changes() & KSysGuard::Process::Login) {
839             totalUpdated++;
840             QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process);
841             emit q->dataChanged(index, index);
842         }
843         if (process->changes() & KSysGuard::Process::IO) {
844             totalUpdated++;
845             QModelIndex index = q->createIndex(row, ProcessModel::HeadingIoRead, process);
846             emit q->dataChanged(index, index);
847             index = q->createIndex(row, ProcessModel::HeadingIoWrite, process);
848             emit q->dataChanged(index, index);
849         }
850 
851         /* Normally this would only be called if changes() tells
852          * us to. We need to update the timestamp even if the value
853          * didn't change though. */
854         auto historyMapEntry = mMapProcessCPUHistory.find(process);
855         if (historyMapEntry != mMapProcessCPUHistory.end()) {
856             auto &history = *historyMapEntry;
857             unsigned long timestamp = QDateTime::currentMSecsSinceEpoch();
858             // Only add an entry if the latest one is older than MIN_HIST_AGE
859             if (history.isEmpty() || timestamp - history.constLast().timestamp > MIN_HIST_AGE) {
860                 if (history.size() == MAX_HIST_ENTRIES) {
861                     history.removeFirst();
862                 }
863 
864                 float usage = (process->totalUserUsage() + process->totalSysUsage()) / (100.0f * mNumProcessorCores);
865                 history.push_back({static_cast<unsigned long>(QDateTime::currentMSecsSinceEpoch()), usage});
866             }
867         }
868     }
869 }
870 
beginInsertRow(KSysGuard::Process * process)871 void ProcessModelPrivate::beginInsertRow(KSysGuard::Process *process)
872 {
873     Q_ASSERT(process);
874     Q_ASSERT(!mRemovingRow);
875     Q_ASSERT(!mInsertingRow);
876     Q_ASSERT(!mMovingRow);
877     mInsertingRow = true;
878 
879 #if HAVE_X11
880     process->hasManagedGuiWindow() = mPidToWindowInfo.contains(process->pid());
881 #endif
882     if (mSimple) {
883         int row = mProcesses->processCount();
884         q->beginInsertRows(QModelIndex(), row, row);
885         return;
886     }
887 
888     // Deal with the case that we are showing it as a tree
889     int row = process->parent()->children().count();
890     QModelIndex parentModelIndex = q->getQModelIndex(process->parent(), 0);
891 
892     // Only here can we actually change the model.  First notify the view/proxy models then modify
893     q->beginInsertRows(parentModelIndex, row, row);
894 }
895 
endInsertRow()896 void ProcessModelPrivate::endInsertRow()
897 {
898     Q_ASSERT(!mRemovingRow);
899     Q_ASSERT(mInsertingRow);
900     Q_ASSERT(!mMovingRow);
901     mInsertingRow = false;
902 
903     q->endInsertRows();
904 }
beginRemoveRow(KSysGuard::Process * process)905 void ProcessModelPrivate::beginRemoveRow(KSysGuard::Process *process)
906 {
907     Q_ASSERT(process);
908     Q_ASSERT(process->pid() >= 0);
909     Q_ASSERT(!mRemovingRow);
910     Q_ASSERT(!mInsertingRow);
911     Q_ASSERT(!mMovingRow);
912     mRemovingRow = true;
913 
914     mMapProcessCPUHistory.remove(process);
915 
916     if (mSimple) {
917         return q->beginRemoveRows(QModelIndex(), process->index(), process->index());
918     } else {
919         int row = process->parent()->children().indexOf(process);
920         if (row == -1) {
921             qCDebug(LIBKSYSGUARD_PROCESSUI) << "A serious problem occurred in remove row.";
922             mRemovingRow = false;
923             return;
924         }
925 
926         return q->beginRemoveRows(q->getQModelIndex(process->parent(), 0), row, row);
927     }
928 }
929 
endRemoveRow()930 void ProcessModelPrivate::endRemoveRow()
931 {
932     Q_ASSERT(!mInsertingRow);
933     Q_ASSERT(!mMovingRow);
934     if (!mRemovingRow)
935         return;
936     mRemovingRow = false;
937 
938     q->endRemoveRows();
939 }
940 
beginMoveProcess(KSysGuard::Process * process,KSysGuard::Process * new_parent)941 void ProcessModelPrivate::beginMoveProcess(KSysGuard::Process *process, KSysGuard::Process *new_parent)
942 {
943     Q_ASSERT(!mRemovingRow);
944     Q_ASSERT(!mInsertingRow);
945     Q_ASSERT(!mMovingRow);
946 
947     if (mSimple)
948         return; // We don't need to move processes when in simple mode
949     mMovingRow = true;
950 
951     int current_row = process->parent()->children().indexOf(process);
952     Q_ASSERT(current_row != -1);
953     int new_row = new_parent->children().count();
954     QModelIndex sourceParent = q->getQModelIndex(process->parent(), 0);
955     QModelIndex destinationParent = q->getQModelIndex(new_parent, 0);
956     mMovingRow = q->beginMoveRows(sourceParent, current_row, current_row, destinationParent, new_row);
957     Q_ASSERT(mMovingRow);
958 }
959 
endMoveRow()960 void ProcessModelPrivate::endMoveRow()
961 {
962     Q_ASSERT(!mInsertingRow);
963     Q_ASSERT(!mRemovingRow);
964     if (!mMovingRow)
965         return;
966     mMovingRow = false;
967 
968     q->endMoveRows();
969 }
970 
getQModelIndex(KSysGuard::Process * process,int column) const971 QModelIndex ProcessModel::getQModelIndex(KSysGuard::Process *process, int column) const
972 {
973     Q_ASSERT(process);
974     int pid = process->pid();
975     if (pid == -1)
976         return QModelIndex(); // pid -1 is our fake process meaning the very root (never drawn).  To represent that, we return QModelIndex() which also means
977                               // the top element
978     int row = 0;
979     if (d->mSimple) {
980         row = process->index();
981     } else {
982         row = process->parent()->children().indexOf(process);
983     }
984     Q_ASSERT(row != -1);
985     return createIndex(row, column, process);
986 }
987 
parent(const QModelIndex & index) const988 QModelIndex ProcessModel::parent(const QModelIndex &index) const
989 {
990     if (!index.isValid())
991         return QModelIndex();
992     KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
993     Q_ASSERT(process);
994 
995     if (d->mSimple)
996         return QModelIndex();
997     else
998         return getQModelIndex(process->parent(), 0);
999 }
1000 
columnAlignment(const int section)1001 static inline QVariant columnAlignment(const int section)
1002 {
1003     switch (section) {
1004     case ProcessModel::HeadingUser:
1005     case ProcessModel::HeadingCPUUsage:
1006     case ProcessModel::HeadingNoNewPrivileges:
1007         return QVariant(Qt::AlignHCenter | Qt::AlignVCenter);
1008     case ProcessModel::HeadingPid:
1009     case ProcessModel::HeadingNiceness:
1010     case ProcessModel::HeadingCPUTime:
1011     case ProcessModel::HeadingStartTime:
1012     case ProcessModel::HeadingMemory:
1013     case ProcessModel::HeadingXMemory:
1014     case ProcessModel::HeadingSharedMemory:
1015     case ProcessModel::HeadingVmSize:
1016     case ProcessModel::HeadingIoWrite:
1017     case ProcessModel::HeadingIoRead:
1018     case ProcessModel::HeadingVmPSS:
1019         return QVariant(Qt::AlignRight | Qt::AlignVCenter);
1020     case ProcessModel::HeadingTty:
1021         return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
1022     default:
1023         return QVariant();
1024     }
1025 }
1026 
headerData(int section,Qt::Orientation orientation,int role) const1027 QVariant ProcessModel::headerData(int section, Qt::Orientation orientation, int role) const
1028 {
1029     if (orientation != Qt::Horizontal)
1030         return QVariant();
1031     if (section < 0)
1032         return QVariant(); // is this needed?
1033 
1034     if (section >= d->mHeadings.count() && section < columnCount()) {
1035         int attr = section - d->mHeadings.count();
1036         switch (role) {
1037         case Qt::DisplayRole:
1038             return d->mExtraAttributes[attr]->shortName();
1039         }
1040         return QVariant();
1041     }
1042 
1043     switch (role) {
1044     case Qt::TextAlignmentRole: {
1045         return columnAlignment(section);
1046     }
1047     case Qt::ToolTipRole: {
1048         if (!d->mShowingTooltips)
1049             return QVariant();
1050         switch (section) {
1051         case HeadingName:
1052             return i18n("The process name.");
1053         case HeadingUser:
1054             return i18n("The user who owns this process.");
1055         case HeadingTty:
1056             return i18n("The controlling terminal on which this process is running.");
1057         case HeadingNiceness:
1058             return i18n(
1059                 "The priority with which this process is being run. For the normal scheduler, this ranges from 19 (very nice, least priority) to -19 (top "
1060                 "priority).");
1061         case HeadingCPUUsage:
1062             if (d->mNumProcessorCores == 1)
1063                 return i18n("The current CPU usage of the process.");
1064             else
1065                 // i18n: %1 is always greater than 1, so do not worry about
1066                 // nonsensical verbosity of the singular part.
1067                 if (d->mNormalizeCPUUsage)
1068                 return i18np("The current total CPU usage of the process, divided by the %1 processor core in the machine.",
1069                              "The current total CPU usage of the process, divided by the %1 processor cores in the machine.",
1070                              d->mNumProcessorCores);
1071             else
1072                 return i18n("The current total CPU usage of the process.");
1073         case HeadingCPUTime:
1074             return i18n("<qt>The total user and system time that this process has been running for, displayed as minutes:seconds.");
1075         case HeadingVmSize:
1076             return i18n(
1077                 "<qt>This is the amount of virtual memory space that the process is using, included shared libraries, graphics memory, files on disk, and so "
1078                 "on. This number is almost meaningless.</qt>");
1079         case HeadingMemory:
1080             return i18n(
1081                 "<qt>This is the amount of real physical memory that this process is using by itself, and approximates the Private memory usage of the "
1082                 "process.<br>It does not include any swapped out memory, nor the code size of its shared libraries.<br>This is often the most useful figure to "
1083                 "judge the memory use of a program.  See What's This for more information.</qt>");
1084         case HeadingSharedMemory:
1085             return i18n(
1086                 "<qt>This is approximately the amount of real physical memory that this process's shared libraries are using.<br>This memory is shared among "
1087                 "all processes that use this library.</qt>");
1088         case HeadingStartTime:
1089             return i18n("<qt>The elapsed time since the process was started.</qt>");
1090         case HeadingNoNewPrivileges:
1091             return i18n("<qt>Linux flag NoNewPrivileges, if set the process can't gain further privileges via setuid etc.</qt>");
1092         case HeadingCommand:
1093             return i18n("<qt>The command with which this process was launched.</qt>");
1094         case HeadingXMemory:
1095             return i18n("<qt>The amount of pixmap memory that this process is using.</qt>");
1096         case HeadingXTitle:
1097             return i18n("<qt>The title of any windows that this process is showing.</qt>");
1098         case HeadingPid:
1099             return i18n("The unique Process ID that identifies this process.");
1100         case HeadingIoRead:
1101             return i18n("The number of bytes read.  See What's This for more information.");
1102         case HeadingIoWrite:
1103             return i18n("The number of bytes written.  See What's This for more information.");
1104         case HeadingCGroup:
1105             return i18n("<qt>The control group (cgroup) where this process belongs.</qt>");
1106         case HeadingMACContext:
1107             return i18n("<qt>Mandatory Access Control (SELinux or AppArmor) context for this process.</qt>");
1108         case HeadingVmPSS:
1109             return i18n(
1110                 "The amount of private physical memory used by a process, with the amount of shared memory divided by the amount of processes using that "
1111                 "shared memory added.");
1112         default:
1113             return QVariant();
1114         }
1115     }
1116     case Qt::WhatsThisRole: {
1117         switch (section) {
1118         case HeadingName:
1119             return i18n(
1120                 "<qt><i>Technical information: </i>The kernel process name is a maximum of 8 characters long, so the full command is examined.  If the first "
1121                 "word in the full command line starts with the process name, the first word of the command line is shown, otherwise the process name is used.");
1122         case HeadingUser:
1123             return i18n(
1124                 "<qt>The user who owns this process.  If the effective, setuid etc user is different, the user who owns the process will be shown, followed by "
1125                 "the effective user.  The ToolTip contains the full information.  <p>"
1126                 "<table>"
1127                 "<tr><td>Login Name/Group</td><td>The username of the Real User/Group who created this process</td></tr>"
1128                 "<tr><td>Effective User/Group</td><td>The process is running with privileges of the Effective User/Group.  This is shown if different from the "
1129                 "real user.</td></tr>"
1130                 "<tr><td>Setuid User/Group</td><td>The saved username of the binary.  The process can escalate its Effective User/Group to the Setuid "
1131                 "User/Group.</td></tr>"
1132 #ifdef Q_OS_LINUX
1133                 "<tr><td>File System User/Group</td><td>Accesses to the filesystem are checked with the File System User/Group.  This is a Linux specific "
1134                 "call. See setfsuid(2) for more information.</td></tr>"
1135 #endif
1136                 "</table>");
1137         case HeadingVmSize:
1138             return i18n(
1139                 "<qt>This is the size of allocated address space - not memory, but address space. This value in practice means next to nothing. When a process "
1140                 "requests a large memory block from the system but uses only a small part of it, the real usage will be low, VIRT will be high. "
1141                 "<p><i>Technical information: </i>This is VmSize in proc/*/status and VIRT in top.");
1142         case HeadingMemory:
1143             return i18n(
1144                 "<qt><i>Technical information: </i>This is an approximation of the Private memory usage, calculated as VmRSS - Shared, from /proc/*/statm.  "
1145                 "This tends to underestimate the true Private memory usage of a process (by not including i/o backed memory pages), but is the best estimation "
1146                 "that is fast to determine.  This is sometimes known as URSS (Unique Resident Set Size). For an individual process, see \"Detailed  Memory "
1147                 "Information\" for a more accurate, but slower, calculation of the true Private memory usage.");
1148         case HeadingCPUUsage:
1149             return i18n("The CPU usage of a process and all of its threads.");
1150         case HeadingCPUTime:
1151             return i18n(
1152                 "<qt>The total system and user time that a process and all of its threads have been running on the CPU for. This can be greater than the wall "
1153                 "clock time if the process has been across multiple CPU cores.");
1154         case HeadingSharedMemory:
1155             return i18n(
1156                 "<qt><i>Technical information: </i>This is an approximation of the Shared memory, called SHR in top.  It is the number of pages that are "
1157                 "backed by a file (see kernel Documentation/filesystems/proc.txt).  For an individual process, see \"Detailed Memory Information\" for a more "
1158                 "accurate, but slower, calculation of the true Shared memory usage.");
1159         case HeadingStartTime:
1160             return i18n("<qt><i>Technical information: </i>The underlying value (clock ticks since system boot) is retrieved from /proc/[pid]/stat");
1161         case HeadingNoNewPrivileges:
1162             return i18n("<qt><i>Technical information: </i>The flag is retrieved from /proc/[pid]/status");
1163         case HeadingCommand:
1164             return i18n("<qt><i>Technical information: </i>This is from /proc/*/cmdline");
1165         case HeadingXMemory:
1166             return i18n(
1167                 "<qt><i>Technical information: </i>This is the amount of memory used by the Xorg process for images for this process.  This is memory used in "
1168                 "addition to Memory and Shared Memory.<br><i>Technical information: </i>This only counts the pixmap memory, and does not include resource "
1169                 "memory used by fonts, cursors, glyphsets etc.  See the <code>xrestop</code> program for a more detailed breakdown.");
1170         case HeadingXTitle:
1171             return i18n(
1172                 "<qt><i>Technical information: </i>For each X11 window, the X11 property _NET_WM_PID is used to map the window to a PID.  If a process' "
1173                 "windows are not shown, then that application incorrectly is not setting _NET_WM_PID.");
1174         case HeadingPid:
1175             return i18n(
1176                 "<qt><i>Technical information: </i>This is the Process ID.  A multi-threaded application is treated a single process, with all threads sharing "
1177                 "the same PID.  The CPU usage etc will be the total, accumulated, CPU usage of all the threads.");
1178         case HeadingIoRead:
1179         case HeadingIoWrite:
1180             return i18n(
1181                 "<qt>This column shows the IO statistics for each process. The tooltip provides the following information:<br>"
1182                 "<table>"
1183                 "<tr><td>Characters Read</td><td>The number of bytes which this task has caused to be read from storage. This is simply the sum of bytes which "
1184                 "this process passed to read() and pread(). It includes things like tty IO and it is unaffected by whether or not actual physical disk IO was "
1185                 "required (the read might have been satisfied from pagecache).</td></tr>"
1186                 "<tr><td>Characters Written</td><td>The number of bytes which this task has caused, or shall cause to be written to disk. Similar caveats "
1187                 "apply here as with Characters Read.</td></tr>"
1188                 "<tr><td>Read Syscalls</td><td>The number of read I/O operations, i.e. syscalls like read() and pread().</td></tr>"
1189                 "<tr><td>Write Syscalls</td><td>The number of write I/O operations, i.e. syscalls like write() and pwrite().</td></tr>"
1190                 "<tr><td>Actual Bytes Read</td><td>The number of bytes which this process really did cause to be fetched from the storage layer. Done at the "
1191                 "submit_bio() level, so it is accurate for block-backed filesystems. This may not give sensible values for NFS and CIFS filesystems.</td></tr>"
1192                 "<tr><td>Actual Bytes Written</td><td>Attempt to count the number of bytes which this process caused to be sent to the storage layer. This is "
1193                 "done at page-dirtying time.</td>"
1194                 "</table><p>"
1195                 "The number in brackets shows the rate at which each value is changing, determined from taking the difference between the previous value and "
1196                 "the new value, and dividing by the update interval.<p>"
1197                 "<i>Technical information: </i>This data is collected from /proc/*/io and is documented further in Documentation/accounting and "
1198                 "Documentation/filesystems/proc.txt in the kernel source.");
1199         case HeadingCGroup:
1200             return i18n(
1201                 "<qt><i>Technical information: </i>This shows Linux Control Group (cgroup) membership, retrieved from /proc/[pid]/cgroup. Control groups are "
1202                 "used by Systemd and containers for limiting process group's usage of resources and to monitor them.");
1203         case HeadingMACContext:
1204             return i18n(
1205                 "<qt><i>Technical information: </i>This shows Mandatory Access Control (SELinux or AppArmor) context, retrieved from "
1206                 "/proc/[pid]/attr/current.");
1207         case HeadingVmPSS:
1208             return i18n(
1209                 "<i>Technical information:</i> This is often referred to as \"Proportional Set Size\" and is the closest approximation of the real amount of "
1210                 "total memory used by a process. Note that the number of applications sharing shared memory is determined per shared memory section and thus "
1211                 "can vary per memory section.");
1212         default:
1213             return QVariant();
1214         }
1215     }
1216     case Qt::DisplayRole:
1217         return d->mHeadings[section];
1218     default:
1219         return QVariant();
1220     }
1221 }
1222 
setSimpleMode(bool simple)1223 void ProcessModel::setSimpleMode(bool simple)
1224 {
1225     if (d->mSimple == simple)
1226         return;
1227 
1228     emit layoutAboutToBeChanged();
1229 
1230     d->mSimple = simple;
1231 
1232     int flatrow;
1233     int treerow;
1234     QList<QModelIndex> flatIndexes;
1235     QList<QModelIndex> treeIndexes;
1236     foreach (KSysGuard::Process *process, d->mProcesses->getAllProcesses()) {
1237         flatrow = process->index();
1238         treerow = process->parent()->children().indexOf(process);
1239         flatIndexes.clear();
1240         treeIndexes.clear();
1241 
1242         for (int i = 0; i < columnCount(); i++) {
1243             flatIndexes << createIndex(flatrow, i, process);
1244             treeIndexes << createIndex(treerow, i, process);
1245         }
1246         if (d->mSimple) // change from tree mode to flat mode
1247             changePersistentIndexList(treeIndexes, flatIndexes);
1248         else // change from flat mode to tree mode
1249             changePersistentIndexList(flatIndexes, treeIndexes);
1250     }
1251 
1252     emit layoutChanged();
1253 }
1254 
canUserLogin(long uid) const1255 bool ProcessModel::canUserLogin(long uid) const
1256 {
1257     if (uid == 65534) {
1258         // nobody user
1259         return false;
1260     }
1261 
1262     if (!d->mIsLocalhost)
1263         return true; // We only deal with localhost.  Just always return true for non localhost
1264 
1265     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
1266     if (canLogin != -1)
1267         return canLogin; // We know whether they can log in
1268 
1269     // We got the default, -1, so we don't know.  Look it up
1270 
1271     KUser user(uid);
1272     if (!user.isValid()) {
1273         // for some reason the user isn't recognised.  This might happen under certain security situations.
1274         // Just return true to be safe
1275         d->mUidCanLogin[uid] = 1;
1276         return true;
1277     }
1278     QString shell = user.shell();
1279     if (shell == QLatin1String("/bin/false")) // FIXME - add in any other shells it could be for false
1280     {
1281         d->mUidCanLogin[uid] = 0;
1282         return false;
1283     }
1284     d->mUidCanLogin[uid] = 1;
1285     return true;
1286 }
1287 
getTooltipForUser(const KSysGuard::Process * ps) const1288 QString ProcessModelPrivate::getTooltipForUser(const KSysGuard::Process *ps) const
1289 {
1290     QString userTooltip;
1291     if (!mIsLocalhost) {
1292         return xi18nc("@info:tooltip", "<para><emphasis strong='true'>Login Name:</emphasis> %1</para>", getUsernameForUser(ps->uid(), true));
1293     } else {
1294         KUser user(ps->uid());
1295         if (!user.isValid())
1296             userTooltip += xi18nc("@info:tooltip", "<para>This user is not recognized for some reason.</para>");
1297         else {
1298             if (!user.property(KUser::FullName).isValid())
1299                 userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>%1</emphasis></para>", user.property(KUser::FullName).toString());
1300             userTooltip += xi18nc("@info:tooltip",
1301                                   "<para><emphasis strong='true'>Login Name:</emphasis> %1 (uid: %2)</para>",
1302                                   user.loginName(),
1303                                   QString::number(ps->uid()));
1304             if (!user.property(KUser::RoomNumber).isValid())
1305                 userTooltip +=
1306                     xi18nc("@info:tooltip", "<para><emphasis strong='true'>  Room Number:</emphasis> %1</para>", user.property(KUser::RoomNumber).toString());
1307             if (!user.property(KUser::WorkPhone).isValid())
1308                 userTooltip +=
1309                     xi18nc("@info:tooltip", "<para><emphasis strong='true'>  Work Phone:</emphasis> %1</para>", user.property(KUser::WorkPhone).toString());
1310         }
1311     }
1312     if ((ps->uid() != ps->euid() && ps->euid() != -1) || (ps->uid() != ps->suid() && ps->suid() != -1) || (ps->uid() != ps->fsuid() && ps->fsuid() != -1)) {
1313         if (ps->euid() != -1)
1314             userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Effective User:</emphasis> %1</para>", getUsernameForUser(ps->euid(), true));
1315         if (ps->suid() != -1)
1316             userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Setuid User:</emphasis> %1</para>", getUsernameForUser(ps->suid(), true));
1317         if (ps->fsuid() != -1)
1318             userTooltip +=
1319                 xi18nc("@info:tooltip", "<para><emphasis strong='true'>File System User:</emphasis> %1</para>", getUsernameForUser(ps->fsuid(), true));
1320         userTooltip += QLatin1String("<br/>");
1321     }
1322     if (ps->gid() != -1) {
1323         userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Group:</emphasis> %1</para>", getGroupnameForGroup(ps->gid()));
1324         if ((ps->gid() != ps->egid() && ps->egid() != -1) || (ps->gid() != ps->sgid() && ps->sgid() != -1) || (ps->gid() != ps->fsgid() && ps->fsgid() != -1)) {
1325             if (ps->egid() != -1)
1326                 userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Effective Group:</emphasis> %1</para>", getGroupnameForGroup(ps->egid()));
1327             if (ps->sgid() != -1)
1328                 userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Setuid Group:</emphasis> %1</para>", getGroupnameForGroup(ps->sgid()));
1329             if (ps->fsgid() != -1)
1330                 userTooltip +=
1331                     xi18nc("@info:tooltip", "<para><emphasis strong='true'>File System Group:</emphasis> %1</para>", getGroupnameForGroup(ps->fsgid()));
1332         }
1333     }
1334     return userTooltip;
1335 }
1336 
getStringForProcess(KSysGuard::Process * process) const1337 QString ProcessModel::getStringForProcess(KSysGuard::Process *process) const
1338 {
1339     return i18nc("Short description of a process. PID, name, user",
1340                  "%1: %2, owned by user %3",
1341                  QString::number(process->pid()),
1342                  process->name(),
1343                  d->getUsernameForUser(process->uid(), false));
1344 }
1345 
getGroupnameForGroup(long gid) const1346 QString ProcessModelPrivate::getGroupnameForGroup(long gid) const
1347 {
1348     if (mIsLocalhost) {
1349         QString groupname = KUserGroup(gid).name();
1350         if (!groupname.isEmpty())
1351             return i18nc("Group name and group id", "%1 (gid: %2)", groupname, QString::number(gid));
1352     }
1353     return QString::number(gid);
1354 }
1355 
getUsernameForUser(long uid,bool withuid) const1356 QString ProcessModelPrivate::getUsernameForUser(long uid, bool withuid) const
1357 {
1358     QString &username = mUserUsername[uid];
1359     if (username.isNull()) {
1360         if (!mIsLocalhost) {
1361             username = QLatin1String(""); // empty, but not null
1362         } else {
1363             KUser user(uid);
1364             if (!user.isValid())
1365                 username = QLatin1String("");
1366             else
1367                 username = user.loginName();
1368         }
1369     }
1370     if (username.isEmpty())
1371         return QString::number(uid);
1372     if (withuid)
1373         return i18nc("User name and user id", "%1 (uid: %2)", username, QString::number(uid));
1374     return username;
1375 }
1376 
data(const QModelIndex & index,int role) const1377 QVariant ProcessModel::data(const QModelIndex &index, int role) const
1378 {
1379     // This function must be super duper ultra fast because it's called thousands of times every few second :(
1380     // I think it should be optimised for role first, hence the switch statement (fastest possible case)
1381 
1382     if (!index.isValid()) {
1383         return QVariant();
1384     }
1385 
1386     if (index.column() > columnCount()) {
1387         return QVariant();
1388     }
1389     // plugin stuff first
1390     if (index.column() >= d->mHeadings.count()) {
1391         int attr = index.column() - d->mHeadings.count();
1392         switch (role) {
1393         case ProcessModel::PlainValueRole: {
1394             KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
1395             const QVariant value = d->mExtraAttributes[attr]->data(process);
1396             return value;
1397         }
1398         case Qt::DisplayRole: {
1399             KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
1400             const QVariant value = d->mExtraAttributes[attr]->data(process);
1401             return KSysGuard::Formatter::formatValue(value, d->mExtraAttributes[attr]->unit());
1402         }
1403         case Qt::TextAlignmentRole: {
1404             KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
1405             const QVariant value = d->mExtraAttributes[attr]->data(process);
1406             if (value.canConvert(QMetaType::LongLong) && static_cast<QMetaType::Type>(value.type()) != QMetaType::QString) {
1407                 return Qt::AlignRight + Qt::AlignVCenter;
1408             }
1409             return Qt::AlignLeft + Qt::AlignVCenter;
1410         }
1411         }
1412         return QVariant();
1413     }
1414 
1415     KFormat format;
1416     switch (role) {
1417     case Qt::DisplayRole: {
1418         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
1419         switch (index.column()) {
1420         case HeadingName:
1421             if (d->mShowCommandLineOptions)
1422                 return process->name();
1423             else
1424                 return process->name().section(QLatin1Char(' '), 0, 0);
1425         case HeadingPid:
1426             return (qlonglong)process->pid();
1427         case HeadingUser:
1428             if (!process->login().isEmpty())
1429                 return process->login();
1430             if (process->uid() == process->euid())
1431                 return d->getUsernameForUser(process->uid(), false);
1432             else
1433                 return QString(d->getUsernameForUser(process->uid(), false) + QStringLiteral(", ") + d->getUsernameForUser(process->euid(), false));
1434         case HeadingNiceness:
1435             switch (process->scheduler()) {
1436             case KSysGuard::Process::Other:
1437                 return process->niceLevel();
1438             case KSysGuard::Process::SchedulerIdle:
1439                 return i18nc("scheduler", "Idle"); // neither static nor dynamic priority matter
1440             case KSysGuard::Process::Batch:
1441                 return i18nc("scheduler", "(Batch) %1", process->niceLevel()); // only dynamic priority matters
1442             case KSysGuard::Process::RoundRobin:
1443                 return i18nc("Round robin scheduler", "RR %1", process->niceLevel());
1444             case KSysGuard::Process::Fifo:
1445                 if (process->niceLevel() == 99)
1446                     return i18nc("Real Time scheduler", "RT");
1447                 else
1448                     return i18nc("First in first out scheduler", "FIFO %1", process->niceLevel());
1449             case KSysGuard::Process::Interactive:
1450                 return i18nc("scheduler", "(IA) %1", process->niceLevel());
1451             }
1452             return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through
1453         case HeadingTty:
1454             return process->tty();
1455         case HeadingCPUUsage: {
1456             double total;
1457             if (d->mShowChildTotals && !d->mSimple)
1458                 total = process->totalUserUsage() + process->totalSysUsage();
1459             else
1460                 total = process->userUsage() + process->sysUsage();
1461             if (d->mNormalizeCPUUsage)
1462                 total = total / d->mNumProcessorCores;
1463 
1464             if (total < 1 && process->status() != KSysGuard::Process::Sleeping && process->status() != KSysGuard::Process::Running
1465                 && process->status() != KSysGuard::Process::Ended)
1466                 return process->translatedStatus(); // tell the user when the process is a zombie or stopped
1467             if (total < 0.5)
1468                 return QString();
1469 
1470             return QString(QString::number((int)(total + 0.5)) + QLatin1Char('%'));
1471         }
1472         case HeadingCPUTime: {
1473             qlonglong seconds = (process->userTime() + process->sysTime()) / 100;
1474             return QStringLiteral("%1:%2").arg(seconds / 60).arg((int)seconds % 60, 2, 10, QLatin1Char('0'));
1475         }
1476         case HeadingMemory:
1477             if (process->vmURSS() == -1) {
1478                 // If we don't have the URSS (the memory used by only the process, not the shared libraries)
1479                 // then return the RSS (physical memory used by the process + shared library) as the next best thing
1480                 return formatMemoryInfo(process->vmRSS(), d->mUnits, true);
1481             } else {
1482                 return formatMemoryInfo(process->vmURSS(), d->mUnits, true);
1483             }
1484         case HeadingVmSize:
1485             return formatMemoryInfo(process->vmSize(), d->mUnits, true);
1486         case HeadingSharedMemory:
1487             if (process->vmRSS() - process->vmURSS() <= 0 || process->vmURSS() == -1)
1488                 return QVariant(QVariant::String);
1489             return formatMemoryInfo(process->vmRSS() - process->vmURSS(), d->mUnits);
1490         case HeadingStartTime: {
1491             // NOTE: the next 6 lines are the same as in the next occurrence of 'case HeadingStartTime:' => keep in sync or remove duplicate code
1492             const auto clockTicksSinceSystemBoot = process->startTime();
1493             const auto clockTicksPerSecond = sysconf(_SC_CLK_TCK); // see man proc or https://superuser.com/questions/101183/what-is-a-cpu-tick
1494             const auto secondsSinceSystemBoot = (double)clockTicksSinceSystemBoot / clockTicksPerSecond;
1495             const auto systemBootTime = TimeUtil::systemUptimeAbsolute();
1496             const auto absoluteStartTime = systemBootTime.addSecs(secondsSinceSystemBoot);
1497             const auto relativeStartTime = absoluteStartTime.secsTo(QDateTime::currentDateTime());
1498             return TimeUtil::secondsToHumanElapsedString(relativeStartTime);
1499         }
1500         case HeadingNoNewPrivileges:
1501             return QString::number(process->noNewPrivileges());
1502         case HeadingCommand: {
1503             return process->command().replace(QLatin1Char('\n'), QLatin1Char(' '));
1504             // It would be nice to embolden the process name in command, but this requires that the itemdelegate to support html text
1505             //                QString command = process->command;
1506             //                command.replace(process->name, "<b>" + process->name + "</b>");
1507             //                return "<qt>" + command;
1508         }
1509         case HeadingIoRead: {
1510             switch (d->mIoInformation) {
1511             case ProcessModel::Bytes: // divide by 1024 to convert to kB
1512                 return formatMemoryInfo(process->ioCharactersRead() / 1024, d->mIoUnits, true);
1513             case ProcessModel::Syscalls:
1514                 if (process->ioReadSyscalls())
1515                     return QString::number(process->ioReadSyscalls());
1516                 break;
1517             case ProcessModel::ActualBytes:
1518                 return formatMemoryInfo(process->ioCharactersActuallyRead() / 1024, d->mIoUnits, true);
1519             case ProcessModel::BytesRate:
1520                 if (process->ioCharactersReadRate() / 1024)
1521                     return i18n("%1/s", formatMemoryInfo(process->ioCharactersReadRate() / 1024, d->mIoUnits, true));
1522                 break;
1523             case ProcessModel::SyscallsRate:
1524                 if (process->ioReadSyscallsRate())
1525                     return QString::number(process->ioReadSyscallsRate());
1526                 break;
1527             case ProcessModel::ActualBytesRate:
1528                 if (process->ioCharactersActuallyReadRate() / 1024)
1529                     return i18n("%1/s", formatMemoryInfo(process->ioCharactersActuallyReadRate() / 1024, d->mIoUnits, true));
1530                 break;
1531             }
1532             return QVariant();
1533         }
1534         case HeadingIoWrite: {
1535             switch (d->mIoInformation) {
1536             case ProcessModel::Bytes:
1537                 return formatMemoryInfo(process->ioCharactersWritten() / 1024, d->mIoUnits, true);
1538             case ProcessModel::Syscalls:
1539                 if (process->ioWriteSyscalls())
1540                     return QString::number(process->ioWriteSyscalls());
1541                 break;
1542             case ProcessModel::ActualBytes:
1543                 return formatMemoryInfo(process->ioCharactersActuallyWritten() / 1024, d->mIoUnits, true);
1544             case ProcessModel::BytesRate:
1545                 if (process->ioCharactersWrittenRate() / 1024)
1546                     return i18n("%1/s", formatMemoryInfo(process->ioCharactersWrittenRate() / 1024, d->mIoUnits, true));
1547                 break;
1548             case ProcessModel::SyscallsRate:
1549                 if (process->ioWriteSyscallsRate())
1550                     return QString::number(process->ioWriteSyscallsRate());
1551                 break;
1552             case ProcessModel::ActualBytesRate:
1553                 if (process->ioCharactersActuallyWrittenRate() / 1024)
1554                     return i18n("%1/s", formatMemoryInfo(process->ioCharactersActuallyWrittenRate() / 1024, d->mIoUnits, true));
1555                 break;
1556             }
1557             return QVariant();
1558         }
1559 #if HAVE_X11
1560         case HeadingXMemory:
1561             return formatMemoryInfo(process->pixmapBytes() / 1024, d->mUnits, true);
1562         case HeadingXTitle: {
1563             if (!process->hasManagedGuiWindow())
1564                 return QVariant(QVariant::String);
1565 
1566             WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL);
1567             if (!w)
1568                 return QVariant(QVariant::String);
1569             else
1570                 return w->name;
1571         }
1572 #endif
1573         case HeadingCGroup:
1574             return process->cGroup();
1575         case HeadingMACContext:
1576             return process->macContext();
1577         case HeadingVmPSS:
1578             return process->vmPSS() >= 0 ? formatMemoryInfo(process->vmPSS(), d->mUnits, true) : QVariant{};
1579         default:
1580             return QVariant();
1581         }
1582         break;
1583     }
1584     case Qt::ToolTipRole: {
1585         if (!d->mShowingTooltips)
1586             return QVariant();
1587         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
1588         QString tracer;
1589         if (process->tracerpid() >= 0) {
1590             KSysGuard::Process *process_tracer = d->mProcesses->getProcess(process->tracerpid());
1591             if (process_tracer) // it is possible for this to be not the case in certain race conditions
1592                 tracer =
1593                     xi18nc("tooltip. name,pid ", "This process is being debugged by %1 (%2)", process_tracer->name(), QString::number(process->tracerpid()));
1594         }
1595         switch (index.column()) {
1596         case HeadingName: {
1597             /*   It would be nice to be able to show the icon in the tooltip, but Qt4 won't let us put
1598              *   a picture in a tooltip :(
1599 
1600             QIcon icon;
1601             if(mPidToWindowInfo.contains(process->pid())) {
1602                 WId wid;
1603                 wid = mPidToWindowInfo[process->pid()].wid;
1604                 icon = KWindowSystem::icon(wid);
1605             }
1606             if(icon.isValid()) {
1607                 tooltip = i18n("<qt><table><tr><td>%1", icon);
1608             }
1609             */
1610             QString tooltip;
1611             if (process->parentPid() == -1) {
1612                 // Give a quick explanation of init and kthreadd
1613                 if (process->name() == QLatin1String("init") || process->name() == QLatin1String("systemd")) {
1614                     tooltip = xi18nc("@info:tooltip",
1615                                      "<title>%1</title><para>The parent of all other processes and cannot be killed.</para><para><emphasis "
1616                                      "strong='true'>Process ID:</emphasis> %2</para>",
1617                                      process->name(),
1618                                      QString::number(process->pid()));
1619                 } else if (process->name() == QLatin1String("kthreadd")) {
1620                     tooltip = xi18nc("@info:tooltip",
1621                                      "<title>KThreadd</title><para>Manages kernel threads. The children processes run in the kernel, controlling hard disk "
1622                                      "access, etc.</para>");
1623                 } else {
1624                     tooltip = xi18nc("@info:tooltip",
1625                                      "<title>%1</title><para><emphasis strong='true'>Process ID:</emphasis> %2</para>",
1626                                      process->name(),
1627                                      QString::number(process->pid()));
1628                 }
1629             } else {
1630                 KSysGuard::Process *parent_process = d->mProcesses->getProcess(process->parentPid());
1631                 if (parent_process) { // In race conditions, it's possible for this process to not exist
1632                     tooltip = xi18nc("@info:tooltip",
1633                                      "<title>%1</title>"
1634                                      "<para><emphasis strong='true'>Process ID:</emphasis> %2</para>"
1635                                      "<para><emphasis strong='true'>Parent:</emphasis> %3</para>"
1636                                      "<para><emphasis strong='true'>Parent's ID:</emphasis> %4</para>",
1637                                      process->name(),
1638                                      QString::number(process->pid()),
1639                                      parent_process->name(),
1640                                      QString::number(process->parentPid()));
1641                 } else {
1642                     tooltip = xi18nc("@info:tooltip",
1643                                      "<title>%1</title>"
1644                                      "<para><emphasis strong='true'>Process ID:</emphasis> %2</para>"
1645                                      "<para><emphasis strong='true'>Parent's ID:</emphasis> %3</para>",
1646                                      process->name(),
1647                                      QString::number(process->pid()),
1648                                      QString::number(process->parentPid()));
1649                 }
1650             }
1651             if (process->numThreads() >= 1)
1652                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Number of threads:</emphasis> %1</para>", process->numThreads());
1653             if (!process->command().isEmpty()) {
1654                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Command:</emphasis> %1</para>", process->command());
1655             }
1656             if (!process->tty().isEmpty())
1657                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Running on:</emphasis> %1</para>", QString::fromUtf8(process->tty()));
1658             if (!tracer.isEmpty())
1659                 return QStringLiteral("%1<br />%2").arg(tooltip).arg(tracer);
1660 
1661             return tooltip;
1662         }
1663         case HeadingStartTime: {
1664             // NOTE: the next 6 lines are the same as in the previous occurrence of 'case HeadingStartTime:' => keep in sync or remove duplicate code
1665             const auto clockTicksSinceSystemBoot = process->startTime();
1666             const auto clockTicksPerSecond = sysconf(_SC_CLK_TCK);
1667             const auto secondsSinceSystemBoot = (double)clockTicksSinceSystemBoot / clockTicksPerSecond;
1668             const auto systemBootTime = TimeUtil::systemUptimeAbsolute();
1669             const auto absoluteStartTime = systemBootTime.addSecs(secondsSinceSystemBoot);
1670             const auto relativeStartTime = absoluteStartTime.secsTo(QDateTime::currentDateTime());
1671             return xi18nc("@info:tooltip",
1672                           "<para><emphasis strong='true'>Clock ticks since system boot:</emphasis> %1</para>"
1673                           "<para><emphasis strong='true'>Seconds since system boot:</emphasis> %2 (System boot time: %3)</para>"
1674                           "<para><emphasis strong='true'>Absolute start time:</emphasis> %4</para>"
1675                           "<para><emphasis strong='true'>Relative start time:</emphasis> %5</para>",
1676                           clockTicksSinceSystemBoot,
1677                           secondsSinceSystemBoot,
1678                           systemBootTime.toString(),
1679                           absoluteStartTime.toString(),
1680                           TimeUtil::secondsToHumanElapsedString(relativeStartTime));
1681         }
1682         case HeadingCommand: {
1683             QString tooltip = xi18nc("@info:tooltip",
1684                                      "<para><emphasis strong='true'>This process was run with the following command:</emphasis></para>"
1685                                      "<para>%1</para>",
1686                                      process->command());
1687             if (!process->tty().isEmpty())
1688                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Running on:</emphasis> %1</para>", QString::fromUtf8(process->tty()));
1689             if (!tracer.isEmpty()) {
1690                 return QStringLiteral("%1<br/>%2").arg(tooltip).arg(tracer);
1691             }
1692             return tooltip;
1693         }
1694         case HeadingUser: {
1695             QString tooltip = d->getTooltipForUser(process);
1696             if (tracer.isEmpty()) {
1697                 return tooltip;
1698             }
1699 
1700             return QString(tooltip + QStringLiteral("<br/>") + tracer);
1701         }
1702         case HeadingNiceness: {
1703             QString tooltip;
1704             switch (process->scheduler()) {
1705             case KSysGuard::Process::Other:
1706             case KSysGuard::Process::Batch:
1707             case KSysGuard::Process::Interactive:
1708                 tooltip = xi18nc("@info:tooltip",
1709                                  "<para><emphasis strong='true'>Nice level:</emphasis> %1 (%2)</para>",
1710                                  process->niceLevel(),
1711                                  process->niceLevelAsString());
1712                 break;
1713             case KSysGuard::Process::RoundRobin:
1714             case KSysGuard::Process::Fifo:
1715                 tooltip = xi18nc("@info:tooltip",
1716                                  "<para><emphasis strong='true'>This is a real time process.</emphasis></para>"
1717                                  "<para><emphasis strong='true'>Scheduler priority:</emphasis> %1</para>",
1718                                  process->niceLevel());
1719                 break;
1720             case KSysGuard::Process::SchedulerIdle:
1721                 break; // has neither dynamic (niceness) or static (scheduler priority) priority
1722             }
1723             if (process->scheduler() != KSysGuard::Process::Other)
1724                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Scheduler:</emphasis> %1</para>", process->schedulerAsString());
1725 
1726             if (process->ioPriorityClass() != KSysGuard::Process::None) {
1727                 if ((process->ioPriorityClass() == KSysGuard::Process::RealTime || process->ioPriorityClass() == KSysGuard::Process::BestEffort)
1728                     && process->ioniceLevel() != -1)
1729                     tooltip += xi18nc("@info:tooltip",
1730                                       "<para><emphasis strong='true'>I/O Nice level:</emphasis> %1 (%2)</para>",
1731                                       process->ioniceLevel(),
1732                                       process->ioniceLevelAsString());
1733                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>I/O Class:</emphasis> %1</para>", process->ioPriorityClassAsString());
1734             }
1735             if (tracer.isEmpty())
1736                 return tooltip;
1737             return QString(tooltip + QStringLiteral("<br/>") + tracer);
1738         }
1739         case HeadingCPUUsage:
1740         case HeadingCPUTime: {
1741             int divideby = (d->mNormalizeCPUUsage ? d->mNumProcessorCores : 1);
1742             QString tooltip =
1743                 xi18nc("@info:tooltip",
1744                        "<para><emphasis strong='true'>Process status:</emphasis> %1 %2</para>"
1745                        "<para><emphasis strong='true'>User CPU usage:</emphasis> %3%</para>"
1746                        "<para><emphasis strong='true'>System CPU usage:</emphasis> %4%</para>", /* Please do not add </qt> here - the tooltip is appended to */
1747                        process->translatedStatus(),
1748                        d->getStatusDescription(process->status()),
1749                        (float)(process->userUsage()) / divideby,
1750                        (float)(process->sysUsage()) / divideby);
1751 
1752             if (process->numThreads() >= 1)
1753                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Number of threads:</emphasis> %1</para>", process->numThreads());
1754             if (process->numChildren() > 0) {
1755                 tooltip += xi18nc("@info:tooltip",
1756                                   "<para><emphasis strong='true'>Number of children:</emphasis> %1</para>"
1757                                   "<para><emphasis strong='true'>Total User CPU usage:</emphasis> %2%</para>"
1758                                   "<para><emphasis strong='true'>Total System CPU usage:</emphasis> %3%</para>"
1759                                   "<para><emphasis strong='true'>Total CPU usage:</emphasis> %4%</para>",
1760                                   process->numChildren(),
1761                                   (float)(process->totalUserUsage()) / divideby,
1762                                   (float)(process->totalSysUsage()) / divideby,
1763                                   (float)(process->totalUserUsage() + process->totalSysUsage()) / divideby);
1764             }
1765             if (process->userTime() > 0)
1766                 tooltip += xi18nc("@info:tooltip",
1767                                   "<para><emphasis strong='true'>CPU time spent running as user:</emphasis> %1 seconds</para>",
1768                                   QString::number(process->userTime() / 100.0, 'f', 1));
1769             if (process->sysTime() > 0)
1770                 tooltip += xi18nc("@info:tooltip",
1771                                   "<para><emphasis strong='true'>CPU time spent running in kernel:</emphasis> %1 seconds</para>",
1772                                   QString::number(process->sysTime() / 100.0, 'f', 1));
1773             if (process->niceLevel() != 0)
1774                 tooltip += xi18nc("@info:tooltip",
1775                                   "<para><emphasis strong='true'>Nice level:</emphasis> %1 (%2)</para>",
1776                                   process->niceLevel(),
1777                                   process->niceLevelAsString());
1778             if (process->ioPriorityClass() != KSysGuard::Process::None) {
1779                 if ((process->ioPriorityClass() == KSysGuard::Process::RealTime || process->ioPriorityClass() == KSysGuard::Process::BestEffort)
1780                     && process->ioniceLevel() != -1)
1781                     tooltip += xi18nc("@info:tooltip",
1782                                       "<para><emphasis strong='true'>I/O Nice level:</emphasis> %1 (%2)</para>",
1783                                       process->ioniceLevel(),
1784                                       process->ioniceLevelAsString());
1785                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>I/O Class:</emphasis> %1</para>", process->ioPriorityClassAsString());
1786             }
1787 
1788             if (!tracer.isEmpty())
1789                 return QString(tooltip + QStringLiteral("<br/>") + tracer);
1790             return tooltip;
1791         }
1792         case HeadingVmSize: {
1793             return QVariant();
1794         }
1795         case HeadingMemory: {
1796             QString tooltip;
1797             if (process->vmURSS() != -1) {
1798                 // We don't have information about the URSS, so just fallback to RSS
1799                 if (d->mMemTotal > 0)
1800                     tooltip += xi18nc("@info:tooltip",
1801                                       "<para><emphasis strong='true'>Memory usage:</emphasis> %1 out of %2  (%3 %)</para>",
1802                                       format.formatByteSize(process->vmURSS() * 1024),
1803                                       format.formatByteSize(d->mMemTotal * 1024),
1804                                       process->vmURSS() * 100 / d->mMemTotal);
1805                 else
1806                     tooltip +=
1807                         xi18nc("@info:tooltip", "<emphasis strong='true'>Memory usage:</emphasis> %1<br />", format.formatByteSize(process->vmURSS() * 1024));
1808             }
1809             if (d->mMemTotal > 0)
1810                 tooltip += xi18nc("@info:tooltip",
1811                                   "<para><emphasis strong='true'>RSS Memory usage:</emphasis> %1 out of %2  (%3 %)</para>",
1812                                   format.formatByteSize(process->vmRSS() * 1024),
1813                                   format.formatByteSize(d->mMemTotal * 1024),
1814                                   process->vmRSS() * 100 / d->mMemTotal);
1815             else
1816                 tooltip += xi18nc("@info:tooltip",
1817                                   "<para><emphasis strong='true'>RSS Memory usage:</emphasis> %1</para>",
1818                                   format.formatByteSize(process->vmRSS() * 1024));
1819             return tooltip;
1820         }
1821         case HeadingSharedMemory: {
1822             if (process->vmURSS() == -1) {
1823                 return xi18nc("@info:tooltip",
1824                               "<para><emphasis strong='true'>Your system does not seem to have this information available to be read.</emphasis></para>");
1825             }
1826             if (d->mMemTotal > 0)
1827                 return xi18nc("@info:tooltip",
1828                               "<para><emphasis strong='true'>Shared library memory usage:</emphasis> %1 out of %2  (%3 %)</para>",
1829                               format.formatByteSize((process->vmRSS() - process->vmURSS()) * 1024),
1830                               format.formatByteSize(d->mMemTotal * 1024),
1831                               (process->vmRSS() - process->vmURSS()) * 100 / d->mMemTotal);
1832             else
1833                 return xi18nc("@info:tooltip",
1834                               "<para><emphasis strong='true'>Shared library memory usage:</emphasis> %1</para>",
1835                               format.formatByteSize((process->vmRSS() - process->vmURSS()) * 1024));
1836         }
1837         case HeadingIoWrite:
1838         case HeadingIoRead: {
1839             // FIXME - use the formatByteRate functions when added
1840             return kxi18nc("@info:tooltip",
1841                            "<para><emphasis strong='true'>Characters read:</emphasis> %1 (%2 KiB/s)</para>"
1842                            "<para><emphasis strong='true'>Characters written:</emphasis> %3 (%4 KiB/s)</para>"
1843                            "<para><emphasis strong='true'>Read syscalls:</emphasis> %5 (%6 s⁻¹)</para>"
1844                            "<para><emphasis strong='true'>Write syscalls:</emphasis> %7 (%8 s⁻¹)</para>"
1845                            "<para><emphasis strong='true'>Actual bytes read:</emphasis> %9 (%10 KiB/s)</para>"
1846                            "<para><emphasis strong='true'>Actual bytes written:</emphasis> %11 (%12 KiB/s)</para>")
1847                 .subs(format.formatByteSize(process->ioCharactersRead()))
1848                 .subs(QString::number(process->ioCharactersReadRate() / 1024))
1849                 .subs(format.formatByteSize(process->ioCharactersWritten()))
1850                 .subs(QString::number(process->ioCharactersWrittenRate() / 1024))
1851                 .subs(QString::number(process->ioReadSyscalls()))
1852                 .subs(QString::number(process->ioReadSyscallsRate()))
1853                 .subs(QString::number(process->ioWriteSyscalls()))
1854                 .subs(QString::number(process->ioWriteSyscallsRate()))
1855                 .subs(format.formatByteSize(process->ioCharactersActuallyRead()))
1856                 .subs(QString::number(process->ioCharactersActuallyReadRate() / 1024))
1857                 .subs(format.formatByteSize(process->ioCharactersActuallyWritten()))
1858                 .subs(QString::number(process->ioCharactersActuallyWrittenRate() / 1024))
1859                 .toString();
1860         }
1861         case HeadingXTitle: {
1862 #if HAVE_X11
1863             const auto values = d->mPidToWindowInfo.values(process->pid());
1864             if (values.count() == 1) {
1865                 return values.first()->name;
1866             }
1867 
1868             QString tooltip;
1869 
1870             for (const auto &value : values) {
1871                 if (!tooltip.isEmpty()) {
1872                     tooltip += QLatin1Char('\n');
1873                 }
1874                 tooltip += QLatin1String("• ") + value->name;
1875             }
1876 
1877             return tooltip;
1878 #endif
1879             return QVariant(QVariant::String);
1880         }
1881         case HeadingVmPSS: {
1882             if (process->vmPSS() == -1) {
1883                 return xi18nc("@info:tooltip",
1884                               "<para><emphasis strong='true'>Your system does not seem to have this information available to be read.</emphasis></para>");
1885             }
1886             if (d->mMemTotal > 0) {
1887                 return xi18nc("@info:tooltip",
1888                               "<para><emphasis strong='true'>Total memory usage:</emphasis> %1 out of %2  (%3 %)</para>",
1889                               format.formatByteSize(process->vmPSS() * 1024),
1890                               format.formatByteSize(d->mMemTotal * 1024),
1891                               qRound(process->vmPSS() * 1000.0 / d->mMemTotal) / 10.0);
1892             } else {
1893                 return xi18nc("@info:tooltip",
1894                               "<para><emphasis strong='true'>Shared library memory usage:</emphasis> %1</para>",
1895                               format.formatByteSize(process->vmPSS() * 1024));
1896             }
1897         }
1898         default:
1899             return QVariant(QVariant::String);
1900         }
1901     }
1902     case Qt::TextAlignmentRole:
1903         return columnAlignment(index.column());
1904     case UidRole: {
1905         if (index.column() != 0)
1906             return QVariant(); // If we query with this role, then we want the raw UID for this.
1907         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
1908         return process->uid();
1909     }
1910     case PlainValueRole: // Used to return a plain value.  For copying to a clipboard etc
1911     {
1912         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
1913         switch (index.column()) {
1914         case HeadingName:
1915             return process->name();
1916         case HeadingPid:
1917             return (qlonglong)process->pid();
1918         case HeadingUser:
1919             if (!process->login().isEmpty())
1920                 return process->login();
1921             if (process->uid() == process->euid())
1922                 return d->getUsernameForUser(process->uid(), false);
1923             else
1924                 return QString(d->getUsernameForUser(process->uid(), false) + QStringLiteral(", ") + d->getUsernameForUser(process->euid(), false));
1925         case HeadingNiceness:
1926             return process->niceLevel();
1927         case HeadingTty:
1928             return process->tty();
1929         case HeadingCPUUsage: {
1930             double total;
1931             if (d->mShowChildTotals && !d->mSimple)
1932                 total = process->totalUserUsage() + process->totalSysUsage();
1933             else
1934                 total = process->userUsage() + process->sysUsage();
1935 
1936             if (d->mNormalizeCPUUsage)
1937                 return total / d->mNumProcessorCores;
1938             else
1939                 return total;
1940         }
1941         case HeadingCPUTime:
1942             return (qlonglong)(process->userTime() + process->sysTime());
1943         case HeadingMemory:
1944             if (process->vmRSS() == 0)
1945                 return QVariant(QVariant::String);
1946             if (process->vmURSS() == -1) {
1947                 return (qlonglong)process->vmRSS();
1948             } else {
1949                 return (qlonglong)process->vmURSS();
1950             }
1951         case HeadingVmSize:
1952             return (qlonglong)process->vmSize();
1953         case HeadingSharedMemory:
1954             if (process->vmRSS() - process->vmURSS() < 0 || process->vmURSS() == -1)
1955                 return QVariant(QVariant::String);
1956             return (qlonglong)(process->vmRSS() - process->vmURSS());
1957         case HeadingStartTime:
1958             return process->startTime(); // 2015-01-03, gregormi: can maybe be replaced with something better later
1959         case HeadingNoNewPrivileges:
1960             return process->noNewPrivileges();
1961         case HeadingCommand:
1962             return process->command();
1963         case HeadingIoRead:
1964             switch (d->mIoInformation) {
1965             case ProcessModel::Bytes:
1966                 return process->ioCharactersRead();
1967             case ProcessModel::Syscalls:
1968                 return process->ioReadSyscalls();
1969             case ProcessModel::ActualBytes:
1970                 return process->ioCharactersActuallyRead();
1971             case ProcessModel::BytesRate:
1972                 return (qlonglong)process->ioCharactersReadRate();
1973             case ProcessModel::SyscallsRate:
1974                 return (qlonglong)process->ioReadSyscallsRate();
1975             case ProcessModel::ActualBytesRate:
1976                 return (qlonglong)process->ioCharactersActuallyReadRate();
1977             }
1978             return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through
1979         case HeadingIoWrite:
1980             switch (d->mIoInformation) {
1981             case ProcessModel::Bytes:
1982                 return process->ioCharactersWritten();
1983             case ProcessModel::Syscalls:
1984                 return process->ioWriteSyscalls();
1985             case ProcessModel::ActualBytes:
1986                 return process->ioCharactersActuallyWritten();
1987             case ProcessModel::BytesRate:
1988                 return (qlonglong)process->ioCharactersWrittenRate();
1989             case ProcessModel::SyscallsRate:
1990                 return (qlonglong)process->ioWriteSyscallsRate();
1991             case ProcessModel::ActualBytesRate:
1992                 return (qlonglong)process->ioCharactersActuallyWrittenRate();
1993             }
1994             return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through
1995         case HeadingXMemory:
1996             return (qulonglong)process->pixmapBytes();
1997 #if HAVE_X11
1998         case HeadingXTitle: {
1999             WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL);
2000             if (!w)
2001                 return QString();
2002             return w->name;
2003         }
2004 #endif
2005         case HeadingCGroup:
2006             return process->cGroup();
2007         case HeadingMACContext:
2008             return process->macContext();
2009         case HeadingVmPSS:
2010             return process->vmPSS() >= 0 ? process->vmPSS() : QVariant{};
2011         default:
2012             return QVariant();
2013         }
2014         break;
2015     }
2016 #if HAVE_X11
2017     case WindowIdRole: {
2018         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2019         WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL);
2020         if (!w)
2021             return QVariant();
2022         else
2023             return (int)w->wid;
2024     }
2025 #endif
2026     case PercentageRole: {
2027         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2028         Q_CHECK_PTR(process);
2029         switch (index.column()) {
2030         case HeadingCPUUsage: {
2031             float cpu;
2032             if (d->mSimple || !d->mShowChildTotals)
2033                 cpu = process->userUsage() + process->sysUsage();
2034             else
2035                 cpu = process->totalUserUsage() + process->totalSysUsage();
2036             cpu = cpu / 100.0;
2037             if (!d->mNormalizeCPUUsage)
2038                 return cpu;
2039             return cpu / d->mNumProcessorCores;
2040         }
2041         case HeadingMemory:
2042             if (d->mMemTotal <= 0)
2043                 return -1;
2044             if (process->vmURSS() != -1)
2045                 return float(process->vmURSS()) / d->mMemTotal;
2046             else
2047                 return float(process->vmRSS()) / d->mMemTotal;
2048         case HeadingSharedMemory:
2049             if (process->vmURSS() == -1 || d->mMemTotal <= 0)
2050                 return -1;
2051             return float(process->vmRSS() - process->vmURSS()) / d->mMemTotal;
2052         case HeadingVmPSS:
2053             if (process->vmPSS() == -1 || d->mMemTotal <= 0) {
2054                 return -1;
2055             }
2056             return float(process->vmPSS()) / d->mMemTotal;
2057         default:
2058             return -1;
2059         }
2060     }
2061     case PercentageHistoryRole: {
2062         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2063         Q_CHECK_PTR(process);
2064         switch (index.column()) {
2065         case HeadingCPUUsage: {
2066             auto it = d->mMapProcessCPUHistory.find(process);
2067             if (it == d->mMapProcessCPUHistory.end()) {
2068                 it = d->mMapProcessCPUHistory.insert(process, {});
2069                 it->reserve(ProcessModelPrivate::MAX_HIST_ENTRIES);
2070             }
2071             return QVariant::fromValue(*it);
2072         }
2073         default: {
2074         }
2075         }
2076         return QVariant::fromValue(QVector<PercentageHistoryEntry>{});
2077     }
2078     case Qt::DecorationRole: {
2079         if (index.column() == HeadingName) {
2080 #if HAVE_X11
2081             KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2082             if (!process->hasManagedGuiWindow()) {
2083                 if (d->mSimple) // When not in tree mode, we need to pad the name column where we do not have an icon
2084                     return QIcon(d->mBlankPixmap);
2085                 else // When in tree mode, the padding looks bad, so do not pad in this case
2086                     return QVariant();
2087             }
2088             WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL);
2089             if (w && !w->icon.isNull())
2090                 return w->icon;
2091             return QIcon(d->mBlankPixmap);
2092 #else
2093             return QVariant();
2094 #endif
2095 
2096         } else if (index.column() == HeadingCPUUsage) {
2097             KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2098             if (process->status() == KSysGuard::Process::Stopped || process->status() == KSysGuard::Process::Zombie) {
2099                 //        QPixmap pix = KIconLoader::global()->loadIcon("button_cancel", KIconLoader::Small,
2100                 //                    KIconLoader::SizeSmall, KIconLoader::DefaultState, QStringList(),
2101                 //                0L, true);
2102             }
2103         }
2104         return QVariant();
2105     }
2106     case Qt::BackgroundRole: {
2107         if (index.column() != HeadingUser) {
2108             if (!d->mHaveTimer) // If there is no timer, then no processes are being killed, so no point looking for one
2109                 return QVariant();
2110             KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2111             if (process->timeKillWasSent().isValid()) {
2112                 int elapsed = process->timeKillWasSent().elapsed();
2113                 if (elapsed < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) { // Only show red for about 7 seconds
2114                     int transparency = 255 - elapsed * 250 / MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS;
2115 
2116                     KColorScheme scheme(QPalette::Active, KColorScheme::Selection);
2117                     QBrush brush = scheme.background(KColorScheme::NegativeBackground);
2118                     QColor color = brush.color();
2119                     color.setAlpha(transparency);
2120                     brush.setColor(color);
2121                     return brush;
2122                 }
2123             }
2124             return QVariant();
2125         }
2126         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2127         if (process->status() == KSysGuard::Process::Ended) {
2128             return QColor(Qt::lightGray);
2129         }
2130         if (process->tracerpid() >= 0) {
2131             // It's being debugged, so probably important.  Let's mark it as such
2132             return QColor(Qt::yellow);
2133         }
2134         if (d->mIsLocalhost && process->uid() == getuid()) { // own user
2135             return QColor(0, 208, 214, 50);
2136         }
2137         if (process->uid() < 100 || !canUserLogin(process->uid()))
2138             return QColor(218, 220, 215, 50); // no color for system tasks
2139         // other users
2140         return QColor(2, 154, 54, 50);
2141     }
2142     case Qt::FontRole: {
2143         if (index.column() == HeadingCPUUsage) {
2144             KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2145             if (process->userUsage() == 0) {
2146                 QFont font;
2147                 font.setItalic(true);
2148                 return font;
2149             }
2150         }
2151         return QVariant();
2152     }
2153     default: // This is a very very common case, so the route to this must be very minimal
2154         return QVariant();
2155     }
2156 
2157     return QVariant(); // never get here, but make compiler happy
2158 }
2159 
hasGUIWindow(qlonglong pid) const2160 bool ProcessModel::hasGUIWindow(qlonglong pid) const
2161 {
2162 #if HAVE_X11
2163     return d->mPidToWindowInfo.contains(pid);
2164 #else
2165     return false;
2166 #endif
2167 }
2168 
isLocalhost() const2169 bool ProcessModel::isLocalhost() const
2170 {
2171     return d->mIsLocalhost;
2172 }
2173 
setupHeader()2174 void ProcessModel::setupHeader()
2175 {
2176     // These must be in the same order that they are in the header file
2177     QStringList headings;
2178     headings << i18nc("process heading", "Name");
2179     headings << i18nc("process heading", "Username");
2180     headings << i18nc("process heading", "PID");
2181     headings << i18nc("process heading", "TTY");
2182     headings << i18nc("process heading", "Niceness");
2183     // xgettext: no-c-format
2184     headings << i18nc("process heading", "CPU %");
2185     headings << i18nc("process heading", "CPU Time");
2186     headings << i18nc("process heading", "IO Read");
2187     headings << i18nc("process heading", "IO Write");
2188     headings << i18nc("process heading", "Virtual Size");
2189     headings << i18nc("process heading", "Memory");
2190     headings << i18nc("process heading", "Shared Mem");
2191     headings << i18nc("process heading", "Relative Start Time");
2192     headings << i18nc("process heading", "NNP");
2193     headings << i18nc("process heading", "Command");
2194 #if HAVE_X11
2195     if (d->mIsX11) {
2196         headings << i18nc("process heading", "X11 Memory");
2197         headings << i18nc("process heading", "Window Title");
2198     }
2199 #endif
2200     headings << i18nc("process heading", "CGroup");
2201     headings << i18nc("process heading", "MAC Context");
2202     headings << i18nc("process heading", "Total Memory");
2203 
2204     if (d->mHeadings.isEmpty()) { // If it's empty, this is the first time this has been called, so insert the headings
2205         d->mHeadings = headings;
2206     } else {
2207         // This was called to retranslate the headings.  Just use the new translations and call headerDataChanged
2208         Q_ASSERT(d->mHeadings.count() == headings.count());
2209         d->mHeadings = headings;
2210         headerDataChanged(Qt::Horizontal, 0, headings.count() - 1);
2211     }
2212 }
2213 
retranslateUi()2214 void ProcessModel::retranslateUi()
2215 {
2216     setupHeader();
2217 }
2218 
getProcess(qlonglong pid)2219 KSysGuard::Process *ProcessModel::getProcess(qlonglong pid)
2220 {
2221     return d->mProcesses->getProcess(pid);
2222 }
2223 
showTotals() const2224 bool ProcessModel::showTotals() const
2225 {
2226     return d->mShowChildTotals;
2227 }
2228 
setShowTotals(bool showTotals)2229 void ProcessModel::setShowTotals(bool showTotals) // slot
2230 {
2231     if (showTotals == d->mShowChildTotals)
2232         return;
2233     d->mShowChildTotals = showTotals;
2234 
2235     QModelIndex index;
2236     foreach (KSysGuard::Process *process, d->mProcesses->getAllProcesses()) {
2237         if (process->numChildren() > 0) {
2238             int row;
2239             if (d->mSimple)
2240                 row = process->index();
2241             else
2242                 row = process->parent()->children().indexOf(process);
2243             index = createIndex(row, HeadingCPUUsage, process);
2244             emit dataChanged(index, index);
2245         }
2246     }
2247 }
2248 
totalMemory() const2249 qlonglong ProcessModel::totalMemory() const
2250 {
2251     return d->mMemTotal;
2252 }
2253 
setUnits(Units units)2254 void ProcessModel::setUnits(Units units)
2255 {
2256     if (d->mUnits == units)
2257         return;
2258     d->mUnits = units;
2259 
2260     QModelIndex index;
2261     foreach (KSysGuard::Process *process, d->mProcesses->getAllProcesses()) {
2262         int row;
2263         if (d->mSimple)
2264             row = process->index();
2265         else
2266             row = process->parent()->children().indexOf(process);
2267         index = createIndex(row, HeadingMemory, process);
2268         emit dataChanged(index, index);
2269         index = createIndex(row, HeadingXMemory, process);
2270         emit dataChanged(index, index);
2271         index = createIndex(row, HeadingSharedMemory, process);
2272         emit dataChanged(index, index);
2273         index = createIndex(row, HeadingVmSize, process);
2274         emit dataChanged(index, index);
2275     }
2276 }
2277 
units() const2278 ProcessModel::Units ProcessModel::units() const
2279 {
2280     return (Units)d->mUnits;
2281 }
2282 
setIoUnits(Units units)2283 void ProcessModel::setIoUnits(Units units)
2284 {
2285     if (d->mIoUnits == units)
2286         return;
2287     d->mIoUnits = units;
2288 
2289     QModelIndex index;
2290     foreach (KSysGuard::Process *process, d->mProcesses->getAllProcesses()) {
2291         int row;
2292         if (d->mSimple)
2293             row = process->index();
2294         else
2295             row = process->parent()->children().indexOf(process);
2296         index = createIndex(row, HeadingIoRead, process);
2297         emit dataChanged(index, index);
2298         index = createIndex(row, HeadingIoWrite, process);
2299         emit dataChanged(index, index);
2300     }
2301 }
2302 
ioUnits() const2303 ProcessModel::Units ProcessModel::ioUnits() const
2304 {
2305     return (Units)d->mIoUnits;
2306 }
2307 
setIoInformation(ProcessModel::IoInformation ioInformation)2308 void ProcessModel::setIoInformation(ProcessModel::IoInformation ioInformation)
2309 {
2310     d->mIoInformation = ioInformation;
2311 }
2312 
ioInformation() const2313 ProcessModel::IoInformation ProcessModel::ioInformation() const
2314 {
2315     return d->mIoInformation;
2316 }
2317 
formatMemoryInfo(qlonglong amountInKB,Units units,bool returnEmptyIfValueIsZero) const2318 QString ProcessModel::formatMemoryInfo(qlonglong amountInKB, Units units, bool returnEmptyIfValueIsZero) const
2319 {
2320     // We cache the result of i18n for speed reasons.  We call this function
2321     // hundreds of times, every second or so
2322     if (returnEmptyIfValueIsZero && amountInKB == 0)
2323         return QString();
2324     static QString percentageString = i18n("%1%", QString::fromLatin1("%1"));
2325     if (units == UnitsPercentage) {
2326         if (d->mMemTotal == 0)
2327             return QLatin1String(""); // memory total not determined yet.  Shouldn't happen, but don't crash if it does
2328         float percentage = amountInKB * 100.0 / d->mMemTotal;
2329         if (percentage < 0.1)
2330             percentage = 0.1;
2331         return percentageString.arg(percentage, 0, 'f', 1);
2332     } else
2333         return formatByteSize(amountInKB, units);
2334 }
2335 
hostName() const2336 QString ProcessModel::hostName() const
2337 {
2338     return d->mHostName;
2339 }
2340 
mimeTypes() const2341 QStringList ProcessModel::mimeTypes() const
2342 {
2343     QStringList types;
2344     types << QStringLiteral("text/plain");
2345     types << QStringLiteral("text/csv");
2346     types << QStringLiteral("text/html");
2347     return types;
2348 }
2349 
mimeData(const QModelIndexList & indexes) const2350 QMimeData *ProcessModel::mimeData(const QModelIndexList &indexes) const
2351 {
2352     QMimeData *mimeData = new QMimeData();
2353     QString textCsv;
2354     QString textCsvHeaders;
2355     QString textPlain;
2356     QString textPlainHeaders;
2357     QString textHtml;
2358     QString textHtmlHeaders;
2359     QString display;
2360     int firstColumn = -1;
2361     bool firstrow = true;
2362     foreach (const QModelIndex &index, indexes) {
2363         if (index.isValid()) {
2364             if (firstColumn == -1)
2365                 firstColumn = index.column();
2366             else if (firstColumn != index.column())
2367                 continue;
2368             else {
2369                 textCsv += QLatin1Char('\n');
2370                 textPlain += QLatin1Char('\n');
2371                 textHtml += QLatin1String("</tr><tr>");
2372                 firstrow = false;
2373             }
2374             for (int i = 0; i < d->mHeadings.size(); i++) {
2375                 if (firstrow) {
2376                     QString heading = d->mHeadings[i];
2377                     textHtmlHeaders += QLatin1String("<th>") + heading + QLatin1String("</th>");
2378                     if (i) {
2379                         textCsvHeaders += QLatin1Char(',');
2380                         textPlainHeaders += QLatin1String(", ");
2381                     }
2382                     textPlainHeaders += heading;
2383                     heading.replace(QLatin1Char('"'), QLatin1String("\"\""));
2384                     textCsvHeaders += QLatin1Char('"') + heading + QLatin1Char('"');
2385                 }
2386                 QModelIndex index2 = createIndex(index.row(), i, reinterpret_cast<KSysGuard::Process *>(index.internalPointer()));
2387                 QString display = data(index2, PlainValueRole).toString();
2388                 if (i) {
2389                     textCsv += QLatin1Char(',');
2390                     textPlain += QLatin1String(", ");
2391                 }
2392                 textHtml += QLatin1String("<td>") + display.toHtmlEscaped() + QLatin1String("</td>");
2393                 textPlain += display;
2394                 display.replace(QLatin1Char('"'), QLatin1String("\"\""));
2395                 textCsv += QLatin1Char('"') + display + QLatin1Char('"');
2396             }
2397         }
2398     }
2399     textHtml = QLatin1String("<html><table><tr>") + textHtmlHeaders + QLatin1String("</tr><tr>") + textHtml + QLatin1String("</tr></table>");
2400     textCsv = textCsvHeaders + QLatin1Char('\n') + textCsv;
2401     textPlain = textPlainHeaders + QLatin1Char('\n') + textPlain;
2402 
2403     mimeData->setText(textPlain);
2404     mimeData->setHtml(textHtml);
2405     mimeData->setData(QStringLiteral("text/csv"), textCsv.toUtf8());
2406     return mimeData;
2407 }
2408 
flags(const QModelIndex & index) const2409 Qt::ItemFlags ProcessModel::flags(const QModelIndex &index) const
2410 {
2411     if (!index.isValid())
2412         return Qt::NoItemFlags; // Would this ever happen?
2413 
2414     KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2415     if (process->status() == KSysGuard::Process::Ended)
2416         return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable;
2417     else
2418         return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
2419 }
2420 
isShowCommandLineOptions() const2421 bool ProcessModel::isShowCommandLineOptions() const
2422 {
2423     return d->mShowCommandLineOptions;
2424 }
2425 
setShowCommandLineOptions(bool showCommandLineOptions)2426 void ProcessModel::setShowCommandLineOptions(bool showCommandLineOptions)
2427 {
2428     d->mShowCommandLineOptions = showCommandLineOptions;
2429 }
2430 
isShowingTooltips() const2431 bool ProcessModel::isShowingTooltips() const
2432 {
2433     return d->mShowingTooltips;
2434 }
2435 
setShowingTooltips(bool showTooltips)2436 void ProcessModel::setShowingTooltips(bool showTooltips)
2437 {
2438     d->mShowingTooltips = showTooltips;
2439 }
2440 
isNormalizedCPUUsage() const2441 bool ProcessModel::isNormalizedCPUUsage() const
2442 {
2443     return d->mNormalizeCPUUsage;
2444 }
2445 
setNormalizedCPUUsage(bool normalizeCPUUsage)2446 void ProcessModel::setNormalizedCPUUsage(bool normalizeCPUUsage)
2447 {
2448     d->mNormalizeCPUUsage = normalizeCPUUsage;
2449 }
2450 
timerEvent(QTimerEvent * event)2451 void ProcessModelPrivate::timerEvent(QTimerEvent *event)
2452 {
2453     Q_UNUSED(event);
2454     foreach (qlonglong pid, mPidsToUpdate) {
2455         KSysGuard::Process *process = mProcesses->getProcess(pid);
2456         if (process && process->timeKillWasSent().isValid() && process->timeKillWasSent().elapsed() < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) {
2457             int row;
2458             if (mSimple)
2459                 row = process->index();
2460             else
2461                 row = process->parent()->children().indexOf(process);
2462 
2463             QModelIndex index1 = q->createIndex(row, 0, process);
2464             QModelIndex index2 = q->createIndex(row, mHeadings.count() - 1, process);
2465             emit q->dataChanged(index1, index2);
2466         } else {
2467             mPidsToUpdate.removeAll(pid);
2468         }
2469     }
2470 
2471     if (mPidsToUpdate.isEmpty()) {
2472         mHaveTimer = false;
2473         killTimer(mTimerId);
2474         mTimerId = -1;
2475     }
2476 }
2477