ProcessModel.cpp 108 KB
Newer Older
1
2
3
/*
    KSysGuard, the KDE System Guard

John Tapsell's avatar
John Tapsell committed
4
5
    Copyright (c) 1999, 2000 Chris Schlaeger <cs@kde.org>
    Copyright (c) 2006-2007 John Tapsell <john.tapsell@kde.org>
6

7
8
9
10
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.
11

12
    This library is distributed in the hope that it will be useful,
13
    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
15
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.
16

17
18
19
20
    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
21
22
23
24

*/


25
26
#include "ProcessModel.h"
#include "ProcessModel_p.h"
27
#include "timeutil.h"
28

29
30
#include "processcore/extended_process_list.h"
#include "processcore/formatter.h"
31
#include "processcore/process.h"
32
33
34
#include "processcore/process_attribute.h"
#include "processcore/process_data_provider.h"

35
#include "processui_debug.h"
36

37
#include <kcolorscheme.h>
38
#include <kiconloader.h>
Martin Flöser's avatar
Martin Flöser committed
39
#include <KLocalizedString>
Hrvoje Senjan's avatar
Hrvoje Senjan committed
40
41
#include <KFormat>
#include <QApplication>
42
#include <QBitmap>
Hrvoje Senjan's avatar
Hrvoje Senjan committed
43
#include <QDebug>
44
45
#include <QFont>
#include <QIcon>
Hrvoje Senjan's avatar
Hrvoje Senjan committed
46
#include <QLocale>
47
48
#include <QPixmap>
#include <QList>
49
50
#include <QMimeData>
#include <QTextDocument>
51
52

#define HEADING_X_ICON_SIZE 16
53
#define MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS 2000
54
55
56
57
58
59
60
61
#define GET_OWN_ID

#ifdef GET_OWN_ID
/* For getuid*/
#include <unistd.h>
#include <sys/types.h>
#endif

62
63
64
#ifdef HAVE_XRES
#include <X11/extensions/XRes.h>
#endif
65

Hrvoje Senjan's avatar
Hrvoje Senjan committed
66
extern QApplication* Qapp;
67

68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
static QString formatByteSize(qlonglong amountInKB, int units) {
    enum { UnitsAuto, UnitsKB, UnitsMB, UnitsGB, UnitsTB, UnitsPB };
    static QString kString = i18n("%1 K", QString::fromLatin1("%1"));
    static QString mString = i18n("%1 M", QString::fromLatin1("%1"));
    static QString gString = i18n("%1 G", QString::fromLatin1("%1"));
    static QString tString = i18n("%1 T", QString::fromLatin1("%1"));
    static QString pString = i18n("%1 P", QString::fromLatin1("%1"));
    double amount;

    if (units == UnitsAuto) {
        if (amountInKB < 1024.0*0.9)
            units = UnitsKB; // amount < 0.9 MiB == KiB
        else if (amountInKB < 1024.0*1024.0*0.9)
            units = UnitsMB; // amount < 0.9 GiB == MiB
        else if (amountInKB < 1024.0*1024.0*1024.0*0.9)
            units = UnitsGB; // amount < 0.9 TiB == GiB
        else if (amountInKB < 1024.0*1024.0*1024.0*1024.0*0.9)
            units = UnitsTB; // amount < 0.9 PiB == TiB
        else
            units = UnitsPB;
    }

    switch(units) {
      case UnitsKB:
Hrvoje Senjan's avatar
Hrvoje Senjan committed
92
        return kString.arg(QLocale().toString(amountInKB));
93
94
      case UnitsMB:
        amount = amountInKB/1024.0;
Hrvoje Senjan's avatar
Hrvoje Senjan committed
95
        return mString.arg(QLocale().toString(amount, 'f', 1));
96
97
98
      case UnitsGB:
        amount = amountInKB/(1024.0*1024.0);
        if(amount < 0.1 && amount > 0.05) amount = 0.1;
Hrvoje Senjan's avatar
Hrvoje Senjan committed
99
        return gString.arg(QLocale().toString(amount, 'f', 1));
100
101
102
      case UnitsTB:
        amount = amountInKB/(1024.0*1024.0*1024.0);
        if(amount < 0.1 && amount > 0.05) amount = 0.1;
Hrvoje Senjan's avatar
Hrvoje Senjan committed
103
        return tString.arg(QLocale().toString(amount, 'f', 1));
104
105
106
      case UnitsPB:
        amount = amountInKB/(1024.0*1024.0*1024.0*1024.0);
        if(amount < 0.1 && amount > 0.05) amount = 0.1;
Hrvoje Senjan's avatar
Hrvoje Senjan committed
107
        return pString.arg(QLocale().toString(amount, 'f', 1));
108
      default:
109
          return QLatin1String("");  // error
110
111
112
    }
}

113
114
ProcessModelPrivate::ProcessModelPrivate() :  mBlankPixmap(HEADING_X_ICON_SIZE,1)
{
John Tapsell's avatar
John Tapsell committed
115
116
117
118
119
    mBlankPixmap.fill(QColor(0,0,0,0));
    mSimple = true;
    mIsLocalhost = true;
    mMemTotal = -1;
    mNumProcessorCores = 1;
120
    mProcesses = nullptr;
John Tapsell's avatar
John Tapsell committed
121
122
123
124
    mShowChildTotals = true;
    mShowCommandLineOptions = false;
    mShowingTooltips = true;
    mNormalizeCPUUsage = true;
John Tapsell's avatar
John Tapsell committed
125
    mIoInformation = ProcessModel::ActualBytes;
126
127
128
#ifdef HAVE_XRES
    mHaveXRes = false;
#endif
129
130
    mHaveTimer = false,
    mTimerId = -1,
131
132
133
    mMovingRow = false;
    mRemovingRow = false;
    mInsertingRow = false;
134
135
136
137
138
#if HAVE_X11
    mIsX11 = QX11Info::isPlatformX11();
#else
    mIsX11 = false;
#endif
139
140
}

John Tapsell's avatar
   
John Tapsell committed
141
142
ProcessModelPrivate::~ProcessModelPrivate()
{
143
#if HAVE_X11
144
    qDeleteAll(mPidToWindowInfo);
145
#endif
146
    delete mProcesses;
147
    mProcesses = nullptr;
John Tapsell's avatar
   
John Tapsell committed
148
}
149

