diff --git a/lsofui/lsof.h b/lsofui/lsof.h --- a/lsofui/lsof.h +++ b/lsofui/lsof.h @@ -35,6 +35,7 @@ KLsofWidget(QWidget *parent = nullptr); ~KLsofWidget() override; bool update(); + void setPid(qlonglong pid); private Q_SLOTS: /* For QProcess *process */ @@ -44,7 +45,6 @@ //void readyReadStandardOutput (); //void started (); qlonglong pid() const; - void setPid(qlonglong pid); private: KLsofWidgetPrivate* const d; }; diff --git a/processcore/process.h b/processcore/process.h --- a/processcore/process.h +++ b/processcore/process.h @@ -23,6 +23,7 @@ #define PROCESS_H #include +#include #include #include @@ -190,6 +191,23 @@ int noNewPrivileges() const; void setNoNewPrivileges(int number); ///< Linux process flag NoNewPrivileges + qlonglong voluntaryCtxSwitches() const;///< Number of voluntary context switches. 0 if not known + void setVoluntaryCtxSwitches(qlonglong number); + + qlonglong nonvoluntaryCtxSwitches() const;///< Number of nonvoluntary context switches. 0 if not known + void setNonvoluntaryCtxSwitches(qlonglong number); + + qlonglong minFlt() const;///< Number of minor page faults. 0 if not known + void setMinFlt(qlonglong number); + + qlonglong majFlt() const;///< Number of major page faults. 0 if not known + void setMajFlt(qlonglong number); + + QVector>& memmap();///< Reference to memmap. Empty = permission denied, otherwise the first row is a vector of the columns' names. + + bool memmapRequested() const;///< This is not a property of the process but a flag for updating memmap. As getting the memmap is pretty costly, the memmap is only updated when this flag is set to true. + void setMemmapRequested(bool r); + int index() const; ///< Each process has a parent process. Each sibling has a unique number to identify it under that parent. This is that number. void setIndex(int index); @@ -256,6 +274,8 @@ IO = 0x4000, NumThreads = 0x8000, VmPSS = 0x10000, + CtxSwitches = 0x20000, + PageFaults = 0x40000 }; Q_DECLARE_FLAGS(Changes, Change) diff --git a/processcore/process.cpp b/processcore/process.cpp --- a/processcore/process.cpp +++ b/processcore/process.cpp @@ -79,6 +79,12 @@ long ioCharactersActuallyReadRate; long ioCharactersActuallyWrittenRate; int numThreads; + qlonglong voluntaryCtxSwitches; + qlonglong nonvoluntaryCtxSwitches; + qlonglong minFlt; + qlonglong majFlt; + QVector> memmap; + bool memmapr; QList children; QTime timeKillWasSent; int index; @@ -210,6 +216,12 @@ d->elapsedTimeMilliSeconds = 0; d->numThreads = 0; + d->voluntaryCtxSwitches = 0; + d->nonvoluntaryCtxSwitches = 0; + d->minFlt = 0; + d->majFlt = 0; + d->memmap.clear(); + d->memmapr = false; d->changes = Process::Nothing; } @@ -478,6 +490,31 @@ return d->numThreads; } +qlonglong Process::voluntaryCtxSwitches() const +{ + return d->voluntaryCtxSwitches; +} + +qlonglong Process::nonvoluntaryCtxSwitches() const +{ + return d->nonvoluntaryCtxSwitches; +} + +qlonglong Process::minFlt() const +{ + return d->minFlt; +} + +qlonglong Process::majFlt() const +{ + return d->majFlt; +} + +bool Process::memmapRequested() const +{ + return d->memmapr; +} + QList< Process* > & Process::children() const { return d->children; @@ -802,6 +839,38 @@ d->changes |= Process::NumThreads; } +void Process::setVoluntaryCtxSwitches(qlonglong number) { + if(d->voluntaryCtxSwitches == number) return; + d->voluntaryCtxSwitches = number; + d->changes |= Process::CtxSwitches; +} + +void Process::setNonvoluntaryCtxSwitches(qlonglong number) { + if(d->nonvoluntaryCtxSwitches == number) return; + d->nonvoluntaryCtxSwitches = number; + d->changes |= Process::CtxSwitches; +} + +void Process::setMinFlt(qlonglong number) { + if(d->minFlt == number) return; + d->minFlt = number; + d->changes |= Process::PageFaults; +} + +void Process::setMajFlt(qlonglong number) { + if(d->majFlt == number) return; + d->majFlt = number; + d->changes |= Process::PageFaults; +} + +QVector>& Process::memmap() { + return d->memmap; +} + +void Process::setMemmapRequested(bool r) { + d->memmapr = r; +} + void Process::setIndex(int index) { d->index = index; diff --git a/processcore/processes_linux_p.cpp b/processcore/processes_linux_p.cpp --- a/processcore/processes_linux_p.cpp +++ b/processcore/processes_linux_p.cpp @@ -29,6 +29,7 @@ #include #include #include +#include //for sysconf #include @@ -115,6 +116,7 @@ inline bool readProcCGroup(const QString &dir, Process *process); inline bool readProcAttr(const QString &dir, Process *process); inline bool readProcSmaps(const QString &dir, Process *process); + inline bool readProcSMapsDetails(const QString &dir, Process *process); inline bool getNiceness(long pid, Process *process); inline bool getIOStatistics(const QString &dir, Process *process); QFile mFile; @@ -144,6 +146,7 @@ process->setNoNewPrivileges(0); int size; + const int goalFields = 7; int found = 0; //count how many fields we found while( (size = mFile.readLine( mBuffer, sizeof(mBuffer))) > 0) { //-1 indicates an error switch( mBuffer[0]) { @@ -151,10 +154,10 @@ if((unsigned int)size > sizeof("Name:") && qstrncmp(mBuffer, "Name:", sizeof("Name:")-1) == 0) { if(process->command().isEmpty()) process->setName(QString::fromLocal8Bit(mBuffer + sizeof("Name:")-1, size-sizeof("Name:")+1).trimmed()); - if(++found == 6) goto finish; + if(++found == goalFields) goto finish; } else if((unsigned int)size > sizeof("NoNewPrivs:") && qstrncmp(mBuffer, "NoNewPrivs:", sizeof("NoNewPrivs:")-1) == 0) { process->setNoNewPrivileges(atol(mBuffer + sizeof("NoNewPrivs:")-1)); - if(++found == 6) goto finish; + if(++found == goalFields) goto finish; } break; case 'U': @@ -168,7 +171,7 @@ process->setEuid(euid); process->setSuid(suid); process->setFsuid(fsuid); - if(++found == 6) goto finish; + if(++found == goalFields) goto finish; } break; case 'G': @@ -179,7 +182,7 @@ process->setEgid(egid); process->setSgid(sgid); process->setFsgid(fsgid); - if(++found == 6) goto finish; + if(++found == goalFields) goto finish; } break; case 'T': @@ -187,15 +190,27 @@ process->setTracerpid(atol(mBuffer + sizeof("TracerPid:")-1)); if (process->tracerpid() == 0) process->setTracerpid(-1); - if(++found == 6) goto finish; + if(++found == goalFields) goto finish; } else if((unsigned int)size > sizeof("Threads:") && qstrncmp(mBuffer, "Threads:", sizeof("Threads:")-1) == 0) { process->setNumThreads(atol(mBuffer + sizeof("Threads:")-1)); - if(++found == 6) goto finish; + if(++found == goalFields) goto finish; } break; + case 'v': + if((unsigned int)size > sizeof("voluntary_ctxt_switches:") && qstrncmp(mBuffer, "voluntary_ctxt_switches:", sizeof("voluntary_ctxt_switches:")-1) == 0) { + process->setVoluntaryCtxSwitches(atol(mBuffer + sizeof("voluntary_ctxt_switches:")-1)); + if(++found == goalFields) goto finish; + } + break; + case 'n': + if((unsigned int)size > sizeof("nonvoluntary_ctxt_switches:") && qstrncmp(mBuffer, "nonvoluntary_ctxt_switches:", sizeof("voluntary_ctxt_switches:")-1) == 0) { + process->setNonvoluntaryCtxSwitches(atol(mBuffer + sizeof("nonvoluntary_ctxt_switches:")-1)); + if(++found == goalFields) goto finish; + } + break; default: break; - } + } } finish: @@ -327,6 +342,12 @@ } } break; + case 9: //minFlt + ps->setMinFlt(atoll(word+1)); + break; + case 11: //majFlt + ps->setMajFlt(atoll(word+1)); + break; case 13: //userTime ps->setUserTime(atoll(word+1)); break; @@ -477,6 +498,56 @@ return true; } +bool ProcessesLocal::Private::readProcSMapsDetails(const QString &dir, Process *process) +{ + if (!process->memmapRequested()) { + return true; //if memmap is not requested, do nothing. + } + process->memmap().clear(); + mFile.setFileName(dir + QStringLiteral("smaps")); + if(!mFile.open(QIODevice::ReadOnly)) { + return true; //this function never fails. Empty memmap vector indicates permission denied. + } + process->memmap().push_back(QVector()); + process->memmap()[0].push_back(QString::fromUtf8("Address")); + process->memmap()[0].push_back(QString::fromUtf8("Permission")); + process->memmap()[0].push_back(QString::fromUtf8("Filename")); + + QRegularExpression head_rx(QString::fromUtf8("^([0-9A-Fa-f]+-[0-9A-Fa-f]+) +([rwxp\\-]*) +([0-9A-Fa-f]+) +([0-9A-Fa-f]+:[0-9A-Fa-f]+) +(\\d+) +(.*)$")); + QRegularExpression line_rx(QString::fromUtf8("^([^ ]+): +(.*)$")); + + int size; + int first = 1; + + while ( (size = mFile.readLine( mBuffer, sizeof(mBuffer))) > 0) { //-1 indicates an error + QRegularExpressionMatch head_rxm = head_rx.match(QString::fromUtf8(mBuffer)); + if (head_rxm.hasMatch()) { + if (first == 2) { + first = 0; + } else if(first == 1) { + first = 2; + } + + process->memmap().push_back(QVector()); + process->memmap().back().push_back(head_rxm.captured(1).trimmed()); + process->memmap().back().push_back(head_rxm.captured(2).trimmed()); + process->memmap().back().push_back(head_rxm.captured(6).trimmed()); + } else { + QRegularExpressionMatch line_rxm = line_rx.match(QString::fromUtf8(mBuffer)); + if (line_rxm.hasMatch()) { + process->memmap().back().push_back(line_rxm.captured(2).trimmed()); + + if (first == 2) { + process->memmap()[0].push_back(line_rxm.captured(1).trimmed()); + } + } + } + } + + mFile.close(); + return true; +} + bool ProcessesLocal::Private::getNiceness(long pid, Process *process) { int sched = sched_getscheduler(pid); switch(sched) { @@ -580,6 +651,7 @@ if(!d->readProcCGroup(dir, process)) success = false; if(!d->readProcAttr(dir, process)) success = false; if(!d->readProcSmaps(dir, process)) success = false; + if(!d->readProcSMapsDetails(dir, process)) success = false; if(!d->getNiceness(pid, process)) success = false; if(mUpdateFlags.testFlag(Processes::IOStatistics) && !d->getIOStatistics(dir, process)) success = false; diff --git a/processui/CMakeLists.txt b/processui/CMakeLists.txt --- a/processui/CMakeLists.txt +++ b/processui/CMakeLists.txt @@ -10,6 +10,7 @@ ProcessFilter.cpp ProcessModel.cpp ReniceDlg.cpp + ProcessDetailsDialog.cpp KTextEditVT.cpp scripting.cpp ) @@ -18,6 +19,7 @@ ki18n_wrap_ui( processui_LIB_SRCS + ProcessDetailsDialogUI.ui ReniceDlgUi.ui ProcessWidgetUI.ui ) @@ -32,6 +34,7 @@ target_link_libraries(processui PUBLIC + KF5::LsofUi KF5::ProcessCore Qt5::Widgets KF5::ConfigCore diff --git a/processui/ProcessDetailsDialog.h b/processui/ProcessDetailsDialog.h new file mode 100644 --- /dev/null +++ b/processui/ProcessDetailsDialog.h @@ -0,0 +1,62 @@ +/* + KSysGuard, the KDE System Guard + + Copyright (c) 2018 Chris Xiong + + 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. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + 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. + +*/ +#ifndef PROCESSDETAILSDIALOG_H +#define PROCESSDETAILSDIALOG_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../config-ksysguard.h" + +namespace Ui { +class ProcessDetailsDialog; +} + +class ProcessDetailsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ProcessDetailsDialog(long _pid, ProcessModel *_m, QWidget *parent = nullptr); + ~ProcessDetailsDialog(); + +private: + Ui::ProcessDetailsDialog *ui; + ProcessModel *m; + KLsofWidget *lsof; + QTableWidget *tw; + + long pid; + QTimer *t; + void updateMemmap(); + +private Q_SLOTS: + void updateData(bool updmm = false); + void toggleDetails(bool checked); +}; + +#endif // PROCESSDETAILSDIALOG_H diff --git a/processui/ProcessDetailsDialog.cpp b/processui/ProcessDetailsDialog.cpp new file mode 100644 --- /dev/null +++ b/processui/ProcessDetailsDialog.cpp @@ -0,0 +1,173 @@ +/* + KSysGuard, the KDE System Guard + + Copyright (c) 2018 Chris Xiong + + 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. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + 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. + +*/ +#include "ProcessDetailsDialog.h" +#include +#include +#include +#include "ui_ProcessDetailsDialogUI.h" + +ProcessDetailsDialog::ProcessDetailsDialog(long _pid, ProcessModel *_m, QWidget *parent) + : QDialog(parent) + , ui(new Ui::ProcessDetailsDialog) + , pid(_pid) + , m(_m) +{ + ui->setupUi(this); + KSysGuard::Process *proc = m->getProcess(pid); + QString w = i18n("Process Details of %1 (%2)").arg(proc->name()).arg(pid); + ((QGridLayout *)(ui->tabMemory->layout()))->addWidget(tw = new QTableWidget(), 9, 0, 1, 2); + + tw->setSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding); + tw->hide(); + + setWindowTitle(w); + setModal(true); + + proc->setMemmapRequested(true); + m->processController()->updateOrAddProcess(pid); + updateData(true); + proc->setMemmapRequested(false); + + t = new QTimer(this); + connect(t, SIGNAL(timeout()), this, SLOT(updateData())); + connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(close())); + connect(ui->pbDetails, SIGNAL(clicked(bool)), this, SLOT(toggleDetails(bool))); + + ui->tabOpenFiles->setLayout(new QHBoxLayout()); + lsof = new KLsofWidget(this); + lsof->setPid(pid); + lsof->update(); + ui->tabOpenFiles->layout()->addWidget(lsof); + + t->start(1000); +} + +ProcessDetailsDialog::~ProcessDetailsDialog() +{ + t->stop(); + delete ui; +} + +void ProcessDetailsDialog::updateMemmap() +{ + KSysGuard::Process *proc = m->getProcess(pid); + + int pssidx = proc->memmap()[0].indexOf(QStringLiteral("Pss")); + int prvclnidx = proc->memmap()[0].indexOf(QStringLiteral("Private_Clean")); + int prvdrtidx = proc->memmap()[0].indexOf(QStringLiteral("Private_Dirty")); + int shrclnidx = proc->memmap()[0].indexOf(QStringLiteral("Shared_Clean")); + int shrdrtidx = proc->memmap()[0].indexOf(QStringLiteral("Shared_Dirty")); + qlonglong psstotal = 0; + qlonglong prvtotal = 0; + qlonglong shrtotal = 0; + for (int i = 1; i < proc->memmap().size(); ++i) { + auto qatoll = [](QString s) { + return s.left(s.indexOf(QStringLiteral(" "))).toLongLong(); + }; + psstotal += qatoll(proc->memmap()[i][pssidx]); + prvtotal += qatoll(proc->memmap()[i][prvclnidx]); + prvtotal += qatoll(proc->memmap()[i][prvdrtidx]); + shrtotal += qatoll(proc->memmap()[i][shrclnidx]); + shrtotal += qatoll(proc->memmap()[i][shrdrtidx]); + } + ui->lbPss->setText(m->formatMemoryInfo(psstotal, ProcessModel::Units::UnitsAuto)); + ui->lbPrivateTotal->setText(m->formatMemoryInfo(prvtotal, ProcessModel::Units::UnitsAuto)); + ui->lbSharedTotal->setText(m->formatMemoryInfo(shrtotal, ProcessModel::Units::UnitsAuto)); + + tw->setColumnCount(proc->memmap()[0].size()); + tw->setRowCount(proc->memmap().size() - 1); + + QStringList sl; + for (auto &i : proc->memmap()[0]) { + sl << i; + } + + tw->setHorizontalHeaderLabels(sl); + tw->setSortingEnabled(true); + tw->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectItems); + tw->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); + + for (int i = 1; i < proc->memmap().size(); ++i) { + for (int j = 0; j < proc->memmap()[i].size(); ++j) { + QTableWidgetItem *pt; + tw->setItem(i - 1, j, pt = new QTableWidgetItem(proc->memmap()[i][j])); + pt->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled); + } + } + + tw->verticalHeader()->hide(); + tw->resizeColumnsToContents(); +} + +void ProcessDetailsDialog::updateData(bool updmm) +{ + KSysGuard::Process *proc = m->getProcess(pid); + + if (!proc) { + return; + } + + if (proc->memmap().empty()) { + ui->lbPermission->setText(i18n( + "Permission denied while trying to read from /proc/%1/smaps. Some information may be unavailable.").arg( + pid)); + ui->pbDetails->setEnabled(false); + ui->lbCaptionPrvTot->hide(); + ui->lbPrivateTotal->hide(); + ui->lbCaptionShrTot->hide(); + ui->lbSharedTotal->hide(); + ui->lbCaptionPss->hide(); + ui->lbPss->hide(); + } else { + ui->lbPermission->hide(); + if (updmm) { + updateMemmap(); + } + } + + ui->lbvmRss->setText(m->formatMemoryInfo(proc->vmRSS(), ProcessModel::Units::UnitsAuto)); + ui->lbvmURss->setText(m->formatMemoryInfo(proc->vmURSS(), ProcessModel::Units::UnitsAuto)); + ui->lbvmSize->setText(m->formatMemoryInfo(proc->vmSize(), ProcessModel::Units::UnitsAuto)); + +#if HAVE_X11 + ui->lbXPMMem->setText(m->formatMemoryInfo(proc->pixmapBytes() / 1024, + ProcessModel::Units::UnitsAuto)); +#else + ui->lbXPMMem->hide(); + ui->lbCaptionXPMMem->hide(); +#endif + + ui->lbNumThrd->setText(QString::number(proc->numThreads())); + ui->lbVolCtxSw->setText(QString::number(proc->voluntaryCtxSwitches())); + ui->lbNonvolCtxSw->setText(QString::number(proc->nonvoluntaryCtxSwitches())); + ui->lbMinFlt->setText(QString::number(proc->minFlt())); + ui->lbMajFlt->setText(QString::number(proc->majFlt())); +} + +void ProcessDetailsDialog::toggleDetails(bool checked) +{ + if (checked) { + tw->show(); + } else { + tw->hide(); + } +} diff --git a/processui/ProcessDetailsDialogUI.ui b/processui/ProcessDetailsDialogUI.ui new file mode 100644 --- /dev/null +++ b/processui/ProcessDetailsDialogUI.ui @@ -0,0 +1,392 @@ + + + ProcessDetailsDialog + + + + 0 + 0 + 899 + 589 + + + + Dialog + + + + + + 0 + + + + Memory + + + + + + + 0 + 0 + + + + Real Memory Size: + + + + + + + + 0 + 0 + + + + vmRss + + + + + + + + 0 + 0 + + + + Virtual Memory Size: + + + + + + + + 0 + 0 + + + + vmSize + + + + + + + + 0 + 0 + + + + PixmapMemory + + + + + + + + 0 + 0 + + + + Pss + + + + + + + + 0 + 0 + + + + vmURss + + + + + + + + 0 + 0 + + + + Private Memory Size: + + + + + + + + 0 + 0 + + + + Proportional Set Size: + + + + + + + + 0 + 0 + + + + Shared Memory Size: + + + + + + + + 0 + 0 + + + + PrivateTotal + + + + + + + + 0 + 0 + + + + SharedTotal + + + + + + + + 0 + 0 + + + + CaptionPermissionDenied + + + + + + + + 0 + 0 + + + + Resident Set Size: + + + + + + + + 0 + 0 + + + + X Pixmap Memory: + + + + + + + + 0 + 0 + + + + Detailed Memory Information + + + true + + + + + + + + Statistics + + + + + + Threads: + + + + + + + + 0 + 0 + + + + voluntaryCtxSwitches + + + + + + + Minor Page Faults: + + + + + + + + 0 + 0 + + + + nonvoluntaryCtxSwitches + + + + + + + Context Switches (nonvoluntary): + + + + + + + Context Switches (voluntary): + + + + + + + + 0 + 0 + + + + numThread + + + + + + + + 0 + 0 + + + + minFlt + + + + + + + Major Page Faults: + + + + + + + + 0 + 0 + + + + majFlt + + + + + + + + Open Files + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Close + + + + .. + + + + + + + + + + diff --git a/processui/ksysguardprocesslist.h b/processui/ksysguardprocesslist.h --- a/processui/ksysguardprocesslist.h +++ b/processui/ksysguardprocesslist.h @@ -146,6 +146,9 @@ /** Renice all the processes that the user has selected. Pops up a dialog box to ask for the nice value and confirm */ void reniceSelectedProcesses(); + /** Display a dialog box showing the deails of a single process */ + void processDetails(); + /** Change the CPU scheduler for the given of processes to the given scheduler, with the given scheduler priority. * If the scheduler is Other or Batch, @p newCpuSchedPriority is ignored. * @return Whether the cpu scheduler changing went ahead. True if successful or user cancelled. False if there was a problem diff --git a/processui/ksysguardprocesslist.cpp b/processui/ksysguardprocesslist.cpp --- a/processui/ksysguardprocesslist.cpp +++ b/processui/ksysguardprocesslist.cpp @@ -61,6 +61,7 @@ #include #include "ReniceDlg.h" +#include "ProcessDetailsDialog.h" #include "ui_ProcessWidgetUI.h" #include "scripting.h" #include "process_controller.h" @@ -204,6 +205,7 @@ mResortCountDown = 2; //The items added initially will be already sorted, but without CPU info. On the second refresh we will have CPU usage, so /then/ we can resort renice = new QAction(i18np("Set Priority...", "Set Priority...", 1), q); renice->setShortcut(Qt::Key_F8); + details = new QAction(i18n("Process Details..."), q); selectParent = new QAction(i18n("Jump to Parent Process"), q); selectTracer = new QAction(i18n("Jump to Process Debugging This One"), q); @@ -283,6 +285,7 @@ QAction *jumpToSearchFilter; QAction *window; QAction *resume; + QAction *details; QAction *sigStop; QAction *sigCont; QAction *sigHup; @@ -378,7 +381,7 @@ // Add all the actions to the main widget, and get all the actions to call actionTriggered when clicked QList actions; - actions << d->renice << d->kill << d->terminate << d->selectParent << d->selectTracer << d->window << d->jumpToSearchFilter; + actions << d->renice << d->details << d->kill << d->terminate << d->selectParent << d->selectTracer << d->window << d->jumpToSearchFilter; actions << d->resume << d->sigStop << d->sigCont << d->sigHup << d->sigInt << d->sigTerm << d->sigKill << d->sigUsr1 << d->sigUsr2; foreach(QAction *action, actions) { @@ -621,6 +624,7 @@ d->mProcessContextMenu->addAction(d->terminate); if (numProcesses == 1 && !process->timeKillWasSent().isNull()) d->mProcessContextMenu->addAction(d->kill); + d->mProcessContextMenu->addAction(d->details); } d->mProcessContextMenu->popup(d->mUi->treeView->viewport()->mapToGlobal(point)); @@ -635,6 +639,8 @@ //Escape was pressed. Do nothing. } else if(result == d->renice) { reniceSelectedProcesses(); + } else if(result == d->details) { + processDetails(); } else if(result == d->terminate) { sendSignalToSelectedProcesses(SIGTERM, true); } else if(result == d->kill) { @@ -1277,6 +1283,15 @@ updateList(); } +void KSysGuardProcessList::processDetails() +{ + QList processes = selectedProcesses(); + if(processes.size() != 1)return; + QPointer detailsdlg = new ProcessDetailsDialog(processes.first()->pid(), processModel(), d->mUi->treeView); + detailsdlg->setAttribute(Qt::WA_DeleteOnClose, true); + detailsdlg->exec(); +} + bool KSysGuardProcessList::changeIoScheduler(const QList< long long> &pids, KSysGuard::Process::IoPriorityClass newIoSched, int newIoSchedPriority) { auto result = d->mProcessController->setIOScheduler(pids, newIoSched, newIoSchedPriority);