From 1cbb62ed6a3d9232448a89fe569d253794e5f114 Mon Sep 17 00:00:00 2001 From: Maaz Ahmed Date: Sat, 16 Aug 2025 19:12:23 +0530 Subject: [PATCH] masterlayout: add previous mode for focusmaster command (#11361) --- hyprtester/src/tests/main/master.cpp | 71 ++++++++++++++++++++++++ hyprtester/src/tests/main/persistent.cpp | 2 +- src/layout/MasterLayout.cpp | 27 +++++++-- src/layout/MasterLayout.hpp | 2 + 4 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 hyprtester/src/tests/main/master.cpp diff --git a/hyprtester/src/tests/main/master.cpp b/hyprtester/src/tests/main/master.cpp new file mode 100644 index 000000000..9cd20e83a --- /dev/null +++ b/hyprtester/src/tests/main/master.cpp @@ -0,0 +1,71 @@ +#include "../shared.hpp" +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include "tests.hpp" + +static int ret = 0; + +static void focusMasterPrevious() { + // setup + NLog::log("{}Spawning 1 master and 3 slave windows", Colors::YELLOW); + // order of windows set according to new_status = master (set in test.conf) + for (auto const& win : {"slave1", "slave2", "slave3", "master"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + NLog::log("{}Ensuring focus is on master before testing", Colors::YELLOW); + OK(getFromSocket("/dispatch layoutmsg focusmaster master")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master"); + + // test + NLog::log("{}Testing fallback to focusmaster auto", Colors::YELLOW); + + OK(getFromSocket("/dispatch layoutmsg focusmaster previous")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: slave1"); + + NLog::log("{}Testing focusing from slave to master", Colors::YELLOW); + + OK(getFromSocket("/dispatch layoutmsg cyclenext noloop")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: slave2"); + OK(getFromSocket("/dispatch layoutmsg focusmaster previous")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master"); + + NLog::log("{}Testing focusing on previous window", Colors::YELLOW); + + OK(getFromSocket("/dispatch layoutmsg focusmaster previous")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: slave2"); + + NLog::log("{}Testing focusing back to master", Colors::YELLOW); + + OK(getFromSocket("/dispatch layoutmsg focusmaster previous")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master"); + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +static bool test() { + NLog::log("{}Testing Master layout", Colors::GREEN); + + // setup + OK(getFromSocket("/dispatch workspace name:master")); + OK(getFromSocket("/keyword general:layout master")); + + // test + NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN); + focusMasterPrevious(); + + // clean up + NLog::log("Cleaning up", Colors::YELLOW); + OK(getFromSocket("/dispatch workspace 1")); + OK(getFromSocket("/reload")); + + return !ret; +} + +REGISTER_TEST_FN(test); diff --git a/hyprtester/src/tests/main/persistent.cpp b/hyprtester/src/tests/main/persistent.cpp index bd93e9166..5e2fdc5ae 100644 --- a/hyprtester/src/tests/main/persistent.cpp +++ b/hyprtester/src/tests/main/persistent.cpp @@ -25,7 +25,7 @@ static bool test() { // test on workspace "window" NLog::log("{}Switching to workspace 1", Colors::YELLOW); - OK(getFromSocket("/dispatch workspace 1")); + getFromSocket("/dispatch workspace 1"); // no OK: we might be on 1 already OK(getFromSocket("/keyword workspace 5, monitor:HEADLESS-2, persistent:1")); OK(getFromSocket("/keyword workspace 6, monitor:HEADLESS-PERSISTENT-TEST, persistent:1")); diff --git a/src/layout/MasterLayout.cpp b/src/layout/MasterLayout.cpp index d5a89c94b..5d6eaf6f7 100644 --- a/src/layout/MasterLayout.cpp +++ b/src/layout/MasterLayout.cpp @@ -1123,9 +1123,10 @@ std::any CHyprMasterLayout::layoutMessage(SLayoutMessageHeader header, std::stri return 0; } - // focusmaster + // focusmaster // first message argument can have the following values: // * master - keep the focus at the new master, even if it was focused before + // * previous - focus window which was previously switched from using `focusmaster previous` command, otherwise fallback to `auto` // * auto (default) - swap the focus with the first child, if the current focus was master, otherwise focus master else if (command == "focusmaster") { const auto PWINDOW = header.pWindow; @@ -1138,21 +1139,35 @@ std::any CHyprMasterLayout::layoutMessage(SLayoutMessageHeader header, std::stri if (!PMASTER) return 0; + const auto& ARG = vars[1]; // returns empty string if out of bounds + if (PMASTER->pWindow.lock() != PWINDOW) { switchToWindow(PMASTER->pWindow.lock()); - } else if (vars.size() >= 2 && vars[1] == "master") { + // save previously focused window (only for `previous` mode) + if (ARG == "previous") + getMasterWorkspaceData(PWINDOW->workspaceID())->focusMasterPrev = PWINDOW; return 0; - } else { - // if master is focused keep master focused (don't do anything) + } + + const auto focusAuto = [&]() { + // focus first non-master window for (auto const& n : m_masterNodesData) { if (n.workspaceID == PMASTER->workspaceID && !n.isMaster) { switchToWindow(n.pWindow.lock()); break; } } - } + }; - return 0; + if (ARG == "master") + return 0; + // switch to previously saved window + else if (ARG == "previous") { + const auto PREVWINDOW = getMasterWorkspaceData(PWINDOW->workspaceID())->focusMasterPrev.lock(); + const bool VALID = validMapped(PREVWINDOW) && PWINDOW->workspaceID() == PREVWINDOW->workspaceID() && PWINDOW != PREVWINDOW; + VALID ? switchToWindow(PREVWINDOW) : focusAuto(); + } else + focusAuto(); } else if (command == "cyclenext") { const auto PWINDOW = header.pWindow; diff --git a/src/layout/MasterLayout.hpp b/src/layout/MasterLayout.hpp index 4da31a8d1..a59689167 100644 --- a/src/layout/MasterLayout.hpp +++ b/src/layout/MasterLayout.hpp @@ -42,6 +42,8 @@ struct SMasterNodeData { struct SMasterWorkspaceData { WORKSPACEID workspaceID = WORKSPACE_INVALID; eOrientation orientation = ORIENTATION_LEFT; + // Previously focused non-master window when `focusmaster previous` command was issued + PHLWINDOWREF focusMasterPrev; // bool operator==(const SMasterWorkspaceData& rhs) const {