diff --git a/CMakeLists.txt b/CMakeLists.txt index c8de539..6294380 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) -project(swaymux VERSION 0.1 LANGUAGES CXX) +project(swaymux VERSION 1.0 LANGUAGES CXX) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) diff --git a/Keys/CloseKeyListener.h b/Keys/CloseKeyListener.h index d42dba8..7f9547b 100644 --- a/Keys/CloseKeyListener.h +++ b/Keys/CloseKeyListener.h @@ -14,14 +14,14 @@ public: explicit CloseKeyListener(MainWindow *pWindow) : MainWindowAwareKeyListener(pWindow) {} void handleKeyEvent(const QKeyEvent *keyEvent) const override { - auto containersToKill = getSelectedContainerIds(); + auto containersToKill = getSelectedContainers(); if (containersToKill.empty()) return; Formatter cmd; - for (auto id: containersToKill) { - cmd << "[con_id=" << id << "] kill ; "; + for (auto node: containersToKill) { + cmd << "[con_id=" << node->node.id << "] kill ; "; } std::cout << cmd.str() << "\n"; diff --git a/Keys/CreateWorkspaceKeyListener.h b/Keys/CreateWorkspaceKeyListener.h index 9ebd673..f881c75 100644 --- a/Keys/CreateWorkspaceKeyListener.h +++ b/Keys/CreateWorkspaceKeyListener.h @@ -23,7 +23,7 @@ public: if (selectedOutput != nullptr) cmd << "focus output " << selectedOutput->node.name << " , "; - cmd << "workspace number " << createNewWorkspaceId(); + cmd << "workspace " << createNewWorkspaceId({}); auto resp = SwayTreeModel::sway.sendIPC(swaymsg(0, cmd.str())); std::cout << resp.msg << "\n"; QApplication::quit(); diff --git a/Keys/MainWindowAwareKeyListener.h b/Keys/MainWindowAwareKeyListener.h index 1a48201..d201824 100644 --- a/Keys/MainWindowAwareKeyListener.h +++ b/Keys/MainWindowAwareKeyListener.h @@ -18,28 +18,57 @@ protected: window->updateModel(); } - [[nodiscard]] std::set getSelectedContainerIds() const { - std::set containerIds; + [[nodiscard]] std::set getSelectedContainers() const { + std::set containerIds; auto selectedNodes = getModel()->getSelectedNodes(getTreeView()); for (auto *container: selectedNodes) { auto containers = container->accumulateContainers(); for (auto *containerToMove: container->accumulateContainers()) { - containerIds.insert(containerToMove->node.id); + containerIds.insert(containerToMove); } } return containerIds; } - [[nodiscard]] int createNewWorkspaceId() const { - auto used_workspaces = getModel()->getRoot()->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) {} - return newWorkspace; + [[nodiscard]] std::string createNewWorkspaceId(const std::set &allowedContainers) const { + std::set candidates; + + for (auto *con: allowedContainers) { + const auto *workspace = con->findWorkspace(); + if (workspace == nullptr) + continue; + + if (allowedContainers.contains(workspace)) { + candidates.insert(workspace->node.name); + continue; + } + + auto containers = workspace->accumulateContainers(); + if (std::all_of(containers.begin(), containers.end(), [allowedContainers](const SwayTreeNode *container) { + return allowedContainers.contains(container); + })) { + candidates.insert(workspace->node.name); + } + } + + if (!candidates.empty()) { + auto res = std::min_element(candidates.begin(), candidates.end()); + return *res; + } + + int newWorkspace = 0; + SwayTreeNode* workspaceNode; + do { + newWorkspace++; + workspaceNode = getModel()->getRoot()->findWorkspaceByName(std::to_string(newWorkspace)); + } while (workspaceNode != nullptr && workspaceNode->childCount() != 0); + + return std::to_string(newWorkspace); } - [[nodiscard]] const SwayTreeNode* findSelectedValidOutput() const { + [[nodiscard]] const SwayTreeNode *findSelectedValidOutput() const { auto availableOutputs = SwayOutputList(SwayTreeModel::sway); auto selectedNodes = getModel()->getSelectedNodes(getTreeView()); @@ -56,11 +85,11 @@ protected: protected: MainWindow *window; - [[nodiscard]] SwayTreeModel* getModel() const { + [[nodiscard]] SwayTreeModel *getModel() const { return this->window->model; } - [[nodiscard]] QTreeView* getTreeView() const { + [[nodiscard]] QTreeView *getTreeView() const { return window->ui->treeView; } }; diff --git a/Keys/MoveToScratchpadKeyListener.h b/Keys/MoveToScratchpadKeyListener.h index 3090c0a..01ce8ff 100644 --- a/Keys/MoveToScratchpadKeyListener.h +++ b/Keys/MoveToScratchpadKeyListener.h @@ -16,14 +16,14 @@ public: explicit MoveToScratchpadKeyListener(MainWindow *pWindow) : MainWindowAwareKeyListener(pWindow) {} void handleKeyEvent(const QKeyEvent *keyEvent) const override { - std::set containersToScratch = getSelectedContainerIds(); + auto containersToScratch = getSelectedContainers(); if (containersToScratch.empty()) return; Formatter cmd; - for (auto id: containersToScratch) { - cmd << "[con_id=" << id << "] move scratchpad ; "; + for (auto node: containersToScratch) { + cmd << "[con_id=" << node->node.id << "] move scratchpad ; "; } std::cout << cmd.str() << "\n"; diff --git a/Keys/RestoreKeyListener.h b/Keys/RestoreKeyListener.h index e3d16db..0acf6c3 100644 --- a/Keys/RestoreKeyListener.h +++ b/Keys/RestoreKeyListener.h @@ -16,16 +16,16 @@ public: explicit RestoreKeyListener(MainWindow *pWindow) : MainWindowAwareKeyListener(pWindow) {} void handleKeyEvent(const QKeyEvent *keyEvent) const override { - std::set containersToRestore = getSelectedContainerIds(); + auto containersToRestore = getSelectedContainers(); if (containersToRestore.empty()) return; Formatter cmd; - int newWorkspaceId = createNewWorkspaceId(); + auto newWorkspaceId = createNewWorkspaceId(containersToRestore); - for (auto id: containersToRestore) { - cmd << "[con_id=" << id << "] move workspace " << newWorkspaceId << " ; "; + for (auto node: containersToRestore) { + cmd << "[con_id=" << node->node.id << "] move workspace " << newWorkspaceId << " ; "; } std::cout << cmd.str() << "\n"; diff --git a/Keys/SwitchToKeybindListener.h b/Keys/SwitchToKeybindListener.h index 1f28272..97c55d3 100644 --- a/Keys/SwitchToKeybindListener.h +++ b/Keys/SwitchToKeybindListener.h @@ -11,18 +11,17 @@ #include "../tree/SwayTreeModel.h" #include "../sway_bindings/swayoutputs.h" -class SwitchToKeybindListener : public AbstractKeyListener { +class SwitchToKeybindListener : public MainWindowAwareKeyListener { public: - explicit SwitchToKeybindListener(SwayTreeModel *swayTreeModel, QTreeView *treeView) : swayTreeModel( - swayTreeModel), treeView(treeView) {} + explicit SwitchToKeybindListener(MainWindow *pWindow) : MainWindowAwareKeyListener(pWindow) {} void handleKeyEvent(const QKeyEvent *keyEvent) const override { - auto *root = swayTreeModel->getRoot(); + auto *root = getModel()->getRoot(); if (root == nullptr) return; Formatter cmd; - auto selectedNodes = swayTreeModel->getSelectedNodes(treeView); + auto const selectedNodes = getModel()->getSelectedNodes(getTreeView()); if (selectedNodes.empty()) return; @@ -52,10 +51,7 @@ public: } } - 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) {} - + auto newWorkspace = createNewWorkspaceId(selectedNodes); std::set containersToMove; for (auto* container: selectedNodes) { @@ -72,7 +68,7 @@ public: cmd << "[con_id=" << id << "] move container workspace " << newWorkspace << " ; "; } - cmd << "workspace number " << newWorkspace; + cmd << "workspace " << newWorkspace; std::cout << cmd.str() << "\n"; auto resp = SwayTreeModel::sway.sendIPC(swaymsg(0, cmd.str())); @@ -91,10 +87,6 @@ public: [[nodiscard]] bool canAcceptKey(int key) const override { return key == Qt::Key_Enter || key == Qt::Key_Return; } - -private: - SwayTreeModel *swayTreeModel; - QTreeView *treeView; }; diff --git a/README.md b/README.md index 670f5e6..3f46cb7 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ Swaymux aims at making navigation in sway easier. To achieve this, swaymux provi This main interface is primarily inspired by tmux. Swaymux can be navigated using the keyboard, with keybindings that closely follow the tmux bindings. -This project is *not* feature-complete, far from it. So far swaymux supports listing and navigating into outputs, workspaces and containers, creating a new workspace, or creating a workspace with all currently marked containers. +This project is *not* feature-complete, far from it. So far swaymux supports listing and navigating into outputs, workspaces and containers, creating a new workspace, or creating a workspace with all currently marked containers. The user can close applications without first focusing their windows, as well as view debug information (e.g. whether a window uses native wayland or xwayland). The scratchpad is supported too, both for dumping and retrieving windows from swaymux. -Planned features and fixes include closing windows from within swaymux without specifically jumping to them, better keyboard navigation through the sway tree in the main interface, a closer look at performance tweaking, as well as maybe even iconification of containers where applicable. +Planned features and fixes include a closer look at performance tweaking, as well as maybe iconification of containers where applicable. # Installing diff --git a/flake.lock b/flake.lock index 078af2f..cc34a11 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1709968316, - "narHash": "sha256-4rZEtEDT6jcgRaqxsatBeds7x1PoEiEjb6QNGb4mNrk=", + "lastModified": 1711370797, + "narHash": "sha256-2xu0jVSjuKhN97dqc4bVtvEH52Rwh6+uyI1XCnzoUyI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "0e7f98a5f30166cbed344569426850b21e4091d4", + "rev": "c726225724e681b3626acc941c6f95d2b0602087", "type": "github" }, "original": { diff --git a/mainwindow.cpp b/mainwindow.cpp index 54d3a44..9f14954 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -30,7 +30,7 @@ MainWindow::MainWindow(QWidget *parent) swayTreeKeyHandler->addListener(new HelpKeyListener(ui->help_page)); swayTreeKeyHandler->addListener(new CloseSwaymuxKeyListener()); swayTreeKeyHandler->addListener(new CreateWorkspaceKeyListener(this)); - swayTreeKeyHandler->addListener(new SwitchToKeybindListener(model, ui->treeView)); + swayTreeKeyHandler->addListener(new SwitchToKeybindListener(this)); swayTreeKeyHandler->addListener(new PrevOutputKeyListener(model, ui->treeView)); swayTreeKeyHandler->addListener(new NextOutputKeyListener(model, ui->treeView)); swayTreeKeyHandler->addListener(new CloseKeyListener(this)); diff --git a/sway_bindings/SwayRecords.h b/sway_bindings/SwayRecords.h index 838b3d0..0477b8d 100644 --- a/sway_bindings/SwayRecords.h +++ b/sway_bindings/SwayRecords.h @@ -55,6 +55,10 @@ namespace NodeType { static QString toQString(NodeType nodeType) { return QString::fromStdString(toString(nodeType)); } + + [[nodiscard]] static inline bool isContainer(NodeType type) { + return type == con || type == floating_con; + } } namespace NodeLayout { diff --git a/tree/SwayTreeModel.cpp b/tree/SwayTreeModel.cpp index 731987f..3697826 100644 --- a/tree/SwayTreeModel.cpp +++ b/tree/SwayTreeModel.cpp @@ -19,8 +19,8 @@ QModelIndex SwayTreeModel::findIndexByNode(const SwayTreeNode* node) const { return idx; } -std::set SwayTreeModel::getSelectedNodes(const QTreeView *treeView) const { - std::set selectedNodes; +std::set SwayTreeModel::getSelectedNodes(const QTreeView *treeView) const { + std::set selectedNodes; if (rootItem == nullptr) return selectedNodes; diff --git a/tree/SwayTreeModel.h b/tree/SwayTreeModel.h index 56186a5..c111fcd 100644 --- a/tree/SwayTreeModel.h +++ b/tree/SwayTreeModel.h @@ -50,7 +50,7 @@ public: return findIndexByNode(getRoot()->findFocused()); } - [[nodiscard]] std::set getSelectedNodes(const QTreeView *treeView) const; + [[nodiscard]] std::set getSelectedNodes(const QTreeView *treeView) const; [[nodiscard]] SwayTreeNode *getSelectedNode(const QModelIndex &i) const; diff --git a/tree/swaytree.h b/tree/swaytree.h index 38af2bc..ac52818 100644 --- a/tree/swaytree.h +++ b/tree/swaytree.h @@ -73,8 +73,12 @@ public: return accumulateMatchingChildrenRecursive(matchNodeType({NodeType::con, NodeType::floating_con})); } - [[nodiscard]] std::set accumulateWorkspaces() const { - auto workspaces = accumulateMatchingChildrenRecursive(matchNodeType(NodeType::workspace)); + [[nodiscard]] std::set accumulateWorkspaces() const { + return accumulateMatchingChildrenRecursive(matchNodeType(NodeType::workspace)); + } + + [[nodiscard]] std::set accumulateWorkspacesStr() const { + auto workspaces = accumulateWorkspaces(); std::set result; std::transform(workspaces.begin(), workspaces.end(), std::inserter(result, result.begin()), [](const SwayTreeNode *workspace) { return workspace->node.name; }); @@ -87,6 +91,12 @@ public: })); } + [[nodiscard]] SwayTreeNode * findWorkspaceByName(const std::string& name) { + return const_cast(findChildRecursive([name](const SwayTreeNode *test) { + return test->node.name == name && test->node.type == NodeType::workspace; + })); + } + private: [[nodiscard]] static std::function matchNodeType(NodeType::NodeType type) { return matchNodeType(std::vector{type});