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