diff --git a/hyprtester/src/tests/main/scroll.cpp b/hyprtester/src/tests/main/scroll.cpp index bd33e345e..8e379d827 100644 --- a/hyprtester/src/tests/main/scroll.cpp +++ b/hyprtester/src/tests/main/scroll.cpp @@ -195,5 +195,952 @@ TEST_CASE(scrollWindowRule) { ASSERT(Tests::windowCount(), 2); // not the greatest test, but as long as res and gaps don't change, we good. - EXPECT_CONTAINS(getFromSocket("/activewindow"), "size: 174,1036"); + ASSERT_CONTAINS(getFromSocket("/activewindow"), "size: 174,1036"); +} + +TEST_CASE(testScrollingViewBehaviourDispatchFocusWindowFollowFocusFalse) { + + /* + focuswindow DOES NOT move the scrolling view when follow_focus = false + --------------------------------------------------------------------------------- + */ + + NLog::log("{}Testing scrolling view behaviour: focuswindow dispatch SHOULD NOT move scrolling view when follow_focus = false", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + // ensure variables are correctly set for the test + OK(getFromSocket("/eval hl.config({scrolling = {follow_focus = false}})")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("Could not spawn kitty with win class `a`"); + } + + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("Could not spawn kitty with win class `b`"); + } + + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:a'})")); + + // if the view does not move, we expect the x coordinate of the window of class "a" to be negative, as it would be to the left of the viewport + const std::string posA = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const int posAx = std::stoi(posA.substr(0, posA.find(','))); + if (posAx < 0) { + NLog::log("{}Passed: {}Expected the x coordinate of window of class \"a\" to be < 0, got {}.", Colors::GREEN, Colors::RESET, posAx); + } else { + FAIL_TEST("{}Failed: {}Expected the x coordinate of window of class \"a\" to be < 0, got {}.", Colors::RED, Colors::RESET, posAx); + } + + // clean up + + // revert the changes made to config + NLog::log("{}Restoring config state", Colors::YELLOW); + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourDispatchFocusWindowFollowFocustrue) { + + /* + focuswindow DOES move the view when follow_focus = true + -------------------------------------------------------------------- + */ + + NLog::log("{}Testing scrolling view behaviour: focuswindow dispatch SHOULD move scrolling view when follow_focus = true", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("Could not spawn kitty with win class `a`"); + } + + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("Could not spawn kitty with win class `b`"); + } + + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:a'})")); + + // If the view does not move, we expect the x coordinate of the window of class "a" to be negative, as it would be to the left of the viewport. + // If it is not, the view moved, which is what we expect to happen. + const std::string posA = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const int posAx = std::stoi(posA.substr(0, posA.find(','))); + if (posAx < 0) { + FAIL_TEST("{}Failed: {}Expected the x coordinate of window of class \"a\" to be >= 0, got {}.", Colors::RED, Colors::RESET, posAx); + } else { + NLog::log("{}Passed: {}Expected the x coordinate of window of class \"a\" to be >= 0, got {}.", Colors::GREEN, Colors::RESET, posAx); + } + + // clean up + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourFocusFallback) { + + /* + Focus fallback from killing a floating window onto a tiled window must NOT move scrolling view, regardless of follow_focus + -------------------------------------------------------------------------------------------------------------------------- + */ + + NLog::log("{}Testing scrolling view behaviour: focus fallback from floating window to a tiled window should not move scrolling view", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + // ensure variables are correctly set for the test + OK(getFromSocket("/eval hl.config({scrolling = {follow_focus = false}})")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("{}Failed to spawn kitty with win class `a`", Colors::RED); + } + + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("{}Failed to spawn kitty with class `b`", Colors::RED); + } + + if (!Tests::spawnKitty("c")) { + FAIL_TEST("{}Failed to spawn kitty with class `c`", Colors::RED); + } + + // make it (window of class:c) float - the view now mush have shifted to fit window class:b + OK(getFromSocket("/dispatch hl.dsp.window.float({action = 'enable', window = 'class:c'})")); + + // establish focus history + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:c'})")); + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:a'})")); + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:c'})")); + + // kill the floating window + // Expect the focus to fall back to the left tiled window + OK(getFromSocket("/dispatch hl.dsp.window.kill({window = 'class:c'})")); + Tests::waitUntilWindowsN(2); + + // The focus now must have fallen back to tiled window of class "a". + + // If the view did not move, we expect currently focused window's (class:a) to have "at: " x coordinat value <0 (must be left of the viewport) + + const std::string currentWindowPos = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const std::string currentWindowPosX = currentWindowPos.substr(0, currentWindowPos.find(',')); + // test pass + if (std::stoi(currentWindowPosX) < 0) { + NLog ::log("{}Passed: {}Expected the x coordinate of window of class \"a\" to be < 0, got {}.", Colors ::GREEN, Colors::RESET, currentWindowPosX); + } + // test fail + else { + FAIL_TEST("{}Failed: {}Expected the x coordinate of window of class \"a\" to be < 0, got {}.", Colors::RED, Colors::RESET, currentWindowPosX); + } + + // clean up + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourFocusFallbackWithGroups) { + + // same idea as testScrollingViewBehaviourFocusFallback, but with window of class "a" being grouped. + + NLog::log("{}Testing scrolling view behaviour: focus fallback from floating window to a grouped tiled should not move scrolling view", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + // ensure variables are correctly set for the test + + // to correctly set up windows for the test + OK(getFromSocket("/eval hl.config({scrolling = {follow_focus = false}})")); + // only one tiled window will be grouped for the test + OK(getFromSocket("/eval hl.config({group = {auto_group = false}})")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("{}Failed to spawn kitty with win class `a`", Colors::RED); + } + + // make it a grouped. There need not be any other windows in the group for this test + OK(getFromSocket("/dispatch hl.dsp.group.toggle({window = 'class:a'})")); + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("{}Failed to spawn kitty with class `b`", Colors::RED); + } + + if (!Tests::spawnKitty("c")) { + FAIL_TEST("{}Failed to spawn kitty with class `c`", Colors::RED); + } + + // make it float - the view now mush have shifted to fit window class:b + OK(getFromSocket("/dispatch hl.dsp.window.float({action = 'enable', window = 'class:c'})")); + + // establish focus history + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:c'})")); + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:a'})")); + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:c'})")); + + // kill the floating window + OK(getFromSocket("/dispatch hl.dsp.window.kill({window = 'class:c'})")); + Tests::waitUntilWindowsN(2); + + // The focus now must have fallen back to tiled window of class "a". + + // If the view did not move, we expect currently focused window's (class:a) to have "at: " x coordinat value <0 (must be left of the viewport) + + const std::string currentWindowPos = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const std::string currentWindowPosX = currentWindowPos.substr(0, currentWindowPos.find(',')); + + // test pass + if (std::stoi(currentWindowPosX) < 0) { + NLog ::log("{}Passed: {}Expected the x coordinate of window of class \"a\" to be < 0, got {}.", Colors ::GREEN, Colors::RESET, currentWindowPosX); + } + // test fail + else { + FAIL_TEST("{}Failed: {}Expected the x coordinate of window of class \"a\" to be < 0, got {}.", Colors::RED, Colors::RESET, currentWindowPosX); + } + + // clean up + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourWorkspaceChange) { + + /* + When you change to a scrolling workspace, the focused window in that workspace must not be pulled into view, regardless of follow_focus + --------------------------------------------------------------------------------------------------------------------------------------- + */ + + NLog::log("{}Testing scrolling view behaviour: changing to a scrolling workspace should not move scrolling view", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + // ensure variables are correctly set for the test - this is to avoid unwanted view shifts when setting up the windows + OK(getFromSocket("/eval hl.config({scrolling = {follow_focus = false}})")); + + // switch to workspace 1 for this test + OK(getFromSocket("/dispatch hl.dsp.focus({workspace = '1'})")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("{}Failed to spawn kitty with win class `a`", Colors::RED); + } + + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("{}Failed to spawn kitty with win class `b`", Colors::RED); + } + + // does not move view when follow_focus = 0 + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:a'})")); + + // change to workspace 2, then back to workspace 1 again + OK(getFromSocket("/dispatch hl.dsp.focus({workspace = '2'})")); + OK(getFromSocket("/dispatch hl.dsp.focus({workspace = '1'})")); + + // If the scrolling view did not move, the x value for `at:` of the currently focused window, class:a, must be <0 (must be left of the viewport) + const std::string currentWindowPos = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const std::string currentWindowPosX = currentWindowPos.substr(0, currentWindowPos.find(',')); + + // test pass + if (std::stoi(currentWindowPosX) < 0) { + NLog ::log("{}Passed: {}window of class 'a' has negative x coordinates for its position: {}", Colors ::GREEN, Colors::RESET, currentWindowPosX); + } + // test fail + else { + FAIL_TEST("{}Failed: {}window of class 'a' does not have negative x coordinates for its position: {}", Colors::RED, Colors::RESET, currentWindowPosX); + } + + // clean up + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourSpecialWorkspaceChange) { + + /* + When you change to a special scrolling workspace from a normal workspace, the focused window in that workspace must not be pulled into view, regardless of follow_focus + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- + */ + + NLog::log("{}Testing scrolling view behaviour: changing to a special scrolling workspace from a normal workspace should not move scrolling view", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + // ensure variables are correctly set for the test - this is to avoid unwanted view shifts when setting up the windows + OK(getFromSocket("/eval hl.config({scrolling = {follow_focus = false}})")); + + // We'll test in this special workspace + OK(getFromSocket("/dispatch hl.dsp.workspace.toggle_special('name:scroll_S')")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("{}Failed to spawn kitty with win class `a`", Colors::RED); + } + + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("{}Failed to spawn kitty with win class `b`", Colors::RED); + } + + // does not move view when follow_focus = 0 + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:a'})")); + + // change to workspace 2, then back to special "scroll_S" workspace again + OK(getFromSocket("/dispatch hl.dsp.focus({workspace = '2'})")); + OK(getFromSocket("/dispatch hl.dsp.workspace.toggle_special('name:scroll_S')")); + + // Reestablish focus since it is finnicky in hyprtester - Harmless and does not move view when follow_focus = 0 + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:a'})")); + + // If the scrolling view did not move, the x value for `at:` of the currently focused windows, class:c, must be <0 (must be left of the viewport) + const std::string currentWindowPos = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const std::string currentWindowPosX = currentWindowPos.substr(0, currentWindowPos.find(',')); + // test pass + if (std::stoi(currentWindowPosX) < 0) { + NLog ::log("{}Passed: {}window of class 'a' has negative x coordinates for its position: {}", Colors ::GREEN, Colors::RESET, currentWindowPosX); + } + // test fail + else { + FAIL_TEST("{}Failed: {}window of class 'a' does not have negative x coordinates for its position: {}", Colors::RED, Colors::RESET, currentWindowPosX); + } + + // clean up + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourSpecialToSpecialWorkspaceChange) { + + /* + We also test switching between 2 special workspaces + This follows the same idea and dependencies as the test testScrollingViewBehaviourSpecialWorkspaceChange() + */ + + NLog::log("{}Testing scrolling view behaviour: changing to a special scrolling workspace from another special workspace should not move scrolling view", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + // ensure variables are correctly set for the test - this is to avoid unwanted view shifts when setting up the windows + OK(getFromSocket("/eval hl.config({scrolling = {follow_focus = false}})")); + + // We'll test in this special workspace + OK(getFromSocket("/dispatch hl.dsp.workspace.toggle_special('name:scroll_S')")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("{}Failed to spawn kitty with win class `a`", Colors::RED); + } + + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("{}Failed to spawn kitty with win class `b`", Colors::RED); + } + + // does not move view when follow_focus = 0 + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:a'})")); + + // change to special workspace "scroll_F", then back to special "scroll_S" workspace again + OK(getFromSocket("/dispatch hl.dsp.workspace.toggle_special('name:scroll_F')")); + OK(getFromSocket("/dispatch hl.dsp.workspace.toggle_special('name:scroll_S')")); + + // Reestablish focus since it is finnicky in hyprtester - Harmless and does not move view when follow_focus = 0 + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:a'})")); + + // If the scrolling view did not move, the x value for `at:` of the currently focused windows, class:c, must be <0 (must be left of the viewport) + + const std::string currentWindowPosSPECIAL = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const std::string currentWindowPosSPECIALX = currentWindowPosSPECIAL.substr(0, currentWindowPosSPECIAL.find(',')); + // test pass + if (std::stoi(currentWindowPosSPECIALX) < 0) { + NLog ::log("{}Passed: {}window of class 'a' has negative x coordinates for its position: {}", Colors ::GREEN, Colors::RESET, currentWindowPosSPECIALX); + } + // test fail + else { + FAIL_TEST("{}Failed: {}window of class 'a' does not have negative x coordinates for its position: {}", Colors::RED, Colors::RESET, currentWindowPosSPECIALX); + } + + // clean up + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourCloseWindowInGroup) { + + /* + When you change close a window inside a group (NOT destroying the group!), it should not cause scrolling view to shift to pull that group into view, regardless of follow_focus + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + */ + + NLog::log("{}Testing scrolling view behaviour: closing a window in a group (> 1 window in group) should not move scrolling view", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + // ensure variables are correctly set for the test + + // this is to avoid unwanted view shifts when setting up the windows + OK(getFromSocket("/eval hl.config({scrolling = {follow_focus = false}})")); + // We need 2 windows to be grouped, the third one not. + OK(getFromSocket("/eval hl.config({group = {auto_group = false}})")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("{}Failed to spawn kitty with win class `a`", Colors::RED); + } + + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + OK(getFromSocket("/dispatch hl.dsp.group.toggle({window = 'class:a'})")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("{}Failed to spawn kitty with win class `b`", Colors::RED); + } + + OK(getFromSocket("/dispatch hl.dsp.window.move({ into_group = 'left' })")); + + if (!Tests::spawnKitty("c")) { + FAIL_TEST("{}Failed to spawn kitty with win class `c`", Colors::RED); + } + + // switch focus to group. This will not move view when follow_focus = 0 + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:b'})")); + + // kill window class:b. we expect that this should cause not difference in the position of the group + OK(getFromSocket("/dispatch hl.dsp.window.kill({window = 'class:b'})")); + Tests::waitUntilWindowsN(2); + + // If the scrolling view did not move, the x value for `at:` of the currently focused windows, class:a, must be <0 (must be left of the viewport) + + const std::string currentWindowPos = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const std::string currentWindowPosX = currentWindowPos.substr(0, currentWindowPos.find(',')); + // test pass + if (std::stoi(currentWindowPosX) < 0) { + NLog ::log("{}Passed: {}window of class 'a' has negative x coordinates for its position: {}", Colors ::GREEN, Colors::RESET, currentWindowPosX); + } + // test fail + else { + FAIL_TEST("{}Failed: {}window of class 'a' does not have negative x coordinates for its position: {}", Colors::RED, Colors::RESET, currentWindowPosX); + } + + // clean up + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourMoveWindowIntoGroupFollowFocusFalse) { + + /* + when a window is moved inside a group, scrolling view should not move to fit that group when follow_focus = false + ----------------------------------------------------------------------------------------------------------------- + */ + + NLog::log("{}Testing scrolling view behaviour: moving a window into a group SHOULD NOT move scrolling view if follow_focus = 0", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + // ensure variables are correctly set for the test + OK(getFromSocket("/eval hl.config({scrolling = {follow_focus = false}})")); + OK(getFromSocket("/eval hl.config({group = {auto_group = false}})")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("{}Failed to spawn kitty with win class `a`", Colors::RED); + } + + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + OK(getFromSocket("/dispatch hl.dsp.group.toggle({window = 'class:a'})")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("{}Failed to spawn kitty with win class `b`", Colors::RED); + } + + if (!Tests::spawnKitty("c")) { + FAIL_TEST("{}Failed to spawn kitty with win class `c`", Colors::RED); + } + + // focus class:b + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:b'})")); + + // move it into the group where class:a is + OK(getFromSocket("/dispatch hl.dsp.window.move({ into_group = 'left' })")); + + // the focus now should still be on class:b window. If the view did not move, its x coordinate for its `at:` value should be <0 + + const std::string currentWindowPos = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const std::string currentWindowPosX = currentWindowPos.substr(0, currentWindowPos.find(',')); + // test pass + if (std::stoi(currentWindowPosX) < 0) { + NLog ::log("{}Passed: {}window of class 'b' has negative x coordinates for its position: {}", Colors ::GREEN, Colors::RESET, currentWindowPosX); + } + // test fail + else { + FAIL_TEST("{}Failed: {}window of class 'b' does not have negative x coordinates for its position: {}", Colors::RED, Colors::RESET, currentWindowPosX); + } + + // clean up + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourMoveWindowInGroupFollowFocusTrue) { + + /* + when a window is moved inside a group, scrolling view should move to fit that group when follow_focus = true + ------------------------------------------------------------------------------------------------------------ + */ + + NLog::log("{}Testing scrolling view behaviour: moving a window in a group SHOULD move scrolling view if follow_focus = true", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + // ensure variables are correctly set for the test + OK(getFromSocket("/eval hl.config({group = {auto_group = false}})")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("{}Failed to spawn kitty with win class `a`", Colors::RED); + } + + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + OK(getFromSocket("/dispatch hl.dsp.group.toggle({window = 'class:a'})")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("{}Failed to spawn kitty with win class `b`", Colors::RED); + } + + if (!Tests::spawnKitty("c")) { + FAIL_TEST("{}Failed to spawn kitty with win class `c`", Colors::RED); + } + + // focus class:b + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:b'})")); + + // move it into the group where class:a is + OK(getFromSocket("/dispatch hl.dsp.window.move({ into_group = 'left' })")); + + // the focus now should still be on class:b window. If the scrolling view did move, its x coordinate for its `at:` value should be >= 0 + + const std::string currentWindowPos = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const std::string currentWindowPosX = currentWindowPos.substr(0, currentWindowPos.find(',')); + // test fail + if (std::stoi(currentWindowPosX) < 0) { + FAIL_TEST("{}window of class 'b' does not have x coordinates >= 0 for its position: {}", Colors::RED, currentWindowPosX); + } + // test pass + else { + NLog ::log("{}Passed: {}window of class 'b' has x coordinates >= 0 for its position: {}", Colors ::GREEN, Colors::RESET, currentWindowPosX); + } + + // clean up + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourNewLayer) { + + /* + Starting a program on a different layer shouldn't cause scrolling view to move to fit the window that was focused when this program was started, regardless of follow_focus + --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + */ + + NLog::log("{}Testing scrolling view behaviour: new program occupying another layer shouldn't move scrolling view", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + // ensure variables are correctly set for the test - this is to avoid unwanted view shifts when setting up the windows + OK(getFromSocket("/eval hl.config({scrolling = {follow_focus = false}})")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("{}Failed to spawn kitty with win class `a`", Colors::RED); + } + + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("{}Failed to spawn kitty with win class `b`", Colors::RED); + } + + // focus class:a - this does not move scrolling view when follow_focus = 0 + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:a'})")); + + NLog::log("{}Spawning kitty layer {}", Colors::YELLOW, "myLayer"); + if (!Tests::spawnLayerKitty("myLayer")) { + FAIL_TEST("{}Error: {} layer did not spawn", Colors::RED, "myLayer"); + } + + // If the scrolling view did not move, class:a window's x coordinate for its `at:` value should be <0 + + const std::string currentWindowPos = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const std::string currentWindowPosX = currentWindowPos.substr(0, currentWindowPos.find(',')); + // test pass + if (std::stoi(currentWindowPosX) < 0) { + NLog ::log("{}Passed: {}window of class 'a' has negative x coordinates for its position: {}", Colors ::GREEN, Colors::RESET, currentWindowPosX); + } + // test fail + else { + FAIL_TEST("{}Failed: {}window of class 'a' does not have negative x coordinates for its position: {}", Colors::RED, Colors::RESET, currentWindowPosX); + } + + // clean up + + // kill all layers + NLog::log("{}Killing all layers", Colors::YELLOW); + Tests::killAllLayers(); + ASSERT(Tests::layerCount(), 0); + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourMaximise) { + + /* + maximising and then unmaximising a window shouldn't move scrolling view, regardless of follow_focus + --------------------------------------------------------------------------------------------------- + */ + + NLog::log("{}Testing scrolling view behaviour: maximising and then unmaximising a window shouldn't move scrolling view", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + // ensure variables are correctly set for the test - this is to avoid unwanted view shifts when setting up the windows + OK(getFromSocket("/eval hl.config({scrolling = {follow_focus = false}})")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("{}Failed to spawn kitty with win class `a`", Colors::RED); + } + + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("{}Failed to spawn kitty with win class `b`", Colors::RED); + } + + // focus class:a - this does not move scrolling view when follow_focus = 0 + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:a'})")); + + // fullscreen class:a window + OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({mode = 'maximized', action = 'set', window = 'class:a'})")); + + // unfullscreen class:a window + OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({mode = 'maximized', action = 'unset', window = 'class:a'})")); + + // If the scrolling view did not move, class:a window's x coordinate for its `at:` value should be <0 + + const std::string currentWindowPos = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const std::string currentWindowPosX = currentWindowPos.substr(0, currentWindowPos.find(',')); + // test pass + if (std::stoi(currentWindowPosX) < 0) { + NLog ::log("{}Passed: {}window of class 'a' has negative x coordinates for its position: {}", Colors ::GREEN, Colors::RESET, currentWindowPosX); + } + // test fail + else { + FAIL_TEST("{}Failed: {}window of class 'a' does not have negative x coordinates for its position: {}", Colors::RED, Colors::RESET, currentWindowPosX); + } + + // clean up + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourFullscreen) { + + /* + This is almost the same as the testScrollingViewBehaviourMaximise() test, just with fullscreen 1 (fullscreen) instead of fullscreen 0 (maximise) + + fullscreening and then unfullscreening a window shouldn't move scrolling view, regardless of follow_focus + */ + + NLog::log("{}Testing scrolling view behaviour: fullscreening and then unfullscreening a window shouldn't move scrolling view", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + // ensure variables are correctly set for the test - this is to avoid unwanted view shifts when setting up the windows + OK(getFromSocket("/eval hl.config({scrolling = {follow_focus = false}})")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("{}Failed to spawn kitty with win class `a`", Colors::RED); + } + + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("{}Failed to spawn kitty with win class `b`", Colors::RED); + } + + // focus class:a - this does not move scrolling view when follow_focus = 0 + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:a'})")); + + // maximise class:a window + OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({mode = 'fullscreen', action = 'set', window = 'class:a'})")); + + // unmaximise class:a window + OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({mode = 'fullscreen', action = 'unset', window = 'class:a'})")); + + // If the scrolling view did not move, class:a window's x coordinate for its `at:` value should be <0 + const std::string currentWindowPos = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const std::string currentWindowPosX = currentWindowPos.substr(0, currentWindowPos.find(',')); + // test pass + if (std::stoi(currentWindowPosX) < 0) { + NLog ::log("{}Passed: {}window of class 'a' has negative x coordinates for its position: {}", Colors ::GREEN, Colors::RESET, currentWindowPosX); + } + // test fail + else { + FAIL_TEST("{}Failed: {}window of class 'a' does not have negative x coordinates for its position: {}", Colors::RED, Colors::RESET, currentWindowPosX); + } + + // clean up + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourMoveFocusFollowFocusFalse) { + + /* + dispatching movefocus when follow_focus = false should not cause scrolling view to move + --------------------------------------------------------------------------------------- + */ + + NLog::log("{}Testing scrolling view behaviour: movefocus does not cause scrolling view to move if follow_focus = false", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + // ensure variables are correctly set for the test + OK(getFromSocket("/eval hl.config({scrolling = {follow_focus = false}})")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("{}Failed to spawn kitty with win class `a`", Colors::RED); + } + + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("{}Failed to spawn kitty with win class `b`", Colors::RED); + } + + // we expect that after dispatching this, scrolling view must not have moved + OK(getFromSocket("/dispatch hl.dsp.focus({direction = 'left'})")); + + // If the scrolling view did not move, class:a window's x coordinate for its `at:` value should be < 0. + const std::string currentWindowPos = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const std::string currentWindowPosX = currentWindowPos.substr(0, currentWindowPos.find(',')); + // test pass + if (std::stoi(currentWindowPosX) < 0) { + NLog::log("{}Passed: {}window of class 'a' has negative x coordinates for its position: {}", Colors ::GREEN, Colors::RESET, currentWindowPosX); + } + // test fail + else { + FAIL_TEST("{}Failed: {}window of class 'a' does not have negative x coordinates for its position: {}", Colors::RED, Colors::RESET, currentWindowPosX); + } + + // clean up + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourMoveFocusFollowFocusTrue) { + + /* + dispatching movefocus when follow_focus = true should cause scrolling view to move + ---------------------------------------------------------------------------------- + */ + + NLog::log("{}Testing scrolling view behaviour: movefocus does cause scrolling view to move if follow_focus = true", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("{}Failed to spawn kitty with win class `a`", Colors::RED); + } + + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("{}Failed to spawn kitty with win class `b`", Colors::RED); + } + + // we expect that after dispatching this, scrolling view must have moved since follow_focus = true + OK(getFromSocket("/dispatch hl.dsp.focus({direction = 'left'})")); + + // If the scrolling view moved, class:a window's x coordinate for its `at:` value should be >= 0 + + const std::string currentWindowPos = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const std::string currentWindowPosX = currentWindowPos.substr(0, currentWindowPos.find(',')); + // test fail + if (std::stoi(currentWindowPosX) < 0) { + FAIL_TEST("{}Failed: {}window of class 'a' does not have x coordinates >= 0 for its position: {}", Colors::RED, Colors::RESET, currentWindowPosX); + } + // test pass + else { + NLog ::log("{}Passed: {}window of class 'a' has x coordinates >= 0 for its position: {}", Colors ::GREEN, Colors::RESET, currentWindowPosX); + } + + // clean up + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourMoveFocusInGroupFollowFocusFalse) { + + /* + When movefocus is dispatched within groups to move focus from one group member to another, scrolling view must not move if follow_focus = false + ----------------------------------------------------------------------------------------------------------------------------------------------- + */ + + NLog::log("{}Testing scrolling view behaviour: movefocus within groups does not cause scrolling view to move if follow_focus = false", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + // ensure variables are correctly set for the test + + // necessary to make sure movefocus first cycles through tabs in a group + OK(getFromSocket("/eval hl.config({ binds = {movefocus_cycles_groupfirst = true}})")); + OK(getFromSocket("/eval hl.config({group = {auto_group = false}})")); + OK(getFromSocket("/eval hl.config({scrolling = {follow_focus = false}})")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("{}Failed to spawn kitty with win class `a`", Colors::RED); + } + + OK(getFromSocket("/dispatch hl.dsp.group.toggle({window = 'class:a'})")); + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("{}Failed to spawn kitty with win class `b`", Colors::RED); + } + + if (!Tests::spawnKitty("c")) { + FAIL_TEST("{}Failed to spawn kitty with win class `c`", Colors::RED); + } + + // focus class:b. This does not cause scrolling view to move when follow_focus = false + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:b'})")); + + // move it into the group where class:a is. This does not cause scrolling view to move when follow_focus = false + OK(getFromSocket("/dispatch hl.dsp.window.move({ into_group = 'left' })")); + + // we move from one window of a group to another (from class:b to class:a) via movefocus + OK(getFromSocket("/dispatch hl.dsp.focus({direction = 'left'})")); + + // the focus now should still be on class:a window. If the scrolling view did not move, its x coordinate for its `at:` value should be < 0 + + const std::string currentWindowPos = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const std::string currentWindowPosX = currentWindowPos.substr(0, currentWindowPos.find(',')); + // test pass + if (std::stoi(currentWindowPosX) < 0) { + NLog ::log("{}Passed: {}window of class 'a' has negative x coordinates for its position: {}", Colors ::GREEN, Colors::RESET, currentWindowPosX); + } + // test fail + else { + FAIL_TEST("{}Failed: {}window of class 'a' does not have negative x coordinates for its position: {}", Colors::RED, Colors::RESET, currentWindowPosX); + } + + // clean up + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); +} + +TEST_CASE(testScrollingViewBehaviourMoveFocusInGroupFollowFocusTrue) { + + /* + When movefocus is dispatched within groups to move focus from one group member to another, scrolling view must move if follow_focus = true + ------------------------------------------------------------------------------------------------------------------------------------------ + */ + + NLog::log("{}Testing scrolling view behaviour: movefocus within groups does causes scrolling view to move if follow_focus = true", Colors::GREEN); + + OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })")); + + // ensure variables are correctly set for the test + + // necessary to make sure movefocus first cycles through tabs in a group + OK(getFromSocket("/eval hl.config({ binds = {movefocus_cycles_groupfirst = true}})")); + OK(getFromSocket("/eval hl.config({group = {auto_group = false}})")); + + if (!Tests::spawnKitty("a")) { + FAIL_TEST("{}Failed to spawn kitty with win class `a`", Colors::RED); + } + + OK(getFromSocket("/dispatch hl.dsp.group.toggle({window = 'class:a'})")); + OK(getFromSocket("/dispatch hl.dsp.layout('colresize 0.8')")); + + if (!Tests::spawnKitty("b")) { + FAIL_TEST("{}Failed to spawn kitty with win class `b`", Colors::RED); + } + + if (!Tests::spawnKitty("c")) { + FAIL_TEST("{}Failed to spawn kitty with win class `c`", Colors::RED); + } + + // focus class:b. This does not cause scrolling view to move when follow_focus = false + OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:b'})")); + + // move it into the group where class:a is. This does not cause scrolling view to move when follow_focus = false + OK(getFromSocket("/dispatch hl.dsp.window.move({ into_group = 'left' })")); + + // we move from one window of a group to another (from class:b to class:a) via movefocus + OK(getFromSocket("/dispatch hl.dsp.focus({direction = 'left'})")); + + // the focus now should still be on class:a window. If the scrolling view moved, its x coordinate for its `at:` value should be >= 0 + + const std::string currentWindowPos = Tests::getAttribute(getFromSocket("/activewindow"), "at"); + const std::string currentWindowPosX = currentWindowPos.substr(0, currentWindowPos.find(',')); + // test fail + if (std::stoi(currentWindowPosX) < 0) { + FAIL_TEST("{}Failed: {}window of class 'a' does not have x coordinates >= 0 for its position: {}", Colors::RED, Colors::RESET, currentWindowPosX); + } + // test pass + else { + NLog ::log("{}Passed: {}window of class 'a' has x coordinates >= 0 for its position: {}", Colors ::GREEN, Colors::RESET, currentWindowPosX); + } + + // clean up + + // kill all windows + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + ASSERT(Tests::windowCount(), 0); } diff --git a/src/Compositor.cpp b/src/Compositor.cpp index b5dba342e..5a360df4c 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -2259,7 +2259,7 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::Vie Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE); PWINDOW->updateDecorationValues(); - g_layoutManager->recalculateMonitor(PMONITOR); + g_layoutManager->recalculateMonitor(PMONITOR, Layout::CLayoutManager::RECALCULATE_MONITOR_REASON_TOGGLE_FULLSCREEN); // make all windows and layers on the same workspace under the fullscreen window for (auto const& w : m_windows) { diff --git a/src/config/shared/actions/ConfigActions.cpp b/src/config/shared/actions/ConfigActions.cpp index 56fb88e98..11839ead1 100644 --- a/src/config/shared/actions/ConfigActions.cpp +++ b/src/config/shared/actions/ConfigActions.cpp @@ -60,10 +60,10 @@ static void switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool forceFSCycle = fals g_pInputManager->unconstrainMouse(); if (PLASTWINDOW && PLASTWINDOW->m_workspace == PWINDOWTOCHANGETO->m_workspace && PLASTWINDOW->isFullscreen()) - Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, Desktop::FOCUS_REASON_KEYBIND, nullptr, forceFSCycle); + Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, Desktop::FOCUS_REASON_SWITCH_TO_WINDOW_HARD, nullptr, forceFSCycle); else { updateRelativeCursorCoords(); - Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, Desktop::FOCUS_REASON_KEYBIND, nullptr, forceFSCycle); + Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, Desktop::FOCUS_REASON_SWITCH_TO_WINDOW_SOFT, nullptr, forceFSCycle); PWINDOWTOCHANGETO->warpCursor(); if (*PNOWARPS == 0 || *PFOLLOWMOUSE < 2) { @@ -430,7 +430,7 @@ ActionResult Actions::focus(PHLWINDOW window) { Desktop::focusState()->monitor()->m_activeSpecialWorkspace != window->m_workspace) // NOLINTNEXTLINE Actions::changeWorkspace(PWORKSPACE); - Desktop::focusState()->fullWindowFocus(window, Desktop::FOCUS_REASON_KEYBIND, nullptr, false); + Desktop::focusState()->fullWindowFocus(window, Desktop::FOCUS_REASON_DISPATCH_FOCUSWINDOW, nullptr, false); window->warpCursor(); return {}; @@ -1269,7 +1269,7 @@ static void moveWindowIntoGroupHelper(PHLWINDOW pWindow, PHLWINDOW pWindowInDire pWindowInDirection->m_group->add(pWindow); pWindowInDirection->m_group->setCurrent(pWindow); pWindow->updateWindowDecos(); - Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_DISPATCH_MOVEWINDOWINTOGROUP); pWindow->warpCursor(); g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveintogroup", .data = std::format("{:x}", rc(pWindow.get()))}); diff --git a/src/desktop/state/FocusState.cpp b/src/desktop/state/FocusState.cpp index 5dd22f94b..2ebc2d186 100644 --- a/src/desktop/state/FocusState.cpp +++ b/src/desktop/state/FocusState.cpp @@ -305,5 +305,6 @@ void CFocusState::resetWindowFocus() { } bool Desktop::isHardInputFocusReason(eFocusReason r) { - return r == FOCUS_REASON_NEW_WINDOW || r == FOCUS_REASON_KEYBIND || r == FOCUS_REASON_GHOSTS || r == FOCUS_REASON_CLICK || r == FOCUS_REASON_DESKTOP_STATE_CHANGE; + return r == FOCUS_REASON_NEW_WINDOW || r == FOCUS_REASON_KEYBIND || r == FOCUS_REASON_GHOSTS || r == FOCUS_REASON_CLICK || r == FOCUS_REASON_DESKTOP_STATE_CHANGE || + r == FOCUS_REASON_UNMAP_WINDOW_TILING || r == FOCUS_REASON_SWITCH_TO_WINDOW_HARD; } diff --git a/src/desktop/state/FocusState.hpp b/src/desktop/state/FocusState.hpp index 71330a3eb..334c426d6 100644 --- a/src/desktop/state/FocusState.hpp +++ b/src/desktop/state/FocusState.hpp @@ -10,9 +10,19 @@ namespace Desktop { FOCUS_REASON_UNKNOWN = 0, FOCUS_REASON_FFM, FOCUS_REASON_KEYBIND, + FOCUS_REASON_DISPATCH_FOCUSWINDOW, + FOCUS_REASON_DISPATCH_MOVEWINDOWINTOGROUP, FOCUS_REASON_CLICK, FOCUS_REASON_OTHER, FOCUS_REASON_DESKTOP_STATE_CHANGE, + FOCUS_REASON_SWITCH_TO_WINDOW_SOFT, + FOCUS_REASON_SWITCH_TO_WINDOW_HARD, + FOCUS_REASON_GROUP_CURRENT_WINDOW_CHANGE, + FOCUS_REASON_WORKSPACE_CHANGE, + FOCUS_REASON_TOGGLE_SPECIAL_WORKSPACE, + FOCUS_REASON_UNMAP_WINDOW_TILING, + FOCUS_REASON_UNMAP_WINDOW_FLOATING, + FOCUS_REASON_UNMAP_GROUPED_WINDOW, FOCUS_REASON_NEW_WINDOW, FOCUS_REASON_GHOSTS, }; diff --git a/src/desktop/view/Group.cpp b/src/desktop/view/Group.cpp index f2b2a5642..7073672a4 100644 --- a/src/desktop/view/Group.cpp +++ b/src/desktop/view/Group.cpp @@ -122,7 +122,7 @@ void CGroup::add(PHLWINDOW w) { m_target->recalc(); } -void CGroup::remove(PHLWINDOW w, Math::eDirection dir) { +void CGroup::remove(PHLWINDOW w, Math::eDirection dir, eRemoveFromGroupReason reason) { std::optional idx; for (size_t i = 0; i < m_windows.size(); ++i) { if (m_windows.at(i) == w) { @@ -171,6 +171,10 @@ void CGroup::remove(PHLWINDOW w, Math::eDirection dir) { default: break; } } + + // We don't need to assign a window to a new space if we intend to unmap it + if (reason == REMOVE_FROM_GROUP_REASON_UNMAP_WINDOW) + return; w->m_target->assignToSpace(m_target->space(), focalPoint); } } @@ -215,7 +219,7 @@ void CGroup::setCurrent(size_t idx) { } if (WASFOCUS) - Desktop::focusState()->rawWindowFocus(current(), FOCUS_REASON_DESKTOP_STATE_CHANGE); + Desktop::focusState()->rawWindowFocus(current(), FOCUS_REASON_GROUP_CURRENT_WINDOW_CHANGE); } void CGroup::setCurrent(PHLWINDOW w) { diff --git a/src/desktop/view/Group.hpp b/src/desktop/view/Group.hpp index 048c5023b..3ada4d6ef 100644 --- a/src/desktop/view/Group.hpp +++ b/src/desktop/view/Group.hpp @@ -15,10 +15,15 @@ namespace Desktop::View { static SP create(std::vector&& windows); ~CGroup(); + enum eRemoveFromGroupReason : uint8_t { + REMOVE_FROM_GROUP_REASON_UNKNOWN, // when the remove from group reason is unknown or not important to preserve + REMOVE_FROM_GROUP_REASON_UNMAP_WINDOW, + }; + bool has(PHLWINDOW w) const; void add(PHLWINDOW w); - void remove(PHLWINDOW w, Math::eDirection dir = Math::DIRECTION_DEFAULT); + void remove(PHLWINDOW w, Math::eDirection dir = Math::DIRECTION_DEFAULT, eRemoveFromGroupReason reason = REMOVE_FROM_GROUP_REASON_UNKNOWN); void moveCurrent(bool next); void setCurrent(size_t idx); void setCurrent(PHLWINDOW w); diff --git a/src/desktop/view/Window.cpp b/src/desktop/view/Window.cpp index b2f4821b8..03229190a 100644 --- a/src/desktop/view/Window.cpp +++ b/src/desktop/view/Window.cpp @@ -2361,7 +2361,7 @@ void CWindow::unmapWindow() { PWORKSPACE->m_hasFullscreenWindow = false; if (m_group) - m_group->remove(m_self.lock()); + m_group->remove(m_self.lock(), Math::DIRECTION_DEFAULT, CGroup::REMOVE_FROM_GROUP_REASON_UNMAP_WINDOW); g_layoutManager->removeTarget(m_target); @@ -2390,9 +2390,9 @@ void CWindow::unmapWindow() { if (candidate != Desktop::focusState()->window() && candidate) { if (candidate == nextInGroup) - Desktop::focusState()->rawWindowFocus(candidate, FOCUS_REASON_DESKTOP_STATE_CHANGE); + Desktop::focusState()->rawWindowFocus(candidate, FOCUS_REASON_UNMAP_GROUPED_WINDOW); else - Desktop::focusState()->fullWindowFocus(candidate, FOCUS_REASON_DESKTOP_STATE_CHANGE); + Desktop::focusState()->fullWindowFocus(candidate, m_self->m_isFloating ? FOCUS_REASON_UNMAP_WINDOW_FLOATING : FOCUS_REASON_UNMAP_WINDOW_TILING); if ((*PEXITRETAINSFS || candidate == nextInGroup) && CURRENTWINDOWFSSTATE) g_pCompositor->setWindowFullscreenInternal(candidate, CURRENTFSMODE); diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index c7d4168ae..023bd38b9 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -1409,13 +1409,13 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo pWindow = pWorkspace->getFocusCandidate(); } - Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_WORKSPACE_CHANGE); } if (!noMouseMove) g_pInputManager->simulateMouseMovement(); - g_layoutManager->recalculateMonitor(m_self.lock()); + g_layoutManager->recalculateMonitor(m_self.lock(), Layout::CLayoutManager::RECALCULATE_MONITOR_REASON_WORKSPACE_CHANGE); g_pEventManager->postEvent(SHyprIPCEvent{"workspace", pWorkspace->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"workspacev2", std::format("{},{}", pWorkspace->m_id, pWorkspace->m_name)}); @@ -1478,11 +1478,11 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { if (POLDSPECIAL) POLDSPECIAL->m_events.activeChanged.emit(); - g_layoutManager->recalculateMonitor(m_self.lock()); + g_layoutManager->recalculateMonitor(m_self.lock(), Layout::CLayoutManager::RECALCULATE_MONITOR_REASON_TOGGLE_SPECIAL_WORKSPACE); if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) { if (const auto PLAST = m_activeWorkspace->getLastFocusedWindow(); PLAST) - Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_TOGGLE_SPECIAL_WORKSPACE); else g_pInputManager->refocus(); } @@ -1508,7 +1508,7 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { if (PMONITOR && PMONITOR->m_activeSpecialWorkspace == pWorkspace) { PMONITOR->m_activeSpecialWorkspace.reset(); - g_layoutManager->recalculateMonitor(PMONITOR); + g_layoutManager->recalculateMonitor(PMONITOR, Layout::CLayoutManager::RECALCULATE_MONITOR_REASON_TOGGLE_SPECIAL_WORKSPACE); g_pHyprRenderer->damageMonitor(PMONITOR); g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMONITOR->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + PMONITOR->m_name}); @@ -1573,11 +1573,11 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { } } - g_layoutManager->recalculateMonitor(m_self.lock()); + g_layoutManager->recalculateMonitor(m_self.lock(), Layout::CLayoutManager::RECALCULATE_MONITOR_REASON_TOGGLE_SPECIAL_WORKSPACE); if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) { if (const auto PLAST = pWorkspace->getLastFocusedWindow(); PLAST) - Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_TOGGLE_SPECIAL_WORKSPACE); else g_pInputManager->refocus(); } diff --git a/src/layout/LayoutManager.cpp b/src/layout/LayoutManager.cpp index f90cedf63..f453dfeed 100644 --- a/src/layout/LayoutManager.cpp +++ b/src/layout/LayoutManager.cpp @@ -332,18 +332,19 @@ void CLayoutManager::performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SPm_activeSpecialWorkspace) - m->m_activeSpecialWorkspace->m_space->recalculate(); + m->m_activeSpecialWorkspace->m_space->recalculate(recalcMonitorReasonToRecalcReason(reason)); + if (m->m_activeWorkspace) - m->m_activeWorkspace->m_space->recalculate(); + m->m_activeWorkspace->m_space->recalculate(recalcMonitorReasonToRecalcReason(reason)); } void CLayoutManager::invalidateMonitorGeometries(PHLMONITOR m) { for (const auto& ws : g_pCompositor->getWorkspaces()) { if (ws && ws->m_monitor == m) { ws->m_space->recheckWorkArea(); - ws->m_space->recalculate(); + ws->m_space->recalculate(RECALCULATE_REASON_INVALIDATE_MONITOR_GEOMETRIES); } } } diff --git a/src/layout/LayoutManager.hpp b/src/layout/LayoutManager.hpp index 2660cf289..c78ad30e6 100644 --- a/src/layout/LayoutManager.hpp +++ b/src/layout/LayoutManager.hpp @@ -45,6 +45,13 @@ namespace Layout { CLayoutManager(); ~CLayoutManager() = default; + enum eRecalculateMonitorReason : uint8_t { + RECALCULATE_MONITOR_REASON_UNKNOWN, // when the recalculate monitor reason is unknown or not important to preserve + RECALCULATE_MONITOR_REASON_WORKSPACE_CHANGE, + RECALCULATE_MONITOR_REASON_TOGGLE_SPECIAL_WORKSPACE, + RECALCULATE_MONITOR_REASON_TOGGLE_FULLSCREEN, + }; + void newTarget(SP target, SP space); void removeTarget(SP target); @@ -76,7 +83,7 @@ namespace Layout { void performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP target, eMouseBindMode mode, int corner, const Vector2D& beginSize); void invalidateMonitorGeometries(PHLMONITOR); - void recalculateMonitor(PHLMONITOR); + void recalculateMonitor(PHLMONITOR, eRecalculateMonitorReason reason = RECALCULATE_MONITOR_REASON_UNKNOWN); const UP& dragController(); diff --git a/src/layout/algorithm/Algorithm.cpp b/src/layout/algorithm/Algorithm.cpp index b22eb9bfc..126aa9f8c 100644 --- a/src/layout/algorithm/Algorithm.cpp +++ b/src/layout/algorithm/Algorithm.cpp @@ -101,9 +101,9 @@ size_t CAlgorithm::floatingTargets() const { return m_floatingTargets.size(); } -void CAlgorithm::recalculate() { - m_tiled->recalculate(); - m_floating->recalculate(); +void CAlgorithm::recalculate(eRecalculateReason reason) { + m_tiled->recalculate(reason); + m_floating->recalculate(reason); const auto PWORKSPACE = m_space->workspace(); if (!PWORKSPACE) diff --git a/src/layout/algorithm/Algorithm.hpp b/src/layout/algorithm/Algorithm.hpp index 8b9e471d2..57d824a93 100644 --- a/src/layout/algorithm/Algorithm.hpp +++ b/src/layout/algorithm/Algorithm.hpp @@ -5,6 +5,7 @@ #include "../../helpers/memory/Memory.hpp" #include "../LayoutManager.hpp" +#include "../space/Space.hpp" #include #include @@ -34,7 +35,7 @@ namespace Layout { Config::ErrorResult layoutMsg(const std::string_view& sv); std::optional predictSizeForNewTiledTarget(); - void recalculate(); + void recalculate(eRecalculateReason reason = RECALCULATE_REASON_UNKNOWN); void recenter(SP t); void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); diff --git a/src/layout/algorithm/FloatingAlgorithm.cpp b/src/layout/algorithm/FloatingAlgorithm.cpp index 058887bf0..ed9d2c463 100644 --- a/src/layout/algorithm/FloatingAlgorithm.cpp +++ b/src/layout/algorithm/FloatingAlgorithm.cpp @@ -4,7 +4,7 @@ using namespace Layout; -void IFloatingAlgorithm::recalculate() { +void IFloatingAlgorithm::recalculate(eRecalculateReason reason) { ; } diff --git a/src/layout/algorithm/FloatingAlgorithm.hpp b/src/layout/algorithm/FloatingAlgorithm.hpp index 40e530343..9232888d2 100644 --- a/src/layout/algorithm/FloatingAlgorithm.hpp +++ b/src/layout/algorithm/FloatingAlgorithm.hpp @@ -22,7 +22,7 @@ namespace Layout { virtual void recenter(SP t); - virtual void recalculate(); + virtual void recalculate(eRecalculateReason reason = RECALCULATE_REASON_UNKNOWN); protected: IFloatingAlgorithm() = default; diff --git a/src/layout/algorithm/ModeAlgorithm.hpp b/src/layout/algorithm/ModeAlgorithm.hpp index e53d4391e..ac43c4a77 100644 --- a/src/layout/algorithm/ModeAlgorithm.hpp +++ b/src/layout/algorithm/ModeAlgorithm.hpp @@ -5,6 +5,7 @@ #include "../../helpers/memory/Memory.hpp" #include "../LayoutManager.hpp" +#include "../space/Space.hpp" #include @@ -30,7 +31,7 @@ namespace Layout { virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE) = 0; // recalculate layout - virtual void recalculate() = 0; + virtual void recalculate(eRecalculateReason reason = RECALCULATE_REASON_UNKNOWN) = 0; // swap targets virtual void swapTargets(SP a, SP b) = 0; diff --git a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp index 100856be3..ff1b1b4f2 100644 --- a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp +++ b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp @@ -488,7 +488,7 @@ void CDwindleAlgorithm::swapTargets(SP a, SP b) { nodeB->pTarget = a; } -void CDwindleAlgorithm::recalculate() { +void CDwindleAlgorithm::recalculate(eRecalculateReason reason) { calculateWorkspace(); } diff --git a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp index 300daa639..cf5440897 100644 --- a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp +++ b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp @@ -35,7 +35,7 @@ namespace Layout::Tiled { virtual void removeTarget(SP target); virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); - virtual void recalculate(); + virtual void recalculate(eRecalculateReason reason = RECALCULATE_REASON_UNKNOWN); virtual SP getNextCandidate(SP old); diff --git a/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp b/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp index c57a3639f..b4d4eea1e 100644 --- a/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp +++ b/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp @@ -424,7 +424,7 @@ void CMasterAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir } } -void CMasterAlgorithm::recalculate() { +void CMasterAlgorithm::recalculate(eRecalculateReason reason) { calculateWorkspace(); } diff --git a/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp b/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp index 00e827841..b29542c61 100644 --- a/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp +++ b/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp @@ -53,7 +53,7 @@ namespace Layout::Tiled { virtual void removeTarget(SP target); virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); - virtual void recalculate(); + virtual void recalculate(eRecalculateReason reason = RECALCULATE_REASON_UNKNOWN); virtual SP getNextCandidate(SP old); diff --git a/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp index 84b4f9e2c..e619bf572 100644 --- a/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp +++ b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp @@ -125,7 +125,7 @@ void CMonocleAlgorithm::resizeTarget(const Vector2D& Δ, SP target, eRe // monocle layout doesn't support manual resizing, all windows are fullscreen } -void CMonocleAlgorithm::recalculate() { +void CMonocleAlgorithm::recalculate(eRecalculateReason reason) { if (m_targetDatas.empty()) return; diff --git a/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp index 3f07748b0..072f9cf09 100644 --- a/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp +++ b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp @@ -26,7 +26,7 @@ namespace Layout::Tiled { virtual void removeTarget(SP target); virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); - virtual void recalculate(); + virtual void recalculate(eRecalculateReason reason = RECALCULATE_REASON_UNKNOWN); virtual SP getNextCandidate(SP old); diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp index 28941109d..9c2f4ba88 100644 --- a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp @@ -535,7 +535,16 @@ CScrollingAlgorithm::CScrollingAlgorithm() { if (!TARGET || TARGET->floating()) return; - focusOnInput(TARGET, reason == Desktop::FOCUS_REASON_CLICK ? INPUT_MODE_CLICK : (Desktop::isHardInputFocusReason(reason) ? INPUT_MODE_KB : INPUT_MODE_SOFT)); + // if follow_focus != 0, focuswindow always moves scrolling view + // if follow_focus != 0, change in a group's current window state always moves scrolling view + // if follow_focus != 0, moving a window into group via the corresponding dispatches `moveintogroup`, `movewindoworgroup` always moves scrolling view + // if follow_focus != 0, moving focus via dispatches that cause switching to a specific window via calling switchToWindow(), such as movefocus, cyclenext, focuscurrentor(last/urgent); always moves scrolling view + if (*PFOLLOW_FOCUS && + (reason == Desktop::FOCUS_REASON_DISPATCH_FOCUSWINDOW || reason == Desktop::FOCUS_REASON_GROUP_CURRENT_WINDOW_CHANGE || + reason == Desktop::FOCUS_REASON_DISPATCH_MOVEWINDOWINTOGROUP || reason == Desktop::FOCUS_REASON_SWITCH_TO_WINDOW_SOFT)) + focusOnInput(TARGET, INPUT_MODE_HARD); + else + focusOnInput(TARGET, reason == Desktop::FOCUS_REASON_CLICK ? INPUT_MODE_CLICK : (Desktop::isHardInputFocusReason(reason) ? INPUT_MODE_HARD : INPUT_MODE_SOFT)); }); // Initialize default widths and direction @@ -576,7 +585,7 @@ void CScrollingAlgorithm::focusOnInput(SP target, eInputMode input) { } // if we moved via non-kb, and it's fully visible, ignore - if (m_scrollingData->visible(TARGETDATA->column.lock(), true) && input != INPUT_MODE_KB) + if (m_scrollingData->visible(TARGETDATA->column.lock(), true) && input != INPUT_MODE_HARD) return; static const auto PFITMETHOD = CConfigValue("scrolling:focus_fit_method"); @@ -796,7 +805,7 @@ void CScrollingAlgorithm::resizeTarget(const Vector2D& delta, SP target m_scrollingData->recalculate(true); } -void CScrollingAlgorithm::recalculate() { +void CScrollingAlgorithm::recalculate(eRecalculateReason reason) { // guard against recalculation during transitional monitor states // (e.g. monitor reconnecting after suspend where workspace/monitor may not be ready) if (!m_parent || !m_parent->space() || !m_parent->space()->workspace() || !m_parent->space()->workspace()->m_monitor) @@ -807,8 +816,14 @@ void CScrollingAlgorithm::recalculate() { const auto TARGETDATA = dataFor(TARGET); - if (TARGETDATA && !m_scrollingData->visible(TARGETDATA->column.lock(), true)) - focusOnInput(Desktop::focusState()->window()->layoutTarget(), INPUT_MODE_KB); + if (TARGETDATA && !m_scrollingData->visible(TARGETDATA->column.lock(), true)) { + + /* guard against unwanted scrolling viewport moves - If recalculate() was called, it is assumed that either the INPUT_MODE will be HARD (i.e. it is meant to move the scrolling viewport) or + it is not meant to move the scrolling viewport. + (e.g. changing workspace to a scrolling layout workspace fits the focused window in that workspace into view) */ + if (Layout::isHardRecalculateReason(reason)) + focusOnInput(Desktop::focusState()->window()->layoutTarget(), INPUT_MODE_HARD); + } } m_scrollingData->recalculate(); diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp index 288d15a62..8d864d53c 100644 --- a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp @@ -99,7 +99,7 @@ namespace Layout::Tiled { virtual void removeTarget(SP target); virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); - virtual void recalculate(); + virtual void recalculate(eRecalculateReason reason = RECALCULATE_REASON_UNKNOWN); virtual SP getNextCandidate(SP old); @@ -115,7 +115,7 @@ namespace Layout::Tiled { enum eInputMode : uint8_t { INPUT_MODE_SOFT = 0, INPUT_MODE_CLICK, - INPUT_MODE_KB + INPUT_MODE_HARD }; private: diff --git a/src/layout/space/Space.cpp b/src/layout/space/Space.cpp index 26c02aaec..ae2a12f7d 100644 --- a/src/layout/space/Space.cpp +++ b/src/layout/space/Space.cpp @@ -146,11 +146,11 @@ SP CSpace::algorithm() const { return m_algorithm; } -void CSpace::recalculate() { +void CSpace::recalculate(eRecalculateReason reason) { recheckWorkArea(); if (m_algorithm) - m_algorithm->recalculate(); + m_algorithm->recalculate(reason); } void CSpace::setFullscreen(SP t, eFullscreenMode mode) { @@ -159,7 +159,7 @@ void CSpace::setFullscreen(SP t, eFullscreenMode mode) { if (mode == FSMODE_NONE && m_algorithm && t->floating()) m_algorithm->recenter(t); - recalculate(); + recalculate(RECALCULATE_REASON_TOGGLE_FULLSCREEN); } Config::ErrorResult CSpace::layoutMsg(const std::string_view& sv) { @@ -202,6 +202,21 @@ SP CSpace::getNextCandidate(SP old) { return !m_algorithm ? nullptr : m_algorithm->getNextCandidate(old); } +bool Layout::isHardRecalculateReason(eRecalculateReason reason) { + return reason != RECALCULATE_REASON_WORKSPACE_CHANGE && reason != RECALCULATE_REASON_SPECIAL_WORKSPACE_TOGGLE && reason != RECALCULATE_REASON_TOGGLE_FULLSCREEN && + reason != RECALCULATE_REASON_INVALIDATE_MONITOR_GEOMETRIES && reason != RECALCULATE_REASON_RENDER_MOINTOR; +} + const std::vector>& CSpace::targets() const { return m_targets; } + +eRecalculateReason Layout::recalcMonitorReasonToRecalcReason(CLayoutManager::eRecalculateMonitorReason reason) { + // If eRecalculateMonitorReason doesn't have a eRecalculateReason pair, it'll return nullopt + switch (reason) { + case CLayoutManager::RECALCULATE_MONITOR_REASON_TOGGLE_SPECIAL_WORKSPACE: return RECALCULATE_REASON_SPECIAL_WORKSPACE_TOGGLE; + case CLayoutManager::RECALCULATE_MONITOR_REASON_WORKSPACE_CHANGE: return RECALCULATE_REASON_WORKSPACE_CHANGE; + case CLayoutManager::RECALCULATE_MONITOR_REASON_TOGGLE_FULLSCREEN: return RECALCULATE_REASON_TOGGLE_FULLSCREEN; + default: return RECALCULATE_REASON_UNKNOWN; + } +} \ No newline at end of file diff --git a/src/layout/space/Space.hpp b/src/layout/space/Space.hpp index 5d7f9d014..6f7236966 100644 --- a/src/layout/space/Space.hpp +++ b/src/layout/space/Space.hpp @@ -12,6 +12,18 @@ #include namespace Layout { + + enum eRecalculateReason : uint8_t { + RECALCULATE_REASON_UNKNOWN, // when the recalculate reason is unknown or not important to preserve + RECALCULATE_REASON_WORKSPACE_CHANGE, + RECALCULATE_REASON_SPECIAL_WORKSPACE_TOGGLE, + RECALCULATE_REASON_TOGGLE_FULLSCREEN, + RECALCULATE_REASON_INVALIDATE_MONITOR_GEOMETRIES, + RECALCULATE_REASON_RENDER_MOINTOR, + }; + + eRecalculateReason recalcMonitorReasonToRecalcReason(CLayoutManager::eRecalculateMonitorReason reason); + class ITarget; class CAlgorithm; @@ -34,7 +46,7 @@ namespace Layout { void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); - void recalculate(); + void recalculate(eRecalculateReason reason = RECALCULATE_REASON_UNKNOWN); void toggleTargetFloating(SP t); @@ -68,4 +80,6 @@ namespace Layout { // for recalc CHyprSignalListener m_geomUpdateCallback; }; + + bool isHardRecalculateReason(eRecalculateReason reason); }; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 635bef556..e75b295bd 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -2002,7 +2002,7 @@ void IHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { if (pMonitor->m_scheduledRecalc) { pMonitor->m_scheduledRecalc = false; if (pMonitor->m_activeWorkspace) // might be missing (mirror) - pMonitor->m_activeWorkspace->m_space->recalculate(); + pMonitor->m_activeWorkspace->m_space->recalculate(Layout::RECALCULATE_REASON_RENDER_MOINTOR); } if (!pMonitor->m_output->needsFrame && pMonitor->m_forceFullFrames == 0)