John Tapsell's avatar
John Tapsell committed
150
ProcessModel::ProcessModel(QObject* parent, const QString &host)
John Tapsell's avatar
John Tapsell committed
151
    : QAbstractItemModel(parent), d(new ProcessModelPrivate)
152
{
John Tapsell's avatar
John Tapsell committed
153
    d->q=this;
154
#ifdef HAVE_XRES
155
156
157
158
    if (d->mIsX11) {
        int event, error, major, minor;
        d->mHaveXRes = XResQueryExtension(QX11Info::display(), &event, &error) && XResQueryVersion(QX11Info::display(), &major, &minor);
    }
159
160
#endif

161
    if(host.isEmpty() || host == QLatin1String("localhost")) {
John Tapsell's avatar
John Tapsell committed
162
163
164
165
166
167
168
169
        d->mHostName = QString();
        d->mIsLocalhost = true;
    } else {
        d->mHostName = host;
        d->mIsLocalhost = false;
    }
    setupHeader();
    d->setupProcesses();
170
#if HAVE_X11
John Tapsell's avatar
John Tapsell committed
171
    d->setupWindows();
172
#endif
John Tapsell's avatar
John Tapsell committed
173
    d->mUnits = UnitsKB;
174
    d->mIoUnits = UnitsKB;
175
176
}

177
178
bool ProcessModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
John Tapsell's avatar
John Tapsell committed
179
    //Because we need to sort Descendingly by default for most of the headings, we often return left > right
180
181
    KSysGuard::Process *processLeft = reinterpret_cast< KSysGuard::Process * > (left.internalPointer());
    KSysGuard::Process *processRight = reinterpret_cast< KSysGuard::Process * > (right.internalPointer());
John Tapsell's avatar
John Tapsell committed
182
183
    Q_ASSERT(processLeft);
    Q_ASSERT(processRight);
