sway tree

This commit is contained in:
LordGrimmauld 2024-03-04 00:27:05 +01:00
parent f4a81cfa32
commit 4a6989f5ea
19 changed files with 570 additions and 95 deletions

View file

@ -22,6 +22,14 @@ set(PROJECT_SOURCES
${TS_FILES}
tree/pstree.cpp
tree/PsTreeModel.cpp
sway_bindings/Formatter.h
sway_bindings/Sway.cpp
tree/swaytree.h
tree/swaytree.cpp
tree/AbstractTreeModel.h
tree/AbstractTreeModel.h
tree/SwayTreeModel.h
tree/SwayTreeModel.cpp
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)

View file

@ -1,6 +1,6 @@
#include "mainwindow.h"
#include "tree/pstree.h"
#include "tree/PsTreeModel.h"
#include "sway_bindings/Sway.h"
#include "tree/swaytree.h"
#include <QApplication>
#include <QLocale>
@ -9,11 +9,23 @@
#include <QStringList>
#include <QScreen>
#include <QTreeView>
#include<iostream>
#include <sys/socket.h>
#include <sys/un.h>
using namespace Qt::StringLiterals;
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main(int argc, char *argv[])
{
auto sway = Sway();
auto resp = sway.sendIPC(SWAY_GET_TREE);
json rep = json::parse(resp.msg);
auto rootRecord = SwayRecord(rep);
QApplication a(argc, argv);
QCoreApplication::setOrganizationName("Grimmauld");
QCoreApplication::setApplicationName("SwayMux");

View file

@ -1,6 +1,7 @@
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "tree/PsTreeModel.h"
#include "tree/SwayTreeModel.h"
#include <QScreen>
#include <QTimer>
@ -8,7 +9,7 @@ MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow) {
ui->setupUi(this);
model = new PsTreeModel(ui->treeView);
model = new SwayTreeModel(ui->treeView);
modelUpdateTimer = new QTimer(this);
connect(modelUpdateTimer, &QTimer::timeout, this, QOverload<>::of(&MainWindow::update));
modelUpdateTimer->start(1000);

View file

@ -3,6 +3,7 @@
#include <QMainWindow>
#include "tree/PsTreeModel.h"
#include "tree/SwayTreeModel.h"
QT_BEGIN_NAMESPACE
namespace Ui {
@ -22,7 +23,7 @@ public:
private:
Ui::MainWindow *ui;
PsTreeModel* model;
SwayTreeModel* model;
QTimer* modelUpdateTimer;
};
#endif // MAINWINDOW_H

View file

@ -37,7 +37,7 @@
<widget class="QTreeView" name="treeView">
<property name="font">
<font>
<pointsize>24</pointsize>
<pointsize>20</pointsize>
</font>
</property>
</widget>

41
sway_bindings/Formatter.h Normal file
View file

@ -0,0 +1,41 @@
//
// Created by grimmauld on 03.03.24.
// https://stackoverflow.com/questions/12261915/how-to-throw-stdexceptions-with-variable-messages
//
#ifndef IPCSWAYTEST_FORMATTER_H
#define IPCSWAYTEST_FORMATTER_H
#include <stdexcept>
#include <sstream>
class Formatter
{
public:
Formatter() = default;
~Formatter() = default;
template <typename Type>
Formatter & operator << (const Type & value)
{
stream_ << value;
return *this;
}
std::string str() const { return stream_.str(); }
explicit operator std::string () const { return stream_.str(); }
enum ConvertToString
{
to_str
};
std::string operator >> (ConvertToString) { return stream_.str(); }
Formatter(const Formatter &) = delete;
private:
std::stringstream stream_;
// Formatter & operator = (Formatter &);
};
#endif //IPCSWAYTEST_FORMATTER_H

37
sway_bindings/Sway.cpp Normal file
View file

@ -0,0 +1,37 @@
//
// Created by grimmauld on 03.03.24.
//
#include "Sway.h"
swaymsg Sway::sendIPC(const swaymsg &query) const {
if (send(socketfd, query.asBuffer(), query.size(), 0) == -1) {
throw std::runtime_error("Swaysock: Error on send() call");
}
return swaymsg::fromSock(socketfd);
}
Sway::Sway() : socketfd(socket(AF_UNIX, SOCK_STREAM, 0)) {
initializeSocket();
}
void Sway::initializeSocket() const {
auto *sockaddr = std::getenv("SWAYSOCK");
if (sockaddr == nullptr) {
throw std::runtime_error("Binding socket: reading SWAYSOCK env var failed");
}
if (socketfd == -1) {
throw std::runtime_error("Binding socket: could not get file descriptor");
}
struct sockaddr_un swaysock{};
swaysock.sun_family = AF_UNIX;
strcpy(swaysock.sun_path, sockaddr);
auto dlen = strlen(swaysock.sun_path) + sizeof(swaysock.sun_family);
if (connect(socketfd, (struct sockaddr *) &swaysock, dlen) == -1) {
throw std::runtime_error("Binding socket: could not connect to socket");
}
}

23
sway_bindings/Sway.h Normal file
View file

@ -0,0 +1,23 @@
//
// Created by grimmauld on 03.03.24.
//
#ifndef IPCSWAYTEST_SWAY_H
#define IPCSWAYTEST_SWAY_H
#include "swaymsg.h"
class Sway {
protected:
const int socketfd;
public:
explicit Sway();
void initializeSocket() const;
[[nodiscard]] swaymsg sendIPC(const swaymsg& query) const;
};
#endif //IPCSWAYTEST_SWAY_H

103
sway_bindings/swaymsg.h Normal file
View file

@ -0,0 +1,103 @@
//
// Created by grimmauld on 03.03.24.
//
#ifndef IPCSWAYTEST_SWAYMSG_H
#define IPCSWAYTEST_SWAYMSG_H
#include <sys/socket.h>
#include <sys/un.h>
#include <cstring>
#include <cstdlib>
#include "Formatter.h"
struct swaymsg {
explicit swaymsg(const unsigned int payloadType, const char *msg) : swaymsg(payloadType, msg,
msg == nullptr ? 0 : strlen(msg)) {};
explicit swaymsg(const unsigned int payloadType, const char *msg, const unsigned int payload_len)
: payload_len(payload_len), payload_type(payloadType) {
this->msg = static_cast<char *>(std::malloc(payload_len));
std::memcpy(this->msg, msg, payload_len);
buff = new unsigned char [size()];
std::memcpy(buff, magic, strlen(magic));
std::memcpy(buff + (unsigned int) strlen(magic), &payload_len, sizeof(payload_len));
std::memcpy(buff + (unsigned int) strlen(magic) + sizeof(payload_len), &payload_type, sizeof(payload_type));
std::memcpy(buff + (unsigned int) strlen(magic) + sizeof(payload_len) + sizeof(payload_type), msg, payload_len);
}
explicit swaymsg(const unsigned int payloadType) : swaymsg(payloadType, nullptr) {}
[[nodiscard]] static swaymsg fromSock(const int sockfd) {
char buff[6];
size_t dlen;
if ((dlen = recv(sockfd, buff, 6, 0)) != 6) {
throw std::runtime_error(
Formatter() << "Swaysock: Unexpected length on recv() call for magic: " << dlen
>> Formatter::to_str);
}
if (strncmp(buff, magic, 6) != 0) {
throw std::runtime_error("Swaysock: magic did not match");
}
unsigned int payloadType;
unsigned int payloadLen;
static_assert(sizeof(payloadType) == 4);
static_assert(sizeof(payloadLen) == 4);
if ((dlen = recv(sockfd, &payloadLen, sizeof(payloadLen), 0)) != sizeof(payloadLen)) {
throw std::runtime_error(
Formatter() << "Swaysock: Unexpected length on recv() call for len: " << dlen >> Formatter::to_str);
}
if ((dlen = recv(sockfd, &payloadType, sizeof(payloadType), 0)) != sizeof(payloadType)) {
throw std::runtime_error(
Formatter() << "Swaysock: Unexpected length on recv() call for type: " << dlen
>> Formatter::to_str);
}
char msgBuff[payloadLen];
if ((dlen = recv(sockfd, msgBuff, payloadLen, 0)) != payloadLen) {
throw std::runtime_error(
Formatter() << "Swaysock: Unexpected length on recv() call for message: " << dlen
>> Formatter::to_str);
}
return swaymsg(payloadType, msgBuff, payloadLen);
};
constexpr const static char *magic = "i3-ipc";
const unsigned int payload_len;
const unsigned int payload_type;
char *msg;
unsigned char *buff = nullptr;
static_assert(sizeof(payload_len) == 4);
static_assert(sizeof(payload_type) == 4);
[[nodiscard]] size_t size() const {
return (int) strlen(magic) + sizeof(payload_len) + sizeof(payload_type) + payload_len;
}
[[nodiscard]] void *asBuffer() const {
return buff;
}
~swaymsg() {
delete buff;
delete msg;
}
};
static const swaymsg SWAY_EXIT = swaymsg(0, "exit");
static const swaymsg SWAY_GET_WORKSPACES = swaymsg(1);
static const swaymsg SWAY_GET_OUTPUTS = swaymsg(3);
static const swaymsg SWAY_GET_TREE = swaymsg(4);
static const swaymsg SWAY_GET_VERSION = swaymsg(7);
static const swaymsg SWAY_GET_CONFIG = swaymsg(9);
static const swaymsg SWAY_GET_INPUTS = swaymsg(100);
#endif //IPCSWAYTEST_SWAYMSG_H

91
tree/AbstractTreeModel.h Normal file
View file

@ -0,0 +1,91 @@
//
// Created by grimmauld on 03.03.24.
//
#ifndef SWAYMUX_ABSTRACTTREEMODEL_H
#define SWAYMUX_ABSTRACTTREEMODEL_H
#include <type_traits>
#include "AbstractTreeNode.h"
#include <QAbstractItemModel>
template<class T>
class AbstractTreeModel : public QAbstractItemModel {
static_assert(std::is_base_of<AbstractTreeNode<T>, T>::value,
"Tree model subject class is not derived from AbstractTreeNode");
public:
Q_DISABLE_COPY_MOVE(AbstractTreeModel)
explicit AbstractTreeModel(QObject *parent = nullptr): QAbstractItemModel(parent) {};
~AbstractTreeModel() override = default;
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override {
if (!index.isValid() || role != Qt::DisplayRole)
return {};
const auto *item = static_cast<const T *>(index.internalPointer());
return item->data(index.column());
}
[[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override {
return index.isValid()
? QAbstractItemModel::flags(index) : Qt::ItemFlags(Qt::NoItemFlags);
}
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override {
if (orientation != Qt::Horizontal || role != Qt::DisplayRole)
return QVariant{};
return T{}.headerData(section);
}
[[nodiscard]] QModelIndex index(int row, int column,
const QModelIndex &parent = {}) const override {
if (!hasIndex(row, column, parent))
return {};
const T *parentItem = parent.isValid()
? static_cast<T *>(parent.internalPointer())
: getRoot();
if (auto *childItem = parentItem->child(row))
return createIndex(row, column, childItem);
return {};
}
[[nodiscard]] QModelIndex parent(const QModelIndex &index) const override {
if (!index.isValid())
return {};
auto *childItem = static_cast<T *>(index.internalPointer());
T *parentItem = childItem->parentItem();
return parentItem != getRoot()
? createIndex(parentItem->row(), 0, parentItem) : QModelIndex{};
}
[[nodiscard]] int rowCount(const QModelIndex &parent = {}) const override {
if (parent.column() > 0)
return 0;
const T *parentItem = parent.isValid()
? static_cast<const T *>(parent.internalPointer())
: getRoot();
return parentItem->childCount();
}
[[nodiscard]] int columnCount(const QModelIndex &parent = {}) const override {
if (parent.isValid())
return static_cast<T *>(parent.internalPointer())->columnCount();
return T{}.columnCount();
}
[[nodiscard]] virtual const T* getRoot() const = 0;
};
#endif //SWAYMUX_ABSTRACTTREEMODEL_H

View file

@ -14,7 +14,6 @@
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{};

View file

@ -4,76 +4,17 @@
#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);
/*
auto* root = getRoot();
auto changes = update_process_records(root);
QList<QPersistentModelIndex> changedParents{};
for(auto* node: changes) {
changedParents.push_back(createIndex(node->row(), 0, node));
}
layoutAboutToBeChanged(changedParents);
*/
}
PsTreeModel::PsTreeModel(QObject *parent) : PsTreeModel(get_process_records(), parent) {}

View file

@ -8,10 +8,10 @@
#include <QAbstractItemModel>
#include "pstree.h"
#include "AbstractTreeModel.h"
class PsTreeModel : public QAbstractItemModel {
class PsTreeModel : public AbstractTreeModel<ProcessTreeNode> {
Q_OBJECT
public:
Q_DISABLE_COPY_MOVE(PsTreeModel)
@ -19,26 +19,16 @@ public:
~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;
explicit PsTreeModel(ProcessTreeNode * rootItem, QObject *parent = nullptr) : rootItem(rootItem), AbstractTreeModel<ProcessTreeNode>(parent) {};
void update();
[[nodiscard]] const ProcessTreeNode *getRoot() const override {
return rootItem;
};
private:
ProcessTreeNode rootItem;
const ProcessTreeNode* rootItem;
};

5
tree/SwayTreeModel.cpp Normal file
View file

@ -0,0 +1,5 @@
//
// Created by grimmauld on 03.03.24.
//
#include "SwayTreeModel.h"

49
tree/SwayTreeModel.h Normal file
View file

@ -0,0 +1,49 @@
//
// Created by grimmauld on 03.03.24.
//
#ifndef SWAYMUX_SWAYTREEMODEL_H
#define SWAYMUX_SWAYTREEMODEL_H
#include <QAbstractItemModel>
#include <utility>
#include "pstree.h"
#include "AbstractTreeModel.h"
#include "swaytree.h"
#include "../sway_bindings/Sway.h"
#include <nlohmann/json.hpp>
#include <utility>
using json = nlohmann::json;
class SwayTreeModel : public AbstractTreeModel<SwayTreeNode> {
Q_OBJECT
public:
inline static Sway sway = Sway();
Q_DISABLE_COPY_MOVE(SwayTreeModel)
explicit SwayTreeModel(QObject *parent = nullptr) : AbstractTreeModel<SwayTreeNode>(parent) {
auto res = sway.sendIPC(SWAY_GET_TREE);
auto rep = json::parse(res.msg);
rootItem = new SwayTreeNode(rep);
}
~SwayTreeModel() override = default;
explicit SwayTreeModel(SwayTreeNode * rootItem, QObject *parent = nullptr) : rootItem(rootItem),
AbstractTreeModel<SwayTreeNode>(
parent) {};
[[nodiscard]] const SwayTreeNode *getRoot() const override {
return rootItem;
};
private:
SwayTreeNode * rootItem;
};
#endif //SWAYMUX_SWAYTREEMODEL_H

View file

@ -48,9 +48,9 @@ void populateTree(ProcessTreeNode *node, std::map<pid_t, std::set<ProcessRecord>
}
}
ProcessTreeNode get_process_records() {
ProcessTreeNode root = ProcessTreeNode(); // rootPrc
update_process_records(&root);
ProcessTreeNode * get_process_records() {
auto* root = new ProcessTreeNode(); // rootPrc
update_process_records(root);
return root;
}
@ -125,7 +125,9 @@ void printTree(const ProcessTreeNode &node, const std::string &prefix) {
}
void printTree() {
printTree(get_process_records(), "");
auto* tree = get_process_records();
printTree(*tree, "");
delete tree;
}
QVariant ProcessTreeNode::data(int column) const {

View file

@ -29,7 +29,7 @@ public:
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) {}
ProcessRecord() : name("rootItem"), pid(0), ppid(-1) {}
[[nodiscard]] bool operator<(const ProcessRecord &other) const {
return this->pid < other.pid;
@ -43,6 +43,9 @@ public:
class ProcessTreeNode : public AbstractTreeNode<ProcessTreeNode> {
public:
ProcessTreeNode(ProcessTreeNode& node) = delete; // default constructor to make the static asserts shut up
ProcessTreeNode(const ProcessTreeNode& node) = delete; // default constructor to make the static asserts shut up
explicit ProcessTreeNode(ProcessTreeNode *parent = nullptr) : proc(ProcessRecord()), AbstractTreeNode(parent) {}
const ProcessRecord proc;
@ -67,7 +70,7 @@ public:
};
};
ProcessTreeNode get_process_records();
ProcessTreeNode * get_process_records();
void insert_process_record(const std::filesystem::path &status_path, std::map<pid_t, ProcessRecord> &buff);

14
tree/swaytree.cpp Normal file
View file

@ -0,0 +1,14 @@
//
// Created by grimmauld on 03.03.24.
//
#include "swaytree.h"
QVariant SwayTreeNode::data(int column) const {
// TODO: maybe add a more elaborate representation
return QString::fromStdString(node.name);
}
QVariant SwayTreeNode::headerData(int column) const {
return "Title";
}

155
tree/swaytree.h Normal file
View file

@ -0,0 +1,155 @@
//
// Created by grimmauld on 03.03.24.
//
#ifndef SWAYMUX_SWAYTREE_H
#define SWAYMUX_SWAYTREE_H
#include <nlohmann/json.hpp>
#include <utility>
using json = nlohmann::json;
#include "AbstractTreeNode.h"
namespace NodeType {
enum NodeType {
root, output, workspace, con, floating_con
};
static NodeType fromString(const std::string &str) {
if (str == "output") {
return output;
}
if (str == "workspace") {
return workspace;
}
if (str == "con") {
return con;
}
if (str == "floating_con") {
return floating_con;
}
return root;
}
}
namespace NodeLayout {
enum NodeLayout {
splith, splitv, stacked, tabbed, output
};
static NodeLayout fromString(const std::string &str) {
if (str == "splith") {
return splith;
}
if (str == "splitv") {
return splitv;
}
if (str == "stacked") {
return stacked;
}
if (str == "tabbed") {
return tabbed;
}
return output;
}
}
namespace NodeOrientation {
enum NodeOrientation {
vertical, horizontal, none
};
static NodeOrientation fromString(const std::string &str) {
if (str == "vertical") {
return vertical;
}
if (str == "horizontal") {
return horizontal;
}
return none;
}
}
struct SwayRecord {
explicit SwayRecord() :
id(0),
name("rootItem"),
type(NodeType::root),
current_border_width(0),
layout(NodeLayout::output),
orientation(NodeOrientation::none),
urgent(false),
sticky(false),
focused(false) {}
explicit SwayRecord(const json &rep) :
id(rep["id"]),
name(rep["name"]),
type(NodeType::fromString(rep["type"])),
border(rep["border"]),
current_border_width(rep["current_border_width"]),
layout(NodeLayout::fromString((rep["layout"]))),
orientation(NodeOrientation::fromString(rep["orientation"])),
urgent(rep["urgent"]),
sticky(rep["sticky"]),
focused(rep["focused"]) {};
const int id;
const std::string name;
const NodeType::NodeType type;
const std::string border;
const int current_border_width;
const NodeLayout::NodeLayout layout;
const NodeOrientation::NodeOrientation orientation;
const bool urgent;
const bool sticky;
const bool focused;
[[nodiscard]] bool operator<(const SwayRecord &other) const {
return this->id < other.id;
}
[[nodiscard]] bool operator==(const SwayRecord &other) const {
return this->id == other.id;
}
};
class SwayTreeNode : public AbstractTreeNode<SwayTreeNode> {
public:
const SwayRecord node;
explicit SwayTreeNode(const json &rep, SwayTreeNode *parent = nullptr) : node(SwayRecord(rep)),
AbstractTreeNode(parent) {
for (const auto &child: rep["nodes"]) {
auto childNode = std::make_unique<SwayTreeNode>(child, this);
this->appendChild(childNode);
}
/*
for (const auto &child: rep["floating_nodes"]) {
auto childNode = std::make_unique<SwayTreeNode>(child, this);
this->appendChild(childNode);
}
*/
}
explicit SwayTreeNode(SwayTreeNode *parent = nullptr) : node(SwayRecord()), AbstractTreeNode(parent) {};
explicit SwayTreeNode(SwayRecord node, SwayTreeNode *parent = nullptr) : node(std::move(node)),
AbstractTreeNode(parent) {};
[[nodiscard]] bool operator<(const SwayTreeNode &other) const {
return this->node < other.node;
}
[[nodiscard]] QVariant data(int column) const override;
[[nodiscard]] QVariant headerData(int column) const override;
[[nodiscard]] int columnCount() const override { return 1; };
};
#endif //SWAYMUX_SWAYTREE_H