diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a4bd919..1bee6c7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -59,6 +59,9 @@ set(HEADER_FILES SeerWatchpointsBrowserWidget.h SeerCppSourceHighlighter.h SeerAboutDialog.h + SeerSlashProcWidget.h + SeerSlashProcDialog.h + QProcessInfo.h ) set(SOURCE_FILES @@ -104,6 +107,9 @@ set(SOURCE_FILES SeerWatchpointsBrowserWidget.cpp SeerCppSourceHighlighter.cpp SeerAboutDialog.cpp + SeerSlashProcWidget.cpp + SeerSlashProcDialog.cpp + QProcessInfo.cpp ) qt5_add_resources(SOURCE_FILES resource.qrc) diff --git a/src/QProcessInfo.cpp b/src/QProcessInfo.cpp new file mode 100644 index 0000000..1cc24d0 --- /dev/null +++ b/src/QProcessInfo.cpp @@ -0,0 +1,127 @@ +// My version is based on this person's code. +// +// Copyright (c) 2016, Baldur Karlsson +// Licensed under BSD 2-Clause License, see LICENSE file. +// Obtained from https://github.com/baldurk/qprocessinfo + +#include "QProcessInfo.h" +#include +#include +#include +#include +#include + +QProcessList QProcessInfo::enumerate() { + + QProcessList ret; + + QDir proc(QStringLiteral("/proc")); + + QStringList files = proc.entryList(); + + for (const QString& f : files) { + + bool ok = false; + uint32_t pid = f.toUInt(&ok); + + if (ok) { + + QProcessInfo info; + info.setPid(pid); + + QDir processDir(QStringLiteral("/proc/") + f); + + // default to the exe symlink if valid + QFileInfo exe(processDir.absoluteFilePath(QStringLiteral("exe"))); + exe = QFileInfo(exe.symLinkTarget()); + info.setName(exe.completeBaseName()); + + // if we didn't get a name from the symlink, check in the status file + if (info.name().isEmpty()) { + + QFile status(processDir.absoluteFilePath(QStringLiteral("status"))); + if (status.open(QIODevice::ReadOnly)) { + + QByteArray contents = status.readAll(); + + QTextStream in(&contents); + + while(!in.atEnd()) { + QString line = in.readLine(); + + if (line.startsWith(QStringLiteral("Name:"))) { + line.remove(0, 5); + // if we're using this name, surround with []s to indicate it's not a file + info.setName(QStringLiteral("[%1]").arg(line.trimmed())); + break; + } + } + status.close(); + } + } + + // get the command line + QFile cmdline(processDir.absoluteFilePath(QStringLiteral("cmdline"))); + + if (cmdline.open(QIODevice::ReadOnly)) { + QByteArray contents = cmdline.readAll(); + + int nullIdx = contents.indexOf('\0'); + + if (nullIdx > 0) { + + QString firstparam = QString::fromUtf8(contents.data(), nullIdx); + + // if name is a truncated form of a filename, replace it + if (firstparam.endsWith(info.name()) && QFileInfo::exists(firstparam)) { + info.setName(QFileInfo(firstparam).completeBaseName()); + } + + // if we don't have a name, replace it but with []s + if (info.name().isEmpty()) { + info.setName(QStringLiteral("[%1]").arg(firstparam)); + } + + contents.replace('\0', ' '); + } + + info.setCommandLine(QString::fromUtf8(contents).trimmed()); + + cmdline.close(); + } + + ret.push_back(info); + } + } + + return ret; +} + +QProcessInfo::QProcessInfo() { + _pid = 0; +} + +uint32_t QProcessInfo::pid() const { + return _pid; +} + +void QProcessInfo::setPid(uint32_t pid) { + _pid = pid; +} + +const QString &QProcessInfo::name() const { + return _name; +} + +void QProcessInfo::setName(const QString &name) { + _name = name; +} + +const QString &QProcessInfo::commandLine() const { + return _cmdLine; +} + +void QProcessInfo::setCommandLine(const QString &cmd) { + _cmdLine = cmd; +} + diff --git a/src/QProcessInfo.h b/src/QProcessInfo.h new file mode 100644 index 0000000..62d8ec1 --- /dev/null +++ b/src/QProcessInfo.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +class QProcessInfo; +typedef QList QProcessList; + +class QProcessInfo { + public: + QProcessInfo(); + + static QProcessList enumerate(); + + uint32_t pid () const; + void setPid (uint32_t pid); + + const QString& name () const; + void setName (const QString& name); + + const QString& commandLine () const; + void setCommandLine (const QString& cmd); + + private: + uint32_t _pid; + QString _name; + QString _cmdLine; +}; + diff --git a/src/SeerDebugDialog.cpp b/src/SeerDebugDialog.cpp index 4460230..224d014 100644 --- a/src/SeerDebugDialog.cpp +++ b/src/SeerDebugDialog.cpp @@ -1,6 +1,7 @@ #include "SeerDebugDialog.h" #include "SeerExecutableFilterProxyModel.h" #include "SeerDirectoryFilterProxyModel.h" +#include "SeerSlashProcDialog.h" #include #include #include @@ -32,6 +33,7 @@ SeerDebugDialog::SeerDebugDialog (QWidget* parent) : QDialog(parent) { QObject::connect(executableNameToolButton, &QToolButton::clicked, this, &SeerDebugDialog::handleExecutableNameToolButton); QObject::connect(executableWorkingDirectoryToolButton, &QToolButton::clicked, this, &SeerDebugDialog::handleExecutableWorkingDirectoryToolButton); QObject::connect(loadCoreFilenameToolButton, &QToolButton::clicked, this, &SeerDebugDialog::handleLoadCoreFilenameToolButton); + QObject::connect(attachProgramPidToolButton, &QToolButton::clicked, this, &SeerDebugDialog::handleProgramPidToolButton); QObject::connect(_runModeButtonGroup, QOverload::of(&QButtonGroup::buttonClicked), this, &SeerDebugDialog::handleRunModeChanged); // Set initial run mode. @@ -245,6 +247,16 @@ void SeerDebugDialog::handleLoadCoreFilenameToolButton () { } } +void SeerDebugDialog::handleProgramPidToolButton () { + + SeerSlashProcDialog dlg(this); + + // Execute the dialog and get the result. + if (dlg.exec()) { + setAttachPid(dlg.selectedPid()); + } +} + void SeerDebugDialog::handleRunModeChanged (int id) { // @@ -257,6 +269,7 @@ void SeerDebugDialog::handleRunModeChanged (int id) { // ID == 2 attachProgramPidLineEdit->setEnabled(false); + attachProgramPidToolButton->setEnabled(false); // ID == 3 connectProgramHostPortLineEdit->setEnabled(false); @@ -264,6 +277,7 @@ void SeerDebugDialog::handleRunModeChanged (int id) { // ID == 4 loadCoreFilenameLineEdit->setEnabled(false); + loadCoreFilenameToolButton->setEnabled(false); // // Enable the newly selected one. @@ -278,6 +292,7 @@ void SeerDebugDialog::handleRunModeChanged (int id) { // ID == 2 if (id == 2) { attachProgramPidLineEdit->setEnabled(true); + attachProgramPidToolButton->setEnabled(true); } // ID == 3 @@ -289,6 +304,7 @@ void SeerDebugDialog::handleRunModeChanged (int id) { // ID == 4 if (id == 4) { loadCoreFilenameLineEdit->setEnabled(true); + loadCoreFilenameToolButton->setEnabled(true); } } diff --git a/src/SeerDebugDialog.h b/src/SeerDebugDialog.h index 7814cd3..485cbf6 100644 --- a/src/SeerDebugDialog.h +++ b/src/SeerDebugDialog.h @@ -49,6 +49,7 @@ class SeerDebugDialog : public QDialog, protected Ui::SeerDebugDialogForm { void handleExecutableNameToolButton (); void handleExecutableWorkingDirectoryToolButton (); void handleLoadCoreFilenameToolButton (); + void handleProgramPidToolButton (); void handleRunModeChanged (int id); private: diff --git a/src/SeerSlashProcDialog.cpp b/src/SeerSlashProcDialog.cpp new file mode 100644 index 0000000..4570623 --- /dev/null +++ b/src/SeerSlashProcDialog.cpp @@ -0,0 +1,30 @@ +#include "SeerSlashProcDialog.h" + +SeerSlashProcDialog::SeerSlashProcDialog (QWidget* parent) : QDialog(parent) { + + // Set up the UI. + setupUi(this); + + // Setup the widgets + + // Connect things. +} + +SeerSlashProcDialog::~SeerSlashProcDialog () { +} + +int SeerSlashProcDialog::selectedPid () const { + + return slashProcWidget->selectedPid(); +} + +QString SeerSlashProcDialog::selectedName () const { + + return slashProcWidget->selectedName(); +} + +QString SeerSlashProcDialog::selectedCommandLine () const { + + return slashProcWidget->selectedCommandLine(); +} + diff --git a/src/SeerSlashProcDialog.h b/src/SeerSlashProcDialog.h new file mode 100644 index 0000000..8fc4c96 --- /dev/null +++ b/src/SeerSlashProcDialog.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include "ui_SeerSlashProcDialog.h" + +class SeerSlashProcDialog : public QDialog, protected Ui::SeerSlashProcDialogForm { + + Q_OBJECT + + public: + explicit SeerSlashProcDialog (QWidget* parent = 0); + ~SeerSlashProcDialog (); + + int selectedPid () const; + QString selectedName () const; + QString selectedCommandLine () const; + + public slots: + + private: +}; + diff --git a/src/SeerSlashProcDialog.ui b/src/SeerSlashProcDialog.ui new file mode 100644 index 0000000..f9afb95 --- /dev/null +++ b/src/SeerSlashProcDialog.ui @@ -0,0 +1,84 @@ + + + SeerSlashProcDialogForm + + + + 0 + 0 + 589 + 562 + + + + Seer - System Processes + + + + :/seer/resources/seer_32x32.png:/seer/resources/seer_32x32.png + + + Select a process pid. + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + SeerSlashProcWidget + QWidget +
SeerSlashProcWidget.h
+ 1 +
+
+ + + + + + buttonBox + accepted() + SeerSlashProcDialogForm + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SeerSlashProcDialogForm + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/SeerSlashProcWidget.cpp b/src/SeerSlashProcWidget.cpp new file mode 100644 index 0000000..84ae0dc --- /dev/null +++ b/src/SeerSlashProcWidget.cpp @@ -0,0 +1,207 @@ +#include "SeerSlashProcWidget.h" +#include "QProcessInfo.h" +#include +#include +#include +#include + + +// +// Custom tree item with numeric ordering on column 0. +// +class QProcessInfoWidgetItem : public QTreeWidgetItem { + + public: + QProcessInfoWidgetItem(QTreeWidget* parent = 0) : QTreeWidgetItem(parent) { + } + + private: + bool operator< (const QTreeWidgetItem& other) const { + + int column = treeWidget()->sortColumn(); + + if (column == 0) { + return text(column).toLong() < other.text(column).toLong(); + } + + return text(column) < other.text(column); + } +}; + + +// +// The main widget starts here. +// +SeerSlashProcWidget::SeerSlashProcWidget (QWidget* parent) : QWidget(parent) { + + // Construct the UI. + setupUi(this); + + // Setup the widgets + searchLineEdit->setPlaceholderText("Search..."); + searchLineEdit->setClearButtonEnabled(true); + processTreeWidget->setMouseTracking(true); + processTreeWidget->resizeColumnToContents(0); + processTreeWidget->resizeColumnToContents(1); + processTreeWidget->resizeColumnToContents(2); + processTreeWidget->setSortingEnabled(true); + processTreeWidget->clear(); + + // Connect things. + QObject::connect(searchLineEdit, &QLineEdit::textChanged, this, &SeerSlashProcWidget::handleSearchLineEdit); + QObject::connect(refreshToolButton, &QToolButton::clicked, this, &SeerSlashProcWidget::refresh); + + // Load the initial process list. + refresh(); +} + +SeerSlashProcWidget::~SeerSlashProcWidget () { +} + +int SeerSlashProcWidget::selectedPid () const { + + QList items = processTreeWidget->selectedItems(); + + if (items.size() == 0) { + return -1; + } + + return items[0]->text(0).toLong(); +} + +QString SeerSlashProcWidget::selectedName () const { + + QList items = processTreeWidget->selectedItems(); + + if (items.size() == 0) { + return ""; + } + + return items[0]->text(1); +} + +QString SeerSlashProcWidget::selectedCommandLine () const { + + QList items = processTreeWidget->selectedItems(); + + if (items.size() == 0) { + return ""; + } + + return items[0]->text(2); +} + +void SeerSlashProcWidget::refresh () { + + QApplication::setOverrideCursor(Qt::BusyCursor); + + // Scan the /proc file system. + QProcessList list = QProcessInfo::enumerate(); + + // Loop through each entry and add it to our view. + processTreeWidget->clear(); + + for (const QProcessInfo& info : list) { + + //qDebug() << info.pid() << " " << info.name() << " " << info.commandLine(); + + QTreeWidgetItem* item = new QProcessInfoWidgetItem; + item->setText(0, QString::number(info.pid())); + item->setText(1, info.name()); + item->setText(2, info.commandLine()); + + processTreeWidget->addTopLevelItem(item); + } + + // Adjust the column widths. + processTreeWidget->clearSelection(); + processTreeWidget->resizeColumnToContents(0); + processTreeWidget->resizeColumnToContents(1); + processTreeWidget->resizeColumnToContents(2); + + searchLineEdit->clear(); + + QApplication::restoreOverrideCursor(); +} + +void SeerSlashProcWidget::handleSearchLineEdit (const QString& text) { + + + // Set everything to a normal font. If there is no search text, unhide everything. + // If there is search text, hide everything so the matching ones can be unhidden later on. + QTreeWidgetItemIterator it(processTreeWidget); + + if (*it) { + + QFont f0 = (*it)->font(0); + QFont f1 = (*it)->font(1); + QFont f2 = (*it)->font(2); + + f0.setBold(false); + f1.setBold(false); + f2.setBold(false); + + if (text == "") { + while (*it) { + (*it)->setHidden(false); // No search text, unhide everything. + (*it)->setFont(0,f0); + (*it)->setFont(1,f1); + (*it)->setFont(2,f2); + ++it; + } + + }else{ + while (*it) { + (*it)->setHidden(true); // Has serach text, hide everything. Matching items to be unhidden below. + (*it)->setFont(0,f0); + (*it)->setFont(1,f1); + (*it)->setFont(2,f2); + ++it; + } + } + } + + // Set selected items to a bold font and unhidden. Move to the first match. + if (text != "") { + + QList matches; + + if (text.contains('*')) { + matches = processTreeWidget->findItems(text, Qt::MatchWildcard | Qt::MatchRecursive, 1); + }else{ + matches = processTreeWidget->findItems(text, Qt::MatchStartsWith | Qt::MatchRecursive, 1); + } + + QList::const_iterator it = matches.begin(); + QList::const_iterator e = matches.end(); + + if (it != e) { + + processTreeWidget->setCurrentItem(*it); + + QFont f0 = (*it)->font(0); + QFont f1 = (*it)->font(1); + QFont f2 = (*it)->font(2); + + f0.setBold(false); + f1.setBold(true); + f2.setBold(false); + + while (it != e) { + (*it)->setHidden(false); + (*it)->setFont(0,f0); + (*it)->setFont(1,f1); + (*it)->setFont(2,f2); + + it++; + } + } + + //qDebug() << __PRETTY_FUNCTION__ << ":" << text << matches.size(); + } + + processTreeWidget->resizeColumnToContents(0); + processTreeWidget->resizeColumnToContents(1); + processTreeWidget->resizeColumnToContents(2); +} + diff --git a/src/SeerSlashProcWidget.h b/src/SeerSlashProcWidget.h new file mode 100644 index 0000000..99a2b6c --- /dev/null +++ b/src/SeerSlashProcWidget.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include "ui_SeerSlashProcWidget.h" + +class SeerSlashProcWidget : public QWidget, protected Ui::SeerSlashProcWidgetForm { + + Q_OBJECT + + public: + explicit SeerSlashProcWidget (QWidget* parent = 0); + ~SeerSlashProcWidget (); + + int selectedPid () const; + QString selectedName () const; + QString selectedCommandLine () const; + + public slots: + void refresh (); + + protected slots: + void handleSearchLineEdit (const QString& text); + + protected: + private: +}; + diff --git a/src/SeerSlashProcWidget.ui b/src/SeerSlashProcWidget.ui new file mode 100644 index 0000000..41aa4a8 --- /dev/null +++ b/src/SeerSlashProcWidget.ui @@ -0,0 +1,72 @@ + + + SeerSlashProcWidgetForm + + + + 0 + 0 + 770 + 488 + + + + System Process Browser + + + + + + Search in the list of processes. "*" is allowed. + + + + + + Search... + + + + + + + Refresh the list of processes. + + + + + + + + + + + + + true + + + 3 + + + + PID + + + + + Name + + + + + Command Line + + + + + + + + +