From 8a12d6cf7e3b11ea46e6d76f219b1d1cdde9e797 Mon Sep 17 00:00:00 2001 From: Nikolai Nechaev Date: Tue, 10 Mar 2026 19:04:21 +0900 Subject: algo/dwindle: Respect `force_split` when moving windows to workspaces (#13038) Respect `dwindle:force_split` not only when mapping new windows, but also when moving windows to workspaces. Fixes #13009. --- hyprtester/src/tests/main/dwindle.cpp | 25 ++++++++++++++++++++++ hyprtester/src/tests/main/window.cpp | 23 ++++++-------------- hyprtester/src/tests/shared.cpp | 10 +++++++++ hyprtester/src/tests/shared.hpp | 1 + .../algorithm/tiled/dwindle/DwindleAlgorithm.cpp | 18 +++++++--------- .../algorithm/tiled/dwindle/DwindleAlgorithm.hpp | 4 ++-- 6 files changed, 52 insertions(+), 29 deletions(-) diff --git a/hyprtester/src/tests/main/dwindle.cpp b/hyprtester/src/tests/main/dwindle.cpp index 5504282d..9513db67 100644 --- a/hyprtester/src/tests/main/dwindle.cpp +++ b/hyprtester/src/tests/main/dwindle.cpp @@ -227,6 +227,28 @@ static void testRotatesplit() { OK(getFromSocket("/reload")); } +static void testForceSplitOnMoveToWorkspace() { + OK(getFromSocket("/dispatch workspace 2")); + EXPECT(!!Tests::spawnKitty("kitty"), true); + + OK(getFromSocket("/dispatch workspace 1")); + EXPECT(!!Tests::spawnKitty("kitty"), true); + std::string posBefore = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:"); + + OK(getFromSocket("/keyword dwindle:force_split 2")); + OK(getFromSocket("/dispatch movecursortocorner 3")); // top left + OK(getFromSocket("/dispatch movetoworkspace 2")); + + // Should be moved to the right, so the position should change + std::string activeWindow = getFromSocket("/activewindow"); + EXPECT(activeWindow.contains(posBefore), false); + + // clean up + OK(getFromSocket("/reload")); + Tests::killAllWindows(); + Tests::waitUntilWindowsN(0); +} + static bool test() { NLog::log("{}Testing Dwindle layout", Colors::GREEN); @@ -243,6 +265,9 @@ static bool test() { NLog::log("{}Testing rotatesplit", Colors::GREEN); testRotatesplit(); + NLog::log("{}Testing force_split on move to workspace", Colors::GREEN); + testForceSplitOnMoveToWorkspace(); + // clean up NLog::log("Cleaning up", Colors::YELLOW); getFromSocket("/dispatch workspace 1"); diff --git a/hyprtester/src/tests/main/window.cpp b/hyprtester/src/tests/main/window.cpp index 9dadf3d5..a42ffd38 100644 --- a/hyprtester/src/tests/main/window.cpp +++ b/hyprtester/src/tests/main/window.cpp @@ -47,17 +47,6 @@ static std::string spawnKittyActivating(const std::string& class_ = "kitty_activ return tmpFilename; } -static std::string getWindowAttribute(const std::string& winInfo, const std::string& attr) { - auto pos = winInfo.find(attr); - if (pos == std::string::npos) { - NLog::log("{}Wrong window attribute", Colors::RED); - ret = 1; - return "Wrong window attribute"; - } - auto pos2 = winInfo.find('\n', pos); - return winInfo.substr(pos, pos2 - pos); -} - static std::string getWindowAddress(const std::string& winInfo) { auto pos = winInfo.find("Window "); auto pos2 = winInfo.find(" -> "); @@ -92,7 +81,7 @@ static void testSwapWindow() { // Test swapwindow by direction { getFromSocket("/dispatch focuswindow class:kitty_A"); - auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:"); + auto pos = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:"); NLog::log("{}Testing kitty_A {}, swapwindow with direction 'r'", Colors::YELLOW, pos); OK(getFromSocket("/dispatch swapwindow r")); @@ -104,7 +93,7 @@ static void testSwapWindow() { // Test swapwindow by class { getFromSocket("/dispatch focuswindow class:kitty_A"); - auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:"); + auto pos = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:"); NLog::log("{}Testing kitty_A {}, swapwindow with class:kitty_B", Colors::YELLOW, pos); OK(getFromSocket("/dispatch swapwindow class:kitty_B")); @@ -118,7 +107,7 @@ static void testSwapWindow() { getFromSocket("/dispatch focuswindow class:kitty_B"); auto addr = getWindowAddress(getFromSocket("/activewindow")); getFromSocket("/dispatch focuswindow class:kitty_A"); - auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:"); + auto pos = Tests::getWindowAttribute(getFromSocket("/activewindow"), "at:"); NLog::log("{}Testing kitty_A {}, swapwindow with address:0x{}(kitty_B)", Colors::YELLOW, pos, addr); OK(getFromSocket(std::format("/dispatch swapwindow address:0x{}", addr))); @@ -141,7 +130,7 @@ static void testSwapWindow() { { getFromSocket("/dispatch focuswindow class:kitty_B"); auto addr = getWindowAddress(getFromSocket("/activewindow")); - auto ws = getWindowAttribute(getFromSocket("/activewindow"), "workspace:"); + auto ws = Tests::getWindowAttribute(getFromSocket("/activewindow"), "workspace:"); NLog::log("{}Sending address:0x{}(kitty_B) to workspace \"swapwindow2\"", Colors::YELLOW, addr); OK(getFromSocket("/dispatch movetoworkspacesilent name:swapwindow2")); @@ -222,8 +211,8 @@ static void testGroupRules() { static bool isActiveWindow(const std::string& class_, char fullscreen = '0', bool log = true) { std::string activeWin = getFromSocket("/activewindow"); - auto winClass = getWindowAttribute(activeWin, "class:"); - auto winFullscreen = getWindowAttribute(activeWin, "fullscreen:").back(); + auto winClass = Tests::getWindowAttribute(activeWin, "class:"); + auto winFullscreen = Tests::getWindowAttribute(activeWin, "fullscreen:").back(); if (winClass.substr(strlen("class: ")) == class_ && winFullscreen == fullscreen) return true; else { diff --git a/hyprtester/src/tests/shared.cpp b/hyprtester/src/tests/shared.cpp index f6e2fce9..32824f3b 100644 --- a/hyprtester/src/tests/shared.cpp +++ b/hyprtester/src/tests/shared.cpp @@ -181,3 +181,13 @@ bool Tests::writeFile(const std::string& name, const std::string& contents) { return true; } + +std::string Tests::getWindowAttribute(const std::string& winInfo, const std::string& attr) { + auto pos = winInfo.find(attr); + if (pos == std::string::npos) { + NLog::log("{}Window attribute not found: '{}'", Colors::RED, attr); + return "Wrong window attribute"; + } + auto pos2 = winInfo.find('\n', pos); + return winInfo.substr(pos, pos2 - pos); +} diff --git a/hyprtester/src/tests/shared.hpp b/hyprtester/src/tests/shared.hpp index bf875f8b..95058e04 100644 --- a/hyprtester/src/tests/shared.hpp +++ b/hyprtester/src/tests/shared.hpp @@ -19,4 +19,5 @@ namespace Tests { bool killAllLayers(); std::string execAndGet(const std::string& cmd); bool writeFile(const std::string& name, const std::string& contents); + std::string getWindowAttribute(const std::string& winInfo, const std::string& attr); }; diff --git a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp index 4c9622c3..531b54d8 100644 --- a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp +++ b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp @@ -71,7 +71,7 @@ void CDwindleAlgorithm::newTarget(SP target) { addTarget(target); } -void CDwindleAlgorithm::addTarget(SP target, bool newTarget) { +void CDwindleAlgorithm::addTarget(SP target) { const auto WORK_AREA = m_parent->space()->workArea(); const auto PNODE = m_dwindleNodesData.emplace_back(makeShared()); @@ -215,7 +215,7 @@ void CDwindleAlgorithm::addTarget(SP target, bool newTarget) { NEWPARENT->children[1] = OPENINGON; } } - } else if (*PFORCESPLIT == 0 || !newTarget || m_overrideFocalPoint) { + } else if (*PFORCESPLIT == 0 || m_overrideFocalPoint) { if ((SIDEBYSIDE && MOUSECOORDS.x < NEWPARENT->box.x + (NEWPARENT->box.w / 2.F)) || (!SIDEBYSIDE && MOUSECOORDS.y < NEWPARENT->box.y + (NEWPARENT->box.h / 2.F))) { // we are hovering over the first node, make PNODE first. NEWPARENT->children[1] = OPENINGON; @@ -225,14 +225,12 @@ void CDwindleAlgorithm::addTarget(SP target, bool newTarget) { NEWPARENT->children[0] = OPENINGON; NEWPARENT->children[1] = PNODE; } + } else if (*PFORCESPLIT == 1) { + NEWPARENT->children[1] = OPENINGON; + NEWPARENT->children[0] = PNODE; } else { - if (*PFORCESPLIT == 1) { - NEWPARENT->children[1] = OPENINGON; - NEWPARENT->children[0] = PNODE; - } else { - NEWPARENT->children[0] = OPENINGON; - NEWPARENT->children[1] = PNODE; - } + NEWPARENT->children[0] = OPENINGON; + NEWPARENT->children[1] = PNODE; } // split in favor of a specific window @@ -268,7 +266,7 @@ void CDwindleAlgorithm::addTarget(SP target, bool newTarget) { void CDwindleAlgorithm::movedTarget(SP target, std::optional focalPoint) { m_overrideFocalPoint = focalPoint; - addTarget(target, false); + addTarget(target); m_overrideFocalPoint.reset(); } diff --git a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp index 41cbf8bb..043f8738 100644 --- a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp +++ b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp @@ -39,7 +39,7 @@ namespace Layout::Tiled { std::optional m_overrideFocalPoint; // for onWindowCreatedTiling. - void addTarget(SP target, bool newTarget = true); + void addTarget(SP target); void calculateWorkspace(); SP getNodeFromTarget(SP); SP getNodeFromWindow(PHLWINDOW w); @@ -55,4 +55,4 @@ namespace Layout::Tiled { Math::eDirection m_overrideDirection = Math::DIRECTION_DEFAULT; }; -}; \ No newline at end of file +}; -- cgit v1.2.3