Warning, file /plasma/libksysguard/processui/ProcessModel.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     KSysGuard, the KDE System Guard
0003 
0004     SPDX-FileCopyrightText: 1999, 2000 Chris Schlaeger <cs@kde.org>
0005     SPDX-FileCopyrightText: 2006-2007 John Tapsell <john.tapsell@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 
0009 */
0010 
0011 #include "ProcessModel.h"
0012 #include "ProcessModel_p.h"
0013 #include "timeutil.h"
0014 
0015 #include "processcore/extended_process_list.h"
0016 #include "processcore/formatter.h"
0017 #include "processcore/process.h"
0018 #include "processcore/process_attribute.h"
0019 #include "processcore/process_data_provider.h"
0020 
0021 #include "processui_debug.h"
0022 
0023 #include <KFormat>
0024 #include <KLocalizedString>
0025 #include <QApplication>
0026 #include <QBitmap>
0027 #include <QDebug>
0028 #include <QFont>
0029 #include <QIcon>
0030 #include <QList>
0031 #include <QLocale>
0032 #include <QMimeData>
0033 #include <QPixmap>
0034 #include <QRegExp>
0035 #include <QTextDocument>
0036 #include <kcolorscheme.h>
0037 #include <kiconloader.h>
0038 
0039 #define HEADING_X_ICON_SIZE 16
0040 #define MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS 2000
0041 #define GET_OWN_ID
0042 
0043 #ifdef GET_OWN_ID
0044 /* For getuid*/
0045 #include <sys/types.h>
0046 #include <unistd.h>
0047 #endif
0048 
0049 #if HAVE_XRES
0050 #include <X11/extensions/XRes.h>
0051 #endif
0052 
0053 #if HAVE_X11
0054 #include <KX11Extras>
0055 #endif
0056 
0057 extern QApplication *Qapp;
0058 
0059 static QString formatByteSize(qlonglong amountInKB, int units)
0060 {
0061     enum { UnitsAuto, UnitsKB, UnitsMB, UnitsGB, UnitsTB, UnitsPB };
0062     static QString kString = i18n("%1 K", QString::fromLatin1("%1"));
0063     static QString mString = i18n("%1 M", QString::fromLatin1("%1"));
0064     static QString gString = i18n("%1 G", QString::fromLatin1("%1"));
0065     static QString tString = i18n("%1 T", QString::fromLatin1("%1"));
0066     static QString pString = i18n("%1 P", QString::fromLatin1("%1"));
0067     double amount;
0068 
0069     if (units == UnitsAuto) {
0070         if (amountInKB < 1024.0 * 0.9)
0071             units = UnitsKB; // amount < 0.9 MiB == KiB
0072         else if (amountInKB < 1024.0 * 1024.0 * 0.9)
0073             units = UnitsMB; // amount < 0.9 GiB == MiB
0074         else if (amountInKB < 1024.0 * 1024.0 * 1024.0 * 0.9)
0075             units = UnitsGB; // amount < 0.9 TiB == GiB
0076         else if (amountInKB < 1024.0 * 1024.0 * 1024.0 * 1024.0 * 0.9)
0077             units = UnitsTB; // amount < 0.9 PiB == TiB
0078         else
0079             units = UnitsPB;
0080     }
0081 
0082     switch (units) {
0083     case UnitsKB:
0084         return kString.arg(QLocale().toString(amountInKB));
0085     case UnitsMB:
0086         amount = amountInKB / 1024.0;
0087         return mString.arg(QLocale().toString(amount, 'f', 1));
0088     case UnitsGB:
0089         amount = amountInKB / (1024.0 * 1024.0);
0090         if (amount < 0.1 && amount > 0.05)
0091             amount = 0.1;
0092         return gString.arg(QLocale().toString(amount, 'f', 1));
0093     case UnitsTB:
0094         amount = amountInKB / (1024.0 * 1024.0 * 1024.0);
0095         if (amount < 0.1 && amount > 0.05)
0096             amount = 0.1;
0097         return tString.arg(QLocale().toString(amount, 'f', 1));
0098     case UnitsPB:
0099         amount = amountInKB / (1024.0 * 1024.0 * 1024.0 * 1024.0);
0100         if (amount < 0.1 && amount > 0.05)
0101             amount = 0.1;
0102         return pString.arg(QLocale().toString(amount, 'f', 1));
0103     default:
0104         return QLatin1String(""); // error
0105     }
0106 }
0107 
0108 ProcessModelPrivate::ProcessModelPrivate()
0109     : mBlankPixmap(HEADING_X_ICON_SIZE, 1)
0110 {
0111     mBlankPixmap.fill(QColor(0, 0, 0, 0));
0112     mSimple = true;
0113     mIsLocalhost = true;
0114     mMemTotal = -1;
0115     mNumProcessorCores = 1;
0116     mProcesses = nullptr;
0117     mShowChildTotals = true;
0118     mShowCommandLineOptions = false;
0119     mShowingTooltips = true;
0120     mNormalizeCPUUsage = true;
0121     mIoInformation = ProcessModel::ActualBytes;
0122 #if HAVE_XRES
0123     mHaveXRes = false;
0124 #endif
0125     mHaveTimer = false, mTimerId = -1, mMovingRow = false;
0126     mRemovingRow = false;
0127     mInsertingRow = false;
0128 #if HAVE_X11
0129     mIsX11 = QX11Info::isPlatformX11();
0130 #else
0131     mIsX11 = false;
0132 #endif
0133 }
0134 
0135 ProcessModelPrivate::~ProcessModelPrivate()
0136 {
0137 #if HAVE_X11
0138     qDeleteAll(mPidToWindowInfo);
0139 #endif
0140     mProcesses.clear();
0141 }
0142 
0143 ProcessModel::ProcessModel(QObject *parent, const QString &host)
0144     : QAbstractItemModel(parent)
0145     , d(new ProcessModelPrivate)
0146 {
0147     d->q = this;
0148 #if HAVE_XRES
0149     if (d->mIsX11) {
0150         int event, error, major, minor;
0151         d->mHaveXRes = XResQueryExtension(QX11Info::display(), &event, &error) && XResQueryVersion(QX11Info::display(), &major, &minor);
0152     }
0153 #endif
0154 
0155     if (host.isEmpty() || host == QLatin1String("localhost")) {
0156         d->mHostName = QString();
0157         d->mIsLocalhost = true;
0158     } else {
0159         d->mHostName = host;
0160         d->mIsLocalhost = false;
0161     }
0162     setupHeader();
0163     d->setupProcesses();
0164 #if HAVE_X11
0165     d->setupWindows();
0166 #endif
0167     d->mUnits = UnitsKB;
0168     d->mIoUnits = UnitsKB;
0169 }
0170 
0171 bool ProcessModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
0172 {
0173     // Because we need to sort Descendingly by default for most of the headings, we often return left > right
0174     KSysGuard::Process *processLeft = reinterpret_cast<KSysGuard::Process *>(left.internalPointer());
0175     KSysGuard::Process *processRight = reinterpret_cast<KSysGuard::Process *>(right.internalPointer());
0176     Q_ASSERT(processLeft);
0177     Q_ASSERT(processRight);
0178     Q_ASSERT(left.column() == right.column());
0179     switch (left.column()) {
0180     case HeadingUser: {
0181         /* Sorting by user will be the default and the most common.
0182            We want to sort in the most useful way that we can. We need to return a number though.
0183            This code is based on that sorting ascendingly should put the current user at the top
0184            First the user we are running as should be at the top.
0185            Then any other users in the system.
0186            Then at the bottom the 'system' processes.
0187            We then sort by cpu usage to sort by that, then finally sort by memory usage */
0188 
0189         /* First, place traced processes at the very top, ignoring any other sorting criteria */
0190         if (processLeft->tracerpid() >= 0)
0191             return true;
0192         if (processRight->tracerpid() >= 0)
0193             return false;
0194 
0195         /* Sort by username.  First group into own user, normal users, system users */
0196         if (processLeft->uid() != processRight->uid()) {
0197             // We primarily sort by username
0198             if (d->mIsLocalhost) {
0199                 int ownUid = getuid();
0200                 if (processLeft->uid() == ownUid)
0201                     return true; // Left is our user, right is not.  So left is above right
0202                 if (processRight->uid() == ownUid)
0203                     return false; // Left is not our user, right is.  So right is above left
0204             }
0205             bool isLeftSystemUser = processLeft->uid() < 100 || !canUserLogin(processLeft->uid());
0206             bool isRightSystemUser = processRight->uid() < 100 || !canUserLogin(processRight->uid());
0207             if (isLeftSystemUser && !isRightSystemUser)
0208                 return false; // System users are less than non-system users
0209             if (!isLeftSystemUser && isRightSystemUser)
0210                 return true;
0211             // They are either both system users, or both non-system users.
0212             // So now sort by username
0213             return d->getUsernameForUser(processLeft->uid(), false) < d->getUsernameForUser(processRight->uid(), false);
0214         }
0215 
0216         /* 2nd sort order - Graphics Windows */
0217         // Both columns have the same user.  Place processes with windows at the top
0218         if (processLeft->hasManagedGuiWindow() && !processRight->hasManagedGuiWindow())
0219             return true;
0220         if (!processLeft->hasManagedGuiWindow() && processRight->hasManagedGuiWindow())
0221             return false;
0222 
0223         /* 3rd sort order - CPU Usage */
0224         int leftCpu, rightCpu;
0225         if (d->mSimple || !d->mShowChildTotals) {
0226             leftCpu = processLeft->userUsage() + processLeft->sysUsage();
0227             rightCpu = processRight->userUsage() + processRight->sysUsage();
0228         } else {
0229             leftCpu = processLeft->totalUserUsage() + processLeft->totalSysUsage();
0230             rightCpu = processRight->totalUserUsage() + processRight->totalSysUsage();
0231         }
0232         if (leftCpu != rightCpu)
0233             return leftCpu > rightCpu;
0234 
0235         /* 4th sort order - Memory Usage */
0236         qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmURSS() : processLeft->vmRSS();
0237         qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmURSS() : processRight->vmRSS();
0238         return memoryLeft > memoryRight;
0239     }
0240     case HeadingCPUUsage: {
0241         int leftCpu, rightCpu;
0242         if (d->mSimple || !d->mShowChildTotals) {
0243             leftCpu = processLeft->userUsage() + processLeft->sysUsage();
0244             rightCpu = processRight->userUsage() + processRight->sysUsage();
0245         } else {
0246             leftCpu = processLeft->totalUserUsage() + processLeft->totalSysUsage();
0247             rightCpu = processRight->totalUserUsage() + processRight->totalSysUsage();
0248         }
0249         return leftCpu > rightCpu;
0250     }
0251     case HeadingCPUTime: {
0252         return (processLeft->userTime() + processLeft->sysTime()) > (processRight->userTime() + processRight->sysTime());
0253     }
0254     case HeadingMemory: {
0255         qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmURSS() : processLeft->vmRSS();
0256         qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmURSS() : processRight->vmRSS();
0257         return memoryLeft > memoryRight;
0258     }
0259     case HeadingVmPSS: {
0260         return processLeft->vmPSS() > processRight->vmPSS();
0261     }
0262     case HeadingStartTime: {
0263         return processLeft->startTime() > processRight->startTime();
0264     }
0265     case HeadingNoNewPrivileges:
0266         return processLeft->noNewPrivileges() > processRight->noNewPrivileges();
0267     case HeadingXMemory:
0268         return processLeft->pixmapBytes() > processRight->pixmapBytes();
0269     case HeadingVmSize:
0270         return processLeft->vmSize() > processRight->vmSize();
0271     case HeadingSharedMemory: {
0272         qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmRSS() - processLeft->vmURSS() : 0;
0273         qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmRSS() - processRight->vmURSS() : 0;
0274         return memoryLeft > memoryRight;
0275     }
0276     case HeadingPid:
0277         return processLeft->pid() > processRight->pid();
0278     case HeadingNiceness:
0279         // Sort by scheduler first
0280         if (processLeft->scheduler() != processRight->scheduler()) {
0281             if (processLeft->scheduler() == KSysGuard::Process::RoundRobin || processLeft->scheduler() == KSysGuard::Process::Fifo)
0282                 return true;
0283             if (processRight->scheduler() == KSysGuard::Process::RoundRobin || processRight->scheduler() == KSysGuard::Process::Fifo)
0284                 return false;
0285             if (processLeft->scheduler() == KSysGuard::Process::Other)
0286                 return true;
0287             if (processRight->scheduler() == KSysGuard::Process::Other)
0288                 return false;
0289             if (processLeft->scheduler() == KSysGuard::Process::Batch)
0290                 return true;
0291         }
0292         if (processLeft->niceLevel() == processRight->niceLevel())
0293             return processLeft->pid() < processRight->pid(); // Subsort by pid if the niceLevel is the same
0294         return processLeft->niceLevel() < processRight->niceLevel();
0295     case HeadingTty: {
0296         if (processLeft->tty() == processRight->tty())
0297             return processLeft->pid() < processRight->pid(); // Both ttys are the same.  Sort by pid
0298         if (processLeft->tty().isEmpty())
0299             return false; // Only left is empty (since they aren't the same)
0300         else if (processRight->tty().isEmpty())
0301             return true; // Only right is empty
0302 
0303         // Neither left or right is empty. The tty string is like  "tty10"  so split this into "tty" and "10"
0304         // and sort by the string first, then sort by the number
0305         QRegExp regexpLeft(QStringLiteral("^(\\D*)(\\d*)$"));
0306         QRegExp regexpRight(regexpLeft);
0307         if (regexpLeft.indexIn(QString::fromUtf8(processLeft->tty())) == -1 || regexpRight.indexIn(QString::fromUtf8(processRight->tty())) == -1)
0308             return processLeft->tty() < processRight->tty();
0309         int nameMatch = regexpLeft.cap(1).compare(regexpRight.cap(1));
0310         if (nameMatch < 0)
0311             return true;
0312         if (nameMatch > 0)
0313             return false;
0314         return regexpLeft.cap(2).toInt() < regexpRight.cap(2).toInt();
0315     }
0316     case HeadingIoRead:
0317         switch (d->mIoInformation) {
0318         case ProcessModel::Bytes:
0319             return processLeft->ioCharactersRead() > processRight->ioCharactersRead();
0320         case ProcessModel::Syscalls:
0321             return processLeft->ioReadSyscalls() > processRight->ioReadSyscalls();
0322         case ProcessModel::ActualBytes:
0323             return processLeft->ioCharactersActuallyRead() > processRight->ioCharactersActuallyRead();
0324         case ProcessModel::BytesRate:
0325             return processLeft->ioCharactersReadRate() > processRight->ioCharactersReadRate();
0326         case ProcessModel::SyscallsRate:
0327             return processLeft->ioReadSyscallsRate() > processRight->ioReadSyscallsRate();
0328         case ProcessModel::ActualBytesRate:
0329             return processLeft->ioCharactersActuallyReadRate() > processRight->ioCharactersActuallyReadRate();
0330         }
0331         return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through
0332     case HeadingIoWrite:
0333         switch (d->mIoInformation) {
0334         case ProcessModel::Bytes:
0335             return processLeft->ioCharactersWritten() > processRight->ioCharactersWritten();
0336         case ProcessModel::Syscalls:
0337             return processLeft->ioWriteSyscalls() > processRight->ioWriteSyscalls();
0338         case ProcessModel::ActualBytes:
0339             return processLeft->ioCharactersActuallyWritten() > processRight->ioCharactersActuallyWritten();
0340         case ProcessModel::BytesRate:
0341             return processLeft->ioCharactersWrittenRate() > processRight->ioCharactersWrittenRate();
0342         case ProcessModel::SyscallsRate:
0343             return processLeft->ioWriteSyscallsRate() > processRight->ioWriteSyscallsRate();
0344         case ProcessModel::ActualBytesRate:
0345             return processLeft->ioCharactersActuallyWrittenRate() > processRight->ioCharactersActuallyWrittenRate();
0346         }
0347     }
0348     // Sort by the display string if we do not have an explicit sorting here
0349 
0350     if (data(left, ProcessModel::PlainValueRole).toInt() == data(right, ProcessModel::PlainValueRole).toInt()) {
0351         return data(left, Qt::DisplayRole).toString() < data(right, Qt::DisplayRole).toString();
0352     }
0353     return data(left, ProcessModel::PlainValueRole).toInt() < data(right, ProcessModel::PlainValueRole).toInt();
0354 }
0355 
0356 ProcessModel::~ProcessModel()
0357 {
0358     delete d;
0359 }
0360 
0361 KSysGuard::Processes *ProcessModel::processController() const
0362 {
0363     return d->mProcesses.get();
0364 }
0365 
0366 const QVector<KSysGuard::ProcessAttribute *> ProcessModel::extraAttributes() const
0367 {
0368     return d->mExtraAttributes;
0369 }
0370 
0371 #if HAVE_X11
0372 void ProcessModelPrivate::windowRemoved(WId wid)
0373 {
0374     WindowInfo *window = mWIdToWindowInfo.take(wid);
0375     if (!window)
0376         return;
0377     qlonglong pid = window->pid;
0378 
0379     QMultiHash<qlonglong, WindowInfo *>::iterator i = mPidToWindowInfo.find(pid);
0380     while (i != mPidToWindowInfo.end() && i.key() == pid) {
0381         if (i.value()->wid == wid) {
0382             i = mPidToWindowInfo.erase(i);
0383             break;
0384         } else
0385             i++;
0386     }
0387     delete window;
0388 
0389     // Update the model so that it redraws and resorts
0390     KSysGuard::Process *process = mProcesses->getProcess(pid);
0391     if (!process)
0392         return;
0393 
0394     int row;
0395     if (mSimple)
0396         row = process->index();
0397     else
0398         row = process->parent()->children().indexOf(process);
0399     QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process);
0400     Q_EMIT q->dataChanged(index2, index2);
0401 }
0402 #endif
0403 
0404 #if HAVE_X11
0405 void ProcessModelPrivate::setupWindows()
0406 {
0407     if (!mIsX11) {
0408         return;
0409     }
0410     connect(KX11Extras::self(), &KX11Extras::windowChanged, this, &ProcessModelPrivate::windowChanged);
0411     connect(KX11Extras::self(), &KX11Extras::windowAdded, this, &ProcessModelPrivate::windowAdded);
0412     connect(KX11Extras::self(), &KX11Extras::windowRemoved, this, &ProcessModelPrivate::windowRemoved);
0413 
0414     // Add all the windows that KWin is managing - i.e. windows that the user can see
0415     const QList<WId> windows = KX11Extras::windows();
0416     for (auto it = windows.begin(); it != windows.end(); ++it) {
0417         updateWindowInfo(*it, static_cast<NET::Properties>(~0u), NET::Properties2{}, true);
0418     }
0419 }
0420 #endif
0421 
0422 #if HAVE_XRES
0423 bool ProcessModelPrivate::updateXResClientData()
0424 {
0425     if (!mIsX11) {
0426         return false;
0427     }
0428     XResClient *clients;
0429     int count;
0430 
0431     XResQueryClients(QX11Info::display(), &count, &clients);
0432 
0433     mXResClientResources.clear();
0434     for (int i = 0; i < count; i++)
0435         mXResClientResources.insert(-(qlonglong)(clients[i].resource_base), clients[i].resource_mask);
0436 
0437     if (clients)
0438         XFree(clients);
0439     return true;
0440 }
0441 
0442 void ProcessModelPrivate::queryForAndUpdateAllXWindows()
0443 {
0444     if (!mIsX11) {
0445         return;
0446     }
0447     updateXResClientData();
0448     Window *children, dummy;
0449     unsigned int count;
0450     Status result = XQueryTree(QX11Info::display(), QX11Info::appRootWindow(), &dummy, &dummy, &children, &count);
0451     if (!result)
0452         return;
0453     if (!updateXResClientData())
0454         return;
0455     for (uint i = 0; i < count; ++i) {
0456         WId wid = children[i];
0457         QMap<qlonglong, XID>::iterator iter = mXResClientResources.lowerBound(-(qlonglong)(wid));
0458         if (iter == mXResClientResources.end())
0459             continue; // We couldn't find it this time :-/
0460 
0461         if (-iter.key() != (qlonglong)(wid & ~iter.value()))
0462             continue; // Already added this window
0463 
0464         // Get the PID for this window if we do not know it
0465         NETWinInfo info(QX11Info::connection(), wid, QX11Info::appRootWindow(), NET::WMPid, NET::Properties2());
0466 
0467         qlonglong pid = info.pid();
0468         if (!pid)
0469             continue;
0470         // We found a window with this client
0471         mXResClientResources.erase(iter);
0472         KSysGuard::Process *process = mProcesses->getProcess(pid);
0473         if (!process)
0474             return; // shouldn't really happen.. maybe race condition etc
0475         unsigned long previousPixmapBytes = process->pixmapBytes();
0476         // Now update the pixmap bytes for this window
0477         bool success = XResQueryClientPixmapBytes(QX11Info::display(), wid, &process->pixmapBytes());
0478         if (!success)
0479             process->pixmapBytes() = 0;
0480 
0481         if (previousPixmapBytes != process->pixmapBytes()) {
0482             int row;
0483             if (mSimple)
0484                 row = process->index();
0485             else
0486                 row = process->parent()->children().indexOf(process);
0487             QModelIndex index = q->createIndex(row, ProcessModel::HeadingXMemory, process);
0488             Q_EMIT q->dataChanged(index, index);
0489         }
0490     }
0491     if (children)
0492         XFree((char *)children);
0493 }
0494 #endif
0495 
0496 void ProcessModelPrivate::setupProcesses()
0497 {
0498     if (mProcesses) {
0499 #ifdef Q_WS_X11_DISABLE
0500         mWIdToWindowInfo.clear();
0501         mPidToWindowInfo.clear();
0502 #endif
0503         mProcesses.clear();
0504         q->beginResetModel();
0505         q->endResetModel();
0506     }
0507 
0508     mProcesses = KSysGuard::ExtendedProcesses::instance();
0509 
0510     connect(mProcesses.get(), &KSysGuard::Processes::processChanged, this, &ProcessModelPrivate::processChanged);
0511     connect(mProcesses.get(), &KSysGuard::Processes::beginAddProcess, this, &ProcessModelPrivate::beginInsertRow);
0512     connect(mProcesses.get(), &KSysGuard::Processes::endAddProcess, this, &ProcessModelPrivate::endInsertRow);
0513     connect(mProcesses.get(), &KSysGuard::Processes::beginRemoveProcess, this, &ProcessModelPrivate::beginRemoveRow);
0514     connect(mProcesses.get(), &KSysGuard::Processes::endRemoveProcess, this, &ProcessModelPrivate::endRemoveRow);
0515     connect(mProcesses.get(), &KSysGuard::Processes::beginMoveProcess, this, &ProcessModelPrivate::beginMoveProcess);
0516     connect(mProcesses.get(), &KSysGuard::Processes::endMoveProcess, this, &ProcessModelPrivate::endMoveRow);
0517     mNumProcessorCores = mProcesses->numberProcessorCores();
0518     if (mNumProcessorCores < 1)
0519         mNumProcessorCores = 1; // Default to 1 if there was an error getting the number
0520 
0521     mExtraAttributes = mProcesses->extendedAttributes();
0522     for (int i = 0; i < mExtraAttributes.count(); i++) {
0523         connect(mExtraAttributes[i], &KSysGuard::ProcessAttribute::dataChanged, this, [this, i](KSysGuard::Process *process) {
0524             const QModelIndex index = q->getQModelIndex(process, mHeadings.count() + i);
0525             Q_EMIT q->dataChanged(index, index);
0526         });
0527     }
0528 }
0529 
0530 #if HAVE_X11
0531 void ProcessModelPrivate::windowChanged(WId wid, NET::Properties properties, NET::Properties2 properties2)
0532 {
0533     updateWindowInfo(wid, properties, properties2, false);
0534 }
0535 
0536 void ProcessModelPrivate::windowAdded(WId wid)
0537 {
0538     updateWindowInfo(wid, NET::Properties{}, NET::Properties2{}, true);
0539 }
0540 
0541 void ProcessModelPrivate::updateWindowInfo(WId wid, NET::Properties properties, NET::Properties2 /*properties2*/, bool newWindow)
0542 {
0543     if (!mIsX11) {
0544         return;
0545     }
0546     properties &= (NET::WMPid | NET::WMVisibleName | NET::WMName | NET::WMIcon);
0547 
0548     if (!properties) {
0549         return; // Nothing interesting changed
0550     }
0551 
0552     WindowInfo *w = mWIdToWindowInfo.value(wid);
0553     const qreal dpr = qApp->devicePixelRatio();
0554 
0555     if (!w && !newWindow)
0556         return; // We do not have a record of this window and this is not a new window
0557 
0558     if (properties == NET::WMIcon) {
0559         if (w) {
0560             w->icon = KX11Extras::icon(wid, HEADING_X_ICON_SIZE * dpr, HEADING_X_ICON_SIZE * dpr, true);
0561             w->icon.setDevicePixelRatio(dpr);
0562         }
0563         return;
0564     }
0565     /* Get PID for window */
0566     NETWinInfo info(QX11Info::connection(), wid, QX11Info::appRootWindow(), properties & ~NET::WMIcon, NET::Properties2{});
0567 
0568     if (!w) {
0569         // We know that this must be a newWindow
0570         qlonglong pid = info.pid();
0571         if (!(properties & NET::WMPid && pid))
0572             return; // No PID for the window - this happens if the process did not set _NET_WM_PID
0573 
0574         // If we are to get the PID only, we are only interested in the XRes info for this,
0575         // so don't bother if we already have this info
0576         if (properties == NET::WMPid && mPidToWindowInfo.contains(pid))
0577             return;
0578 
0579         w = new WindowInfo(wid, pid);
0580         mPidToWindowInfo.insert(pid, w);
0581         mWIdToWindowInfo.insert(wid, w);
0582     }
0583 
0584     if (w && (properties & NET::WMIcon)) {
0585         w->icon = KX11Extras::icon(wid, HEADING_X_ICON_SIZE * dpr, HEADING_X_ICON_SIZE * dpr, true);
0586         w->icon.setDevicePixelRatio(dpr);
0587     }
0588     if (properties & NET::WMVisibleName && info.visibleName())
0589         w->name = QString::fromUtf8(info.visibleName());
0590     else if (properties & NET::WMName)
0591         w->name = QString::fromUtf8(info.name());
0592     else if (properties & (NET::WMName | NET::WMVisibleName))
0593         w->name.clear();
0594 
0595     KSysGuard::Process *process = mProcesses->getProcess(w->pid);
0596     if (!process) {
0597         return; // This happens when a new window is detected before we've read in the process
0598     }
0599 
0600     int row;
0601     if (mSimple)
0602         row = process->index();
0603     else
0604         row = process->parent()->children().indexOf(process);
0605     if (!process->hasManagedGuiWindow()) {
0606         process->hasManagedGuiWindow() = true;
0607         // Since this is the first window for a process, invalidate HeadingName so that
0608         // if we are sorting by name this gets taken into account
0609         QModelIndex index1 = q->createIndex(row, ProcessModel::HeadingName, process);
0610         Q_EMIT q->dataChanged(index1, index1);
0611     }
0612     QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process);
0613     Q_EMIT q->dataChanged(index2, index2);
0614 }
0615 #endif
0616 
0617 void ProcessModel::update(long updateDurationMSecs, KSysGuard::Processes::UpdateFlags updateFlags)
0618 {
0619     if (updateFlags != KSysGuard::Processes::XMemory) {
0620         d->mProcesses->updateAllProcesses(updateDurationMSecs, updateFlags);
0621         if (d->mMemTotal <= 0)
0622             d->mMemTotal = d->mProcesses->totalPhysicalMemory();
0623     }
0624 
0625 #if HAVE_XRES
0626     // Add all the rest of the windows
0627     if (d->mHaveXRes && updateFlags.testFlag(KSysGuard::Processes::XMemory))
0628         d->queryForAndUpdateAllXWindows();
0629 #endif
0630 }
0631 
0632 QString ProcessModelPrivate::getStatusDescription(KSysGuard::Process::ProcessStatus status) const
0633 {
0634     switch (status) {
0635     case KSysGuard::Process::Running:
0636         return i18n("- Process is doing some work.");
0637     case KSysGuard::Process::Sleeping:
0638         return i18n("- Process is waiting for something to happen.");
0639     case KSysGuard::Process::Stopped:
0640         return i18n("- Process has been stopped. It will not respond to user input at the moment.");
0641     case KSysGuard::Process::Zombie:
0642         return i18n("- Process has finished and is now dead, but the parent process has not cleaned up.");
0643     case KSysGuard::Process::Ended:
0644         //            return i18n("- Process has finished and no longer exists.");
0645     default:
0646         return QString();
0647     }
0648 }
0649 
0650 KSysGuard::Process *ProcessModel::getProcessAtIndex(int index) const
0651 {
0652     Q_ASSERT(d->mSimple);
0653     return d->mProcesses->getAllProcesses().at(index);
0654 }
0655 
0656 int ProcessModel::rowCount(const QModelIndex &parent) const
0657 {
0658     if (d->mSimple) {
0659         if (parent.isValid())
0660             return 0; // In flat mode, none of the processes have children
0661         return d->mProcesses->processCount();
0662     }
0663 
0664     // Deal with the case that we are showing it as a tree
0665     KSysGuard::Process *process;
0666     if (parent.isValid()) {
0667         if (parent.column() != 0)
0668             return 0; // For a treeview we say that only the first column has children
0669         process = reinterpret_cast<KSysGuard::Process *>(parent.internalPointer()); // when parent is invalid, it must be the root level which we set as 0
0670     } else {
0671         process = d->mProcesses->getProcess(-1);
0672     }
0673     Q_ASSERT(process);
0674     int num_rows = process->children().count();
0675     return num_rows;
0676 }
0677 
0678 int ProcessModel::columnCount(const QModelIndex &) const
0679 {
0680     return d->mHeadings.count() + d->mExtraAttributes.count();
0681 }
0682 
0683 bool ProcessModel::hasChildren(const QModelIndex &parent = QModelIndex()) const
0684 {
0685     if (d->mSimple) {
0686         if (parent.isValid())
0687             return 0; // In flat mode, none of the processes have children
0688         return !d->mProcesses->getAllProcesses().isEmpty();
0689     }
0690 
0691     // Deal with the case that we are showing it as a tree
0692     KSysGuard::Process *process;
0693     if (parent.isValid()) {
0694         if (parent.column() != 0)
0695             return false; // For a treeview we say that only the first column has children
0696         process = reinterpret_cast<KSysGuard::Process *>(parent.internalPointer()); // when parent is invalid, it must be the root level which we set as 0
0697     } else {
0698         process = d->mProcesses->getProcess(-1);
0699     }
0700     Q_ASSERT(process);
0701     bool has_children = !process->children().isEmpty();
0702 
0703     Q_ASSERT((rowCount(parent) > 0) == has_children);
0704     return has_children;
0705 }
0706 
0707 QModelIndex ProcessModel::index(int row, int column, const QModelIndex &parent) const
0708 {
0709     if (row < 0)
0710         return QModelIndex();
0711     if (column < 0 || column >= columnCount())
0712         return QModelIndex();
0713 
0714     if (d->mSimple) {
0715         if (parent.isValid())
0716             return QModelIndex();
0717         if (d->mProcesses->processCount() <= row)
0718             return QModelIndex();
0719         return createIndex(row, column, d->mProcesses->getAllProcesses().at(row));
0720     }
0721 
0722     // Deal with the case that we are showing it as a tree
0723     KSysGuard::Process *parent_process = nullptr;
0724 
0725     if (parent.isValid()) // not valid for init or children without parents, so use our special item with pid of 0
0726         parent_process = reinterpret_cast<KSysGuard::Process *>(parent.internalPointer());
0727     else
0728         parent_process = d->mProcesses->getProcess(-1);
0729     Q_ASSERT(parent_process);
0730 
0731     if (parent_process->children().count() > row)
0732         return createIndex(row, column, parent_process->children()[row]);
0733     else {
0734         return QModelIndex();
0735     }
0736 }
0737 
0738 bool ProcessModel::isSimpleMode() const
0739 {
0740     return d->mSimple;
0741 }
0742 
0743 void ProcessModelPrivate::processChanged(KSysGuard::Process *process, bool onlyTotalCpu)
0744 {
0745     int row;
0746     if (mSimple)
0747         row = process->index();
0748     else
0749         row = process->parent()->children().indexOf(process);
0750 
0751     if (process->timeKillWasSent().isValid()) {
0752         int elapsed = process->timeKillWasSent().elapsed();
0753         if (elapsed < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) {
0754             if (!mPidsToUpdate.contains(process->pid()))
0755                 mPidsToUpdate.append(process->pid());
0756             QModelIndex index1 = q->createIndex(row, 0, process);
0757             QModelIndex index2 = q->createIndex(row, mHeadings.count() - 1, process);
0758             Q_EMIT q->dataChanged(index1, index2);
0759             if (!mHaveTimer) {
0760                 mHaveTimer = true;
0761                 mTimerId = startTimer(100);
0762             }
0763         }
0764     }
0765     int totalUpdated = 0;
0766     Q_ASSERT(row != -1); // Something has gone very wrong
0767     if (onlyTotalCpu) {
0768         if (mShowChildTotals) {
0769             // Only the total cpu usage changed, so only update that
0770             QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process);
0771             Q_EMIT q->dataChanged(index, index);
0772         }
0773         return;
0774     } else {
0775         if (process->changes() & KSysGuard::Process::Uids) {
0776             totalUpdated++;
0777             QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process);
0778             Q_EMIT q->dataChanged(index, index);
0779         }
0780         if (process->changes() & KSysGuard::Process::Tty) {
0781             totalUpdated++;
0782             QModelIndex index = q->createIndex(row, ProcessModel::HeadingTty, process);
0783             Q_EMIT q->dataChanged(index, index);
0784         }
0785         if (process->changes() & (KSysGuard::Process::Usage | KSysGuard::Process::Status)
0786             || (process->changes() & KSysGuard::Process::TotalUsage && mShowChildTotals)) {
0787             totalUpdated += 2;
0788             QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process);
0789             Q_EMIT q->dataChanged(index, index);
0790             index = q->createIndex(row, ProcessModel::HeadingCPUTime, process);
0791             Q_EMIT q->dataChanged(index, index);
0792             // Because of our sorting, changing usage needs to also invalidate the User column
0793             index = q->createIndex(row, ProcessModel::HeadingUser, process);
0794             Q_EMIT q->dataChanged(index, index);
0795         }
0796         if (process->changes() & KSysGuard::Process::Status) {
0797             totalUpdated += 2;
0798             QModelIndex index = q->createIndex(row, ProcessModel::HeadingNoNewPrivileges, process);
0799             Q_EMIT q->dataChanged(index, index);
0800             index = q->createIndex(row, ProcessModel::HeadingCGroup, process);
0801             Q_EMIT q->dataChanged(index, index);
0802             index = q->createIndex(row, ProcessModel::HeadingMACContext, process);
0803             Q_EMIT q->dataChanged(index, index);
0804         }
0805         if (process->changes() & KSysGuard::Process::NiceLevels) {
0806             totalUpdated++;
0807             QModelIndex index = q->createIndex(row, ProcessModel::HeadingNiceness, process);
0808             Q_EMIT q->dataChanged(index, index);
0809         }
0810         if (process->changes() & KSysGuard::Process::VmSize) {
0811             totalUpdated++;
0812             QModelIndex index = q->createIndex(row, ProcessModel::HeadingVmSize, process);
0813             Q_EMIT q->dataChanged(index, index);
0814         }
0815         if (process->changes() & (KSysGuard::Process::VmSize | KSysGuard::Process::VmRSS | KSysGuard::Process::VmURSS)) {
0816             totalUpdated += 2;
0817             QModelIndex index = q->createIndex(row, ProcessModel::HeadingMemory, process);
0818             Q_EMIT q->dataChanged(index, index);
0819             QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingSharedMemory, process);
0820             Q_EMIT q->dataChanged(index2, index2);
0821             // Because of our sorting, changing usage needs to also invalidate the User column
0822             index = q->createIndex(row, ProcessModel::HeadingUser, process);
0823             Q_EMIT q->dataChanged(index, index);
0824         }
0825         if (process->changes() & KSysGuard::Process::VmPSS) {
0826             totalUpdated++;
0827             auto index = q->createIndex(row, ProcessModel::HeadingVmPSS, process);
0828             Q_EMIT q->dataChanged(index, index);
0829         }
0830         if (process->changes() & KSysGuard::Process::Name) {
0831             totalUpdated++;
0832             QModelIndex index = q->createIndex(row, ProcessModel::HeadingName, process);
0833             Q_EMIT q->dataChanged(index, index);
0834         }
0835         if (process->changes() & KSysGuard::Process::Command) {
0836             totalUpdated++;
0837             QModelIndex index = q->createIndex(row, ProcessModel::HeadingCommand, process);
0838             Q_EMIT q->dataChanged(index, index);
0839         }
0840         if (process->changes() & KSysGuard::Process::Login) {
0841             totalUpdated++;
0842             QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process);
0843             Q_EMIT q->dataChanged(index, index);
0844         }
0845         if (process->changes() & KSysGuard::Process::IO) {
0846             totalUpdated++;
0847             QModelIndex index = q->createIndex(row, ProcessModel::HeadingIoRead, process);
0848             Q_EMIT q->dataChanged(index, index);
0849             index = q->createIndex(row, ProcessModel::HeadingIoWrite, process);
0850             Q_EMIT q->dataChanged(index, index);
0851         }
0852 
0853         /* Normally this would only be called if changes() tells
0854          * us to. We need to update the timestamp even if the value
0855          * didn't change though. */
0856         auto historyMapEntry = mMapProcessCPUHistory.find(process);
0857         if (historyMapEntry != mMapProcessCPUHistory.end()) {
0858             auto &history = *historyMapEntry;
0859             unsigned long timestamp = QDateTime::currentMSecsSinceEpoch();
0860             // Only add an entry if the latest one is older than MIN_HIST_AGE
0861             if (history.isEmpty() || timestamp - history.constLast().timestamp > MIN_HIST_AGE) {
0862                 if (history.size() == MAX_HIST_ENTRIES) {
0863                     history.removeFirst();
0864                 }
0865 
0866                 float usage = (process->totalUserUsage() + process->totalSysUsage()) / (100.0f * mNumProcessorCores);
0867                 history.push_back({static_cast<unsigned long>(QDateTime::currentMSecsSinceEpoch()), usage});
0868             }
0869         }
0870     }
0871 }
0872 
0873 void ProcessModelPrivate::beginInsertRow(KSysGuard::Process *process)
0874 {
0875     Q_ASSERT(process);
0876     Q_ASSERT(!mRemovingRow);
0877     Q_ASSERT(!mInsertingRow);
0878     Q_ASSERT(!mMovingRow);
0879     mInsertingRow = true;
0880 
0881 #if HAVE_X11
0882     process->hasManagedGuiWindow() = mPidToWindowInfo.contains(process->pid());
0883 #endif
0884     if (mSimple) {
0885         int row = mProcesses->processCount();
0886         q->beginInsertRows(QModelIndex(), row, row);
0887         return;
0888     }
0889 
0890     // Deal with the case that we are showing it as a tree
0891     int row = process->parent()->children().count();
0892     QModelIndex parentModelIndex = q->getQModelIndex(process->parent(), 0);
0893 
0894     // Only here can we actually change the model.  First notify the view/proxy models then modify
0895     q->beginInsertRows(parentModelIndex, row, row);
0896 }
0897 
0898 void ProcessModelPrivate::endInsertRow()
0899 {
0900     Q_ASSERT(!mRemovingRow);
0901     Q_ASSERT(mInsertingRow);
0902     Q_ASSERT(!mMovingRow);
0903     mInsertingRow = false;
0904 
0905     q->endInsertRows();
0906 }
0907 void ProcessModelPrivate::beginRemoveRow(KSysGuard::Process *process)
0908 {
0909     Q_ASSERT(process);
0910     Q_ASSERT(process->pid() >= 0);
0911     Q_ASSERT(!mRemovingRow);
0912     Q_ASSERT(!mInsertingRow);
0913     Q_ASSERT(!mMovingRow);
0914     mRemovingRow = true;
0915 
0916     mMapProcessCPUHistory.remove(process);
0917 
0918     if (mSimple) {
0919         return q->beginRemoveRows(QModelIndex(), process->index(), process->index());
0920     } else {
0921         int row = process->parent()->children().indexOf(process);
0922         if (row == -1) {
0923             qCDebug(LIBKSYSGUARD_PROCESSUI) << "A serious problem occurred in remove row.";
0924             mRemovingRow = false;
0925             return;
0926         }
0927 
0928         return q->beginRemoveRows(q->getQModelIndex(process->parent(), 0), row, row);
0929     }
0930 }
0931 
0932 void ProcessModelPrivate::endRemoveRow()
0933 {
0934     Q_ASSERT(!mInsertingRow);
0935     Q_ASSERT(!mMovingRow);
0936     if (!mRemovingRow)
0937         return;
0938     mRemovingRow = false;
0939 
0940     q->endRemoveRows();
0941 }
0942 
0943 void ProcessModelPrivate::beginMoveProcess(KSysGuard::Process *process, KSysGuard::Process *new_parent)
0944 {
0945     Q_ASSERT(!mRemovingRow);
0946     Q_ASSERT(!mInsertingRow);
0947     Q_ASSERT(!mMovingRow);
0948 
0949     if (mSimple)
0950         return; // We don't need to move processes when in simple mode
0951     mMovingRow = true;
0952 
0953     int current_row = process->parent()->children().indexOf(process);
0954     Q_ASSERT(current_row != -1);
0955     int new_row = new_parent->children().count();
0956     QModelIndex sourceParent = q->getQModelIndex(process->parent(), 0);
0957     QModelIndex destinationParent = q->getQModelIndex(new_parent, 0);
0958     mMovingRow = q->beginMoveRows(sourceParent, current_row, current_row, destinationParent, new_row);
0959     Q_ASSERT(mMovingRow);
0960 }
0961 
0962 void ProcessModelPrivate::endMoveRow()
0963 {
0964     Q_ASSERT(!mInsertingRow);
0965     Q_ASSERT(!mRemovingRow);
0966     if (!mMovingRow)
0967         return;
0968     mMovingRow = false;
0969 
0970     q->endMoveRows();
0971 }
0972 
0973 QModelIndex ProcessModel::getQModelIndex(KSysGuard::Process *process, int column) const
0974 {
0975     Q_ASSERT(process);
0976     int pid = process->pid();
0977     if (pid == -1)
0978         return QModelIndex(); // pid -1 is our fake process meaning the very root (never drawn).  To represent that, we return QModelIndex() which also means
0979                               // the top element
0980     int row = 0;
0981     if (d->mSimple) {
0982         row = process->index();
0983     } else {
0984         row = process->parent()->children().indexOf(process);
0985     }
0986     Q_ASSERT(row != -1);
0987     return createIndex(row, column, process);
0988 }
0989 
0990 QModelIndex ProcessModel::parent(const QModelIndex &index) const
0991 {
0992     if (!index.isValid())
0993         return QModelIndex();
0994     KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
0995     Q_ASSERT(process);
0996 
0997     if (d->mSimple)
0998         return QModelIndex();
0999     else
1000         return getQModelIndex(process->parent(), 0);
1001 }
1002 
1003 static inline QVariant columnAlignment(const int section)
1004 {
1005     switch (section) {
1006     case ProcessModel::HeadingUser:
1007     case ProcessModel::HeadingCPUUsage:
1008     case ProcessModel::HeadingNoNewPrivileges:
1009         return QVariant(Qt::AlignHCenter | Qt::AlignVCenter);
1010     case ProcessModel::HeadingPid:
1011     case ProcessModel::HeadingNiceness:
1012     case ProcessModel::HeadingCPUTime:
1013     case ProcessModel::HeadingStartTime:
1014     case ProcessModel::HeadingMemory:
1015     case ProcessModel::HeadingXMemory:
1016     case ProcessModel::HeadingSharedMemory:
1017     case ProcessModel::HeadingVmSize:
1018     case ProcessModel::HeadingIoWrite:
1019     case ProcessModel::HeadingIoRead:
1020     case ProcessModel::HeadingVmPSS:
1021         return QVariant(Qt::AlignRight | Qt::AlignVCenter);
1022     case ProcessModel::HeadingTty:
1023         return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
1024     default:
1025         return QVariant();
1026     }
1027 }
1028 
1029 QVariant ProcessModel::headerData(int section, Qt::Orientation orientation, int role) const
1030 {
1031     if (orientation != Qt::Horizontal)
1032         return QVariant();
1033     if (section < 0)
1034         return QVariant(); // is this needed?
1035 
1036     if (section >= d->mHeadings.count() && section < columnCount()) {
1037         int attr = section - d->mHeadings.count();
1038         switch (role) {
1039         case Qt::DisplayRole:
1040             return d->mExtraAttributes[attr]->shortName();
1041         }
1042         return QVariant();
1043     }
1044 
1045     switch (role) {
1046     case Qt::TextAlignmentRole: {
1047         return columnAlignment(section);
1048     }
1049     case Qt::ToolTipRole: {
1050         if (!d->mShowingTooltips)
1051             return QVariant();
1052         switch (section) {
1053         case HeadingName:
1054             return i18n("The process name.");
1055         case HeadingUser:
1056             return i18n("The user who owns this process.");
1057         case HeadingTty:
1058             return i18n("The controlling terminal on which this process is running.");
1059         case HeadingNiceness:
1060             return i18n(
1061                 "The priority with which this process is being run. For the normal scheduler, this ranges from 19 (very nice, least priority) to -19 (top "
1062                 "priority).");
1063         case HeadingCPUUsage:
1064             if (d->mNumProcessorCores == 1)
1065                 return i18n("The current CPU usage of the process.");
1066             else
1067                 // i18n: %1 is always greater than 1, so do not worry about
1068                 // nonsensical verbosity of the singular part.
1069                 if (d->mNormalizeCPUUsage)
1070                 return i18np("The current total CPU usage of the process, divided by the %1 processor core in the machine.",
1071                              "The current total CPU usage of the process, divided by the %1 processor cores in the machine.",
1072                              d->mNumProcessorCores);
1073             else
1074                 return i18n("The current total CPU usage of the process.");
1075         case HeadingCPUTime:
1076             return i18n("<qt>The total user and system time that this process has been running for, displayed as minutes:seconds.");
1077         case HeadingVmSize:
1078             return i18n(
1079                 "<qt>This is the amount of virtual memory space that the process is using, included shared libraries, graphics memory, files on disk, and so "
1080                 "on. This number is almost meaningless.</qt>");
1081         case HeadingMemory:
1082             return i18n(
1083                 "<qt>This is the amount of real physical memory that this process is using by itself, and approximates the Private memory usage of the "
1084                 "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 "
1085                 "judge the memory use of a program.  See What's This for more information.</qt>");
1086         case HeadingSharedMemory:
1087             return i18n(
1088                 "<qt>This is approximately the amount of real physical memory that this process's shared libraries are using.<br>This memory is shared among "
1089                 "all processes that use this library.</qt>");
1090         case HeadingStartTime:
1091             return i18n("<qt>The elapsed time since the process was started.</qt>");
1092         case HeadingNoNewPrivileges:
1093             return i18n("<qt>Linux flag NoNewPrivileges, if set the process can't gain further privileges via setuid etc.</qt>");
1094         case HeadingCommand:
1095             return i18n("<qt>The command with which this process was launched.</qt>");
1096         case HeadingXMemory:
1097             return i18n("<qt>The amount of pixmap memory that this process is using.</qt>");
1098         case HeadingXTitle:
1099             return i18n("<qt>The title of any windows that this process is showing.</qt>");
1100         case HeadingPid:
1101             return i18n("The unique Process ID that identifies this process.");
1102         case HeadingIoRead:
1103             return i18n("The number of bytes read.  See What's This for more information.");
1104         case HeadingIoWrite:
1105             return i18n("The number of bytes written.  See What's This for more information.");
1106         case HeadingCGroup:
1107             return i18n("<qt>The control group (cgroup) where this process belongs.</qt>");
1108         case HeadingMACContext:
1109             return i18n("<qt>Mandatory Access Control (SELinux or AppArmor) context for this process.</qt>");
1110         case HeadingVmPSS:
1111             return i18n(
1112                 "The amount of private physical memory used by a process, with the amount of shared memory divided by the amount of processes using that "
1113                 "shared memory added.");
1114         default:
1115             return QVariant();
1116         }
1117     }
1118     case Qt::WhatsThisRole: {
1119         switch (section) {
1120         case HeadingName:
1121             return i18n(
1122                 "<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 "
1123                 "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.");
1124         case HeadingUser:
1125             return i18n(
1126                 "<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 "
1127                 "the effective user.  The ToolTip contains the full information.  <p>"
1128                 "<table>"
1129                 "<tr><td>Login Name/Group</td><td>The username of the Real User/Group who created this process</td></tr>"
1130                 "<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 "
1131                 "real user.</td></tr>"
1132                 "<tr><td>Setuid User/Group</td><td>The saved username of the binary.  The process can escalate its Effective User/Group to the Setuid "
1133                 "User/Group.</td></tr>"
1134 #ifdef Q_OS_LINUX
1135                 "<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 "
1136                 "call. See setfsuid(2) for more information.</td></tr>"
1137 #endif
1138                 "</table>");
1139         case HeadingVmSize:
1140             return i18n(
1141                 "<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 "
1142                 "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. "
1143                 "<p><i>Technical information: </i>This is VmSize in proc/*/status and VIRT in top.");
1144         case HeadingMemory:
1145             return i18n(
1146                 "<qt><i>Technical information: </i>This is an approximation of the Private memory usage, calculated as VmRSS - Shared, from /proc/*/statm.  "
1147                 "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 "
1148                 "that is fast to determine.  This is sometimes known as URSS (Unique Resident Set Size). For an individual process, see \"Detailed  Memory "
1149                 "Information\" for a more accurate, but slower, calculation of the true Private memory usage.");
1150         case HeadingCPUUsage:
1151             return i18n("The CPU usage of a process and all of its threads.");
1152         case HeadingCPUTime:
1153             return i18n(
1154                 "<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 "
1155                 "clock time if the process has been across multiple CPU cores.");
1156         case HeadingSharedMemory:
1157             return i18n(
1158                 "<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 "
1159                 "backed by a file (see kernel Documentation/filesystems/proc.txt).  For an individual process, see \"Detailed Memory Information\" for a more "
1160                 "accurate, but slower, calculation of the true Shared memory usage.");
1161         case HeadingStartTime:
1162             return i18n("<qt><i>Technical information: </i>The underlying value (clock ticks since system boot) is retrieved from /proc/[pid]/stat");
1163         case HeadingNoNewPrivileges:
1164             return i18n("<qt><i>Technical information: </i>The flag is retrieved from /proc/[pid]/status");
1165         case HeadingCommand:
1166             return i18n("<qt><i>Technical information: </i>This is from /proc/*/cmdline");
1167         case HeadingXMemory:
1168             return i18n(
1169                 "<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 "
1170                 "addition to Memory and Shared Memory.<br><i>Technical information: </i>This only counts the pixmap memory, and does not include resource "
1171                 "memory used by fonts, cursors, glyphsets etc.  See the <code>xrestop</code> program for a more detailed breakdown.");
1172         case HeadingXTitle:
1173             return i18n(
1174                 "<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' "
1175                 "windows are not shown, then that application incorrectly is not setting _NET_WM_PID.");
1176         case HeadingPid:
1177             return i18n(
1178                 "<qt><i>Technical information: </i>This is the Process ID.  A multi-threaded application is treated a single process, with all threads sharing "
1179                 "the same PID.  The CPU usage etc will be the total, accumulated, CPU usage of all the threads.");
1180         case HeadingIoRead:
1181         case HeadingIoWrite:
1182             return i18n(
1183                 "<qt>This column shows the IO statistics for each process. The tooltip provides the following information:<br>"
1184                 "<table>"
1185                 "<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 "
1186                 "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 "
1187                 "required (the read might have been satisfied from pagecache).</td></tr>"
1188                 "<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 "
1189                 "apply here as with Characters Read.</td></tr>"
1190                 "<tr><td>Read Syscalls</td><td>The number of read I/O operations, i.e. syscalls like read() and pread().</td></tr>"
1191                 "<tr><td>Write Syscalls</td><td>The number of write I/O operations, i.e. syscalls like write() and pwrite().</td></tr>"
1192                 "<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 "
1193                 "submit_bio() level, so it is accurate for block-backed filesystems. This may not give sensible values for NFS and CIFS filesystems.</td></tr>"
1194                 "<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 "
1195                 "done at page-dirtying time.</td>"
1196                 "</table><p>"
1197                 "The number in brackets shows the rate at which each value is changing, determined from taking the difference between the previous value and "
1198                 "the new value, and dividing by the update interval.<p>"
1199                 "<i>Technical information: </i>This data is collected from /proc/*/io and is documented further in Documentation/accounting and "
1200                 "Documentation/filesystems/proc.txt in the kernel source.");
1201         case HeadingCGroup:
1202             return i18n(
1203                 "<qt><i>Technical information: </i>This shows Linux Control Group (cgroup) membership, retrieved from /proc/[pid]/cgroup. Control groups are "
1204                 "used by Systemd and containers for limiting process group's usage of resources and to monitor them.");
1205         case HeadingMACContext:
1206             return i18n(
1207                 "<qt><i>Technical information: </i>This shows Mandatory Access Control (SELinux or AppArmor) context, retrieved from "
1208                 "/proc/[pid]/attr/current.");
1209         case HeadingVmPSS:
1210             return i18n(
1211                 "<i>Technical information:</i> This is often referred to as \"Proportional Set Size\" and is the closest approximation of the real amount of "
1212                 "total memory used by a process. Note that the number of applications sharing shared memory is determined per shared memory section and thus "
1213                 "can vary per memory section.");
1214         default:
1215             return QVariant();
1216         }
1217     }
1218     case Qt::DisplayRole:
1219         return d->mHeadings[section];
1220     default:
1221         return QVariant();
1222     }
1223 }
1224 
1225 void ProcessModel::setSimpleMode(bool simple)
1226 {
1227     if (d->mSimple == simple)
1228         return;
1229 
1230     Q_EMIT layoutAboutToBeChanged();
1231 
1232     d->mSimple = simple;
1233 
1234     int flatrow;
1235     int treerow;
1236     QList<QModelIndex> flatIndexes;
1237     QList<QModelIndex> treeIndexes;
1238     foreach (KSysGuard::Process *process, d->mProcesses->getAllProcesses()) {
1239         flatrow = process->index();
1240         treerow = process->parent()->children().indexOf(process);
1241         flatIndexes.clear();
1242         treeIndexes.clear();
1243 
1244         for (int i = 0; i < columnCount(); i++) {
1245             flatIndexes << createIndex(flatrow, i, process);
1246             treeIndexes << createIndex(treerow, i, process);
1247         }
1248         if (d->mSimple) // change from tree mode to flat mode
1249             changePersistentIndexList(treeIndexes, flatIndexes);
1250         else // change from flat mode to tree mode
1251             changePersistentIndexList(flatIndexes, treeIndexes);
1252     }
1253 
1254     Q_EMIT layoutChanged();
1255 }
1256 
1257 bool ProcessModel::canUserLogin(long uid) const
1258 {
1259     if (uid == 65534) {
1260         // nobody user
1261         return false;
1262     }
1263 
1264     if (!d->mIsLocalhost)
1265         return true; // We only deal with localhost.  Just always return true for non localhost
1266 
1267     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
1268     if (canLogin != -1)
1269         return canLogin; // We know whether they can log in
1270 
1271     // We got the default, -1, so we don't know.  Look it up
1272 
1273     KUser user(uid);
1274     if (!user.isValid()) {
1275         // for some reason the user isn't recognised.  This might happen under certain security situations.
1276         // Just return true to be safe
1277         d->mUidCanLogin[uid] = 1;
1278         return true;
1279     }
1280     QString shell = user.shell();
1281     if (shell == QLatin1String("/bin/false")) // FIXME - add in any other shells it could be for false
1282     {
1283         d->mUidCanLogin[uid] = 0;
1284         return false;
1285     }
1286     d->mUidCanLogin[uid] = 1;
1287     return true;
1288 }
1289 
1290 QString ProcessModelPrivate::getTooltipForUser(const KSysGuard::Process *ps) const
1291 {
1292     QString userTooltip;
1293     if (!mIsLocalhost) {
1294         return xi18nc("@info:tooltip", "<para><emphasis strong='true'>Login Name:</emphasis> %1</para>", getUsernameForUser(ps->uid(), true));
1295     } else {
1296         KUser user(ps->uid());
1297         if (!user.isValid())
1298             userTooltip += xi18nc("@info:tooltip", "<para>This user is not recognized for some reason.</para>");
1299         else {
1300             if (!user.property(KUser::FullName).isValid())
1301                 userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>%1</emphasis></para>", user.property(KUser::FullName).toString());
1302             userTooltip += xi18nc("@info:tooltip",
1303                                   "<para><emphasis strong='true'>Login Name:</emphasis> %1 (uid: %2)</para>",
1304                                   user.loginName(),
1305                                   QString::number(ps->uid()));
1306             if (!user.property(KUser::RoomNumber).isValid())
1307                 userTooltip +=
1308                     xi18nc("@info:tooltip", "<para><emphasis strong='true'>  Room Number:</emphasis> %1</para>", user.property(KUser::RoomNumber).toString());
1309             if (!user.property(KUser::WorkPhone).isValid())
1310                 userTooltip +=
1311                     xi18nc("@info:tooltip", "<para><emphasis strong='true'>  Work Phone:</emphasis> %1</para>", user.property(KUser::WorkPhone).toString());
1312         }
1313     }
1314     if ((ps->uid() != ps->euid() && ps->euid() != -1) || (ps->uid() != ps->suid() && ps->suid() != -1) || (ps->uid() != ps->fsuid() && ps->fsuid() != -1)) {
1315         if (ps->euid() != -1)
1316             userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Effective User:</emphasis> %1</para>", getUsernameForUser(ps->euid(), true));
1317         if (ps->suid() != -1)
1318             userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Setuid User:</emphasis> %1</para>", getUsernameForUser(ps->suid(), true));
1319         if (ps->fsuid() != -1)
1320             userTooltip +=
1321                 xi18nc("@info:tooltip", "<para><emphasis strong='true'>File System User:</emphasis> %1</para>", getUsernameForUser(ps->fsuid(), true));
1322         userTooltip += QLatin1String("<br/>");
1323     }
1324     if (ps->gid() != -1) {
1325         userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Group:</emphasis> %1</para>", getGroupnameForGroup(ps->gid()));
1326         if ((ps->gid() != ps->egid() && ps->egid() != -1) || (ps->gid() != ps->sgid() && ps->sgid() != -1) || (ps->gid() != ps->fsgid() && ps->fsgid() != -1)) {
1327             if (ps->egid() != -1)
1328                 userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Effective Group:</emphasis> %1</para>", getGroupnameForGroup(ps->egid()));
1329             if (ps->sgid() != -1)
1330                 userTooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Setuid Group:</emphasis> %1</para>", getGroupnameForGroup(ps->sgid()));
1331             if (ps->fsgid() != -1)
1332                 userTooltip +=
1333                     xi18nc("@info:tooltip", "<para><emphasis strong='true'>File System Group:</emphasis> %1</para>", getGroupnameForGroup(ps->fsgid()));
1334         }
1335     }
1336     return userTooltip;
1337 }
1338 
1339 QString ProcessModel::getStringForProcess(KSysGuard::Process *process) const
1340 {
1341     return i18nc("Short description of a process. PID, name, user",
1342                  "%1: %2, owned by user %3",
1343                  QString::number(process->pid()),
1344                  process->name(),
1345                  d->getUsernameForUser(process->uid(), false));
1346 }
1347 
1348 QString ProcessModelPrivate::getGroupnameForGroup(long gid) const
1349 {
1350     if (mIsLocalhost) {
1351         QString groupname = KUserGroup(gid).name();
1352         if (!groupname.isEmpty())
1353             return i18nc("Group name and group id", "%1 (gid: %2)", groupname, QString::number(gid));
1354     }
1355     return QString::number(gid);
1356 }
1357 
1358 QString ProcessModelPrivate::getUsernameForUser(long uid, bool withuid) const
1359 {
1360     QString &username = mUserUsername[uid];
1361     if (username.isNull()) {
1362         if (!mIsLocalhost) {
1363             username = QLatin1String(""); // empty, but not null
1364         } else {
1365             KUser user(uid);
1366             if (!user.isValid())
1367                 username = QLatin1String("");
1368             else
1369                 username = user.loginName();
1370         }
1371     }
1372     if (username.isEmpty())
1373         return QString::number(uid);
1374     if (withuid)
1375         return i18nc("User name and user id", "%1 (uid: %2)", username, QString::number(uid));
1376     return username;
1377 }
1378 
1379 QVariant ProcessModel::data(const QModelIndex &index, int role) const
1380 {
1381     // This function must be super duper ultra fast because it's called thousands of times every few second :(
1382     // I think it should be optimised for role first, hence the switch statement (fastest possible case)
1383 
1384     if (!index.isValid()) {
1385         return QVariant();
1386     }
1387 
1388     if (index.column() > columnCount()) {
1389         return QVariant();
1390     }
1391     // plugin stuff first
1392     if (index.column() >= d->mHeadings.count()) {
1393         int attr = index.column() - d->mHeadings.count();
1394         switch (role) {
1395         case ProcessModel::PlainValueRole: {
1396             KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
1397             const QVariant value = d->mExtraAttributes[attr]->data(process);
1398             return value;
1399         }
1400         case Qt::DisplayRole: {
1401             KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
1402             const QVariant value = d->mExtraAttributes[attr]->data(process);
1403             return KSysGuard::Formatter::formatValue(value, d->mExtraAttributes[attr]->unit());
1404         }
1405         case Qt::TextAlignmentRole: {
1406             KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
1407             const QVariant value = d->mExtraAttributes[attr]->data(process);
1408             if (value.canConvert(QMetaType::LongLong) && static_cast<QMetaType::Type>(value.type()) != QMetaType::QString) {
1409                 return (int)(Qt::AlignRight | Qt::AlignVCenter);
1410             }
1411             return (int)(Qt::AlignLeft | Qt::AlignVCenter);
1412         }
1413         }
1414         return QVariant();
1415     }
1416 
1417     KFormat format;
1418     switch (role) {
1419     case Qt::DisplayRole: {
1420         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
1421         switch (index.column()) {
1422         case HeadingName:
1423             if (d->mShowCommandLineOptions)
1424                 return process->name();
1425             else
1426                 return process->name().section(QLatin1Char(' '), 0, 0);
1427         case HeadingPid:
1428             return (qlonglong)process->pid();
1429         case HeadingUser:
1430             if (!process->login().isEmpty())
1431                 return process->login();
1432             if (process->uid() == process->euid())
1433                 return d->getUsernameForUser(process->uid(), false);
1434             else
1435                 return QString(d->getUsernameForUser(process->uid(), false) + QStringLiteral(", ") + d->getUsernameForUser(process->euid(), false));
1436         case HeadingNiceness:
1437             switch (process->scheduler()) {
1438             case KSysGuard::Process::Other:
1439                 return process->niceLevel();
1440             case KSysGuard::Process::SchedulerIdle:
1441                 return i18nc("scheduler", "Idle"); // neither static nor dynamic priority matter
1442             case KSysGuard::Process::Batch:
1443                 return i18nc("scheduler", "(Batch) %1", process->niceLevel()); // only dynamic priority matters
1444             case KSysGuard::Process::RoundRobin:
1445                 return i18nc("Round robin scheduler", "RR %1", process->niceLevel());
1446             case KSysGuard::Process::Fifo:
1447                 if (process->niceLevel() == 99)
1448                     return i18nc("Real Time scheduler", "RT");
1449                 else
1450                     return i18nc("First in first out scheduler", "FIFO %1", process->niceLevel());
1451             case KSysGuard::Process::Interactive:
1452                 return i18nc("scheduler", "(IA) %1", process->niceLevel());
1453             }
1454             return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through
1455         case HeadingTty:
1456             return process->tty();
1457         case HeadingCPUUsage: {
1458             double total;
1459             if (d->mShowChildTotals && !d->mSimple)
1460                 total = process->totalUserUsage() + process->totalSysUsage();
1461             else
1462                 total = process->userUsage() + process->sysUsage();
1463             if (d->mNormalizeCPUUsage)
1464                 total = total / d->mNumProcessorCores;
1465 
1466             if (total < 1 && process->status() != KSysGuard::Process::Sleeping && process->status() != KSysGuard::Process::Running
1467                 && process->status() != KSysGuard::Process::Ended)
1468                 return process->translatedStatus(); // tell the user when the process is a zombie or stopped
1469             if (total < 0.5)
1470                 return QString();
1471 
1472             return QString(QString::number((int)(total + 0.5)) + QLatin1Char('%'));
1473         }
1474         case HeadingCPUTime: {
1475             qlonglong seconds = (process->userTime() + process->sysTime()) / 100;
1476             return QStringLiteral("%1:%2").arg(seconds / 60).arg((int)seconds % 60, 2, 10, QLatin1Char('0'));
1477         }
1478         case HeadingMemory:
1479             if (process->vmURSS() == -1) {
1480                 // If we don't have the URSS (the memory used by only the process, not the shared libraries)
1481                 // then return the RSS (physical memory used by the process + shared library) as the next best thing
1482                 return formatMemoryInfo(process->vmRSS(), d->mUnits, true);
1483             } else {
1484                 return formatMemoryInfo(process->vmURSS(), d->mUnits, true);
1485             }
1486         case HeadingVmSize:
1487             return formatMemoryInfo(process->vmSize(), d->mUnits, true);
1488         case HeadingSharedMemory:
1489             if (process->vmRSS() - process->vmURSS() <= 0 || process->vmURSS() == -1)
1490                 return QVariant(QVariant::String);
1491             return formatMemoryInfo(process->vmRSS() - process->vmURSS(), d->mUnits);
1492         case HeadingStartTime: {
1493             // NOTE: the next 6 lines are the same as in the next occurrence of 'case HeadingStartTime:' => keep in sync or remove duplicate code
1494             const auto clockTicksSinceSystemBoot = process->startTime();
1495             const auto clockTicksPerSecond = sysconf(_SC_CLK_TCK); // see man proc or https://superuser.com/questions/101183/what-is-a-cpu-tick
1496             const auto secondsSinceSystemBoot = (double)clockTicksSinceSystemBoot / clockTicksPerSecond;
1497             const auto systemBootTime = TimeUtil::systemUptimeAbsolute();
1498             const auto absoluteStartTime = systemBootTime.addSecs(secondsSinceSystemBoot);
1499             const auto relativeStartTime = absoluteStartTime.secsTo(QDateTime::currentDateTime());
1500             return TimeUtil::secondsToHumanElapsedString(relativeStartTime);
1501         }
1502         case HeadingNoNewPrivileges:
1503             return QString::number(process->noNewPrivileges());
1504         case HeadingCommand: {
1505             return process->command().replace(QLatin1Char('\n'), QLatin1Char(' '));
1506             // It would be nice to embolden the process name in command, but this requires that the itemdelegate to support html text
1507             //                QString command = process->command;
1508             //                command.replace(process->name, "<b>" + process->name + "</b>");
1509             //                return "<qt>" + command;
1510         }
1511         case HeadingIoRead: {
1512             switch (d->mIoInformation) {
1513             case ProcessModel::Bytes: // divide by 1024 to convert to kB
1514                 return formatMemoryInfo(process->ioCharactersRead() / 1024, d->mIoUnits, true);
1515             case ProcessModel::Syscalls:
1516                 if (process->ioReadSyscalls())
1517                     return QString::number(process->ioReadSyscalls());
1518                 break;
1519             case ProcessModel::ActualBytes:
1520                 return formatMemoryInfo(process->ioCharactersActuallyRead() / 1024, d->mIoUnits, true);
1521             case ProcessModel::BytesRate:
1522                 if (process->ioCharactersReadRate() / 1024)
1523                     return i18n("%1/s", formatMemoryInfo(process->ioCharactersReadRate() / 1024, d->mIoUnits, true));
1524                 break;
1525             case ProcessModel::SyscallsRate:
1526                 if (process->ioReadSyscallsRate())
1527                     return QString::number(process->ioReadSyscallsRate());
1528                 break;
1529             case ProcessModel::ActualBytesRate:
1530                 if (process->ioCharactersActuallyReadRate() / 1024)
1531                     return i18n("%1/s", formatMemoryInfo(process->ioCharactersActuallyReadRate() / 1024, d->mIoUnits, true));
1532                 break;
1533             }
1534             return QVariant();
1535         }
1536         case HeadingIoWrite: {
1537             switch (d->mIoInformation) {
1538             case ProcessModel::Bytes:
1539                 return formatMemoryInfo(process->ioCharactersWritten() / 1024, d->mIoUnits, true);
1540             case ProcessModel::Syscalls:
1541                 if (process->ioWriteSyscalls())
1542                     return QString::number(process->ioWriteSyscalls());
1543                 break;
1544             case ProcessModel::ActualBytes:
1545                 return formatMemoryInfo(process->ioCharactersActuallyWritten() / 1024, d->mIoUnits, true);
1546             case ProcessModel::BytesRate:
1547                 if (process->ioCharactersWrittenRate() / 1024)
1548                     return i18n("%1/s", formatMemoryInfo(process->ioCharactersWrittenRate() / 1024, d->mIoUnits, true));
1549                 break;
1550             case ProcessModel::SyscallsRate:
1551                 if (process->ioWriteSyscallsRate())
1552                     return QString::number(process->ioWriteSyscallsRate());
1553                 break;
1554             case ProcessModel::ActualBytesRate:
1555                 if (process->ioCharactersActuallyWrittenRate() / 1024)
1556                     return i18n("%1/s", formatMemoryInfo(process->ioCharactersActuallyWrittenRate() / 1024, d->mIoUnits, true));
1557                 break;
1558             }
1559             return QVariant();
1560         }
1561 #if HAVE_X11
1562         case HeadingXMemory:
1563             return formatMemoryInfo(process->pixmapBytes() / 1024, d->mUnits, true);
1564         case HeadingXTitle: {
1565             if (!process->hasManagedGuiWindow())
1566                 return QVariant(QVariant::String);
1567 
1568             WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL);
1569             if (!w)
1570                 return QVariant(QVariant::String);
1571             else
1572                 return w->name;
1573         }
1574 #endif
1575         case HeadingCGroup:
1576             return process->cGroup();
1577         case HeadingMACContext:
1578             return process->macContext();
1579         case HeadingVmPSS:
1580             return process->vmPSS() >= 0 ? formatMemoryInfo(process->vmPSS(), d->mUnits, true) : QVariant{};
1581         default:
1582             return QVariant();
1583         }
1584         break;
1585     }
1586     case Qt::ToolTipRole: {
1587         if (!d->mShowingTooltips)
1588             return QVariant();
1589         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
1590         QString tracer;
1591         if (process->tracerpid() >= 0) {
1592             KSysGuard::Process *process_tracer = d->mProcesses->getProcess(process->tracerpid());
1593             if (process_tracer) // it is possible for this to be not the case in certain race conditions
1594                 tracer =
1595                     xi18nc("tooltip. name,pid ", "This process is being debugged by %1 (%2)", process_tracer->name(), QString::number(process->tracerpid()));
1596         }
1597         switch (index.column()) {
1598         case HeadingName: {
1599             /*   It would be nice to be able to show the icon in the tooltip, but Qt4 won't let us put
1600              *   a picture in a tooltip :(
1601 
1602             QIcon icon;
1603             if(mPidToWindowInfo.contains(process->pid())) {
1604                 WId wid;
1605                 wid = mPidToWindowInfo[process->pid()].wid;
1606                 icon = KWindowSystem::icon(wid);
1607             }
1608             if(icon.isValid()) {
1609                 tooltip = i18n("<qt><table><tr><td>%1", icon);
1610             }
1611             */
1612             QString tooltip;
1613             if (process->parentPid() == -1) {
1614                 // Give a quick explanation of init and kthreadd
1615                 if (process->name() == QLatin1String("init") || process->name() == QLatin1String("systemd")) {
1616                     tooltip = xi18nc("@info:tooltip",
1617                                      "<title>%1</title><para>The parent of all other processes and cannot be killed.</para><para><emphasis "
1618                                      "strong='true'>Process ID:</emphasis> %2</para>",
1619                                      process->name(),
1620                                      QString::number(process->pid()));
1621                 } else if (process->name() == QLatin1String("kthreadd")) {
1622                     tooltip = xi18nc("@info:tooltip",
1623                                      "<title>KThreadd</title><para>Manages kernel threads. The children processes run in the kernel, controlling hard disk "
1624                                      "access, etc.</para>");
1625                 } else {
1626                     tooltip = xi18nc("@info:tooltip",
1627                                      "<title>%1</title><para><emphasis strong='true'>Process ID:</emphasis> %2</para>",
1628                                      process->name(),
1629                                      QString::number(process->pid()));
1630                 }
1631             } else {
1632                 KSysGuard::Process *parent_process = d->mProcesses->getProcess(process->parentPid());
1633                 if (parent_process) { // In race conditions, it's possible for this process to not exist
1634                     tooltip = xi18nc("@info:tooltip",
1635                                      "<title>%1</title>"
1636                                      "<para><emphasis strong='true'>Process ID:</emphasis> %2</para>"
1637                                      "<para><emphasis strong='true'>Parent:</emphasis> %3</para>"
1638                                      "<para><emphasis strong='true'>Parent's ID:</emphasis> %4</para>",
1639                                      process->name(),
1640                                      QString::number(process->pid()),
1641                                      parent_process->name(),
1642                                      QString::number(process->parentPid()));
1643                 } else {
1644                     tooltip = xi18nc("@info:tooltip",
1645                                      "<title>%1</title>"
1646                                      "<para><emphasis strong='true'>Process ID:</emphasis> %2</para>"
1647                                      "<para><emphasis strong='true'>Parent's ID:</emphasis> %3</para>",
1648                                      process->name(),
1649                                      QString::number(process->pid()),
1650                                      QString::number(process->parentPid()));
1651                 }
1652             }
1653             if (process->numThreads() >= 1)
1654                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Number of threads:</emphasis> %1</para>", process->numThreads());
1655             if (!process->command().isEmpty()) {
1656                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Command:</emphasis> %1</para>", process->command());
1657             }
1658             if (!process->tty().isEmpty())
1659                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Running on:</emphasis> %1</para>", QString::fromUtf8(process->tty()));
1660             if (!tracer.isEmpty())
1661                 return QStringLiteral("%1<br />%2").arg(tooltip).arg(tracer);
1662 
1663             return tooltip;
1664         }
1665         case HeadingStartTime: {
1666             // NOTE: the next 6 lines are the same as in the previous occurrence of 'case HeadingStartTime:' => keep in sync or remove duplicate code
1667             const auto clockTicksSinceSystemBoot = process->startTime();
1668             const auto clockTicksPerSecond = sysconf(_SC_CLK_TCK);
1669             const auto secondsSinceSystemBoot = (double)clockTicksSinceSystemBoot / clockTicksPerSecond;
1670             const auto systemBootTime = TimeUtil::systemUptimeAbsolute();
1671             const auto absoluteStartTime = systemBootTime.addSecs(secondsSinceSystemBoot);
1672             const auto relativeStartTime = absoluteStartTime.secsTo(QDateTime::currentDateTime());
1673             return xi18nc("@info:tooltip",
1674                           "<para><emphasis strong='true'>Clock ticks since system boot:</emphasis> %1</para>"
1675                           "<para><emphasis strong='true'>Seconds since system boot:</emphasis> %2 (System boot time: %3)</para>"
1676                           "<para><emphasis strong='true'>Absolute start time:</emphasis> %4</para>"
1677                           "<para><emphasis strong='true'>Relative start time:</emphasis> %5</para>",
1678                           clockTicksSinceSystemBoot,
1679                           secondsSinceSystemBoot,
1680                           systemBootTime.toString(),
1681                           absoluteStartTime.toString(),
1682                           TimeUtil::secondsToHumanElapsedString(relativeStartTime));
1683         }
1684         case HeadingCommand: {
1685             QString tooltip = xi18nc("@info:tooltip",
1686                                      "<para><emphasis strong='true'>This process was run with the following command:</emphasis></para>"
1687                                      "<para>%1</para>",
1688                                      process->command());
1689             if (!process->tty().isEmpty())
1690                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Running on:</emphasis> %1</para>", QString::fromUtf8(process->tty()));
1691             if (!tracer.isEmpty()) {
1692                 return QStringLiteral("%1<br/>%2").arg(tooltip).arg(tracer);
1693             }
1694             return tooltip;
1695         }
1696         case HeadingUser: {
1697             QString tooltip = d->getTooltipForUser(process);
1698             if (tracer.isEmpty()) {
1699                 return tooltip;
1700             }
1701 
1702             return QString(tooltip + QStringLiteral("<br/>") + tracer);
1703         }
1704         case HeadingNiceness: {
1705             QString tooltip;
1706             switch (process->scheduler()) {
1707             case KSysGuard::Process::Other:
1708             case KSysGuard::Process::Batch:
1709             case KSysGuard::Process::Interactive:
1710                 tooltip = xi18nc("@info:tooltip",
1711                                  "<para><emphasis strong='true'>Nice level:</emphasis> %1 (%2)</para>",
1712                                  process->niceLevel(),
1713                                  process->niceLevelAsString());
1714                 break;
1715             case KSysGuard::Process::RoundRobin:
1716             case KSysGuard::Process::Fifo:
1717                 tooltip = xi18nc("@info:tooltip",
1718                                  "<para><emphasis strong='true'>This is a real time process.</emphasis></para>"
1719                                  "<para><emphasis strong='true'>Scheduler priority:</emphasis> %1</para>",
1720                                  process->niceLevel());
1721                 break;
1722             case KSysGuard::Process::SchedulerIdle:
1723                 break; // has neither dynamic (niceness) or static (scheduler priority) priority
1724             }
1725             if (process->scheduler() != KSysGuard::Process::Other)
1726                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Scheduler:</emphasis> %1</para>", process->schedulerAsString());
1727 
1728             if (process->ioPriorityClass() != KSysGuard::Process::None) {
1729                 if ((process->ioPriorityClass() == KSysGuard::Process::RealTime || process->ioPriorityClass() == KSysGuard::Process::BestEffort)
1730                     && process->ioniceLevel() != -1)
1731                     tooltip += xi18nc("@info:tooltip",
1732                                       "<para><emphasis strong='true'>I/O Nice level:</emphasis> %1 (%2)</para>",
1733                                       process->ioniceLevel(),
1734                                       process->ioniceLevelAsString());
1735                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>I/O Class:</emphasis> %1</para>", process->ioPriorityClassAsString());
1736             }
1737             if (tracer.isEmpty())
1738                 return tooltip;
1739             return QString(tooltip + QStringLiteral("<br/>") + tracer);
1740         }
1741         case HeadingCPUUsage:
1742         case HeadingCPUTime: {
1743             int divideby = (d->mNormalizeCPUUsage ? d->mNumProcessorCores : 1);
1744             QString tooltip =
1745                 xi18nc("@info:tooltip",
1746                        "<para><emphasis strong='true'>Process status:</emphasis> %1 %2</para>"
1747                        "<para><emphasis strong='true'>User CPU usage:</emphasis> %3%</para>"
1748                        "<para><emphasis strong='true'>System CPU usage:</emphasis> %4%</para>", /* Please do not add </qt> here - the tooltip is appended to */
1749                        process->translatedStatus(),
1750                        d->getStatusDescription(process->status()),
1751                        (float)(process->userUsage()) / divideby,
1752                        (float)(process->sysUsage()) / divideby);
1753 
1754             if (process->numThreads() >= 1)
1755                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>Number of threads:</emphasis> %1</para>", process->numThreads());
1756             if (process->numChildren() > 0) {
1757                 tooltip += xi18nc("@info:tooltip",
1758                                   "<para><emphasis strong='true'>Number of children:</emphasis> %1</para>"
1759                                   "<para><emphasis strong='true'>Total User CPU usage:</emphasis> %2%</para>"
1760                                   "<para><emphasis strong='true'>Total System CPU usage:</emphasis> %3%</para>"
1761                                   "<para><emphasis strong='true'>Total CPU usage:</emphasis> %4%</para>",
1762                                   process->numChildren(),
1763                                   (float)(process->totalUserUsage()) / divideby,
1764                                   (float)(process->totalSysUsage()) / divideby,
1765                                   (float)(process->totalUserUsage() + process->totalSysUsage()) / divideby);
1766             }
1767             if (process->userTime() > 0)
1768                 tooltip += xi18nc("@info:tooltip",
1769                                   "<para><emphasis strong='true'>CPU time spent running as user:</emphasis> %1 seconds</para>",
1770                                   QString::number(process->userTime() / 100.0, 'f', 1));
1771             if (process->sysTime() > 0)
1772                 tooltip += xi18nc("@info:tooltip",
1773                                   "<para><emphasis strong='true'>CPU time spent running in kernel:</emphasis> %1 seconds</para>",
1774                                   QString::number(process->sysTime() / 100.0, 'f', 1));
1775             if (process->niceLevel() != 0)
1776                 tooltip += xi18nc("@info:tooltip",
1777                                   "<para><emphasis strong='true'>Nice level:</emphasis> %1 (%2)</para>",
1778                                   process->niceLevel(),
1779                                   process->niceLevelAsString());
1780             if (process->ioPriorityClass() != KSysGuard::Process::None) {
1781                 if ((process->ioPriorityClass() == KSysGuard::Process::RealTime || process->ioPriorityClass() == KSysGuard::Process::BestEffort)
1782                     && process->ioniceLevel() != -1)
1783                     tooltip += xi18nc("@info:tooltip",
1784                                       "<para><emphasis strong='true'>I/O Nice level:</emphasis> %1 (%2)</para>",
1785                                       process->ioniceLevel(),
1786                                       process->ioniceLevelAsString());
1787                 tooltip += xi18nc("@info:tooltip", "<para><emphasis strong='true'>I/O Class:</emphasis> %1</para>", process->ioPriorityClassAsString());
1788             }
1789 
1790             if (!tracer.isEmpty())
1791                 return QString(tooltip + QStringLiteral("<br/>") + tracer);
1792             return tooltip;
1793         }
1794         case HeadingVmSize: {
1795             return QVariant();
1796         }
1797         case HeadingMemory: {
1798             QString tooltip;
1799             if (process->vmURSS() != -1) {
1800                 // We don't have information about the URSS, so just fallback to RSS
1801                 if (d->mMemTotal > 0)
1802                     tooltip += xi18nc("@info:tooltip",
1803                                       "<para><emphasis strong='true'>Memory usage:</emphasis> %1 out of %2  (%3 %)</para>",
1804                                       format.formatByteSize(process->vmURSS() * 1024),
1805                                       format.formatByteSize(d->mMemTotal * 1024),
1806                                       process->vmURSS() * 100 / d->mMemTotal);
1807                 else
1808                     tooltip +=
1809                         xi18nc("@info:tooltip", "<emphasis strong='true'>Memory usage:</emphasis> %1<br />", format.formatByteSize(process->vmURSS() * 1024));
1810             }
1811             if (d->mMemTotal > 0)
1812                 tooltip += xi18nc("@info:tooltip",
1813                                   "<para><emphasis strong='true'>RSS Memory usage:</emphasis> %1 out of %2  (%3 %)</para>",
1814                                   format.formatByteSize(process->vmRSS() * 1024),
1815                                   format.formatByteSize(d->mMemTotal * 1024),
1816                                   process->vmRSS() * 100 / d->mMemTotal);
1817             else
1818                 tooltip += xi18nc("@info:tooltip",
1819                                   "<para><emphasis strong='true'>RSS Memory usage:</emphasis> %1</para>",
1820                                   format.formatByteSize(process->vmRSS() * 1024));
1821             return tooltip;
1822         }
1823         case HeadingSharedMemory: {
1824             if (process->vmURSS() == -1) {
1825                 return xi18nc("@info:tooltip",
1826                               "<para><emphasis strong='true'>Your system does not seem to have this information available to be read.</emphasis></para>");
1827             }
1828             if (d->mMemTotal > 0)
1829                 return xi18nc("@info:tooltip",
1830                               "<para><emphasis strong='true'>Shared library memory usage:</emphasis> %1 out of %2  (%3 %)</para>",
1831                               format.formatByteSize((process->vmRSS() - process->vmURSS()) * 1024),
1832                               format.formatByteSize(d->mMemTotal * 1024),
1833                               (process->vmRSS() - process->vmURSS()) * 100 / d->mMemTotal);
1834             else
1835                 return xi18nc("@info:tooltip",
1836                               "<para><emphasis strong='true'>Shared library memory usage:</emphasis> %1</para>",
1837                               format.formatByteSize((process->vmRSS() - process->vmURSS()) * 1024));
1838         }
1839         case HeadingIoWrite:
1840         case HeadingIoRead: {
1841             // FIXME - use the formatByteRate functions when added
1842             return kxi18nc("@info:tooltip",
1843                            "<para><emphasis strong='true'>Characters read:</emphasis> %1 (%2 KiB/s)</para>"
1844                            "<para><emphasis strong='true'>Characters written:</emphasis> %3 (%4 KiB/s)</para>"
1845                            "<para><emphasis strong='true'>Read syscalls:</emphasis> %5 (%6 s⁻¹)</para>"
1846                            "<para><emphasis strong='true'>Write syscalls:</emphasis> %7 (%8 s⁻¹)</para>"
1847                            "<para><emphasis strong='true'>Actual bytes read:</emphasis> %9 (%10 KiB/s)</para>"
1848                            "<para><emphasis strong='true'>Actual bytes written:</emphasis> %11 (%12 KiB/s)</para>")
1849                 .subs(format.formatByteSize(process->ioCharactersRead()))
1850                 .subs(QString::number(process->ioCharactersReadRate() / 1024))
1851                 .subs(format.formatByteSize(process->ioCharactersWritten()))
1852                 .subs(QString::number(process->ioCharactersWrittenRate() / 1024))
1853                 .subs(QString::number(process->ioReadSyscalls()))
1854                 .subs(QString::number(process->ioReadSyscallsRate()))
1855                 .subs(QString::number(process->ioWriteSyscalls()))
1856                 .subs(QString::number(process->ioWriteSyscallsRate()))
1857                 .subs(format.formatByteSize(process->ioCharactersActuallyRead()))
1858                 .subs(QString::number(process->ioCharactersActuallyReadRate() / 1024))
1859                 .subs(format.formatByteSize(process->ioCharactersActuallyWritten()))
1860                 .subs(QString::number(process->ioCharactersActuallyWrittenRate() / 1024))
1861                 .toString();
1862         }
1863         case HeadingXTitle: {
1864 #if HAVE_X11
1865             const auto values = d->mPidToWindowInfo.values(process->pid());
1866             if (values.count() == 1) {
1867                 return values.first()->name;
1868             }
1869 
1870             QString tooltip;
1871 
1872             for (const auto &value : values) {
1873                 if (!tooltip.isEmpty()) {
1874                     tooltip += QLatin1Char('\n');
1875                 }
1876                 tooltip += QStringLiteral("• ") + value->name;
1877             }
1878 
1879             return tooltip;
1880 #else
1881             return QVariant(QVariant::String);
1882 #endif
1883         }
1884         case HeadingVmPSS: {
1885             if (process->vmPSS() == -1) {
1886                 return xi18nc("@info:tooltip",
1887                               "<para><emphasis strong='true'>Your system does not seem to have this information available to be read.</emphasis></para>");
1888             }
1889             if (d->mMemTotal > 0) {
1890                 return xi18nc("@info:tooltip",
1891                               "<para><emphasis strong='true'>Total memory usage:</emphasis> %1 out of %2  (%3 %)</para>",
1892                               format.formatByteSize(process->vmPSS() * 1024),
1893                               format.formatByteSize(d->mMemTotal * 1024),
1894                               qRound(process->vmPSS() * 1000.0 / d->mMemTotal) / 10.0);
1895             } else {
1896                 return xi18nc("@info:tooltip",
1897                               "<para><emphasis strong='true'>Shared library memory usage:</emphasis> %1</para>",
1898                               format.formatByteSize(process->vmPSS() * 1024));
1899             }
1900         }
1901         default:
1902             return QVariant(QVariant::String);
1903         }
1904     }
1905     case Qt::TextAlignmentRole:
1906         return columnAlignment(index.column());
1907     case UidRole: {
1908         if (index.column() != 0)
1909             return QVariant(); // If we query with this role, then we want the raw UID for this.
1910         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
1911         return process->uid();
1912     }
1913     case PlainValueRole: // Used to return a plain value.  For copying to a clipboard etc
1914     {
1915         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
1916         switch (index.column()) {
1917         case HeadingName:
1918             return process->name();
1919         case HeadingPid:
1920             return (qlonglong)process->pid();
1921         case HeadingUser:
1922             if (!process->login().isEmpty())
1923                 return process->login();
1924             if (process->uid() == process->euid())
1925                 return d->getUsernameForUser(process->uid(), false);
1926             else
1927                 return QString(d->getUsernameForUser(process->uid(), false) + QStringLiteral(", ") + d->getUsernameForUser(process->euid(), false));
1928         case HeadingNiceness:
1929             return process->niceLevel();
1930         case HeadingTty:
1931             return process->tty();
1932         case HeadingCPUUsage: {
1933             double total;
1934             if (d->mShowChildTotals && !d->mSimple)
1935                 total = process->totalUserUsage() + process->totalSysUsage();
1936             else
1937                 total = process->userUsage() + process->sysUsage();
1938 
1939             if (d->mNormalizeCPUUsage)
1940                 return total / d->mNumProcessorCores;
1941             else
1942                 return total;
1943         }
1944         case HeadingCPUTime:
1945             return (qlonglong)(process->userTime() + process->sysTime());
1946         case HeadingMemory:
1947             if (process->vmRSS() == 0)
1948                 return QVariant(QVariant::String);
1949             if (process->vmURSS() == -1) {
1950                 return (qlonglong)process->vmRSS();
1951             } else {
1952                 return (qlonglong)process->vmURSS();
1953             }
1954         case HeadingVmSize:
1955             return (qlonglong)process->vmSize();
1956         case HeadingSharedMemory:
1957             if (process->vmRSS() - process->vmURSS() < 0 || process->vmURSS() == -1)
1958                 return QVariant(QVariant::String);
1959             return (qlonglong)(process->vmRSS() - process->vmURSS());
1960         case HeadingStartTime:
1961             return process->startTime(); // 2015-01-03, gregormi: can maybe be replaced with something better later
1962         case HeadingNoNewPrivileges:
1963             return process->noNewPrivileges();
1964         case HeadingCommand:
1965             return process->command();
1966         case HeadingIoRead:
1967             switch (d->mIoInformation) {
1968             case ProcessModel::Bytes:
1969                 return process->ioCharactersRead();
1970             case ProcessModel::Syscalls:
1971                 return process->ioReadSyscalls();
1972             case ProcessModel::ActualBytes:
1973                 return process->ioCharactersActuallyRead();
1974             case ProcessModel::BytesRate:
1975                 return (qlonglong)process->ioCharactersReadRate();
1976             case ProcessModel::SyscallsRate:
1977                 return (qlonglong)process->ioReadSyscallsRate();
1978             case ProcessModel::ActualBytesRate:
1979                 return (qlonglong)process->ioCharactersActuallyReadRate();
1980             }
1981             return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through
1982         case HeadingIoWrite:
1983             switch (d->mIoInformation) {
1984             case ProcessModel::Bytes:
1985                 return process->ioCharactersWritten();
1986             case ProcessModel::Syscalls:
1987                 return process->ioWriteSyscalls();
1988             case ProcessModel::ActualBytes:
1989                 return process->ioCharactersActuallyWritten();
1990             case ProcessModel::BytesRate:
1991                 return (qlonglong)process->ioCharactersWrittenRate();
1992             case ProcessModel::SyscallsRate:
1993                 return (qlonglong)process->ioWriteSyscallsRate();
1994             case ProcessModel::ActualBytesRate:
1995                 return (qlonglong)process->ioCharactersActuallyWrittenRate();
1996             }
1997             return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through
1998         case HeadingXMemory:
1999             return (qulonglong)process->pixmapBytes();
2000 #if HAVE_X11
2001         case HeadingXTitle: {
2002             WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL);
2003             if (!w)
2004                 return QString();
2005             return w->name;
2006         }
2007 #endif
2008         case HeadingCGroup:
2009             return process->cGroup();
2010         case HeadingMACContext:
2011             return process->macContext();
2012         case HeadingVmPSS:
2013             return process->vmPSS() >= 0 ? process->vmPSS() : QVariant{};
2014         default:
2015             return QVariant();
2016         }
2017         break;
2018     }
2019 #if HAVE_X11
2020     case WindowIdRole: {
2021         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2022         WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL);
2023         if (!w)
2024             return QVariant();
2025         else
2026             return (int)w->wid;
2027     }
2028 #endif
2029     case PercentageRole: {
2030         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2031         Q_CHECK_PTR(process);
2032         switch (index.column()) {
2033         case HeadingCPUUsage: {
2034             float cpu;
2035             if (d->mSimple || !d->mShowChildTotals)
2036                 cpu = process->userUsage() + process->sysUsage();
2037             else
2038                 cpu = process->totalUserUsage() + process->totalSysUsage();
2039             cpu = cpu / 100.0;
2040             if (!d->mNormalizeCPUUsage)
2041                 return cpu;
2042             return cpu / d->mNumProcessorCores;
2043         }
2044         case HeadingMemory:
2045             if (d->mMemTotal <= 0)
2046                 return -1;
2047             if (process->vmURSS() != -1)
2048                 return float(process->vmURSS()) / d->mMemTotal;
2049             else
2050                 return float(process->vmRSS()) / d->mMemTotal;
2051         case HeadingSharedMemory:
2052             if (process->vmURSS() == -1 || d->mMemTotal <= 0)
2053                 return -1;
2054             return float(process->vmRSS() - process->vmURSS()) / d->mMemTotal;
2055         case HeadingVmPSS:
2056             if (process->vmPSS() == -1 || d->mMemTotal <= 0) {
2057                 return -1;
2058             }
2059             return float(process->vmPSS()) / d->mMemTotal;
2060         default:
2061             return -1;
2062         }
2063     }
2064     case PercentageHistoryRole: {
2065         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2066         Q_CHECK_PTR(process);
2067         switch (index.column()) {
2068         case HeadingCPUUsage: {
2069             auto it = d->mMapProcessCPUHistory.find(process);
2070             if (it == d->mMapProcessCPUHistory.end()) {
2071                 it = d->mMapProcessCPUHistory.insert(process, {});
2072                 it->reserve(ProcessModelPrivate::MAX_HIST_ENTRIES);
2073             }
2074             return QVariant::fromValue(*it);
2075         }
2076         default: {
2077         }
2078         }
2079         return QVariant::fromValue(QVector<PercentageHistoryEntry>{});
2080     }
2081     case Qt::DecorationRole: {
2082         if (index.column() == HeadingName) {
2083 #if HAVE_X11
2084             KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2085             if (!process->hasManagedGuiWindow()) {
2086                 if (d->mSimple) // When not in tree mode, we need to pad the name column where we do not have an icon
2087                     return QIcon(d->mBlankPixmap);
2088                 else // When in tree mode, the padding looks bad, so do not pad in this case
2089                     return QVariant();
2090             }
2091             WindowInfo *w = d->mPidToWindowInfo.value(process->pid(), NULL);
2092             if (w && !w->icon.isNull())
2093                 return w->icon;
2094             return QIcon(d->mBlankPixmap);
2095 #else
2096             return QVariant();
2097 #endif
2098 
2099         } else if (index.column() == HeadingCPUUsage) {
2100             KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2101             if (process->status() == KSysGuard::Process::Stopped || process->status() == KSysGuard::Process::Zombie) {
2102                 //        QPixmap pix = KIconLoader::global()->loadIcon("button_cancel", KIconLoader::Small,
2103                 //                    KIconLoader::SizeSmall, KIconLoader::DefaultState, QStringList(),
2104                 //                0L, true);
2105             }
2106         }
2107         return QVariant();
2108     }
2109     case Qt::BackgroundRole: {
2110         if (index.column() != HeadingUser) {
2111             if (!d->mHaveTimer) // If there is no timer, then no processes are being killed, so no point looking for one
2112                 return QVariant();
2113             KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2114             if (process->timeKillWasSent().isValid()) {
2115                 int elapsed = process->timeKillWasSent().elapsed();
2116                 if (elapsed < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) { // Only show red for about 7 seconds
2117                     int transparency = 255 - elapsed * 250 / MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS;
2118 
2119                     KColorScheme scheme(QPalette::Active, KColorScheme::Selection);
2120                     QBrush brush = scheme.background(KColorScheme::NegativeBackground);
2121                     QColor color = brush.color();
2122                     color.setAlpha(transparency);
2123                     brush.setColor(color);
2124                     return brush;
2125                 }
2126             }
2127             return QVariant();
2128         }
2129         KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2130         if (process->status() == KSysGuard::Process::Ended) {
2131             return QColor(Qt::lightGray);
2132         }
2133         if (process->tracerpid() >= 0) {
2134             // It's being debugged, so probably important.  Let's mark it as such
2135             return QColor(Qt::yellow);
2136         }
2137         if (d->mIsLocalhost && process->uid() == getuid()) { // own user
2138             return QColor(0, 208, 214, 50);
2139         }
2140         if (process->uid() < 100 || !canUserLogin(process->uid()))
2141             return QColor(218, 220, 215, 50); // no color for system tasks
2142         // other users
2143         return QColor(2, 154, 54, 50);
2144     }
2145     case Qt::FontRole: {
2146         if (index.column() == HeadingCPUUsage) {
2147             KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2148             if (process->userUsage() == 0) {
2149                 QFont font;
2150                 font.setItalic(true);
2151                 return font;
2152             }
2153         }
2154         return QVariant();
2155     }
2156     default: // This is a very very common case, so the route to this must be very minimal
2157         return QVariant();
2158     }
2159 
2160     return QVariant(); // never get here, but make compiler happy
2161 }
2162 
2163 bool ProcessModel::hasGUIWindow(qlonglong pid) const
2164 {
2165 #if HAVE_X11
2166     return d->mPidToWindowInfo.contains(pid);
2167 #else
2168     return false;
2169 #endif
2170 }
2171 
2172 bool ProcessModel::isLocalhost() const
2173 {
2174     return d->mIsLocalhost;
2175 }
2176 
2177 void ProcessModel::setupHeader()
2178 {
2179     // These must be in the same order that they are in the header file
2180     QStringList headings;
2181     headings << i18nc("process heading", "Name");
2182     headings << i18nc("process heading", "Username");
2183     headings << i18nc("process heading", "PID");
2184     headings << i18nc("process heading", "TTY");
2185     headings << i18nc("process heading", "Niceness");
2186     // xgettext: no-c-format
2187     headings << i18nc("process heading", "CPU %");
2188     headings << i18nc("process heading", "CPU Time");
2189     headings << i18nc("process heading", "IO Read");
2190     headings << i18nc("process heading", "IO Write");
2191     headings << i18nc("process heading", "Virtual Size");
2192     headings << i18nc("process heading", "Memory");
2193     headings << i18nc("process heading", "Shared Mem");
2194     headings << i18nc("process heading", "Relative Start Time");
2195     headings << i18nc("process heading", "NNP");
2196     headings << i18nc("process heading", "Command");
2197 #if HAVE_X11
2198     if (d->mIsX11) {
2199         headings << i18nc("process heading", "X11 Memory");
2200         headings << i18nc("process heading", "Window Title");
2201     }
2202 #endif
2203     headings << i18nc("process heading", "CGroup");
2204     headings << i18nc("process heading", "MAC Context");
2205     headings << i18nc("process heading", "Total Memory");
2206 
2207     if (d->mHeadings.isEmpty()) { // If it's empty, this is the first time this has been called, so insert the headings
2208         d->mHeadings = headings;
2209     } else {
2210         // This was called to retranslate the headings.  Just use the new translations and call headerDataChanged
2211         Q_ASSERT(d->mHeadings.count() == headings.count());
2212         d->mHeadings = headings;
2213         headerDataChanged(Qt::Horizontal, 0, headings.count() - 1);
2214     }
2215 }
2216 
2217 void ProcessModel::retranslateUi()
2218 {
2219     setupHeader();
2220 }
2221 
2222 KSysGuard::Process *ProcessModel::getProcess(qlonglong pid)
2223 {
2224     return d->mProcesses->getProcess(pid);
2225 }
2226 
2227 bool ProcessModel::showTotals() const
2228 {
2229     return d->mShowChildTotals;
2230 }
2231 
2232 void ProcessModel::setShowTotals(bool showTotals) // slot
2233 {
2234     if (showTotals == d->mShowChildTotals)
2235         return;
2236     d->mShowChildTotals = showTotals;
2237 
2238     QModelIndex index;
2239     foreach (KSysGuard::Process *process, d->mProcesses->getAllProcesses()) {
2240         if (process->numChildren() > 0) {
2241             int row;
2242             if (d->mSimple)
2243                 row = process->index();
2244             else
2245                 row = process->parent()->children().indexOf(process);
2246             index = createIndex(row, HeadingCPUUsage, process);
2247             Q_EMIT dataChanged(index, index);
2248         }
2249     }
2250 }
2251 
2252 qlonglong ProcessModel::totalMemory() const
2253 {
2254     return d->mMemTotal;
2255 }
2256 
2257 void ProcessModel::setUnits(Units units)
2258 {
2259     if (d->mUnits == units)
2260         return;
2261     d->mUnits = units;
2262 
2263     QModelIndex index;
2264     foreach (KSysGuard::Process *process, d->mProcesses->getAllProcesses()) {
2265         int row;
2266         if (d->mSimple)
2267             row = process->index();
2268         else
2269             row = process->parent()->children().indexOf(process);
2270         index = createIndex(row, HeadingMemory, process);
2271         Q_EMIT dataChanged(index, index);
2272         index = createIndex(row, HeadingXMemory, process);
2273         Q_EMIT dataChanged(index, index);
2274         index = createIndex(row, HeadingSharedMemory, process);
2275         Q_EMIT dataChanged(index, index);
2276         index = createIndex(row, HeadingVmSize, process);
2277         Q_EMIT dataChanged(index, index);
2278     }
2279 }
2280 
2281 ProcessModel::Units ProcessModel::units() const
2282 {
2283     return (Units)d->mUnits;
2284 }
2285 
2286 void ProcessModel::setIoUnits(Units units)
2287 {
2288     if (d->mIoUnits == units)
2289         return;
2290     d->mIoUnits = units;
2291 
2292     QModelIndex index;
2293     foreach (KSysGuard::Process *process, d->mProcesses->getAllProcesses()) {
2294         int row;
2295         if (d->mSimple)
2296             row = process->index();
2297         else
2298             row = process->parent()->children().indexOf(process);
2299         index = createIndex(row, HeadingIoRead, process);
2300         Q_EMIT dataChanged(index, index);
2301         index = createIndex(row, HeadingIoWrite, process);
2302         Q_EMIT dataChanged(index, index);
2303     }
2304 }
2305 
2306 ProcessModel::Units ProcessModel::ioUnits() const
2307 {
2308     return (Units)d->mIoUnits;
2309 }
2310 
2311 void ProcessModel::setIoInformation(ProcessModel::IoInformation ioInformation)
2312 {
2313     d->mIoInformation = ioInformation;
2314 }
2315 
2316 ProcessModel::IoInformation ProcessModel::ioInformation() const
2317 {
2318     return d->mIoInformation;
2319 }
2320 
2321 QString ProcessModel::formatMemoryInfo(qlonglong amountInKB, Units units, bool returnEmptyIfValueIsZero) const
2322 {
2323     // We cache the result of i18n for speed reasons.  We call this function
2324     // hundreds of times, every second or so
2325     if (returnEmptyIfValueIsZero && amountInKB == 0)
2326         return QString();
2327     static QString percentageString = i18n("%1%", QString::fromLatin1("%1"));
2328     if (units == UnitsPercentage) {
2329         if (d->mMemTotal == 0)
2330             return QLatin1String(""); // memory total not determined yet.  Shouldn't happen, but don't crash if it does
2331         float percentage = amountInKB * 100.0 / d->mMemTotal;
2332         if (percentage < 0.1)
2333             percentage = 0.1;
2334         return percentageString.arg(percentage, 0, 'f', 1);
2335     } else
2336         return formatByteSize(amountInKB, units);
2337 }
2338 
2339 QString ProcessModel::hostName() const
2340 {
2341     return d->mHostName;
2342 }
2343 
2344 QStringList ProcessModel::mimeTypes() const
2345 {
2346     QStringList types;
2347     types << QStringLiteral("text/plain");
2348     types << QStringLiteral("text/csv");
2349     types << QStringLiteral("text/html");
2350     return types;
2351 }
2352 
2353 QMimeData *ProcessModel::mimeData(const QModelIndexList &indexes) const
2354 {
2355     QMimeData *mimeData = new QMimeData();
2356     QString textCsv;
2357     QString textCsvHeaders;
2358     QString textPlain;
2359     QString textPlainHeaders;
2360     QString textHtml;
2361     QString textHtmlHeaders;
2362     QString display;
2363     int firstColumn = -1;
2364     bool firstrow = true;
2365     foreach (const QModelIndex &index, indexes) {
2366         if (index.isValid()) {
2367             if (firstColumn == -1)
2368                 firstColumn = index.column();
2369             else if (firstColumn != index.column())
2370                 continue;
2371             else {
2372                 textCsv += QLatin1Char('\n');
2373                 textPlain += QLatin1Char('\n');
2374                 textHtml += QLatin1String("</tr><tr>");
2375                 firstrow = false;
2376             }
2377             for (int i = 0; i < d->mHeadings.size(); i++) {
2378                 if (firstrow) {
2379                     QString heading = d->mHeadings[i];
2380                     textHtmlHeaders += QLatin1String("<th>") + heading + QLatin1String("</th>");
2381                     if (i) {
2382                         textCsvHeaders += QLatin1Char(',');
2383                         textPlainHeaders += QLatin1String(", ");
2384                     }
2385                     textPlainHeaders += heading;
2386                     heading.replace(QLatin1Char('"'), QLatin1String("\"\""));
2387                     textCsvHeaders += QLatin1Char('"') + heading + QLatin1Char('"');
2388                 }
2389                 QModelIndex index2 = createIndex(index.row(), i, reinterpret_cast<KSysGuard::Process *>(index.internalPointer()));
2390                 QString display = data(index2, PlainValueRole).toString();
2391                 if (i) {
2392                     textCsv += QLatin1Char(',');
2393                     textPlain += QLatin1String(", ");
2394                 }
2395                 textHtml += QLatin1String("<td>") + display.toHtmlEscaped() + QLatin1String("</td>");
2396                 textPlain += display;
2397                 display.replace(QLatin1Char('"'), QLatin1String("\"\""));
2398                 textCsv += QLatin1Char('"') + display + QLatin1Char('"');
2399             }
2400         }
2401     }
2402     textHtml = QLatin1String("<html><table><tr>") + textHtmlHeaders + QLatin1String("</tr><tr>") + textHtml + QLatin1String("</tr></table>");
2403     textCsv = textCsvHeaders + QLatin1Char('\n') + textCsv;
2404     textPlain = textPlainHeaders + QLatin1Char('\n') + textPlain;
2405 
2406     mimeData->setText(textPlain);
2407     mimeData->setHtml(textHtml);
2408     mimeData->setData(QStringLiteral("text/csv"), textCsv.toUtf8());
2409     return mimeData;
2410 }
2411 
2412 Qt::ItemFlags ProcessModel::flags(const QModelIndex &index) const
2413 {
2414     if (!index.isValid())
2415         return Qt::NoItemFlags; // Would this ever happen?
2416 
2417     KSysGuard::Process *process = reinterpret_cast<KSysGuard::Process *>(index.internalPointer());
2418     if (process->status() == KSysGuard::Process::Ended)
2419         return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable;
2420     else
2421         return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
2422 }
2423 
2424 bool ProcessModel::isShowCommandLineOptions() const
2425 {
2426     return d->mShowCommandLineOptions;
2427 }
2428 
2429 void ProcessModel::setShowCommandLineOptions(bool showCommandLineOptions)
2430 {
2431     d->mShowCommandLineOptions = showCommandLineOptions;
2432 }
2433 
2434 bool ProcessModel::isShowingTooltips() const
2435 {
2436     return d->mShowingTooltips;
2437 }
2438 
2439 void ProcessModel::setShowingTooltips(bool showTooltips)
2440 {
2441     d->mShowingTooltips = showTooltips;
2442 }
2443 
2444 bool ProcessModel::isNormalizedCPUUsage() const
2445 {
2446     return d->mNormalizeCPUUsage;
2447 }
2448 
2449 void ProcessModel::setNormalizedCPUUsage(bool normalizeCPUUsage)
2450 {
2451     d->mNormalizeCPUUsage = normalizeCPUUsage;
2452 }
2453 
2454 void ProcessModelPrivate::timerEvent(QTimerEvent *event)
2455 {
2456     Q_UNUSED(event);
2457     foreach (qlonglong pid, mPidsToUpdate) {
2458         KSysGuard::Process *process = mProcesses->getProcess(pid);
2459         if (process && process->timeKillWasSent().isValid() && process->timeKillWasSent().elapsed() < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) {
2460             int row;
2461             if (mSimple)
2462                 row = process->index();
2463             else
2464                 row = process->parent()->children().indexOf(process);
2465 
2466             QModelIndex index1 = q->createIndex(row, 0, process);
2467             QModelIndex index2 = q->createIndex(row, mHeadings.count() - 1, process);
2468             Q_EMIT q->dataChanged(index1, index2);
2469         } else {
2470             mPidsToUpdate.removeAll(pid);
2471         }
2472     }
2473 
2474     if (mPidsToUpdate.isEmpty()) {
2475         mHaveTimer = false;
2476         killTimer(mTimerId);
2477         mTimerId = -1;
2478     }
2479 }