summaryrefslogtreecommitdiff
path: root/hyprtester/src/tests/main/window.cpp
diff options
context:
space:
mode:
authorNikolai Nechaev <nikolay_nechaev@mail.ru>2025-11-26 07:44:26 +0900
committerGitHub <noreply@github.com>2025-11-25 22:44:26 +0000
commit40d8fa84919c6d0e9fcb1536f4bc09710d4c031c (patch)
treee45044c92e5d8f716f2aa15668c61fed5aaf5895 /hyprtester/src/tests/main/window.cpp
parent1c1746de61a90dbbae072f3fc618c45af33fdd59 (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.cpp197
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