From f7516518e071128b47b121ec19996d519b2ad665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miskolczi=20Rich=C3=A1rd?= Date: Fri, 9 Jan 2026 00:03:12 +0100 Subject: [PATCH] Feature: moving a window to another workspace changes its windowing mode to respect the new workspace's one --- README.md | 39 +++++++++++++++++++++-- src/HyprlandService.cpp | 70 ++++++++++++++++++++++++++++++++++------- src/main.cpp | 50 ++++++++++++++--------------- 3 files changed, 121 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 9a10151..72647d1 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Check out [the demo](https://typofelho.ddns.net/TypoMustakes/hyprland-toggle-til Switch to floating mode again and newly opened windows will be in floating mode. - Floating/tiling window rules are isolated between workspaces. You can set one workspace to be floating, and all the rest to tiling for example. - Returns applied rule for the current workspace. Useful for scripting. +- Moving a window from a tiling workspace to a floating one changes the float state of the window to respect the new workspace. - Useful for workflows that require both tiling and floating window management. - Lightweight and easy to integrate with your Hyprland setup. @@ -76,7 +77,7 @@ or go nuts and... # Usage ```shell -./htt (-q) +./htt (flags) ``` - `-q`: Print the rule applied on the current workspace to STDOUT, but don't change anything. If there isn't an active rule for the current workspace, it returns a tiling rule. The returned string is a valid Hyprland window rule configuration line, like so: @@ -89,6 +90,8 @@ windowrule = tile on, match:workspace 2 Potential applications for this are mainly scripts, like my waybar module here: ![wayland module showcase](https://typofelho.ddns.net/TypoMustakes/hyprland-toggle-tiling/raw/branch/master/assets/waybar_module.gif) +- `-m [integer]`: Move currently active window to specified workspace. Upon moving, the window will adapt to the windowing mode of the new workspace. Doesn't work with `-q`. See ![the 1.4 changelog](https://typofelho.ddns.net/TypoMustakes/hyprland-toggle-tiling/releases/tag/v1.4) for details. + - If the specified configuration file does not exist, it will be created. - If the configuration contains existing rules, this should still work, but your existing configuration will probably get a bit messy, syntax-wise. I advise against it. @@ -104,5 +107,37 @@ Potential applications for this are mainly scripts, like my waybar module here: bind = $mod + t, exec, /path/to/htt ``` - Or not. Do whatever you want. +3. If you want windows to respect workspaces' rules after moving them to another workspace (see ![the 1.4 changelog](https://typofelho.ddns.net/TypoMustakes/hyprland-toggle-tiling/releases/tag/v1.4) for details), you might want to tell HTT to move your windows instead of telling Hyprland. +On the default configuration, this would mean changing this: + +```sh +bind = $mainMod SHIFT, 1, movetoworkspace, 1 +bind = $mainMod SHIFT, 2, movetoworkspace, 2 +bind = $mainMod SHIFT, 3, movetoworkspace, 3 +bind = $mainMod SHIFT, 4, movetoworkspace, 4 +bind = $mainMod SHIFT, 5, movetoworkspace, 5 +bind = $mainMod SHIFT, 6, movetoworkspace, 6 +bind = $mainMod SHIFT, 7, movetoworkspace, 7 +bind = $mainMod SHIFT, 8, movetoworkspace, 8 +bind = $mainMod SHIFT, 9, movetoworkspace, 9 +bind = $mainMod SHIFT, 0, movetoworkspace, 10 +``` + +to this: + +```sh +bind = $mainMod SHIFT, 1, exec, htt [your htt config file] -m 1 +bind = $mainMod SHIFT, 2, exec, htt [your htt config file] -m 2 +bind = $mainMod SHIFT, 3, exec, htt [your htt config file] -m 3 +bind = $mainMod SHIFT, 4, exec, htt [your htt config file] -m 4 +bind = $mainMod SHIFT, 5, exec, htt [your htt config file] -m 5 +bind = $mainMod SHIFT, 6, exec, htt [your htt config file] -m 6 +bind = $mainMod SHIFT, 7, exec, htt [your htt config file] -m 7 +bind = $mainMod SHIFT, 8, exec, htt [your htt config file] -m 8 +bind = $mainMod SHIFT, 9, exec, htt [your htt config file] -m 9 +bind = $mainMod SHIFT, 0, exec, htt [your htt config file] -m 10 +``` + +... in your configuration. +Otherwise, just use `exec, htt [your htt config file] -m [workspace ID]` wherever you used `movetoworkspace [workspace ID]` before. diff --git a/src/HyprlandService.cpp b/src/HyprlandService.cpp index 21907f0..6d0fb13 100644 --- a/src/HyprlandService.cpp +++ b/src/HyprlandService.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include "../include/HyprlandService.h" #include "../include/ShellService.h" #include "../include/Macros.h" @@ -22,11 +24,35 @@ std::string HyprlandService::getConfigFilePath() { return configFilePath; }; +std::list HyprlandService::getWorkspaces() { + json j = json::parse(ShellService::exec(HYPRCTL_BINARY " workspaces -j")); + return j.get>(); +} + +std::optional HyprlandService::getWorkspace(int id) { + std::list workspaces = getWorkspaces(); + + for (auto it = workspaces.begin(); it != workspaces.end(); ) { + if (it->id == id) { + return *it; + } else { + ++it; + } + } + + return std::nullopt; +} + Workspace HyprlandService::getCurrentWorkspace() { json j = json::parse(ShellService::exec(HYPRCTL_BINARY " activeworkspace -j")); return j.get(); }; +Client HyprlandService::getActiveClient() { + json j = json::parse(ShellService::exec(HYPRCTL_BINARY " activewindow -j")); + return j.get(); +} + std::list HyprlandService::getClients() { json j = json::parse(ShellService::exec(HYPRCTL_BINARY " clients -j")); return j.get>(); @@ -57,18 +83,14 @@ std::list HyprlandService::getWindowRules() { return rules; }; -void HyprlandService::setClientFloating(Client& c) { +void HyprlandService::setClientFloating(Client c) { ShellService::exec(HYPRCTL_BINARY " dispatch setfloating address:" + c.address); }; -void HyprlandService::setClientTiled(Client& c) { +void HyprlandService::setClientTiled(Client c) { ShellService::exec(HYPRCTL_BINARY " dispatch settiled address:" + c.address); } -void HyprlandService::toggleClientFloating(Client& c) { - ShellService::exec(HYPRCTL_BINARY " dispatch togglefloating address:" + c.address); -}; - //on = true -> creates a window rule to ENABLE floating mode for currently active workspace //on = false -> creates a window rule to DISABLE floating mode for currently active workspace void HyprlandService::setFloatingRule(bool on) { @@ -122,18 +144,20 @@ void HyprlandService::removeRule(WindowRule rule) { //else: rule not found, do nothing } -bool HyprlandService::isFloatingRulePresent() { - //Checks if there's a valid window rule in place that enables floating mode for the currently active workspace +bool HyprlandService::isFloatingRulePresent(int workspaceId) { std::list rules = getWindowRules(); - int id = getCurrentWorkspace().id; for (auto& rule : rules) { - if (rule.workspaceID == id && rule.tile == false) { + if (rule.workspaceID == workspaceId && rule.tile == false) { return true; } } return false; +} + +bool HyprlandService::isFloatingRulePresent(Workspace workspace) { + return isFloatingRulePresent(workspace.id); }; WindowRule HyprlandService::getActiveWorkspaceRule() { @@ -148,4 +172,28 @@ WindowRule HyprlandService::getActiveWorkspaceRule() { //If no rule is found, return a default rule (tiled) return WindowRule {.tile = true, .workspaceID = id}; -}; \ No newline at end of file +}; + +void HyprlandService::moveToWorkspace(int workspaceId) { + if (isFloatingRulePresent(workspaceId)) { + setClientFloating(getActiveClient()); + } else { + setClientTiled(getActiveClient()); + } + + ShellService::exec(HYPRCTL_BINARY " dispatch movetoworkspace " + std::to_string(workspaceId)); +} + +void HyprlandService::toggleFloating() { + if (isFloatingRulePresent(getCurrentWorkspace())) { + for (auto& c : getClientsOnActiveWorkspace()) { + setClientTiled(c); + } + setFloatingRule(false); + } else { + for (auto& c : getClientsOnActiveWorkspace()) { + setClientFloating(c); + } + setFloatingRule(true); + } +} diff --git a/src/main.cpp b/src/main.cpp index 80b32dc..b33f7ba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,37 +1,37 @@ #include +#include #include "../include/HyprlandService.h" void help(char* execPath) { - std::cerr << "Usage: " << execPath << " (-q)\n\n"; - std::cerr << "-q:\tQuery current windowing mode, don't change anything.\n\tReturns Hyprland window rule active on current workspace.\n\tIf no rule is active, returns a default tiled rule.\n"; + std::cerr << "Usage: " << execPath << " (flags)\n\n"; + std::cerr << "Flags:\n\n"; + std::cerr << "-q:\t\tQuery current windowing mode, don't change anything.\n\t\tReturns Hyprland window rule active on current workspace.\n\t\tIf no rule is active, returns a default tiled rule.\n\n"; + std::cerr << "-m [integer]:\tMove currently active window to specified workspace.\n\t\tUpon moving, the window will adapt to the windowing mode of the new workspace.\n\t\tDoesn't work with -q.\n"; exit(1); } int main(int argc, char** argv) { - if (argc < 2) { + if (argc >= 2) { + HyprlandService::setConfigFilePath(argv[1]); + + if (argc == 2) { + HyprlandService::toggleFloating(); + } + else if (argc == 3 && argv[2] == std::string("-q")) { + std::cout << HyprlandService::getActiveWorkspaceRule().toString() << std::endl; + } else if (argc == 4 && argv[2] == std::string("-m")) { + int workspace = 0; + try { + HyprlandService::moveToWorkspace(std::stoi(argv[3])); + } catch (std::invalid_argument) { + help(argv[0]); + } + } else { + help(argv[0]); + } + } else { help(argv[0]); } - HyprlandService::setConfigFilePath(argv[1]); - - if (argc == 3 && argv[2] == std::string("-q")) { - WindowRule rule = HyprlandService::getActiveWorkspaceRule(); - std::cout << rule.toString() << std::endl; - } - else if (argc == 2) { - if (HyprlandService::isFloatingRulePresent()) { - for (auto& c : HyprlandService::getClientsOnActiveWorkspace()) { - HyprlandService::setClientTiled(c); - } - HyprlandService::setFloatingRule(false); - } else { - for (auto& c : HyprlandService::getClientsOnActiveWorkspace()) { - HyprlandService::setClientFloating(c); - } - HyprlandService::setFloatingRule(true); - } - } - else (help(argv[0])); - exit(0); -} \ No newline at end of file +}