184
185
186
187
188
189
190
191
192
193
194
195
196
    Q_ASSERT(left.column() == right.column());
    switch(left.column()) {
        case HeadingUser:
        {
            /* Sorting by user will be the default and the most common.
               We want to sort in the most useful way that we can. We need to return a number though.
               This code is based on that sorting ascendingly should put the current user at the top
               First the user we are running as should be at the top.
               Then any other users in the system.
               Then at the bottom the 'system' processes.
               We then sort by cpu usage to sort by that, then finally sort by memory usage */

            /* First, place traced processes at the very top, ignoring any other sorting criteria */
197
            if(processLeft->tracerpid() >= 0)
198
                return true;
199
            if(processRight->tracerpid() >= 0)
200
201
202
                return false;

            /* Sort by username.  First group into own user, normal users, system users */
203
            if(processLeft->uid() != processRight->uid()) {
204
205
206
                //We primarily sort by username
                if(d->mIsLocalhost) {
                    int ownUid = getuid();
207
                    if(processLeft->uid() == ownUid)
208
                        return true; //Left is our user, right is not.  So left is above right
209
                    if(processRight->uid() == ownUid)
210
211
                        return false; //Left is not our user, right is.  So right is above left
                }
212
213
                bool isLeftSystemUser = processLeft->uid() < 100 || !canUserLogin(processLeft->uid());
                bool isRightSystemUser = processRight->uid() < 100 || !canUserLogin(processRight->uid());
214
215
216
217
218
219
                if(isLeftSystemUser && !isRightSystemUser)
                    return false; //System users are less than non-system users
                if(!isLeftSystemUser && isRightSystemUser)
                    return true;
                //They are either both system users, or both non-system users.
                //So now sort by username
220
                return d->getUsernameForUser(processLeft->uid(), false) < d->getUsernameForUser(processRight->uid(), false);
221
222
223
224
            }

            /* 2nd sort order - Graphics Windows */
            //Both columns have the same user.  Place processes with windows at the top
Gregor Mi's avatar
Gregor Mi committed
225
            if(processLeft->hasManagedGuiWindow() && !processRight->hasManagedGuiWindow())
John Tapsell's avatar
John Tapsell committed
226
                return true;
Gregor Mi's avatar
Gregor Mi committed
227
            if(!processLeft->hasManagedGuiWindow() && processRight->hasManagedGuiWindow())
228
229
230
231
232
                return false;

            /* 3rd sort order - CPU Usage */
            int leftCpu, rightCpu;
            if(d->mSimple || !d->mShowChildTotals) {
Gregor Mi's avatar
Gregor Mi committed
233
234
                leftCpu = processLeft->userUsage() + processLeft->sysUsage();
                rightCpu = processRight->userUsage() + processRight->sysUsage();
235
            } else {
Gregor Mi's avatar
Gregor Mi committed
236
237
                leftCpu = processLeft->totalUserUsage() + processLeft->totalSysUsage();
                rightCpu = processRight->totalUserUsage() + processRight->totalSysUsage();
238
239
240
241
242
            }
            if(leftCpu != rightCpu)
                return leftCpu > rightCpu;

            /* 4th sort order - Memory Usage */
Gregor Mi's avatar
Gregor Mi committed
243
244
            qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmURSS() : processLeft->vmRSS();
            qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmURSS() : processRight->vmRSS();
245
246
247
248
249
            return memoryLeft > memoryRight;
        }
        case HeadingCPUUsage: {
            int leftCpu, rightCpu;
            if(d->mSimple || !d->mShowChildTotals) {
Gregor Mi's avatar
Gregor Mi committed
250
251
                leftCpu = processLeft->userUsage() + processLeft->sysUsage();
                rightCpu = processRight->userUsage() + processRight->sysUsage();
252
            } else {
Gregor Mi's avatar
Gregor Mi committed
253
254
                leftCpu = processLeft->totalUserUsage() + processLeft->totalSysUsage();
                rightCpu = processRight->totalUserUsage() + processRight->totalSysUsage();
255
256
257
            }
            return leftCpu > rightCpu;
         }
258
        case HeadingCPUTime: {
259
            return (processLeft->userTime() + processLeft->sysTime()) > (processRight->userTime() + processRight->sysTime());
260
        }
261
        case HeadingMemory: {
Gregor Mi's avatar
Gregor Mi committed
262
263
            qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmURSS() : processLeft->vmRSS();
            qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmURSS() : processRight->vmRSS();
264
265
            return memoryLeft > memoryRight;
        }
266
267
268
        case HeadingVmPSS: {
            return processLeft->vmPSS() > processRight->vmPSS();
        }
269
        case HeadingStartTime: {
270
            return processLeft->startTime() > processRight->startTime();
271
        }
Topi Miettinen's avatar
Topi Miettinen committed
272
273
        case HeadingNoNewPrivileges:
            return processLeft->noNewPrivileges() > processRight->noNewPrivileges();
274
        case HeadingXMemory:
Gregor Mi's avatar
Gregor Mi committed
275
            return processLeft->pixmapBytes() > processRight->pixmapBytes();
276
        case HeadingVmSize:
Gregor Mi's avatar
Gregor Mi committed
277
            return processLeft->vmSize() > processRight->vmSize();
278
        case HeadingSharedMemory: {
Gregor Mi's avatar
Gregor Mi committed
279
280
            qlonglong memoryLeft = (processLeft->vmURSS() != -1) ? processLeft->vmRSS() - processLeft->vmURSS() : 0;
            qlonglong memoryRight = (processRight->vmURSS() != -1) ? processRight->vmRSS() - processRight->vmURSS() : 0;
281
282
            return memoryLeft > memoryRight;
        }
John Tapsell's avatar
John Tapsell committed
283
        case HeadingPid:
Gregor Mi's avatar
Gregor Mi committed
284
            return processLeft->pid() > processRight->pid();
285
        case HeadingNiceness:
286
            //Sort by scheduler first
Gregor Mi's avatar
Gregor Mi committed
287
288
            if(processLeft->scheduler() != processRight->scheduler()) {
                if(processLeft->scheduler() == KSysGuard::Process::RoundRobin || processLeft->scheduler() == KSysGuard::Process::Fifo)
289
                    return true;
Gregor Mi's avatar
Gregor Mi committed
290
                if(processRight->scheduler() == KSysGuard::Process::RoundRobin || processRight->scheduler() == KSysGuard::Process::Fifo)
291
                    return false;
Gregor Mi's avatar
Gregor Mi committed
292
                if(processLeft->scheduler() == KSysGuard::Process::Other)
293
                    return true;
Gregor Mi's avatar
Gregor Mi committed
294
                if(processRight->scheduler() == KSysGuard::Process::Other)
295
                    return false;
Gregor Mi's avatar
Gregor Mi committed
296
                if(processLeft->scheduler() == KSysGuard::Process::Batch)
297
298
                    return true;
            }
Gregor Mi's avatar
Gregor Mi committed
299
            if(processLeft->niceLevel() == processRight->niceLevel())
Gregor Mi's avatar
Gregor Mi committed
300
                return processLeft->pid() < processRight->pid(); //Subsort by pid if the niceLevel is the same
Gregor Mi's avatar
Gregor Mi committed
301
            return processLeft->niceLevel() < processRight->niceLevel();
302
        case HeadingTty: {
303
            if(processLeft->tty() == processRight->tty())
Gregor Mi's avatar
Gregor Mi committed
304
                return processLeft->pid() < processRight->pid(); //Both ttys are the same.  Sort by pid
305
            if(processLeft->tty().isEmpty())
306
                return false; //Only left is empty (since they aren't the same)
307
            else if(processRight->tty().isEmpty())
308
309
310
311
                return true; //Only right is empty

            //Neither left or right is empty. The tty string is like  "tty10"  so split this into "tty" and "10"
            //and sort by the string first, then sort by the number
312
            QRegExp regexpLeft(QStringLiteral("^(\\D*)(\\d*)$"));
313
            QRegExp regexpRight(regexpLeft);
314
            if(regexpLeft.indexIn(QString::fromUtf8(processLeft->tty())) == -1 || regexpRight.indexIn(QString::fromUtf8(processRight->tty())) == -1)
315
                return processLeft->tty() < processRight->tty();
316
317
318
319
320
321
322
            int nameMatch = regexpLeft.cap(1).compare(regexpRight.cap(1));
            if(nameMatch < 0)
                return true;
            if(nameMatch > 0)
                return false;
            return regexpLeft.cap(2).toInt() < regexpRight.cap(2).toInt();
        }
323
324
325
        case HeadingIoRead:
            switch(d->mIoInformation) {
                case ProcessModel::Bytes:
Gregor Mi's avatar
Gregor Mi committed
326
                    return processLeft->ioCharactersRead() > processRight->ioCharactersRead();
327
                case ProcessModel::Syscalls:
Gregor Mi's avatar
Gregor Mi committed
328
                    return processLeft->ioReadSyscalls() > processRight->ioReadSyscalls();
329
                case ProcessModel::ActualBytes:
Gregor Mi's avatar
Gregor Mi committed
330
                    return processLeft->ioCharactersActuallyRead() > processRight->ioCharactersActuallyRead();
331
                case ProcessModel::BytesRate:
Gregor Mi's avatar
Gregor Mi committed
332
                    return processLeft->ioCharactersReadRate() > processRight->ioCharactersReadRate();
333
                case ProcessModel::SyscallsRate:
Gregor Mi's avatar
Gregor Mi committed
334
                    return processLeft->ioReadSyscallsRate() > processRight->ioReadSyscallsRate();
335
                case ProcessModel::ActualBytesRate:
Gregor Mi's avatar
Gregor Mi committed
336
                    return processLeft->ioCharactersActuallyReadRate() > processRight->ioCharactersActuallyReadRate();
337
338

            }
339
            return {}; // It actually never gets here since all cases are handled in the switch, but makes gcc not complain about a possible fall through
340
341
342
        case HeadingIoWrite:
            switch(d->mIoInformation) {
                case ProcessModel::Bytes:
Gregor Mi's avatar
Gregor Mi committed
343
                    return processLeft->ioCharactersWritten() > processRight->ioCharactersWritten();
344
                case ProcessModel::Syscalls:
Gregor Mi's avatar
Gregor Mi committed
345
                    return processLeft->ioWriteSyscalls() > processRight->ioWriteSyscalls();
346
                case ProcessModel::ActualBytes:
Gregor Mi's avatar
Gregor Mi committed
347
                    return processLeft->ioCharactersActuallyWritten() > processRight->ioCharactersActuallyWritten();
348
                case ProcessModel::BytesRate:
Gregor Mi's avatar
Gregor Mi committed
349
                    return processLeft->ioCharactersWrittenRate() > processRight->ioCharactersWrittenRate();
350
                case ProcessModel::SyscallsRate:
Gregor Mi's avatar
Gregor Mi committed
351
                    return processLeft->ioWriteSyscallsRate() > processRight->ioWriteSyscallsRate();
352
                case ProcessModel::ActualBytesRate:
Gregor Mi's avatar
Gregor Mi committed
353
                    return processLeft->ioCharactersActuallyWrittenRate() > processRight->ioCharactersActuallyWrittenRate();
354
355
356
        }
    }
    //Sort by the display string if we do not have an explicit sorting here
