diff --git a/hyprtester/src/tests/main/scroll.cpp b/hyprtester/src/tests/main/scroll.cpp index 26be6a9a..8bb950dd 100644 --- a/hyprtester/src/tests/main/scroll.cpp +++ b/hyprtester/src/tests/main/scroll.cpp @@ -57,6 +57,147 @@ static void testFocusCycling() { Tests::killAllWindows(); } +static void testFocusWrapping() { + for (auto const& win : {"a", "b", "c", "d"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + // set wrap_focus to true + OK(getFromSocket("/keyword scrolling:wrap_focus true")); + + OK(getFromSocket("/dispatch focuswindow class:a")); + + OK(getFromSocket("/dispatch layoutmsg focus l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: d"); + } + + OK(getFromSocket("/dispatch layoutmsg focus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: a"); + } + + // set wrap_focus to false + OK(getFromSocket("/keyword scrolling:wrap_focus false")); + + OK(getFromSocket("/dispatch focuswindow class:a")); + + OK(getFromSocket("/dispatch layoutmsg focus l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: a"); + } + + OK(getFromSocket("/dispatch focuswindow class:d")); + + OK(getFromSocket("/dispatch layoutmsg focus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: d"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +static void testSwapcolWrapping() { + for (auto const& win : {"a", "b", "c", "d"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + // set wrap_swapcol to true + OK(getFromSocket("/keyword scrolling:wrap_swapcol true")); + + OK(getFromSocket("/dispatch focuswindow class:a")); + + OK(getFromSocket("/dispatch layoutmsg swapcol l")); + OK(getFromSocket("/dispatch layoutmsg focus l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: c"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + for (auto const& win : {"a", "b", "c", "d"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + OK(getFromSocket("/dispatch focuswindow class:d")); + OK(getFromSocket("/dispatch layoutmsg swapcol r")); + OK(getFromSocket("/dispatch layoutmsg focus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: b"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + for (auto const& win : {"a", "b", "c", "d"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + // set wrap_swapcol to false + OK(getFromSocket("/keyword scrolling:wrap_swapcol false")); + + OK(getFromSocket("/dispatch focuswindow class:a")); + + OK(getFromSocket("/dispatch layoutmsg swapcol l")); + OK(getFromSocket("/dispatch layoutmsg focus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: b"); + } + + OK(getFromSocket("/dispatch focuswindow class:d")); + + OK(getFromSocket("/dispatch layoutmsg swapcol r")); + OK(getFromSocket("/dispatch layoutmsg focus l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: c"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + static bool test() { NLog::log("{}Testing Scroll layout", Colors::GREEN); @@ -68,6 +209,14 @@ static bool test() { NLog::log("{}Testing focus cycling", Colors::GREEN); testFocusCycling(); + // test + NLog::log("{}Testing focus wrap", Colors::GREEN); + testFocusWrapping(); + + // test + NLog::log("{}Testing swapcol wrap", Colors::GREEN); + testSwapcolWrapping(); + // clean up NLog::log("Cleaning up", Colors::YELLOW); OK(getFromSocket("/dispatch workspace 1")); diff --git a/hyprtester/test.conf b/hyprtester/test.conf index f249a80a..e55906e8 100644 --- a/hyprtester/test.conf +++ b/hyprtester/test.conf @@ -186,6 +186,8 @@ scrolling { follow_focus = true follow_min_visible = 1 explicit_column_widths = 0.25, 0.333, 0.5, 0.667, 0.75, 1.0 + wrap_focus = true + wrap_swapcol = true } # https://wiki.hyprland.org/Configuring/Variables/#misc diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp index ef6ca535..31d67c12 100644 --- a/src/config/ConfigDescriptions.hpp +++ b/src/config/ConfigDescriptions.hpp @@ -2093,6 +2093,18 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_CHOICE, .data = SConfigOptionDescription::SChoiceData{.firstIndex = 0, .choices = "right,left,down,up"}, }, + SConfigOptionDescription{ + .value = "scrolling:wrap_focus", + .description = "Determines if column focus wraps around when going before the first column or past the last column", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{.value = true}, + }, + SConfigOptionDescription{ + .value = "scrolling:wrap_swapcol", + .description = "Determines if column movement wraps around when moving to before the first column or past the last column", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{.value = true}, + }, /* * Quirks diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index e62130a4..46073c09 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -655,6 +655,8 @@ CConfigManager::CConfigManager() { registerConfigVar("scrolling:follow_min_visible", Hyprlang::FLOAT{0.4}); registerConfigVar("scrolling:explicit_column_widths", Hyprlang::STRING{"0.333, 0.5, 0.667, 1.0"}); registerConfigVar("scrolling:direction", Hyprlang::STRING{"right"}); + registerConfigVar("scrolling:wrap_focus", Hyprlang::INT{1}); + registerConfigVar("scrolling:wrap_swapcol", Hyprlang::INT{1}); registerConfigVar("animations:enabled", Hyprlang::INT{1}); registerConfigVar("animations:workspace_wraparound", Hyprlang::INT{0}); diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp index afba398f..ae7c6ecc 100644 --- a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp @@ -1255,8 +1255,9 @@ std::expected CScrollingAlgorithm::layoutMsg(const std::strin m_scrollingData->recalculate(); } } else if (ARGS[0] == "focus") { - const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); - static const auto PNOFALLBACK = CConfigValue("general:no_focus_fallback"); + const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); + static const auto PNOFALLBACK = CConfigValue("general:no_focus_fallback"); + static const auto PCONFWRAPFOCUS = CConfigValue("scrolling:wrap_focus"); if (!TDATA || ARGS[1].empty()) return std::unexpected("no window to focus"); @@ -1312,7 +1313,7 @@ std::expected CScrollingAlgorithm::layoutMsg(const std::strin g_pCompositor->warpCursorTo(TDATA->target->window()->middle()); return {}; } else - PREV = m_scrollingData->columns.back(); + PREV = (*PCONFWRAPFOCUS == 1) ? m_scrollingData->columns.back() : m_scrollingData->columns.front(); } auto pTargetData = findBestNeighbor(TDATA, PREV); @@ -1334,7 +1335,7 @@ std::expected CScrollingAlgorithm::layoutMsg(const std::strin g_pCompositor->warpCursorTo(TDATA->target->window()->middle()); return {}; } else - NEXT = m_scrollingData->columns.front(); + NEXT = (*PCONFWRAPFOCUS == 1) ? m_scrollingData->columns.front() : m_scrollingData->columns.back(); } auto pTargetData = findBestNeighbor(TDATA, NEXT); @@ -1361,6 +1362,8 @@ std::expected CScrollingAlgorithm::layoutMsg(const std::strin m_scrollingData->recalculate(); } else if (ARGS[0] == "swapcol") { + static const auto PCONFWRAPSWAPCOL = CConfigValue("scrolling:wrap_swapcol"); + if (ARGS.size() < 2) return std::unexpected("not enough args"); @@ -1386,9 +1389,15 @@ std::expected CScrollingAlgorithm::layoutMsg(const std::strin // wrap around swaps if (direction == "l") - targetIdx = (currentIdx == 0) ? (colCount - 1) : (currentIdx - 1); + if (*PCONFWRAPSWAPCOL == 1) + targetIdx = (currentIdx == 0) ? (colCount - 1) : (currentIdx - 1); + else + targetIdx = (currentIdx == 0) ? 0 : (currentIdx - 1); else if (direction == "r") - targetIdx = (currentIdx == (int64_t)colCount - 1) ? 0 : (currentIdx + 1); + if (*PCONFWRAPSWAPCOL == 1) + targetIdx = (currentIdx == (int64_t)colCount - 1) ? 0 : (currentIdx + 1); + else + targetIdx = (currentIdx == (int64_t)colCount - 1) ? (colCount - 1) : (currentIdx + 1); else return std::unexpected("no target (invalid direction?)"); ;