File indexing completed on 2024-04-28 05:31:43

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