357
358
359
360
361

    if (data(left, ProcessModel::PlainValueRole).toInt() == data(right, ProcessModel::PlainValueRole).toInt()) {
        return data(left, Qt::DisplayRole).toString() < data(right, Qt::DisplayRole).toString();
    }
    return data(left, ProcessModel::PlainValueRole).toInt() < data(right, ProcessModel::PlainValueRole).toInt();
362
363
}

364
365
366
367
368
ProcessModel::~ProcessModel()
{
    delete d;
}

369
370
KSysGuard::Processes *ProcessModel::processController() const
{
371
    return d->mProcesses;
372
}
373

374
375
376
377
378
const QVector<KSysGuard::ProcessAttribute *> ProcessModel::extraAttributes() const
{
    return d->mExtraAttributes;
}

379
#if HAVE_X11
380
void ProcessModelPrivate::windowRemoved(WId wid) {
381
382
383
    WindowInfo *window = mWIdToWindowInfo.take(wid);
    if(!window) return;
    qlonglong pid = window->pid;
John Tapsell's avatar
John Tapsell committed
384

385
    QMultiHash<qlonglong, WindowInfo*>::iterator i = mPidToWindowInfo.find(pid);
John Tapsell's avatar
John Tapsell committed
386
    while (i != mPidToWindowInfo.end() && i.key() == pid) {
387
        if(i.value()->wid == wid) {
John Tapsell's avatar
John Tapsell committed
388
            i = mPidToWindowInfo.erase(i);
389
            break;
John Tapsell's avatar
John Tapsell committed
390
391
392
        } else
            i++;
    }
393
    delete window;
394
395

    //Update the model so that it redraws and resorts
John Tapsell's avatar
John Tapsell committed
396
397
398
399
400
    KSysGuard::Process *process = mProcesses->getProcess(pid);
    if(!process) return;

    int row;
    if(mSimple)
401
        row = process->index();
John Tapsell's avatar
John Tapsell committed
402
    else
403
        row = process->parent()->children().indexOf(process);
John Tapsell's avatar
John Tapsell committed
404
405
    QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process);
    emit q->dataChanged(index2, index2);
406
}
407
#endif
408

409
#if HAVE_X11
410
void ProcessModelPrivate::setupWindows() {
411
412
413
    if (!mIsX11) {
        return;
    }
Laurent Montel's avatar
Laurent Montel committed
414
    connect( KWindowSystem::self(), SIGNAL(windowChanged(WId,uint)), this, SLOT(windowChanged(WId,uint)));
415
416
    connect( KWindowSystem::self(), &KWindowSystem::windowAdded, this, &ProcessModelPrivate::windowAdded);
    connect( KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &ProcessModelPrivate::windowRemoved);
John Tapsell's avatar
John Tapsell committed
417
418

    //Add all the windows that KWin is managing - i.e. windows that the user can see
419
420
    const QList<WId> windows = KWindowSystem::windows();
    for (auto it = windows.begin(); it != windows.end(); ++it ) {
John Tapsell's avatar
John Tapsell committed
421
422
        updateWindowInfo(*it, ~0u, true);
    }
John Tapsell's avatar
John Tapsell committed
423
}
424
#endif
425

John Tapsell's avatar
John Tapsell committed
426
#ifdef HAVE_XRES
427
bool ProcessModelPrivate::updateXResClientData() {
428
429
430
    if (!mIsX11) {
        return false;
    }
431
432
433
434
435
    XResClient *clients;
    int count;

    XResQueryClients(QX11Info::display(), &count, &clients);

436
    mXResClientResources.clear();
437
438
439
440
441
442
443
    for (int i=0; i < count; i++)
        mXResClientResources.insert(-(qlonglong)(clients[i].resource_base), clients[i].resource_mask);

    if(clients)
        XFree(clients);
    return true;
}
444

