From 57be5317ee6c7750462d6b3f6bf53f5d1e766624 Mon Sep 17 00:00:00 2001 From: TEJASJONDHALE Date: Tue, 1 Jul 2025 07:47:28 +0530 Subject: [PATCH] Add config option to focus master window on close in master layout - Add master:focus_master_on_close config option (default: 0) - When enabled, closing a master window focuses the next master window - Maintains backward compatibility with existing configs - Improve workflow continuity for master layout users Fixes usability issue where closing master windows could focus stack windows instead of staying in the master area. --- src/config/ConfigDescriptions.hpp | 18 ++++++++++++------ src/config/ConfigManager.cpp | 1 + src/layout/MasterLayout.cpp | 31 +++++++++++++++++++++++++++++++ src/layout/MasterLayout.hpp | 2 ++ 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp index 1af5a3ca0..3db70f84e 100644 --- a/src/config/ConfigDescriptions.hpp +++ b/src/config/ConfigDescriptions.hpp @@ -1122,7 +1122,7 @@ inline static const std::vector CONFIG_OPTIONS = { }, SConfigOptionDescription{ .value = "misc:force_default_wallpaper", - .description = "Enforce any of the 3 default wallpapers. Setting this to 0 or 1 disables the anime background. -1 means “random”. [-1/0/1/2]", + .description = "Enforce any of the 3 default wallpapers. Setting this to 0 or 1 disables the anime background. -1 means "random". [-1/0/1/2]", .type = CONFIG_OPTION_INT, .data = SConfigOptionDescription::SRangeData{-1, -1, 2}, }, @@ -1196,7 +1196,7 @@ inline static const std::vector CONFIG_OPTIONS = { SConfigOptionDescription{ .value = "misc:swallow_exception_regex", .description = "The title regex to be used for windows that should not be swallowed by the windows specified in swallow_regex (e.g. wev). The regex is matched against the " - "parent (e.g. Kitty) window’s title on the assumption that it changes to whatever process it’s running.", + "parent (e.g. Kitty) window's title on the assumption that it changes to whatever process it's running.", .type = CONFIG_OPTION_STRING_SHORT, .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? }, @@ -1322,7 +1322,7 @@ inline static const std::vector CONFIG_OPTIONS = { }, SConfigOptionDescription{ .value = "binds:workspace_back_and_forth", - .description = "If enabled, an attempt to switch to the currently focused workspace will instead switch to the previous workspace. Akin to i3’s auto_back_and_forth.", + .description = "If enabled, an attempt to switch to the currently focused workspace will instead switch to the previous workspace. Akin to i3's auto_back_and_forth.", .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{false}, }, @@ -1334,7 +1334,7 @@ inline static const std::vector CONFIG_OPTIONS = { }, SConfigOptionDescription{ .value = "binds:allow_workspace_cycles", - .description = "If enabled, workspaces don’t forget their previous workspace, so cycles can be created by switching to the first workspace in a sequence, then endlessly " + .description = "If enabled, workspaces don't forget their previous workspace, so cycles can be created by switching to the first workspace in a sequence, then endlessly " "going to the previous workspace.", .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{false}, @@ -1520,7 +1520,7 @@ inline static const std::vector CONFIG_OPTIONS = { }, SConfigOptionDescription{ .value = "cursor:inactive_timeout", - .description = "in seconds, after how many seconds of cursor’s inactivity to hide it. Set to 0 for never.", + .description = "in seconds, after how many seconds of cursor's inactivity to hide it. Set to 0 for never.", .type = CONFIG_OPTION_INT, .data = SConfigOptionDescription::SRangeData{0, 0, 20}, }, @@ -1905,7 +1905,13 @@ inline static const std::vector CONFIG_OPTIONS = { }, SConfigOptionDescription{ .value = "master:always_keep_position", - .description = "whether to keep the master window in its configured position when there are no slave windows", + .description = "If enabled, the master window will always keep its position and size even when it's the only window on the workspace.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "master:focus_master_on_close", + .description = "If enabled, when a master window is closed, focus will automatically shift to the next master window instead of potentially focusing a stack window.", .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{false}, }, diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 5ebb7a269..874c8de8c 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -615,6 +615,7 @@ CConfigManager::CConfigManager() { registerConfigVar("master:smart_resizing", Hyprlang::INT{1}); registerConfigVar("master:drop_at_cursor", Hyprlang::INT{1}); registerConfigVar("master:always_keep_position", Hyprlang::INT{0}); + registerConfigVar("master:focus_master_on_close", Hyprlang::INT{0}); registerConfigVar("animations:enabled", Hyprlang::INT{1}); registerConfigVar("animations:first_launch_animation", Hyprlang::INT{1}); diff --git a/src/layout/MasterLayout.cpp b/src/layout/MasterLayout.cpp index abdf80329..4437595e3 100644 --- a/src/layout/MasterLayout.cpp +++ b/src/layout/MasterLayout.cpp @@ -1484,3 +1484,34 @@ void CHyprMasterLayout::onEnable() { void CHyprMasterLayout::onDisable() { m_masterNodesData.clear(); } + +PHLWINDOW CHyprMasterLayout::getNextWindowCandidate(PHLWINDOW pWindow) { + static auto PFOCUSMASTERONCLOSE = CConfigValue("master:focus_master_on_close"); + + // If the config is enabled and we have a valid window that was closed + if (*PFOCUSMASTERONCLOSE && pWindow && pWindow->m_workspace) { + const auto PNODE = getNodeFromWindow(pWindow); + + // If the closed window was a master window, try to focus the next master window + if (PNODE && PNODE->isMaster) { + const auto WORKSPACEID = pWindow->workspaceID(); + + // Find the next master window in the same workspace + for (auto& nd : m_masterNodesData) { + if (nd.workspaceID == WORKSPACEID && nd.isMaster && nd.pWindow.lock() != pWindow && nd.pWindow.lock() && nd.pWindow.lock()->m_isMapped) { + return nd.pWindow.lock(); + } + } + + // If no other master window found, try to find any master window in the workspace + for (auto& nd : m_masterNodesData) { + if (nd.workspaceID == WORKSPACEID && nd.isMaster && nd.pWindow.lock() && nd.pWindow.lock()->m_isMapped) { + return nd.pWindow.lock(); + } + } + } + } + + // Fall back to default behavior + return IHyprLayout::getNextWindowCandidate(pWindow); +} diff --git a/src/layout/MasterLayout.hpp b/src/layout/MasterLayout.hpp index 20743690e..c1987e622 100644 --- a/src/layout/MasterLayout.hpp +++ b/src/layout/MasterLayout.hpp @@ -70,6 +70,8 @@ class CHyprMasterLayout : public IHyprLayout { virtual void onEnable(); virtual void onDisable(); + virtual PHLWINDOW getNextWindowCandidate(PHLWINDOW) override; + private: std::list m_masterNodesData; std::vector m_masterWorkspacesData;