diff --git a/.gitignore b/.gitignore index f531417..1ece43c 100644 --- a/.gitignore +++ b/.gitignore @@ -75,4 +75,5 @@ cmake-build-debug # -------- *.dll *.exe +build diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d3bdca..c5c0ca2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ set(PROJECT_SOURCES Keys/CloseSwaymuxKeyListener.h Keys/CloseSwaymuxKeyListener.h sway_bindings/swayoutputs.h + Keys/SwitchToKeybindListener.h ) if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) diff --git a/Keys/CreateWorkspaceKeyListener.h b/Keys/CreateWorkspaceKeyListener.h index 46e4df3..962e444 100644 --- a/Keys/CreateWorkspaceKeyListener.h +++ b/Keys/CreateWorkspaceKeyListener.h @@ -36,7 +36,7 @@ public: int i = 1; // workspace 1 is the first one. We don't expect our users to be programmers. for (; used_workspaces.contains(std::to_string(i)); ++i) {} cmd << "workspace number " << i; - auto resp = SwayTreeModel::sway.sendIPC(swaymsg(0, cmd.str().c_str(), cmd.str().length())); + auto resp = SwayTreeModel::sway.sendIPC(swaymsg(0, cmd.str())); std::cout << resp.msg << "\n"; QApplication::quit(); } @@ -52,7 +52,7 @@ public: [[nodiscard]] bool canAcceptKey(int key) const override { return key == Qt::Key_C; } - +private: SwayTreeModel *swayTreeModel; QTreeView *treeView; }; diff --git a/Keys/SwitchToKeybindListener.h b/Keys/SwitchToKeybindListener.h new file mode 100644 index 0000000..d99b2f4 --- /dev/null +++ b/Keys/SwitchToKeybindListener.h @@ -0,0 +1,101 @@ +// +// Created by grimmauld on 08.03.24. +// + +#ifndef SWAYMUX_SWITCHTOKEYBINDLISTENER_H +#define SWAYMUX_SWITCHTOKEYBINDLISTENER_H + +#include +#include +#include "AbstractKeyListener.h" +#include "../tree/SwayTreeModel.h" +#include "../sway_bindings/swayoutputs.h" + +class SwitchToKeybindListener : public AbstractKeyListener { +public: + explicit SwitchToKeybindListener(SwayTreeModel *swayTreeModel, QTreeView *treeView) : swayTreeModel( + swayTreeModel), treeView(treeView) {} + + void handleKeyEvent(const QKeyEvent *keyEvent) const override { + auto *root = swayTreeModel->getRoot(); + if (root == nullptr) + return; + + Formatter cmd; + auto selectedNodes = swayTreeModel->getSelectedNodes(treeView); + if (selectedNodes.empty()) + return; + + auto availableOutputs = SwayOutputList(SwayTreeModel::sway); + if (selectedNodes.size() == 1) { + auto *selected = *selectedNodes.begin(); + if (availableOutputs.isOutputValid(selected->findOutput())) { + switch (selected->node.type) { + case NodeType::output: + cmd << "focus output " << selected->node.name; + break; + case NodeType::workspace: + cmd << "workspace " << selected->node.name; + break; + case NodeType::con: + case NodeType::floating_con: + cmd << "[con_id=" << selected->node.id << "] focus"; + break; + case NodeType::root: + return; // this should never happen! isOutputValid should guard against this. + } + std::cout << cmd.str() << "\n"; + auto resp = SwayTreeModel::sway.sendIPC(swaymsg(0, cmd.str())); + std::cout << resp.msg << "\n"; + QApplication::quit(); + return; + } + } + + auto used_workspaces = root->accumulateWorkspaces(); + int newWorkspace = 1; // workspace 1 is the first one. We don't expect our users to be programmers. + for (; used_workspaces.contains(std::to_string(newWorkspace)); ++newWorkspace) {} + + + std::set containersToMove; + for (auto* container: selectedNodes) { + auto containers = container->accumulateContainers(); + for (auto* containerToMove : container->accumulateContainers()) { + containersToMove.insert(containerToMove->node.id); + } + } + + if (containersToMove.empty()) + return; + + for (auto id: containersToMove) { + cmd << "[con_id=" << id << "] focus , move container workspace " << newWorkspace << " ; "; + } + + cmd << "workspace number " << newWorkspace; + + std::cout << cmd.str() << "\n"; + auto resp = SwayTreeModel::sway.sendIPC(swaymsg(0, cmd.str())); + std::cout << resp.msg << "\n"; + QApplication::quit(); + } + + std::string getDescription() override { + return "Switch to selected container. If multiple containers or a container from scratchpad is selected,\ncreate new workspace and move all windows that are part of the selection to it."; + } + + std::string getKeyText() override { + return "ENTER"; + } + + [[nodiscard]] bool canAcceptKey(int key) const override { + return key == Qt::Key_Enter || key == Qt::Key_Return; + } + +private: + SwayTreeModel *swayTreeModel; + QTreeView *treeView; +}; + + +#endif //SWAYMUX_SWITCHTOKEYBINDLISTENER_H diff --git a/mainwindow.cpp b/mainwindow.cpp index 511faf0..44a6b2d 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -5,6 +5,7 @@ #include "Keys/CloseHelpKeyListener.h" #include "Keys/CreateWorkspaceKeyListener.h" #include "Keys/CloseSwaymuxKeyListener.h" +#include "Keys/SwitchToKeybindListener.h" #include #include @@ -32,6 +33,7 @@ MainWindow::MainWindow(QWidget *parent) swayTreeKeyHandler->addListener(new HelpKeyListener(ui->help_page)); swayTreeKeyHandler->addListener(new CloseSwaymuxKeyListener()); swayTreeKeyHandler->addListener(new CreateWorkspaceKeyListener(model, ui->treeView)); + swayTreeKeyHandler->addListener(new SwitchToKeybindListener(model, ui->treeView)); closeHelpKeyHandler = new KeyHandler(ui->tree_page); closeHelpKeyHandler->addListener(new CloseHelpKeyListener(ui->tree_page)); diff --git a/sway_bindings/swaymsg.h b/sway_bindings/swaymsg.h index 50eff16..8024bd4 100644 --- a/sway_bindings/swaymsg.h +++ b/sway_bindings/swaymsg.h @@ -15,6 +15,8 @@ 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 std::string& msg) : swaymsg(payloadType, msg.c_str(), msg.length()) {}; + explicit swaymsg(const unsigned int payloadType, const char *msg, const unsigned int payload_len) : payload_len(payload_len), payload_type(payloadType) { diff --git a/tree/AbstractTreeNode.h b/tree/AbstractTreeNode.h index ec27701..dc70af3 100644 --- a/tree/AbstractTreeNode.h +++ b/tree/AbstractTreeNode.h @@ -11,6 +11,7 @@ #include #include #include +#include template class AbstractTreeNode { @@ -56,6 +57,21 @@ public: return nullptr; }; + [[nodiscard]] std::set accumulateMatchingChildrenRecursive(std::function test) const { + std::set matching; + if (test((const T *) this)) { + matching.insert((T *) this); + } + + for (int i = 0; i < childCount(); ++i) { + auto *c = child(i); + for(T * element: c->accumulateMatchingChildrenRecursive(test)) { + matching.insert(element); + } + } + return matching; + } + [[nodiscard]] const T *findChildRecursive(std::function test) const { if (test((const T *) this)) return (const T*) this; diff --git a/tree/swaytree.h b/tree/swaytree.h index 40b0dc8..be73449 100644 --- a/tree/swaytree.h +++ b/tree/swaytree.h @@ -7,6 +7,7 @@ #include #include +#include using json = nlohmann::json; @@ -96,7 +97,6 @@ struct SwayRecord { urgent(rep["urgent"]), sticky(rep["sticky"]), focused(rep["focused"]) {}; - const int id; const std::string name; const NodeType::NodeType type; @@ -170,24 +170,29 @@ public: return this->findParentRecursive(matchNodeType(NodeType::workspace)); } - [[nodiscard]] std::set accumulateWorkspaces() const { - std::set workspaces; - if (node.type == NodeType::workspace) { - workspaces.insert(node.name); - return workspaces; // assumption: no workspaces within workspaces. This might be dangerous for the future, but is currently not a sway feature. - } + [[nodiscard]] inline std::set accumulateContainers() const { + return accumulateMatchingChildrenRecursive(matchNodeType({NodeType::con, NodeType::floating_con})); + } - for (int i = 0; i < childCount(); ++i) { - auto child_workspaces = child(i)->accumulateWorkspaces(); - workspaces.insert(child_workspaces.cbegin(), child_workspaces.cend()); - } - return workspaces; + [[nodiscard]] std::set accumulateWorkspaces() const { + auto workspaces = accumulateMatchingChildrenRecursive(matchNodeType(NodeType::workspace)); + std::set result; + std::transform(workspaces.begin(), workspaces.end(), std::inserter(result, result.begin()), + [](const SwayTreeNode *workspace) { return workspace->node.name; }); + return result; } private: - [[nodiscard]] static std::function matchNodeType(const NodeType::NodeType type) { - return [type](const SwayTreeNode *testNode) { - return testNode->node.type == type; + [[nodiscard]] static std::function matchNodeType(NodeType::NodeType type) { + return matchNodeType(std::vector{type}); + } + + [[nodiscard]] static std::function + matchNodeType(const std::vector& types) { + return [types](const SwayTreeNode *testNode) { + return std::ranges::any_of(types.begin(), types.end(), [testNode](const auto t) { + return testNode->node.type == t; + }); }; } };