John Tapsell's avatar
John Tapsell committed
445
void ProcessModelPrivate::queryForAndUpdateAllXWindows() {
446
447
448
    if (!mIsX11) {
        return;
    }
449
    updateXResClientData();
John Tapsell's avatar
John Tapsell committed
450
451
452
    Window       *children, dummy;
    unsigned int  count;
    Status result = XQueryTree(QX11Info::display(), QX11Info::appRootWindow(), &dummy, &dummy, &children, &count);
John Tapsell's avatar
John Tapsell committed
453
454
    if(!result)
        return;
455
456
    if(!updateXResClientData())
        return;
John Tapsell's avatar
John Tapsell committed
457
458
    for (uint i=0; i < count; ++i) {
        WId wid = children[i];
459
460
        QMap<qlonglong, XID>::iterator iter = mXResClientResources.lowerBound(-(qlonglong)(wid));
        if(iter == mXResClientResources.end())
461
462
            continue; //We couldn't find it this time :-/

463
        if(-iter.key() != (qlonglong)(wid & ~iter.value()))
464
            continue; //Already added this window
John Tapsell's avatar
John Tapsell committed
465
466

        //Get the PID for this window if we do not know it
467
        NETWinInfo info(QX11Info::connection(), wid, QX11Info::appRootWindow(), NET::WMPid, NET::Properties2());
John Tapsell's avatar
John Tapsell committed
468
469

        qlonglong pid = info.pid();
470
        if(!pid)
John Tapsell's avatar
John Tapsell committed
471
            continue;
472
        //We found a window with this client
473
        mXResClientResources.erase(iter);
John Tapsell's avatar
John Tapsell committed
474
475
        KSysGuard::Process *process = mProcesses->getProcess(pid);
        if(!process) return; //shouldn't really happen.. maybe race condition etc
Gregor Mi's avatar
Gregor Mi committed
476
        unsigned long previousPixmapBytes = process->pixmapBytes();
John Tapsell's avatar
John Tapsell committed
477
        //Now update the pixmap bytes for this window
Gregor Mi's avatar
Gregor Mi committed
478
        bool success = XResQueryClientPixmapBytes(QX11Info::display(), wid, &process->pixmapBytes());
John Tapsell's avatar
John Tapsell committed
479
        if(!success)
Gregor Mi's avatar
Gregor Mi committed
480
            process->pixmapBytes() = 0;
John Tapsell's avatar
John Tapsell committed
481

Gregor Mi's avatar
Gregor Mi committed
482
        if(previousPixmapBytes != process->pixmapBytes()) {
John Tapsell's avatar
John Tapsell committed
483
484
            int row;
            if(mSimple)
485
                row = process->index();
John Tapsell's avatar
John Tapsell committed
486
            else
487
                row = process->parent()->children().indexOf(process);
John Tapsell's avatar
John Tapsell committed
488
489
            QModelIndex index = q->createIndex(row, ProcessModel::HeadingXMemory, process);
            emit q->dataChanged(index, index);
John Tapsell's avatar
John Tapsell committed
490
        }
John Tapsell's avatar
John Tapsell committed
491
    }
John Tapsell's avatar
John Tapsell committed
492
493
    if(children)
        XFree((char*)children);
494
}
John Tapsell's avatar
John Tapsell committed
495
#endif
496

497
void ProcessModelPrivate::setupProcesses() {
John Tapsell's avatar
John Tapsell committed
498
    if(mProcesses) {
499
#ifdef Q_WS_X11_DISABLE
500
        mWIdToWindowInfo.clear();
John Tapsell's avatar
John Tapsell committed
501
        mPidToWindowInfo.clear();
502
#endif
503
        delete mProcesses;
504
        mProcesses = nullptr;
Hrvoje Senjan's avatar
Hrvoje Senjan committed
505
506
        q->beginResetModel();
        q->endResetModel();
John Tapsell's avatar
John Tapsell committed
507
508
    }

509
    mProcesses = new KSysGuard::ExtendedProcesses();
John Tapsell's avatar
John Tapsell committed
510

511
512
513
514
515
516
517
518
    connect( mProcesses, &KSysGuard::Processes::processChanged, this, &ProcessModelPrivate::processChanged);
    connect( mProcesses, &KSysGuard::Processes::beginAddProcess, this, &ProcessModelPrivate::beginInsertRow);
    connect( mProcesses, &KSysGuard::Processes::endAddProcess, this, &ProcessModelPrivate::endInsertRow);
    connect( mProcesses, &KSysGuard::Processes::beginRemoveProcess, this, &ProcessModelPrivate::beginRemoveRow);
    connect( mProcesses, &KSysGuard::Processes::endRemoveProcess, this, &ProcessModelPrivate::endRemoveRow);
    connect( mProcesses, &KSysGuard::Processes::beginMoveProcess, this,
            &ProcessModelPrivate::beginMoveProcess);
    connect( mProcesses, &KSysGuard::Processes::endMoveProcess, this, &ProcessModelPrivate::endMoveRow);
John Tapsell's avatar
John Tapsell committed
519
520
    mNumProcessorCores = mProcesses->numberProcessorCores();
    if(mNumProcessorCores < 1) mNumProcessorCores=1;  //Default to 1 if there was an error getting the number
521
522
523
524
525
526
527
528

    mExtraAttributes = mProcesses->attributes();
    for (int i = 0 ; i < mExtraAttributes.count(); i ++) {
        connect(mExtraAttributes[i], &KSysGuard::ProcessAttribute::dataChanged, this, [this, i](KSysGuard::Process *process) {
            const QModelIndex index = q->getQModelIndex(process, mHeadings.count() + i);
            emit q->dataChanged(index, index);
        });
    }
529
530
}

531
#if HAVE_X11
532
533
void ProcessModelPrivate::windowChanged(WId wid, unsigned int properties)
{
John Tapsell's avatar
John Tapsell committed
534
535
536
537
538
539
540
541
542
543
    updateWindowInfo(wid, properties, false);
}

void ProcessModelPrivate::windowAdded(WId wid)
{
    updateWindowInfo(wid, ~0u, true);
}

