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.
This commit is contained in:
TEJASJONDHALE 2025-07-01 07:47:28 +05:30
parent cf7e3aa448
commit 57be5317ee
4 changed files with 46 additions and 6 deletions

View file

@ -1122,7 +1122,7 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
}, },
SConfigOptionDescription{ SConfigOptionDescription{
.value = "misc:force_default_wallpaper", .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, .type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{-1, -1, 2}, .data = SConfigOptionDescription::SRangeData{-1, -1, 2},
}, },
@ -1196,7 +1196,7 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
SConfigOptionDescription{ SConfigOptionDescription{
.value = "misc:swallow_exception_regex", .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 " .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) windows title on the assumption that it changes to whatever process its 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, .type = CONFIG_OPTION_STRING_SHORT,
.data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET?
}, },
@ -1322,7 +1322,7 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
}, },
SConfigOptionDescription{ SConfigOptionDescription{
.value = "binds:workspace_back_and_forth", .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 i3s 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, .type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false}, .data = SConfigOptionDescription::SBoolData{false},
}, },
@ -1334,7 +1334,7 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
}, },
SConfigOptionDescription{ SConfigOptionDescription{
.value = "binds:allow_workspace_cycles", .value = "binds:allow_workspace_cycles",
.description = "If enabled, workspaces dont 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.", "going to the previous workspace.",
.type = CONFIG_OPTION_BOOL, .type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false}, .data = SConfigOptionDescription::SBoolData{false},
@ -1520,7 +1520,7 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
}, },
SConfigOptionDescription{ SConfigOptionDescription{
.value = "cursor:inactive_timeout", .value = "cursor:inactive_timeout",
.description = "in seconds, after how many seconds of cursors 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, .type = CONFIG_OPTION_INT,
.data = SConfigOptionDescription::SRangeData{0, 0, 20}, .data = SConfigOptionDescription::SRangeData{0, 0, 20},
}, },
@ -1905,7 +1905,13 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
}, },
SConfigOptionDescription{ SConfigOptionDescription{
.value = "master:always_keep_position", .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, .type = CONFIG_OPTION_BOOL,
.data = SConfigOptionDescription::SBoolData{false}, .data = SConfigOptionDescription::SBoolData{false},
}, },

View file

@ -615,6 +615,7 @@ CConfigManager::CConfigManager() {
registerConfigVar("master:smart_resizing", Hyprlang::INT{1}); registerConfigVar("master:smart_resizing", Hyprlang::INT{1});
registerConfigVar("master:drop_at_cursor", Hyprlang::INT{1}); registerConfigVar("master:drop_at_cursor", Hyprlang::INT{1});
registerConfigVar("master:always_keep_position", Hyprlang::INT{0}); 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:enabled", Hyprlang::INT{1});
registerConfigVar("animations:first_launch_animation", Hyprlang::INT{1}); registerConfigVar("animations:first_launch_animation", Hyprlang::INT{1});

View file

@ -1484,3 +1484,34 @@ void CHyprMasterLayout::onEnable() {
void CHyprMasterLayout::onDisable() { void CHyprMasterLayout::onDisable() {
m_masterNodesData.clear(); m_masterNodesData.clear();
} }
PHLWINDOW CHyprMasterLayout::getNextWindowCandidate(PHLWINDOW pWindow) {
static auto PFOCUSMASTERONCLOSE = CConfigValue<Hyprlang::INT>("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);
}

View file

@ -70,6 +70,8 @@ class CHyprMasterLayout : public IHyprLayout {
virtual void onEnable(); virtual void onEnable();
virtual void onDisable(); virtual void onDisable();
virtual PHLWINDOW getNextWindowCandidate(PHLWINDOW) override;
private: private:
std::list<SMasterNodeData> m_masterNodesData; std::list<SMasterNodeData> m_masterNodesData;
std::vector<SMasterWorkspaceData> m_masterWorkspacesData; std::vector<SMasterWorkspaceData> m_masterWorkspacesData;