Define version 1.0, reduce unnecessary navigation actions

This commit is contained in:
LordGrimmauld 2024-03-26 18:01:53 +01:00
parent 4e6afa08fd
commit 144be992b1
14 changed files with 83 additions and 48 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.5) 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_AUTOUIC ON)
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)

View File

@ -14,14 +14,14 @@ public:
explicit CloseKeyListener(MainWindow *pWindow) : MainWindowAwareKeyListener(pWindow) {} explicit CloseKeyListener(MainWindow *pWindow) : MainWindowAwareKeyListener(pWindow) {}
void handleKeyEvent(const QKeyEvent *keyEvent) const override { void handleKeyEvent(const QKeyEvent *keyEvent) const override {
auto containersToKill = getSelectedContainerIds(); auto containersToKill = getSelectedContainers();
if (containersToKill.empty()) if (containersToKill.empty())
return; return;
Formatter cmd; Formatter cmd;
for (auto id: containersToKill) { for (auto node: containersToKill) {
cmd << "[con_id=" << id << "] kill ; "; cmd << "[con_id=" << node->node.id << "] kill ; ";
} }
std::cout << cmd.str() << "\n"; std::cout << cmd.str() << "\n";

View File

@ -23,7 +23,7 @@ public:
if (selectedOutput != nullptr) if (selectedOutput != nullptr)
cmd << "focus output " << selectedOutput->node.name << " , "; cmd << "focus output " << selectedOutput->node.name << " , ";
cmd << "workspace number " << createNewWorkspaceId(); cmd << "workspace " << createNewWorkspaceId({});
auto resp = SwayTreeModel::sway.sendIPC(swaymsg(0, cmd.str())); auto resp = SwayTreeModel::sway.sendIPC(swaymsg(0, cmd.str()));
std::cout << resp.msg << "\n"; std::cout << resp.msg << "\n";
QApplication::quit(); QApplication::quit();

View File

@ -18,28 +18,57 @@ protected:
window->updateModel(); window->updateModel();
} }
[[nodiscard]] std::set<int> getSelectedContainerIds() const { [[nodiscard]] std::set<const SwayTreeNode *> getSelectedContainers() const {
std::set<int> containerIds; std::set<const SwayTreeNode *> containerIds;
auto selectedNodes = getModel()->getSelectedNodes(getTreeView()); auto selectedNodes = getModel()->getSelectedNodes(getTreeView());
for (auto *container: selectedNodes) { for (auto *container: selectedNodes) {
auto containers = container->accumulateContainers(); auto containers = container->accumulateContainers();
for (auto *containerToMove: container->accumulateContainers()) { for (auto *containerToMove: container->accumulateContainers()) {
containerIds.insert(containerToMove->node.id); containerIds.insert(containerToMove);
} }
} }
return containerIds; return containerIds;
} }
[[nodiscard]] int createNewWorkspaceId() const { [[nodiscard]] std::string createNewWorkspaceId(const std::set<const SwayTreeNode *> &allowedContainers) const {
auto used_workspaces = getModel()->getRoot()->accumulateWorkspaces(); std::set<std::string> candidates;
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) {} for (auto *con: allowedContainers) {
return newWorkspace; 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 availableOutputs = SwayOutputList(SwayTreeModel::sway);
auto selectedNodes = getModel()->getSelectedNodes(getTreeView()); auto selectedNodes = getModel()->getSelectedNodes(getTreeView());
@ -56,11 +85,11 @@ protected:
protected: protected:
MainWindow *window; MainWindow *window;
[[nodiscard]] SwayTreeModel* getModel() const { [[nodiscard]] SwayTreeModel *getModel() const {
return this->window->model; return this->window->model;
} }
[[nodiscard]] QTreeView* getTreeView() const { [[nodiscard]] QTreeView *getTreeView() const {
return window->ui->treeView; return window->ui->treeView;
} }
}; };

View File