void ProcessModelPrivate::updateWindowInfo(WId wid, unsigned int properties, bool newWindow)
{
544
545
546
    if (!mIsX11) {
        return;
    }
John Tapsell's avatar
John Tapsell committed
547
    properties &= (NET::WMPid | NET::WMVisibleName | NET::WMName | NET::WMIcon);
548

549
550
551
552
    if(!properties)
        return; //Nothing interesting changed

    WindowInfo *w = mWIdToWindowInfo.value(wid);
Yunhe Guo's avatar
Yunhe Guo committed
553
554
    const qreal dpr = qApp->devicePixelRatio();

555
556
    if(!w && !newWindow)
        return; //We do not have a record of this window and this is not a new window
557

John Tapsell's avatar
John Tapsell committed
558
    if(properties == NET::WMIcon) {
Yunhe Guo's avatar
Yunhe Guo committed
559
560
561
562
        if(w) {
            w->icon = KWindowSystem::icon(wid, HEADING_X_ICON_SIZE * dpr, HEADING_X_ICON_SIZE * dpr, true);
            w->icon.setDevicePixelRatio(dpr);
        }
John Tapsell's avatar
John Tapsell committed
563
564
565
        return;
    }
    /* Get PID for window */
566
    NETWinInfo info(QX11Info::connection(), wid, QX11Info::appRootWindow(), NET::Properties(properties) & ~NET::WMIcon, NET::Properties2());
John Tapsell's avatar
John Tapsell committed
567

568
    if(!w) {
John Tapsell's avatar
John Tapsell committed
569
        //We know that this must be a newWindow
570
571
572
        qlonglong pid = info.pid();
        if(!(properties & NET::WMPid && pid))
            return; //No PID for the window - this happens if the process did not set _NET_WM_PID
John Tapsell's avatar
John Tapsell committed
573
574
575
576
577
578

        //If we are to get the PID only, we are only interested in the XRes info for this,
        //so don't bother if we already have this info
        if(properties == NET::WMPid && mPidToWindowInfo.contains(pid))
            return;

579
580
581
582
        w = new WindowInfo(wid, pid);
        mPidToWindowInfo.insertMulti(pid, w);
        mWIdToWindowInfo.insert(wid, w);
    }
John Tapsell's avatar
John Tapsell committed
583

Yunhe Guo's avatar
Yunhe Guo committed
584
585
586
587
    if(w && (properties & NET::WMIcon)) {
        w->icon = KWindowSystem::icon(wid, HEADING_X_ICON_SIZE * dpr, HEADING_X_ICON_SIZE * dpr, true);
        w->icon.setDevicePixelRatio(dpr);
    }
588
589
590
591
592
593
594
595
    if(properties & NET::WMVisibleName && info.visibleName())
        w->name = QString::fromUtf8(info.visibleName());
    else if(properties & NET::WMName)
        w->name = QString::fromUtf8(info.name());
    else if(properties & (NET::WMName | NET::WMVisibleName))
        w->name.clear();

    KSysGuard::Process *process = mProcesses->getProcess(w->pid);
596
    if(!process) {
597
        return; //This happens when a new window is detected before we've read in the process
598
    }
John Tapsell's avatar
John Tapsell committed
599

John Tapsell's avatar
John Tapsell committed
600
601
    int row;
    if(mSimple)
602
        row = process->index();
John Tapsell's avatar
John Tapsell committed
603
    else
604
        row = process->parent()->children().indexOf(process);
Gregor Mi's avatar
Gregor Mi committed
605
606
    if(!process->hasManagedGuiWindow()) {
        process->hasManagedGuiWindow() = true;
607
608
609
610
611
        //Since this is the first window for a process, invalidate HeadingName so that
        //if we are sorting by name this gets taken into account
        QModelIndex index1 = q->createIndex(row, ProcessModel::HeadingName, process);
        emit q->dataChanged(index1, index1);
    }
John Tapsell's avatar
John Tapsell committed
612
613
    QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingXTitle, process);
    emit q->dataChanged(index2, index2);
614
}
615
616
#endif

617
void ProcessModel::update(long updateDurationMSecs, KSysGuard::Processes::UpdateFlags updateFlags) {
618
619
620
621
622
    if(updateFlags != KSysGuard::Processes::XMemory) {
        d->mProcesses->updateAllProcesses(updateDurationMSecs, updateFlags);
        if(d->mMemTotal <= 0)
            d->mMemTotal = d->mProcesses->totalPhysicalMemory();
    }
John Tapsell's avatar
John Tapsell committed
623
624
625
626
627
628

#ifdef HAVE_XRES
    //Add all the rest of the windows
    if(d->mHaveXRes && updateFlags.testFlag(KSysGuard::Processes::XMemory))
        d->queryForAndUpdateAllXWindows();
#endif
629
630
631
632
}

QString ProcessModelPrivate::getStatusDescription(KSysGuard::Process::ProcessStatus status) const
{
John Tapsell's avatar
John Tapsell committed
633
634
635
636
637
638
639
640
641
    switch( status) {
        case KSysGuard::Process::Running:
            return i18n("- Process is doing some work.");
        case KSysGuard::Process::Sleeping:
            return i18n("- Process is waiting for something to happen.");
        case KSysGuard::Process::Stopped:
            return i18n("- Process has been stopped. It will not respond to user input at the moment.");
        case KSysGuard::Process::Zombie:
            return i18n("- Process has finished and is now dead, but the parent process has not cleaned up.");
642
643
        case KSysGuard::Process::Ended:
//            return i18n("- Process has finished and no longer exists.");
John Tapsell's avatar
John Tapsell committed
644
645
646
        default:
            return QString();
    }
647
648
649
650
}

KSysGuard::Process *ProcessModel::getProcessAtIndex(int index) const
{
John Tapsell's avatar
John Tapsell committed
651
652
    Q_ASSERT(d->mSimple);
    return d->mProcesses->getAllProcesses().at(index);
653
}
654

655
656
int ProcessModel::rowCount(const QModelIndex &parent) const
{
John Tapsell's avatar
John Tapsell committed
657
658
    if(d->mSimple) {
        if(parent.isValid()) return 0; //In flat mode, none of the processes have children
659
        return d->mProcesses->processCount();
John Tapsell's avatar
John Tapsell committed
660
661
662
663
664
665
666
667
    }

    //Deal with the case that we are showing it as a tree
    KSysGuard::Process *process;
    if(parent.isValid()) {
        if(parent.column() != 0) return 0;  //For a treeview we say that only the first column has children
        process = reinterpret_cast< KSysGuard::Process * > (parent.internalPointer()); //when parent is invalid, it must be the root level which we set as 0
    } else {
668
        process = d->mProcesses->getProcess(-1);
John Tapsell's avatar
John Tapsell committed
669
670
    }
    Q_ASSERT(process);
671
    int num_rows = process->children().count();
John Tapsell's avatar
John Tapsell committed
672
    return num_rows;
673
674
675
676
}

int ProcessModel::columnCount ( const QModelIndex & ) const
{
677
    return d->mHeadings.count() + d->mExtraAttributes.count();
678
679
680
681
682
}

