diff options
| author | Nikolai Nechaev <nikolay_nechaev@mail.ru> | 2025-11-26 07:44:26 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-25 22:44:26 +0000 |
| commit | 40d8fa84919c6d0e9fcb1536f4bc09710d4c031c (patch) | |
| tree | e45044c92e5d8f716f2aa15668c61fed5aaf5895 /hyprtester/src/tests/main/window.cpp | |
| parent | 1c1746de61a90dbbae072f3fc618c45af33fdd59 (diff) | |
compositor: Configurable behavior when window to be focused conflicts with fullscreen (#12033)
Renames `misc:new_window_takes_over_fullscreen` into
`misc:on_focus_under_fullscreen` and implements the following behavior:
- By default, when a tiling window is being focused on a workspace where
a fullscreen/maximized window exists, respect
the `misc:on_focus_under_fullscreen` config variable.
Diffstat (limited to 'hyprtester/src/tests/main/window.cpp')
| -rw-r--r-- | hyprtester/src/tests/main/window.cpp | 197 |
1 files changed, 184 insertions, 13 deletions
diff --git a/hyprtester/src/tests/main/window.cpp b/hyprtester/src/tests/main/window.cpp index e7dfd38d..3265bf72 100644 --- a/hyprtester/src/tests/main/window.cpp +++ b/hyprtester/src/tests/main/window.cpp @@ -1,6 +1,9 @@ #include <cmath> -#include <thread> #include <chrono> +#include <cstdlib> +#include <cstring> +#include <filesystem> +#include <thread> #include <hyprutils/os/Process.hpp> #include <hyprutils/memory/WeakPtr.hpp> @@ -11,9 +14,9 @@ static int ret = 0; -static bool spawnKitty(const std::string& class_) { +static bool spawnKitty(const std::string& class_, const std::vector<std::string>& args = {}) { NLog::log("{}Spawning {}", Colors::YELLOW, class_); - if (!Tests::spawnKitty(class_)) { + if (!Tests::spawnKitty(class_, args)) { NLog::log("{}Error: {} did not spawn", Colors::RED, class_); return false; } @@ -193,6 +196,153 @@ static void testGroupRules() { Tests::killAllWindows(); } +static bool isActiveWindow(const std::string& class_, char fullscreen, bool log = true) { + std::string activeWin = getFromSocket("/activewindow"); + auto winClass = getWindowAttribute(activeWin, "class:"); + auto winFullscreen = getWindowAttribute(activeWin, "fullscreen:").back(); + if (winClass.substr(strlen("class: ")) == class_ && winFullscreen == fullscreen) + return true; + else { + if (log) + NLog::log("{}Wrong active window: expected class {} fullscreen '{}', found class {}, fullscreen '{}'", Colors::RED, class_, fullscreen, winClass, winFullscreen); + return false; + } +} + +static bool waitForActiveWindow(const std::string& class_, char fullscreen, int maxTries = 50) { + int cnt = 0; + while (!isActiveWindow(class_, fullscreen, false)) { + ++cnt; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + if (cnt > maxTries) { + return isActiveWindow(class_, fullscreen, true); + } + } + return true; +} + +/// Tests behavior of a window being focused when on that window's workspace +/// another fullscreen window exists. +static bool testWindowFocusOnFullscreenConflict() { + if (!spawnKitty("kitty_A")) + return false; + if (!spawnKitty("kitty_B")) + return false; + + OK(getFromSocket("/keyword misc:focus_on_activate true")); + + auto spawnKittyActivating = [] -> std::string { + // `XXXXXX` is what `mkstemp` expects to find in the string + std::string tmpFilename = (std::filesystem::temp_directory_path() / "XXXXXX").string(); + int fd = mkstemp(tmpFilename.data()); + if (fd < 0) { + NLog::log("{}Error: could not create tmp file: errno {}", Colors::RED, errno); + return ""; + } + (void)close(fd); + bool ok = spawnKitty("kitty_activating", + {"-o", "allow_remote_control=yes", "--", "/bin/sh", "-c", "while [ -f \"" + tmpFilename + "\" ]; do :; done; kitten @ focus-window; sleep infinity"}); + if (!ok) { + NLog::log("{}Error: failed to spawn kitty", Colors::RED); + return ""; + } + return tmpFilename; + }; + + // Unfullscreen on conflict + { + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2")); + + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + OK(getFromSocket("/dispatch fullscreen 0 set")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + + // Dispatch-focus the same window + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + + // Dispatch-focus a different window + OK(getFromSocket("/dispatch focuswindow class:kitty_B")); + EXPECT(isActiveWindow("kitty_B", '0'), true); + + // Make a window that will request focus + const std::string removeToActivate = spawnKittyActivating(); + if (removeToActivate.empty()) + return false; + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + OK(getFromSocket("/dispatch fullscreen 0 set")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + std::filesystem::remove(removeToActivate); + EXPECT(waitForActiveWindow("kitty_activating", '0'), true); + OK(getFromSocket("/dispatch forcekillactive")); + Tests::waitUntilWindowsN(2); + } + + // Take over on conflict + { + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1")); + + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + OK(getFromSocket("/dispatch fullscreen 0 set")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + + // Dispatch-focus the same window + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + + // Dispatch-focus a different window + OK(getFromSocket("/dispatch focuswindow class:kitty_B")); + EXPECT(isActiveWindow("kitty_B", '2'), true); + OK(getFromSocket("/dispatch fullscreenstate 0 0")); + + // Make a window that will request focus + const std::string removeToActivate = spawnKittyActivating(); + if (removeToActivate.empty()) + return false; + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + OK(getFromSocket("/dispatch fullscreen 0 set")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + std::filesystem::remove(removeToActivate); + EXPECT(waitForActiveWindow("kitty_activating", '2'), true); + OK(getFromSocket("/dispatch forcekillactive")); + Tests::waitUntilWindowsN(2); + } + + // Keep the old focus on conflict + { + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0")); + + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + OK(getFromSocket("/dispatch fullscreen 0 set")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + + // Dispatch-focus the same window + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + + // Make a window that will request focus - the setting is treated normally + const std::string removeToActivate = spawnKittyActivating(); + if (removeToActivate.empty()) + return false; + OK(getFromSocket("/dispatch focuswindow class:kitty_A")); + OK(getFromSocket("/dispatch fullscreen 0 set")); + EXPECT(isActiveWindow("kitty_A", '2'), true); + std::filesystem::remove(removeToActivate); + EXPECT(waitForActiveWindow("kitty_A", '2'), true); + } + + NLog::log("{}Reloading config", Colors::YELLOW); + OK(getFromSocket("/reload")); + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + NLog::log("{}Expecting 0 windows", Colors::YELLOW); + EXPECT(Tests::windowCount(), 0); + + return true; +} + static bool test() { NLog::log("{}Testing windows", Colors::GREEN); @@ -256,16 +406,7 @@ static bool test() { getFromSocket("/dispatch exec xeyes"); NLog::log("{}Keep checking if xeyes spawned", Colors::YELLOW); - int counter = 0; - while (Tests::windowCount() != 3) { - counter++; - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - if (counter > 50) { - EXPECT(Tests::windowCount(), 3); - return !ret; - } - } + Tests::waitUntilWindowsN(3); NLog::log("{}Expecting 3 windows", Colors::YELLOW); EXPECT(Tests::windowCount(), 3); @@ -292,6 +433,36 @@ static bool test() { getFromSocket("/dispatch workspace 1"); + if (!testWindowFocusOnFullscreenConflict()) { + ret = 1; + return false; + } + + NLog::log("{}Testing spawning a floating window over a fullscreen window", Colors::YELLOW); + { + if (!spawnKitty("kitty_A")) + return false; + OK(getFromSocket("/dispatch fullscreen 0 set")); + EXPECT(Tests::windowCount(), 1); + + OK(getFromSocket("/dispatch exec [float] kitty")); + Tests::waitUntilWindowsN(2); + + OK(getFromSocket("/dispatch focuswindow class:^kitty$")); + const auto focused1 = getFromSocket("/activewindow"); + EXPECT_CONTAINS(focused1, "class: kitty\n"); + + OK(getFromSocket("/dispatch killwindow activewindow")); + Tests::waitUntilWindowsN(1); + + // The old window should be focused again + const auto focused2 = getFromSocket("/activewindow"); + EXPECT_CONTAINS(focused2, "class: kitty_A\n"); + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + } + NLog::log("{}Testing minsize/maxsize rules for tiled windows", Colors::YELLOW); { // Enable the config for testing, test max/minsize for tiled windows and centering |