@ -16,14 +16,14 @@ public:
explicit MoveToScratchpadKeyListener(MainWindow *pWindow) : MainWindowAwareKeyListener(pWindow) {} explicit MoveToScratchpadKeyListener(MainWindow *pWindow) : MainWindowAwareKeyListener(pWindow) {}
void handleKeyEvent(const QKeyEvent *keyEvent) const override { void handleKeyEvent(const QKeyEvent *keyEvent) const override {
std::set<int> containersToScratch = getSelectedContainerIds(); auto containersToScratch = getSelectedContainers();
if (containersToScratch.empty()) if (containersToScratch.empty())
return; return;
Formatter cmd; Formatter cmd;
for (auto id: containersToScratch) { for (auto node: containersToScratch) {
cmd << "[con_id=" << id << "] move scratchpad ; "; cmd << "[con_id=" << node->node.id << "] move scratchpad ; ";
} }
std::cout << cmd.str() << "\n"; std::cout << cmd.str() << "\n";

View File

@ -16,16 +16,16 @@ public:
explicit RestoreKeyListener(MainWindow *pWindow) : MainWindowAwareKeyListener(pWindow) {} explicit RestoreKeyListener(MainWindow *pWindow) : MainWindowAwareKeyListener(pWindow) {}
void handleKeyEvent(const QKeyEvent *keyEvent) const override { void handleKeyEvent(const QKeyEvent *keyEvent) const override {
std::set<int> containersToRestore = getSelectedContainerIds(); auto containersToRestore = getSelectedContainers();
if (containersToRestore.empty()) if (containersToRestore.empty())
return; return;
Formatter cmd; Formatter cmd;
int newWorkspaceId = createNewWorkspaceId(); auto newWorkspaceId = createNewWorkspaceId(containersToRestore);
for (auto id: containersToRestore) { for (auto node: containersToRestore) {
cmd << "[con_id=" << id << "] move workspace " << newWorkspaceId << " ; "; cmd << "[con_id=" << node->node.id << "] move workspace " << newWorkspaceId << " ; ";
} }
std::cout << cmd.str() << "\n"; std::cout << cmd.str() << "\n";

View File

@ -11,18 +11,17 @@
#include "../tree/SwayTreeModel.h" #include "../tree/SwayTreeModel.h"
#include "../sway_bindings/swayoutputs.h" #include "../sway_bindings/swayoutputs.h"
class SwitchToKeybindListener : public AbstractKeyListener { class SwitchToKeybindListener : public MainWindowAwareKeyListener {
public: public:
explicit SwitchToKeybindListener(SwayTreeModel *swayTreeModel, QTreeView *treeView) : swayTreeModel( explicit SwitchToKeybindListener(MainWindow *pWindow) : MainWindowAwareKeyListener(pWindow) {}
swayTreeModel), treeView(treeView) {}
void handleKeyEvent(const QKeyEvent *keyEvent) const override { void handleKeyEvent(const QKeyEvent *keyEvent) const override {
auto *root = swayTreeModel->getRoot(); auto *root = getModel()->getRoot();
if (root == nullptr) if (root == nullptr)
return; return;
Formatter cmd; Formatter cmd;
auto selectedNodes = swayTreeModel->getSelectedNodes(treeView); auto const selectedNodes = getModel()->getSelectedNodes(getTreeView());
if (selectedNodes.empty()) if (selectedNodes.empty())
return; return;
@ -52,10 +51,7 @@ public:
} }
} }
auto used_workspaces = root->accumulateWorkspaces(); auto newWorkspace = createNewWorkspaceId(selectedNodes);
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<int> containersToMove; std::set<int> containersToMove;
for (auto* container: selectedNodes) { for (auto* container: selectedNodes) {
@ -72,7 +68,7 @@ public:
cmd << "[con_id=" << id << "] move container workspace " << newWorkspace << " ; "; cmd << "[con_id=" << id << "] move container workspace " << newWorkspace << " ; ";
} }
cmd << "workspace number " << newWorkspace; cmd << "workspace " << newWorkspace;
std::cout << cmd.str() << "\n"; std::cout << cmd.str() << "\n";
auto resp = SwayTreeModel::sway.sendIPC(swaymsg(0, cmd.str())); auto resp = SwayTreeModel::sway.sendIPC(swaymsg(0, cmd.str()));
@ -91,10 +87,6 @@ public:
[[nodiscard]] bool canAcceptKey(int key) const override { [[nodiscard]] bool canAcceptKey(int key) const override {
return key == Qt::Key_Enter || key == Qt::Key_Return; return key == Qt::Key_Enter || key == Qt::Key_Return;
} }
private:
SwayTreeModel *swayTreeModel;
QTreeView *treeView;
}; };

View File

@ -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. This main interface is primarily inspired by tmux.
Swaymux can be navigated using the keyboard, with keybindings that closely follow the tmux bindings. 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 # Installing

View File

@ -2,11 +2,11 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1709968316, "lastModified": 1711370797,
"narHash": "sha256-4rZEtEDT6jcgRaqxsatBeds7x1PoEiEjb6QNGb4mNrk=", "narHash": "sha256-2xu0jVSjuKhN97dqc4bVtvEH52Rwh6+uyI1XCnzoUyI=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "0e7f98a5f30166cbed344569426850b21e4091d4", "rev": "c726225724e681b3626acc941c6f95d2b0602087",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -30,7 +30,7 @@ MainWindow::MainWindow(QWidget *parent)
swayTreeKeyHandler->addListener(new HelpKeyListener(ui->help_page)); swayTreeKeyHandler->addListener(new HelpKeyListener(ui->help_page));
swayTreeKeyHandler->addListener(new CloseSwaymuxKeyListener()); swayTreeKeyHandler->addListener(new CloseSwaymuxKeyListener());
swayTreeKeyHandler->addListener(new CreateWorkspaceKeyListener(this)); 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 PrevOutputKeyListener(model, ui->treeView));
swayTreeKeyHandler->addListener(new NextOutputKeyListener(model, ui->treeView)); swayTreeKeyHandler->addListener(new NextOutputKeyListener(model, ui->treeView));
swayTreeKeyHandler->addListener(new CloseKeyListener(this)); swayTreeKeyHandler->addListener(new CloseKeyListener(this));

View File

@ -55,6 +55,10 @@ namespace NodeType {
static QString toQString(NodeType nodeType) { static QString toQString(NodeType nodeType) {
return QString::fromStdString(toString(nodeType)); return QString::fromStdString(toString(nodeType));
} }
[[nodiscard]] static inline bool isContainer(NodeType type) {
return type == con || type == floating_con;
}
} }
namespace NodeLayout { namespace NodeLayout {

View File

@ -19,8 +19,8 @@ QModelIndex SwayTreeModel::findIndexByNode(const SwayTreeNode* node) const {
return idx; return idx;
} }
std::set<SwayTreeNode *> SwayTreeModel::getSelectedNodes(const QTreeView *treeView) const { std::set<const SwayTreeNode *> SwayTreeModel::getSelectedNodes(const QTreeView *treeView) const {
std::set<SwayTreeNode *> selectedNodes; std::set<const SwayTreeNode *> selectedNodes;
if (rootItem == nullptr) if (rootItem == nullptr)
return selectedNodes; return selectedNodes;

View File

@ -50,7 +50,7 @@ public:
return findIndexByNode(getRoot()->findFocused()); return findIndexByNode(getRoot()->findFocused());
} }
[[nodiscard]] std::set<SwayTreeNode *> getSelectedNodes(const QTreeView *treeView) const; [[nodiscard]] std::set<const SwayTreeNode *> getSelectedNodes(const QTreeView *treeView) const;
[[nodiscard]] SwayTreeNode *getSelectedNode(const QModelIndex &i) const; [[nodiscard]] SwayTreeNode *getSelectedNode(const QModelIndex &i) const;

View File

@ -73,8 +73,12 @@ public:
return accumulateMatchingChildrenRecursive(matchNodeType({NodeType::con, NodeType::floating_con})); return accumulateMatchingChildrenRecursive(matchNodeType({NodeType::con, NodeType::floating_con}));
} }
[[nodiscard]] std::set<std::string> accumulateWorkspaces() const { [[nodiscard]] std::set<SwayTreeNode*> accumulateWorkspaces() const {
auto workspaces = accumulateMatchingChildrenRecursive(matchNodeType(NodeType::workspace)); return accumulateMatchingChildrenRecursive(matchNodeType(NodeType::workspace));
}
[[nodiscard]] std::set<std::string> accumulateWorkspacesStr() const {
auto workspaces = accumulateWorkspaces();
std::set<std::string> result; std::set<std::string> result;
std::transform(workspaces.begin(), workspaces.end(), std::inserter(result, result.begin()), std::transform(workspaces.begin(), workspaces.end(), std::inserter(result, result.begin()),
[](const SwayTreeNode *workspace) { return workspace->node.name; }); [](const SwayTreeNode *workspace) { return workspace->node.name; });
@ -87,6 +91,12 @@ public:
})); }));
} }
[[nodiscard]] SwayTreeNode * findWorkspaceByName(const std::string& name) {
return const_cast<SwayTreeNode *>(findChildRecursive([name](const SwayTreeNode *test) {
return test->node.name == name && test->node.type == NodeType::workspace;
}));
}
private: private:
[[nodiscard]] static std::function<bool(const SwayTreeNode *)> matchNodeType(NodeType::NodeType type) { [[nodiscard]] static std::function<bool(const SwayTreeNode *)> matchNodeType(NodeType::NodeType type) {
return matchNodeType(std::vector{type}); return matchNodeType(std::vector{type});