bool ProcessModel::hasChildren ( const QModelIndex & parent = QModelIndex() ) const
{

John Tapsell's avatar
John Tapsell committed
683
684
685
686
687
688
689
690
691
692
693
    if(d->mSimple) {
        if(parent.isValid()) return 0; //In flat mode, none of the processes have children
        return !d->mProcesses->getAllProcesses().isEmpty();
    }

    //Deal with the case that we are showing it as a tree
    KSysGuard::Process *process;
    if(parent.isValid()) {
        if(parent.column() != 0) return false;  //For a treeview we say that only the first column has children
        process = reinterpret_cast< KSysGuard::Process * > (parent.internalPointer()); //when parent is invalid, it must be the root level which we set as 0
    } else {
694
        process = d->mProcesses->getProcess(-1);
John Tapsell's avatar
John Tapsell committed
695
696
    }
    Q_ASSERT(process);
697
    bool has_children = !process->children().isEmpty();
John Tapsell's avatar
John Tapsell committed
698
699
700

    Q_ASSERT((rowCount(parent) > 0) == has_children);
    return has_children;
701
702
703
704
}

QModelIndex ProcessModel::index ( int row, int column, const QModelIndex & parent ) const
{
John Tapsell's avatar
John Tapsell committed
705
    if(row<0) return QModelIndex();
706
    if(column<0 || column >= columnCount() ) return QModelIndex();
John Tapsell's avatar
John Tapsell committed
707
708
709

    if(d->mSimple) {
        if( parent.isValid()) return QModelIndex();
710
        if( d->mProcesses->processCount() <= row) return QModelIndex();
John Tapsell's avatar
John Tapsell committed
711
712
713
714
        return createIndex( row, column, d->mProcesses->getAllProcesses().at(row));
    }

    //Deal with the case that we are showing it as a tree
715
    KSysGuard::Process *parent_process = nullptr;
John Tapsell's avatar
John Tapsell committed
716

717
    if(parent.isValid()) //not valid for init or children without parents, so use our special item with pid of 0
John Tapsell's avatar
John Tapsell committed
718
719
        parent_process = reinterpret_cast< KSysGuard::Process * > (parent.internalPointer());
    else
720
        parent_process = d->mProcesses->getProcess(-1);
John Tapsell's avatar
John Tapsell committed
721
722
    Q_ASSERT(parent_process);

723
724
    if(parent_process->children().count() > row)
        return createIndex(row,column, parent_process->children()[row]);
John Tapsell's avatar
John Tapsell committed
725
726
727
728
    else
    {
        return QModelIndex();
    }
729
730
731
732
733
734
735
}

bool ProcessModel::isSimpleMode() const
{
    return d->mSimple;
}

736
void ProcessModelPrivate::processChanged(KSysGuard::Process *process, bool onlyTotalCpu)
737
{
John Tapsell's avatar
John Tapsell committed
738
739
    int row;
    if(mSimple)
740
        row = process->index();
John Tapsell's avatar
John Tapsell committed
741
    else
742
        row = process->parent()->children().indexOf(process);
John Tapsell's avatar
John Tapsell committed
743

744
    if (process->timeKillWasSent().isValid()) {
745
        int elapsed = process->timeKillWasSent().elapsed();
746
        if (elapsed < MILLISECONDS_TO_SHOW_RED_FOR_KILLED_PROCESS) {
Gregor Mi's avatar
Gregor Mi committed
747
748
            if (!mPidsToUpdate.contains(process->pid()))
                mPidsToUpdate.append(process->pid());
749
750
751
752
753
754
755
756
757
            QModelIndex index1 = q->createIndex(row, 0, process);
            QModelIndex index2 = q->createIndex(row, mHeadings.count()-1, process);
            emit q->dataChanged(index1, index2);
            if (!mHaveTimer) {
                mHaveTimer = true;
                mTimerId = startTimer(100);
            }
        }
    }
John Tapsell's avatar
John Tapsell committed
758
759
760
761
762
763
764
765
766
767
    int totalUpdated = 0;
    Q_ASSERT(row != -1);  //Something has gone very wrong
    if(onlyTotalCpu) {
        if(mShowChildTotals) {
            //Only the total cpu usage changed, so only update that
            QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process);
            emit q->dataChanged(index, index);
        }
        return;
    } else {
768
        if(process->changes() == KSysGuard::Process::Nothing) {
John Tapsell's avatar
John Tapsell committed
769
770
            return; //Nothing changed
        }
771
        if(process->changes() & KSysGuard::Process::Uids) {
John Tapsell's avatar
John Tapsell committed
772
773
774
775
            totalUpdated++;
            QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process);
            emit q->dataChanged(index, index);
        }
776
        if(process->changes() & KSysGuard::Process::Tty) {
John Tapsell's avatar
John Tapsell committed
777
778
779
780
            totalUpdated++;
            QModelIndex index = q->createIndex(row, ProcessModel::HeadingTty, process);
            emit q->dataChanged(index, index);
        }
781
        if(process->changes() & (KSysGuard::Process::Usage | KSysGuard::Process::Status) || (process->changes() & KSysGuard::Process::TotalUsage && mShowChildTotals)) {
John Tapsell's avatar
John Tapsell committed
782
            totalUpdated+=2;
John Tapsell's avatar
John Tapsell committed
783
784
            QModelIndex index = q->createIndex(row, ProcessModel::HeadingCPUUsage, process);
            emit q->dataChanged(index, index);
785
786
            index = q->createIndex(row, ProcessModel::HeadingCPUTime, process);
            emit q->dataChanged(index, index);
John Tapsell's avatar
John Tapsell committed
787
788
789
            //Because of our sorting, changing usage needs to also invalidate the User column
            index = q->createIndex(row, ProcessModel::HeadingUser, process);
            emit q->dataChanged(index, index);
John Tapsell's avatar
John Tapsell committed
790
        }
Topi Miettinen's avatar
Topi Miettinen committed
791
        if(process->changes() & KSysGuard::Process::Status) {
792
            totalUpdated+=2;
Topi Miettinen's avatar
Topi Miettinen committed
793
794
            QModelIndex index = q->createIndex(row, ProcessModel::HeadingNoNewPrivileges, process);
            emit q->dataChanged(index, index);
795
796
            index = q->createIndex(row, ProcessModel::HeadingCGroup, process);
            emit q->dataChanged(index, index);
797
798
            index = q->createIndex(row, ProcessModel::HeadingMACContext, process);
            emit q->dataChanged(index, index);
Topi Miettinen's avatar
Topi Miettinen committed
799
        }
