add basic QT tree
This commit is contained in:
commit
f4a81cfa32
78
.gitignore
vendored
Normal file
78
.gitignore
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# This file is used to ignore files which are generated
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
*~
|
||||||
|
*.autosave
|
||||||
|
*.a
|
||||||
|
*.core
|
||||||
|
*.moc
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*_pch.h.cpp
|
||||||
|
*_resource.rc
|
||||||
|
*.qm
|
||||||
|
.#*
|
||||||
|
*.*#
|
||||||
|
core
|
||||||
|
!core/
|
||||||
|
tags
|
||||||
|
.DS_Store
|
||||||
|
.directory
|
||||||
|
*.debug
|
||||||
|
Makefile*
|
||||||
|
*.prl
|
||||||
|
*.app
|
||||||
|
moc_*.cpp
|
||||||
|
ui_*.h
|
||||||
|
qrc_*.cpp
|
||||||
|
Thumbs.db
|
||||||
|
*.res
|
||||||
|
*.rc
|
||||||
|
/.qmake.cache
|
||||||
|
/.qmake.stash
|
||||||
|
|
||||||
|
# qtcreator generated files
|
||||||
|
*.pro.user*
|
||||||
|
CMakeLists.txt.user*
|
||||||
|
|
||||||
|
# xemacs temporary files
|
||||||
|
*.flc
|
||||||
|
|
||||||
|
# Vim temporary files
|
||||||
|
.*.swp
|
||||||
|
|
||||||
|
# Visual Studio generated files
|
||||||
|
*.ib_pdb_index
|
||||||
|
*.idb
|
||||||
|
*.ilk
|
||||||
|
*.pdb
|
||||||
|
*.sln
|
||||||
|
*.suo
|
||||||
|
*.vcproj
|
||||||
|
*vcproj.*.*.user
|
||||||
|
*.ncb
|
||||||
|
*.sdf
|
||||||
|
*.opensdf
|
||||||
|
*.vcxproj
|
||||||
|
*vcxproj.*
|
||||||
|
|
||||||
|
# idea
|
||||||
|
cmake-build-debug
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# MinGW generated files
|
||||||
|
*.Debug
|
||||||
|
*.Release
|
||||||
|
|
||||||
|
# Python byte code
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Binaries
|
||||||
|
# --------
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
|
79
CMakeLists.txt
Normal file
79
CMakeLists.txt
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
|
project(swaymux VERSION 0.1 LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools)
|
||||||
|
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools)
|
||||||
|
|
||||||
|
set(TS_FILES swaymux_en_US.ts)
|
||||||
|
|
||||||
|
set(PROJECT_SOURCES
|
||||||
|
main.cpp
|
||||||
|
mainwindow.cpp
|
||||||
|
mainwindow.h
|
||||||
|
mainwindow.ui
|
||||||
|
${TS_FILES}
|
||||||
|
tree/pstree.cpp
|
||||||
|
tree/PsTreeModel.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
||||||
|
qt_add_executable(swaymux
|
||||||
|
MANUAL_FINALIZATION
|
||||||
|
${PROJECT_SOURCES}
|
||||||
|
)
|
||||||
|
# Define target properties for Android with Qt 6 as:
|
||||||
|
# set_property(TARGET swaymux APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
||||||
|
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
|
||||||
|
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
|
||||||
|
|
||||||
|
qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
|
||||||
|
else()
|
||||||
|
if(ANDROID)
|
||||||
|
add_library(swaymux SHARED
|
||||||
|
${PROJECT_SOURCES}
|
||||||
|
)
|
||||||
|
# Define properties for Android with Qt 5 after find_package() calls as:
|
||||||
|
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
|
||||||
|
else()
|
||||||
|
add_executable(swaymux
|
||||||
|
${PROJECT_SOURCES}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(swaymux PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
|
||||||
|
|
||||||
|
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
|
||||||
|
# If you are developing for iOS or macOS you should consider setting an
|
||||||
|
# explicit, fixed bundle identifier manually though.
|
||||||
|
if(${QT_VERSION} VERSION_LESS 6.1.0)
|
||||||
|
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.swaymux)
|
||||||
|
endif()
|
||||||
|
set_target_properties(swaymux PROPERTIES
|
||||||
|
${BUNDLE_ID_OPTION}
|
||||||
|
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||||
|
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
|
||||||
|
MACOSX_BUNDLE TRUE
|
||||||
|
WIN32_EXECUTABLE TRUE
|
||||||
|
)
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
install(TARGETS swaymux
|
||||||
|
BUNDLE DESTINATION .
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(QT_VERSION_MAJOR EQUAL 6)
|
||||||
|
qt_finalize_executable(swaymux)
|
||||||
|
endif()
|
33
main.cpp
Normal file
33
main.cpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#include "mainwindow.h"
|
||||||
|
#include "tree/pstree.h"
|
||||||
|
#include "tree/PsTreeModel.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QLocale>
|
||||||
|
#include <QTranslator>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QTreeView>
|
||||||
|
|
||||||
|
using namespace Qt::StringLiterals;
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QApplication a(argc, argv);
|
||||||
|
QCoreApplication::setOrganizationName("Grimmauld");
|
||||||
|
QCoreApplication::setApplicationName("SwayMux");
|
||||||
|
|
||||||
|
QTranslator translator;
|
||||||
|
const QStringList uiLanguages = QLocale::system().uiLanguages();
|
||||||
|
for (const QString &locale : uiLanguages) {
|
||||||
|
const QString baseName = "swaymux_" + QLocale(locale).name();
|
||||||
|
if (translator.load(":/i18n/" + baseName)) {
|
||||||
|
a.installTranslator(&translator);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MainWindow w;
|
||||||
|
w.showMaximized();
|
||||||
|
return a.exec();
|
||||||
|
}
|
32
mainwindow.cpp
Normal file
32
mainwindow.cpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#include "mainwindow.h"
|
||||||
|
#include "./ui_mainwindow.h"
|
||||||
|
#include "tree/PsTreeModel.h"
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
MainWindow::MainWindow(QWidget *parent)
|
||||||
|
: QMainWindow(parent), ui(new Ui::MainWindow) {
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
model = new PsTreeModel(ui->treeView);
|
||||||
|
modelUpdateTimer = new QTimer(this);
|
||||||
|
connect(modelUpdateTimer, &QTimer::timeout, this, QOverload<>::of(&MainWindow::update));
|
||||||
|
modelUpdateTimer->start(1000);
|
||||||
|
|
||||||
|
ui->statusbar->showMessage(QString::fromStdString("test test 123"));
|
||||||
|
ui->treeView->setModel(model);
|
||||||
|
ui->treeView->expandAll();
|
||||||
|
|
||||||
|
for (int c = 0; c < model->columnCount(); ++c)
|
||||||
|
ui->treeView->resizeColumnToContents(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWindow::~MainWindow() {
|
||||||
|
delete ui;
|
||||||
|
delete model;
|
||||||
|
delete modelUpdateTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::update() {
|
||||||
|
// model->update();
|
||||||
|
}
|
28
mainwindow.h
Normal file
28
mainwindow.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#ifndef MAINWINDOW_H
|
||||||
|
#define MAINWINDOW_H
|
||||||
|
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include "tree/PsTreeModel.h"
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
namespace Ui {
|
||||||
|
class MainWindow;
|
||||||
|
}
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
class MainWindow : public QMainWindow
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit MainWindow(QWidget *parent = nullptr);
|
||||||
|
~MainWindow() override;
|
||||||
|
|
||||||
|
void update() ;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::MainWindow *ui;
|
||||||
|
PsTreeModel* model;
|
||||||
|
QTimer* modelUpdateTimer;
|
||||||
|
};
|
||||||
|
#endif // MAINWINDOW_H
|
51
mainwindow.ui
Normal file
51
mainwindow.ui
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MainWindow</class>
|
||||||
|
<widget class="QMainWindow" name="MainWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>1080</width>
|
||||||
|
<height>740</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>SwayMux</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QTreeView" name="treeView">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>24</pointsize>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QStatusBar" name="statusbar"/>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
4
swaymux_en_US.ts
Normal file
4
swaymux_en_US.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="en_US"></TS>
|
||||||
|
|
71
tree/AbstractTreeNode.h
Normal file
71
tree/AbstractTreeNode.h
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
//
|
||||||
|
// Created by grimmauld on 27.02.24.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef SWAYMUX_ABSTRACTTREENODE_H
|
||||||
|
#define SWAYMUX_ABSTRACTTREENODE_H
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <set>
|
||||||
|
#include <utility>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
class AbstractTreeNode {
|
||||||
|
// static_assert(std::is_base_of<AbstractTreeNode, T>::value, "Derived not derived from BaseClass");
|
||||||
|
public:
|
||||||
|
std::vector<std::unique_ptr<T>> children{};
|
||||||
|
|
||||||
|
explicit AbstractTreeNode(T *parent = nullptr) : parent(parent) {}
|
||||||
|
|
||||||
|
[[nodiscard]] T *parentItem() { return parent; };
|
||||||
|
|
||||||
|
[[nodiscard]] size_t childCount() const { return children.size(); };
|
||||||
|
|
||||||
|
[[nodiscard]] T *child(int row) const { return row >= 0 && row < childCount() ? children.at(row).get() : nullptr; };
|
||||||
|
|
||||||
|
[[nodiscard]] virtual QVariant data(int column) const = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual QVariant headerData(int column) const = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual int columnCount() const = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] int row() {
|
||||||
|
if (parent == nullptr)
|
||||||
|
return 0;
|
||||||
|
const auto it = std::find_if(parent->children.cbegin(), parent->children.cend(),
|
||||||
|
[this](const std::unique_ptr<T> &treeItem) {
|
||||||
|
return treeItem.get() == this;
|
||||||
|
});
|
||||||
|
if (it != parent->children.cend())
|
||||||
|
return std::distance(parent->children.cbegin(), it);
|
||||||
|
Q_ASSERT(false); // should not happen
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void appendChild(std::unique_ptr<T> &child) { children.push_back(std::move(child)); }
|
||||||
|
|
||||||
|
[[nodiscard]] T* findChild(std::function<bool(const T &)> test) {
|
||||||
|
|
||||||
|
for (int i = 0; i < childCount(); ++i) {
|
||||||
|
auto* c = child(i);
|
||||||
|
if (test(*c))
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
void removeChild(int i) {
|
||||||
|
if (i >= 0 && i < childCount()) {
|
||||||
|
children.erase(std::next(children.begin(), i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
T *parent;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //SWAYMUX_ABSTRACTTREENODE_H
|
79
tree/PsTreeModel.cpp
Normal file
79
tree/PsTreeModel.cpp
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
//
|
||||||
|
// Created by grimmauld on 25.02.24.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "PsTreeModel.h"
|
||||||
|
|
||||||
|
PsTreeModel::PsTreeModel(QObject *parent) : QAbstractItemModel(parent), rootItem(get_process_records()) {}
|
||||||
|
|
||||||
|
QModelIndex PsTreeModel::index(int row, int column, const QModelIndex &parent) const {
|
||||||
|
if (!hasIndex(row, column, parent))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const ProcessTreeNode *parentItem = parent.isValid()
|
||||||
|
? static_cast<ProcessTreeNode *>(parent.internalPointer())
|
||||||
|
: &rootItem;
|
||||||
|
|
||||||
|
if (auto *childItem = parentItem->child(row))
|
||||||
|
return createIndex(row, column, childItem);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex PsTreeModel::parent(const QModelIndex &index) const {
|
||||||
|
if (!index.isValid())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto *childItem = static_cast<ProcessTreeNode *>(index.internalPointer());
|
||||||
|
ProcessTreeNode *parentItem = childItem->parentItem();
|
||||||
|
|
||||||
|
return parentItem != &rootItem
|
||||||
|
? createIndex(parentItem->row(), 0, parentItem) : QModelIndex{};
|
||||||
|
}
|
||||||
|
|
||||||
|
int PsTreeModel::rowCount(const QModelIndex &parent) const {
|
||||||
|
if (parent.column() > 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
const ProcessTreeNode *parentItem = parent.isValid()
|
||||||
|
? static_cast<const ProcessTreeNode *>(parent.internalPointer())
|
||||||
|
: &rootItem;
|
||||||
|
|
||||||
|
return parentItem->childCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
int PsTreeModel::columnCount(const QModelIndex &parent) const {
|
||||||
|
if (parent.isValid())
|
||||||
|
return static_cast<ProcessTreeNode *>(parent.internalPointer())->columnCount();
|
||||||
|
return ProcessTreeNode{}.columnCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant PsTreeModel::data(const QModelIndex &index, int role) const {
|
||||||
|
if (!index.isValid() || role != Qt::DisplayRole)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const auto *item = static_cast<const ProcessTreeNode *>(index.internalPointer());
|
||||||
|
return item->data(index.column());
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::ItemFlags PsTreeModel::flags(const QModelIndex &index) const {
|
||||||
|
return index.isValid()
|
||||||
|
? QAbstractItemModel::flags(index) : Qt::ItemFlags(Qt::NoItemFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant PsTreeModel::headerData(int section, Qt::Orientation orientation,
|
||||||
|
int role) const {
|
||||||
|
if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
|
||||||
|
return QVariant{};
|
||||||
|
|
||||||
|
return ProcessTreeNode{}.headerData(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PsTreeModel::update() {
|
||||||
|
auto changes = update_process_records(&rootItem);
|
||||||
|
|
||||||
|
QList<QPersistentModelIndex> changedParents{};
|
||||||
|
for(auto* node: changes) {
|
||||||
|
changedParents.push_back(createIndex(node->row(), 0, node));
|
||||||
|
}
|
||||||
|
layoutAboutToBeChanged(changedParents);
|
||||||
|
}
|
45
tree/PsTreeModel.h
Normal file
45
tree/PsTreeModel.h
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// Created by grimmauld on 25.02.24.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef SWAYMUX_PSTREEMODEL_H
|
||||||
|
#define SWAYMUX_PSTREEMODEL_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <QAbstractItemModel>
|
||||||
|
#include "pstree.h"
|
||||||
|
|
||||||
|
class PsTreeModel : public QAbstractItemModel {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
Q_DISABLE_COPY_MOVE(PsTreeModel)
|
||||||
|
|
||||||
|
explicit PsTreeModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
~PsTreeModel() override = default;
|
||||||
|
|
||||||
|
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation,
|
||||||
|
int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] QModelIndex index(int row, int column,
|
||||||
|
const QModelIndex &parent = {}) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] QModelIndex parent(const QModelIndex &index) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] int rowCount(const QModelIndex &parent = {}) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] int columnCount(const QModelIndex &parent = {}) const override;
|
||||||
|
|
||||||
|
void update();
|
||||||
|
|
||||||
|
private:
|
||||||
|
ProcessTreeNode rootItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //SWAYMUX_PSTREEMODEL_H
|
151
tree/pstree.cpp
Normal file
151
tree/pstree.cpp
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
//
|
||||||
|
// Created by grimmauld on 25.02.24.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "pstree.h"
|
||||||
|
#include "../util/StringUtil.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
const char *procFileDelim = ":";
|
||||||
|
|
||||||
|
void populateTree(ProcessTreeNode *node, std::map<pid_t, std::set<ProcessRecord>> &ppid_map, std::set<ProcessTreeNode*>& changedParents) {
|
||||||
|
if (node == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int i = (int) node->childCount() - 1; i >= 0; i--) { // backwards to avoid changing stuff that might change later
|
||||||
|
std::set<ProcessRecord> children = ppid_map[node->proc.ppid];
|
||||||
|
auto* c = node->child(i);
|
||||||
|
if (c->proc.pid == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (children.find(c->proc) == children.end()) {
|
||||||
|
// node->removeChild(i); // fixme: segfaults
|
||||||
|
changedParents.insert(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &child: ppid_map[node->proc.pid]) {
|
||||||
|
// check child exists, if so no need to update
|
||||||
|
auto* existingChildNode = node->findChild(ProcessTreeNode::matchingRecord(child));
|
||||||
|
if (existingChildNode != nullptr) {
|
||||||
|
populateTree(existingChildNode, ppid_map, changedParents);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// else add the new child node, keep track in notifier
|
||||||
|
changedParents.insert(node);
|
||||||
|
auto childNode = std::make_unique<ProcessTreeNode>(child, node);
|
||||||
|
populateTree(childNode.get(), ppid_map, changedParents);
|
||||||
|
node->appendChild(childNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessTreeNode get_process_records() {
|
||||||
|
ProcessTreeNode root = ProcessTreeNode(); // rootPrc
|
||||||
|
update_process_records(&root);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<ProcessTreeNode*> update_process_records(ProcessTreeNode *node) {
|
||||||
|
std::map<pid_t, ProcessRecord> pid_map;
|
||||||
|
std::map<pid_t, std::set<ProcessRecord>> ppid_map;
|
||||||
|
std::set<ProcessTreeNode*> changedParents{};
|
||||||
|
|
||||||
|
const auto proc_fs = fs::path(PROCFS_ROOT);
|
||||||
|
auto content = fs::directory_iterator(proc_fs);
|
||||||
|
for (const auto &proc_dir: content) {
|
||||||
|
if (fs::is_directory(proc_dir)) {
|
||||||
|
insert_process_record(proc_dir.path() / "status", pid_map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t rootPid = INT32_MAX;
|
||||||
|
for (auto &pair: pid_map) {
|
||||||
|
auto proc = pair.second;
|
||||||
|
if (proc.pid < rootPid) {
|
||||||
|
rootPid = proc.pid;
|
||||||
|
}
|
||||||
|
ppid_map[proc.ppid].insert(proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// auto rootPrc = pid_map[rootPid];
|
||||||
|
// ProcessTreeNode root = ProcessTreeNode(); // rootPrc
|
||||||
|
populateTree(node, ppid_map, changedParents);
|
||||||
|
return changedParents;
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert_process_record(const std::filesystem::path &status_path, std::map<pid_t, ProcessRecord> &buff) {
|
||||||
|
if (!fs::exists(status_path) || !fs::is_regular_file(status_path))
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::string proc_name;
|
||||||
|
pid_t pid, ppid;
|
||||||
|
bool foundPid, foundPPid, foundName;
|
||||||
|
|
||||||
|
auto reader = std::ifstream{status_path, std::ios::in};
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(reader, line)) {
|
||||||
|
auto data = line.data();
|
||||||
|
auto name = strtok(data, procFileDelim);
|
||||||
|
auto val = strtok(nullptr, procFileDelim);
|
||||||
|
if (name == nullptr || val == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (strcmp(name, "Name") == 0) {
|
||||||
|
proc_name = std::string(val);
|
||||||
|
trim(proc_name);
|
||||||
|
foundName = true;
|
||||||
|
} else if (strcmp(name, "Pid") == 0) {
|
||||||
|
pid = std::stoi(val);
|
||||||
|
foundPid = true;
|
||||||
|
} else if (strcmp(name, "PPid") == 0) {
|
||||||
|
ppid = std::stoi(val);
|
||||||
|
foundPPid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundPPid && foundName && foundPid) {
|
||||||
|
buff.insert({pid, ProcessRecord(proc_name, pid, ppid)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printTree(const ProcessTreeNode &node, const std::string &prefix) {
|
||||||
|
std::cout << prefix << node.proc.name << ": " << node.proc.pid << "\n";
|
||||||
|
for (const auto &child: node.children) {
|
||||||
|
printTree(*child, prefix + "\t");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printTree() {
|
||||||
|
printTree(get_process_records(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ProcessTreeNode::data(int column) const {
|
||||||
|
switch (column) {
|
||||||
|
case 0:
|
||||||
|
return QString::fromStdString(std::to_string(proc.pid));
|
||||||
|
case 1:
|
||||||
|
return QString::fromStdString(proc.name);
|
||||||
|
default:
|
||||||
|
throw std::exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ProcessTreeNode::headerData(int column) const {
|
||||||
|
switch (column) {
|
||||||
|
case 0:
|
||||||
|
return "PID";
|
||||||
|
case 1:
|
||||||
|
return "Process Name";
|
||||||
|
default:
|
||||||
|
return QVariant{};
|
||||||
|
}
|
||||||
|
}
|
76
tree/pstree.h
Normal file
76
tree/pstree.h
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// Created by grimmauld on 25.02.24.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef SWAYMUX_PSTREE_H
|
||||||
|
#define SWAYMUX_PSTREE_H
|
||||||
|
|
||||||
|
#define PROCFS_ROOT "/proc"
|
||||||
|
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <set>
|
||||||
|
#include <utility>
|
||||||
|
#include <map>
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
#include <QVariant>
|
||||||
|
#include "AbstractTreeNode.h"
|
||||||
|
|
||||||
|
void printTree();
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
class ProcessRecord {
|
||||||
|
public:
|
||||||
|
const std::string name;
|
||||||
|
const pid_t pid;
|
||||||
|
const pid_t ppid;
|
||||||
|
|
||||||
|
ProcessRecord(std::string name, pid_t pid, pid_t ppid) : name(std::move(name)), pid(pid), ppid(ppid) {}
|
||||||
|
|
||||||
|
ProcessRecord() : name("root"), pid(0), ppid(-1) {}
|
||||||
|
|
||||||
|
[[nodiscard]] bool operator<(const ProcessRecord &other) const {
|
||||||
|
return this->pid < other.pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool operator==(const ProcessRecord &other) const {
|
||||||
|
return this->pid == other.pid && this->ppid == other.ppid && this->name == other.name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessTreeNode : public AbstractTreeNode<ProcessTreeNode> {
|
||||||
|
public:
|
||||||
|
explicit ProcessTreeNode(ProcessTreeNode *parent = nullptr) : proc(ProcessRecord()), AbstractTreeNode(parent) {}
|
||||||
|
|
||||||
|
const ProcessRecord proc;
|
||||||
|
|
||||||
|
explicit ProcessTreeNode(ProcessRecord proc, ProcessTreeNode *parent = nullptr) : proc(std::move(proc)),
|
||||||
|
AbstractTreeNode(parent) {}
|
||||||
|
|
||||||
|
[[nodiscard]] bool operator<(const ProcessTreeNode &other) const {
|
||||||
|
return this->proc < other.proc;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] QVariant data(int column) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] QVariant headerData(int column) const override;
|
||||||
|
|
||||||
|
[[nodiscard]] int columnCount() const override { return 2; };
|
||||||
|
|
||||||
|
std::function<bool(const ProcessTreeNode &)> static matchingRecord(const ProcessRecord &other) {
|
||||||
|
return [&other](const ProcessTreeNode &node) {
|
||||||
|
return node.proc == other;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
ProcessTreeNode get_process_records();
|
||||||
|
|
||||||
|
void insert_process_record(const std::filesystem::path &status_path, std::map<pid_t, ProcessRecord> &buff);
|
||||||
|
|
||||||
|
std::set<ProcessTreeNode*> update_process_records(ProcessTreeNode *node);
|
||||||
|
|
||||||
|
#endif //SWAYMUX_PSTREE_H
|
13
util/StringUtil.h
Normal file
13
util/StringUtil.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// Created by grimmauld on 25.02.24.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
inline void trim(std::string & s) {
|
||||||
|
s.erase(std::remove_if(s.begin(), s.end(), ::isspace),
|
||||||
|
s.end());
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user