800
        if(process->changes() & KSysGuard::Process::NiceLevels) {
John Tapsell's avatar
John Tapsell committed
801
802
803
804
            totalUpdated++;
            QModelIndex index = q->createIndex(row, ProcessModel::HeadingNiceness, process);
            emit q->dataChanged(index, index);
        }
805
        if(process->changes() & KSysGuard::Process::VmSize) {
John Tapsell's avatar
John Tapsell committed
806
807
808
809
            totalUpdated++;
            QModelIndex index = q->createIndex(row, ProcessModel::HeadingVmSize, process);
            emit q->dataChanged(index, index);
        }
810
        if(process->changes() & (KSysGuard::Process::VmSize | KSysGuard::Process::VmRSS | KSysGuard::Process::VmURSS)) {
John Tapsell's avatar
John Tapsell committed
811
812
813
814
815
            totalUpdated+=2;
            QModelIndex index = q->createIndex(row, ProcessModel::HeadingMemory, process);
            emit q->dataChanged(index, index);
            QModelIndex index2 = q->createIndex(row, ProcessModel::HeadingSharedMemory, process);
            emit q->dataChanged(index2, index2);
John Tapsell's avatar
John Tapsell committed
816
817
818
            //Because of our sorting, changing usage needs to also invalidate the User column
            index = q->createIndex(row, ProcessModel::HeadingUser, process);
            emit q->dataChanged(index, index);
John Tapsell's avatar
John Tapsell committed
819
        }
820
821
822
823
824
        if (process->changes() & KSysGuard::Process::VmPSS) {
            totalUpdated++;
            auto index = q->createIndex(row, ProcessModel::HeadingVmPSS, process);
            emit q->dataChanged(index, index);
        }
825
        if(process->changes() & KSysGuard::Process::Name) {
John Tapsell's avatar
John Tapsell committed
826
827
828
829
            totalUpdated++;
            QModelIndex index = q->createIndex(row, ProcessModel::HeadingName, process);
            emit q->dataChanged(index, index);
        }
830
        if(process->changes() & KSysGuard::Process::Command) {
John Tapsell's avatar
John Tapsell committed
831
832
833
834
            totalUpdated++;
            QModelIndex index = q->createIndex(row, ProcessModel::HeadingCommand, process);
            emit q->dataChanged(index, index);
        }
835
        if(process->changes() & KSysGuard::Process::Login) {
John Tapsell's avatar
John Tapsell committed
836
837
838
839
            totalUpdated++;
            QModelIndex index = q->createIndex(row, ProcessModel::HeadingUser, process);
            emit q->dataChanged(index, index);
        }
840
        if(process->changes() & KSysGuard::Process::IO) {
841
842
843
844
845
846
            totalUpdated++;
            QModelIndex index = q->createIndex(row, ProcessModel::HeadingIoRead, process);
            emit q->dataChanged(index, index);
            index = q->createIndex(row, ProcessModel::HeadingIoWrite, process);
            emit q->dataChanged(index, index);
        }
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864

        /* Normally this would only be called if changes() tells
         * us to. We need to update the timestamp even if the value
         * didn't change though. */
        auto historyMapEntry = mMapProcessCPUHistory.find(process);
        if(historyMapEntry != mMapProcessCPUHistory.end()) {
            auto &history = *historyMapEntry;
            unsigned long timestamp = QDateTime::currentMSecsSinceEpoch();
            // Only add an entry if the latest one is older than MIN_HIST_AGE
            if(history.isEmpty() || timestamp - history.constLast().timestamp > MIN_HIST_AGE) {
                if(history.size() == MAX_HIST_ENTRIES) {
                    history.removeFirst();
                }

                float usage = (process->totalUserUsage() + process->totalSysUsage()) / (100.0f * mNumProcessorCores);
                history.push_back({static_cast<unsigned long>(QDateTime::currentMSecsSinceEpoch()), usage});
            }
        }
John Tapsell's avatar
John Tapsell committed
865
    }
866
867
868
869
}

void ProcessModelPrivate::beginInsertRow( KSysGuard::Process *process)
{
John Tapsell's avatar
John Tapsell committed
870
    Q_ASSERT(process);
871
872
873
874
875
    Q_ASSERT(!mRemovingRow);
    Q_ASSERT(!mInsertingRow);
    Q_ASSERT(!mMovingRow);
    mInsertingRow = true;

876
#if HAVE_X11
Gregor Mi's avatar
Gregor Mi committed
877
    process->hasManagedGuiWindow() = mPidToWindowInfo.contains(process->pid());
878
#endif
John Tapsell's avatar
John Tapsell committed
879
    if(mSimple) {
880
        int row = mProcesses->processCount();
John Tapsell's avatar
John Tapsell committed
881
882
883
884
885
        q->beginInsertRows( QModelIndex(), row, row );
        return;
    }

    //Deal with the case that we are showing it as a tree
886
    int row = process->parent()->children().count();
887
    QModelIndex parentModelIndex = q->getQModelIndex(process->parent(), 0);
John Tapsell's avatar
John Tapsell committed
888
889
890

    //Only here can we actually change the model.  First notify the view/proxy models then modify
    q->beginInsertRows(parentModelIndex, row, row);
891
}
892

893
void ProcessModelPrivate::endInsertRow() {
894
895
896
897
898
    Q_ASSERT(!mRemovingRow);
    Q_ASSERT(mInsertingRow);
    Q_ASSERT(!mMovingRow);
    mInsertingRow = false;

John Tapsell's avatar
John Tapsell committed
899
    q->endInsertRows();
900
901
902
}
void ProcessModelPrivate::beginRemoveRow( KSysGuard::Process *process )
{
John Tapsell's avatar
John Tapsell committed
903
    Q_ASSERT(process);
Gregor Mi's avatar
Gregor Mi committed
904
    Q_ASSERT(process->pid() >= 0);
905
906
907
908
    Q_ASSERT(!mRemovingRow);
    Q_ASSERT(!mInsertingRow);
    Q_ASSERT(!mMovingRow);
    mRemovingRow = true;
John Tapsell's avatar
John Tapsell committed
909

910
911
    mMapProcessCPUHistory.remove(process);