core/fullscreen: set pos/size of all group members to the same value when FSing groups (#15577)

* Fix group members not having uniform size when FSed

* fix the same bug - but for scrolling

* format

* fix include

* ditto

* groups are evil

* format

* TEST - Default handled FS -- tested in dwindle

Co-authored-by: Aqa-Ib <16420574+Aqa-Ib@users.noreply.github.com>

* TEST - Default handled FS -- tested in scrolling

Co-authored-by: Aqa-Ib <16420574+Aqa-Ib@users.noreply.github.com>

* TEST - layout handled FS -- Scrolling

Co-authored-by: Aqa-Ib <16420574+Aqa-Ib@users.noreply.github.com>

* format and comment fix

* spelling is hard

* slight fix for posterity

* spelling is very hard

* thankyouclangdverycool

* revert change that made all windows in a group show up as FS in hyprctl clients

* change test to reflect the reversion

* mirror mirror can you tell me who forgot to revert that change after it was redundant?

* tribute to the magic mirror

* clean up includes

* format

* Hope is the first step on the road to disappointment

Fix tests failing

---------

Co-authored-by: Aqa-Ib <16420574+Aqa-Ib@users.noreply.github.com>
This commit is contained in:
erStarr
2026-08-07 16:53:28 +02:00
committed by GitHub
co-authored by Aqa-Ib
parent 7d4a3c5768
commit face6d144b
8 changed files with 736 additions and 33 deletions
+178
View File
@@ -344,6 +344,184 @@ TEST_CASE(dwindleFullscreenMaximiseDispatchers) {
}
}
TEST_CASE(dwindleTestFsingGroupedWindows) {
// Shared test among all default handled FS
/*
When a group of windows are FSed, all the windows in the group are expected to have the same size
*/
OK(getFromSocket("/eval hl.config({ general = { layout = 'dwindle' } })"));
OK(getFromSocket("/eval hl.config({ group = { auto_group = true } })"));
// Config opt for adding gaps_out and border_size and a workspace rule that removes them from maximised windows to make sure maximise works properly
OK(getFromSocket("r/eval hl.config({ general = { gaps_out = 10, border_size = 10 } })"));
OK(getFromSocket("/eval hl.workspace_rule({ workspace = 'f[1]', gaps_out = 0, border_size = 0 })"));
ASSERT(Tests::windowCount(), 0);
auto kitten1 = Tests::spawnKitty("kitten1");
if (!kitten1) {
FAIL_TEST("Could not spawn kitty");
}
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
auto kitten2 = Tests::spawnKitty("kitten2");
if (!kitten2) {
FAIL_TEST("Could not spawn kitty");
}
ASSERT(Tests::windowCount(), 2);
// Fullscreen
{
auto checkHiddenGroupMember = [&](const std::string& targetKitten) {
auto clients = getFromSocket("/clients");
auto classPos = clients.find("class: " + targetKitten);
if (classPos == std::string::npos) {
FAIL_TEST("Could not find specific kitten in clients output");
} else {
auto entryStart = clients.rfind("Window ", classPos);
auto entryEnd = clients.find("\n\n", classPos);
auto windowEntry = clients.substr(entryStart, entryEnd - entryStart);
EXPECT_CONTAINS(windowEntry, "size: 1920,1080");
EXPECT_CONTAINS(windowEntry, "at: 0,0");
}
};
// Tiled
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set', layout_aware = false })"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten2");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,1080");
}
checkHiddenGroupMember("kitten1");
// try switching to next window in group
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten1");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,1080");
}
checkHiddenGroupMember("kitten2");
// go back for next test
OK(getFromSocket("/dispatch hl.dsp.group.prev()"));
// unset FS for next test
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'unset', layout_aware = false })"));
// floating
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set', layout_aware = false })"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten2");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,1080");
}
checkHiddenGroupMember("kitten1");
// try switching to next window in group
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten1");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,1080");
}
checkHiddenGroupMember("kitten2");
// go back for next test
OK(getFromSocket("/dispatch hl.dsp.group.prev()"));
// prep for next test
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'unset' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'unset', layout_aware = false })"));
}
// Maximized
{
auto checkHiddenGroupMember = [&](const std::string& targetKitten) {
auto clients = getFromSocket("/clients");
auto classPos = clients.find("class: " + targetKitten);
if (classPos == std::string::npos) {
FAIL_TEST("Could not find specific kitten in clients output");
} else {
auto entryStart = clients.rfind("Window ", classPos);
auto entryEnd = clients.find("\n\n", classPos);
auto windowEntry = clients.substr(entryStart, entryEnd - entryStart);
EXPECT_CONTAINS(windowEntry, "size: 1920,1059");
EXPECT_CONTAINS(windowEntry, "at: 0,21");
}
};
// Tiled
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized', action = 'set', layout_aware = false })"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten2");
EXPECT_CONTAINS(str, "at: 0,21");
EXPECT_CONTAINS(str, "size: 1920,1059");
}
checkHiddenGroupMember("kitten1");
// try switching to next window in group
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten1");
EXPECT_CONTAINS(str, "at: 0,21");
EXPECT_CONTAINS(str, "size: 1920,1059");
}
checkHiddenGroupMember("kitten2");
// go back for next test
OK(getFromSocket("/dispatch hl.dsp.group.prev()"));
// unset FS for next test
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized', action = 'unset', layout_aware = false })"));
// floating
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized', action = 'set', layout_aware = false })"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten2");
EXPECT_CONTAINS(str, "at: 0,21");
EXPECT_CONTAINS(str, "size: 1920,1059");
}
checkHiddenGroupMember("kitten1");
// try switching to next window in group
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten1");
EXPECT_CONTAINS(str, "at: 0,21");
EXPECT_CONTAINS(str, "size: 1920,1059");
}
checkHiddenGroupMember("kitten2");
}
}
TEST_CASE(dwindleTestFsFocusUnderFSWindow) {
// Shared test among all default handled FS
+496 -1
View File
@@ -346,6 +346,323 @@ TEST_CASE(scroll_LAYOUT_HANDLED_maximized) {
}
}
TEST_CASE(scroll_LAYOUT_HANDLED_TestFsingGroupedWindows) {
/*
When a group of windows are FSed, all the windows in the group are expected to have the same size
In scrolling's layout FS handling, this is expected to hold true even when we scroll away from the window
*/
OK(getFromSocket("/eval hl.config({ general = { layout = 'scrolling' } })"));
OK(getFromSocket("/eval hl.config({ group = { auto_group = true } })"));
OK(getFromSocket("/eval hl.config({ scrolling = { follow_focus = false } })"));
OK(getFromSocket("/eval hl.config({ scrolling = { wrap_swapcol = false } })"));
// Config opt for adding gaps_out and border_size and a workspace rule that removes them from maximised windows to make sure maximise works properly
OK(getFromSocket("r/eval hl.config({ general = { gaps_out = 10, border_size = 10 } })"));
OK(getFromSocket("/eval hl.workspace_rule({ workspace = 'f[1]', gaps_out = 0, border_size = 0 })"));
// 2 grouped windows, 1 ungrouped window
ASSERT(Tests::windowCount(), 0);
auto kitten1 = Tests::spawnKitty("kitten1");
if (!kitten1) {
FAIL_TEST("Could not spawn kitty");
}
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
auto kitten2 = Tests::spawnKitty("kitten2");
if (!kitten2) {
FAIL_TEST("Could not spawn kitty");
}
OK(getFromSocket("/eval hl.config({ group = { auto_group = false } })"));
auto kitten3 = Tests::spawnKitty("kitten3");
if (!kitten3) {
FAIL_TEST("Could not spawn kitty");
}
ASSERT(Tests::windowCount(), 3);
OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:kitten2'})"));
// Fullscreen
{
auto checkHiddenGroupMember = [&](const std::string& targetKitten) {
auto clients = getFromSocket("/clients");
auto classPos = clients.find("class: " + targetKitten);
if (classPos == std::string::npos) {
FAIL_TEST("Could not find specific kitten in clients output");
} else {
auto entryStart = clients.rfind("Window ", classPos);
auto entryEnd = clients.find("\n\n", classPos);
auto windowEntry = clients.substr(entryStart, entryEnd - entryStart);
EXPECT_CONTAINS(windowEntry, "size: 1920,1080");
EXPECT_CONTAINS(windowEntry, "at: 0,0");
}
};
auto checkHiddenGroupMember_notCovering = [&](const std::string& targetKitten) {
auto clients = getFromSocket("/clients");
auto classPos = clients.find("class: " + targetKitten);
if (classPos == std::string::npos) {
FAIL_TEST("Could not find specific kitten in clients output");
} else {
auto entryStart = clients.rfind("Window ", classPos);
auto entryEnd = clients.find("\n\n", classPos);
auto windowEntry = clients.substr(entryStart, entryEnd - entryStart);
EXPECT_CONTAINS(windowEntry, "at: -960,0");
// fullscreen windows are MONBOX sized so they disregard borders and gaps_out even when not covering
EXPECT_CONTAINS(windowEntry, "size: 1920,1080");
EXPECT_CONTAINS(windowEntry, "fullscreen: 0");
}
};
// Tiled
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set', layout_aware = true })"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten2");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,1080");
}
checkHiddenGroupMember("kitten1");
// try switching to next window in group
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten1");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,1080");
}
checkHiddenGroupMember("kitten2");
// go back for next test
OK(getFromSocket("/dispatch hl.dsp.group.prev()"));
// Try scrolling away
OK(getFromSocket("/dispatch hl.dsp.layout('focus r')"));
// not scrolling onto it because follow_focus = false
OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:kitten2'})"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten2");
EXPECT_CONTAINS(str, "at: -960,0");
EXPECT_CONTAINS(str, "size: 1920,1080");
}
checkHiddenGroupMember_notCovering("kitten1");
// atm the viewport moves if you switch to next window in group, this it to compensate
OK(getFromSocket("/dispatch hl.dsp.layout('inhibit_scroll 1')"));
// try switching to next window in group when you have scrolled away
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
OK(getFromSocket("/dispatch hl.dsp.layout('inhibit_scroll 0')"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten1");
EXPECT_CONTAINS(str, "at: -960,0");
EXPECT_CONTAINS(str, "size: 1920,1080");
}
checkHiddenGroupMember_notCovering("kitten2");
// go back for next test
OK(getFromSocket("/dispatch hl.dsp.group.prev()"));
// re-establish covering status of FS group for the next test
OK(getFromSocket("/eval hl.config({ scrolling = { follow_focus = true } })"));
OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:kitten2'})"));
OK(getFromSocket("/eval hl.config({ scrolling = { follow_focus = false } })"));
// unset FS for next test
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'unset', layout_aware = true })"));
// floating
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set', layout_aware = true })"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten2");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,1080");
}
checkHiddenGroupMember("kitten1");
// try switching to next window in group
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten1");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,1080");
}
checkHiddenGroupMember("kitten2");
// go back for next test
OK(getFromSocket("/dispatch hl.dsp.group.prev()"));
// prep for next test
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'unset' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'unset', layout_aware = true })"));
// The window may have been dropped from floating to tiling in the wrong place. make sure it is the leftmost
OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:kitten2'})"));
OK(getFromSocket("/dispatch hl.dsp.layout('swapcol l')"));
}
// Maximized
{
auto checkHiddenGroupMember_tiled = [&](const std::string& targetKitten) {
auto clients = getFromSocket("/clients");
auto classPos = clients.find("class: " + targetKitten);
if (classPos == std::string::npos) {
FAIL_TEST("Could not find specific kitten in clients output");
} else {
auto entryStart = clients.rfind("Window ", classPos);
auto entryEnd = clients.find("\n\n", classPos);
auto windowEntry = clients.substr(entryStart, entryEnd - entryStart);
EXPECT_CONTAINS(windowEntry, "size: 1915,1059");
EXPECT_CONTAINS(windowEntry, "at: 0,21");
}
};
auto checkHiddenGroupMember_floating = [&](const std::string& targetKitten) {
auto clients = getFromSocket("/clients");
auto classPos = clients.find("class: " + targetKitten);
if (classPos == std::string::npos) {
FAIL_TEST("Could not find specific kitten in clients output");
} else {
auto entryStart = clients.rfind("Window ", classPos);
auto entryEnd = clients.find("\n\n", classPos);
auto windowEntry = clients.substr(entryStart, entryEnd - entryStart);
EXPECT_CONTAINS(windowEntry, "size: 1920,1059");
EXPECT_CONTAINS(windowEntry, "at: 0,21");
}
};
auto checkHiddenGroupMember_notCovering = [&](const std::string& targetKitten) {
auto clients = getFromSocket("/clients");
auto classPos = clients.find("class: " + targetKitten);
if (classPos == std::string::npos) {
FAIL_TEST("Could not find specific kitten in clients output");
} else {
auto entryStart = clients.rfind("Window ", classPos);
auto entryEnd = clients.find("\n\n", classPos);
auto windowEntry = clients.substr(entryStart, entryEnd - entryStart);
EXPECT_CONTAINS(windowEntry, "at: -940,41");
// cuz the gaps and borders workspace rule now applies as it is no longer covering
EXPECT_CONTAINS(windowEntry, "size: 1875,1019");
EXPECT_CONTAINS(windowEntry, "fullscreen: 0");
}
};
// Tiled
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized', action = 'set', layout_aware = true })"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten2");
EXPECT_CONTAINS(str, "at: 0,21");
EXPECT_CONTAINS(str, "size: 1915,1059");
}
checkHiddenGroupMember_tiled("kitten1");
// try switching to next window in group
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten1");
EXPECT_CONTAINS(str, "at: 0,21");
EXPECT_CONTAINS(str, "size: 1915,1059");
}
checkHiddenGroupMember_tiled("kitten2");
// go back for next test
OK(getFromSocket("/dispatch hl.dsp.group.prev()"));
// Try scrolling away
OK(getFromSocket("/dispatch hl.dsp.layout('focus r')"));
// not scrolling onto it because follow_focus = false
OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:kitten2'})"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten2");
EXPECT_CONTAINS(str, "at: -940,41");
EXPECT_CONTAINS(str, "size: 1875,1019");
EXPECT_CONTAINS(str, "fullscreen: 1");
}
checkHiddenGroupMember_notCovering("kitten1");
// atm the viewport moves if you switch to next window in group, this it to compensate
OK(getFromSocket("/dispatch hl.dsp.layout('inhibit_scroll 1')"));
// try switching to next window in group when you have scrolled away
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
OK(getFromSocket("/dispatch hl.dsp.layout('inhibit_scroll 0')"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten1");
EXPECT_CONTAINS(str, "at: -940,41");
EXPECT_CONTAINS(str, "size: 1875,1019");
EXPECT_CONTAINS(str, "fullscreen: 1");
}
checkHiddenGroupMember_notCovering("kitten2");
// go back for next test
OK(getFromSocket("/dispatch hl.dsp.group.prev()"));
// re-establish covering status of FS group for the next test
OK(getFromSocket("/eval hl.config({ scrolling = { follow_focus = true } })"));
OK(getFromSocket("/dispatch hl.dsp.focus({window = 'class:kitten2'})"));
OK(getFromSocket("/eval hl.config({ scrolling = { follow_focus = false } })"));
// unset FS for next test
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized', action = 'unset', layout_aware = true })"));
// floating
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized', action = 'set', layout_aware = true })"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten2");
EXPECT_CONTAINS(str, "at: 0,21");
EXPECT_CONTAINS(str, "size: 1920,1059");
}
checkHiddenGroupMember_floating("kitten1");
// try switching to next window in group
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten1");
EXPECT_CONTAINS(str, "at: 0,21");
EXPECT_CONTAINS(str, "size: 1920,1059");
}
checkHiddenGroupMember_floating("kitten2");
}
}
TEST_CASE(scroll_LAYOUT_HANDLED_fullscreenRetainsGeometryWhileScrolling) {
OK(getFromSocket("/eval hl.config({ general = { layout = 'scrolling' } })"));
@@ -1075,7 +1392,7 @@ TEST_CASE(scroll_LAYOUT_HANDLED_focusInDirectionFocusFollowFocusTrue) {
// if on_focus_under_fullscreen = 0 focus({direction}) is disallowed from moving focus from FS window
// if on_focus_under_fullscreen = 1/2, standard behaviour of the config option won't be followed but focus will move in the firection specified as if window was not FS
OK(getFromSocket("r/eval hl.config({ general = { layout = 'scrolling' } })"));
OK(getFromSocket("/eval hl.config({ general = { layout = 'scrolling' } })"));
/*
This test serves as a test for all layouts that use deafult FS behaviour
@@ -1396,6 +1713,184 @@ TEST_CASE(scroll_DEFAULT_HANDLED_fullscreenMaximiseDispatchers) {
}
}
TEST_CASE(scroll_DEFAULT_HANDLED_TestFsingGroupedWindows) {
// Shared test among all default handled FS
/*
When a group of windows are FSed, all the windows in the group are expected to have the same size
*/
OK(getFromSocket("/eval hl.config({ general = { layout = 'scrolling' } })"));
OK(getFromSocket("/eval hl.config({ group = { auto_group = true } })"));
// Config opt for adding gaps_out and border_size and a workspace rule that removes them from maximised windows to make sure maximise works properly
OK(getFromSocket("r/eval hl.config({ general = { gaps_out = 10, border_size = 10 } })"));
OK(getFromSocket("/eval hl.workspace_rule({ workspace = 'f[1]', gaps_out = 0, border_size = 0 })"));
ASSERT(Tests::windowCount(), 0);
auto kitten1 = Tests::spawnKitty("kitten1");
if (!kitten1) {
FAIL_TEST("Could not spawn kitty");
}
OK(getFromSocket("/dispatch hl.dsp.group.toggle()"));
auto kitten2 = Tests::spawnKitty("kitten2");
if (!kitten2) {
FAIL_TEST("Could not spawn kitty");
}
ASSERT(Tests::windowCount(), 2);
// Fullscreen
{
auto checkHiddenGroupMember = [&](const std::string& targetKitten) {
auto clients = getFromSocket("/clients");
auto classPos = clients.find("class: " + targetKitten);
if (classPos == std::string::npos) {
FAIL_TEST("Could not find specific kitten in clients output");
} else {
auto entryStart = clients.rfind("Window ", classPos);
auto entryEnd = clients.find("\n\n", classPos);
auto windowEntry = clients.substr(entryStart, entryEnd - entryStart);
EXPECT_CONTAINS(windowEntry, "size: 1920,1080");
EXPECT_CONTAINS(windowEntry, "at: 0,0");
}
};
// Tiled
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set', layout_aware = false })"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten2");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,1080");
}
checkHiddenGroupMember("kitten1");
// try switching to next window in group
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten1");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,1080");
}
checkHiddenGroupMember("kitten2");
// go back for next test
OK(getFromSocket("/dispatch hl.dsp.group.prev()"));
// unset FS for next test
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'unset', layout_aware = false })"));
// floating
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'set', layout_aware = false })"));
{
// check position and size for the focused group member.
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten2");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,1080");
}
checkHiddenGroupMember("kitten1");
// try switching to next window in group
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
{
// check position and size for the focused group member.
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten1");
EXPECT_CONTAINS(str, "at: 0,0");
EXPECT_CONTAINS(str, "size: 1920,1080");
}
checkHiddenGroupMember("kitten2");
// go back for next test
OK(getFromSocket("/dispatch hl.dsp.group.prev()"));
// prep for next test
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'unset' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'fullscreen', action = 'unset', layout_aware = false })"));
}
// Maximized
{
auto checkHiddenGroupMember = [&](const std::string& targetKitten) {
auto clients = getFromSocket("/clients");
auto classPos = clients.find("class: " + targetKitten);
if (classPos == std::string::npos) {
FAIL_TEST("Could not find specific kitten in clients output");
} else {
auto entryStart = clients.rfind("Window ", classPos);
auto entryEnd = clients.find("\n\n", classPos);
auto windowEntry = clients.substr(entryStart, entryEnd - entryStart);
EXPECT_CONTAINS(windowEntry, "size: 1920,1059");
EXPECT_CONTAINS(windowEntry, "at: 0,21");
}
};
// Tiled
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized', action = 'set', layout_aware = false })"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten2");
EXPECT_CONTAINS(str, "at: 0,21");
EXPECT_CONTAINS(str, "size: 1920,1059");
}
checkHiddenGroupMember("kitten1");
// try switching to next window in group
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
{
// check position and size for the focused group member.
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten1");
EXPECT_CONTAINS(str, "at: 0,21");
EXPECT_CONTAINS(str, "size: 1920,1059");
}
checkHiddenGroupMember("kitten2");
// go back for next test
OK(getFromSocket("/dispatch hl.dsp.group.prev()"));
// unset FS for next test
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized', action = 'unset', layout_aware = false })"));
// floating
OK(getFromSocket("/dispatch hl.dsp.window.float({ action = 'set' })"));
OK(getFromSocket("/dispatch hl.dsp.window.fullscreen({ mode = 'maximized', action = 'set', layout_aware = false })"));
{
// check position and size for the focused group member (kitten2).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten2");
EXPECT_CONTAINS(str, "at: 0,21");
EXPECT_CONTAINS(str, "size: 1920,1059");
}
checkHiddenGroupMember("kitten1");
// try switching to next window in group
OK(getFromSocket("/dispatch hl.dsp.group.next()"));
{
// check position and size for the focused group member (kitten1).
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "class: kitten1");
EXPECT_CONTAINS(str, "at: 0,21");
EXPECT_CONTAINS(str, "size: 1920,1059");
}
checkHiddenGroupMember("kitten2");
}
}
TEST_CASE(scroll_DEFAULT_HANDLED_testFsFocusUnderFSWindow) {
// Shared test among all default handled FS
+7 -4
View File
@@ -8,7 +8,7 @@
#include "../../../desktop/state/FocusState.hpp"
#include "../../../protocols/types/ContentType.hpp"
#include "../../../config/shared/parserUtils/ParserUtils.hpp"
#include "desktop/rule/windowRule/WindowRuleEffectContainer.hpp"
#include "../../../desktop/view/Group.hpp"
#include <hyprutils/string/Numeric.hpp>
#include <hyprutils/string/String.hpp>
@@ -415,7 +415,8 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) {
return false;
break;
case RULE_PROP_FULLSCREEN:
if (!engine->match(Fullscreen::controller()->isFullscreen(w)))
// FS states of a group are owned by the current window of the group
if (!engine->match(Fullscreen::controller()->isFullscreen(w->m_group ? w->m_group->current() : w)))
return false;
break;
case RULE_PROP_PINNED:
@@ -435,11 +436,13 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) {
return false;
break;
case RULE_PROP_FULLSCREENSTATE_INTERNAL:
if (!engine->match(Fullscreen::controller()->getFullscreenModes(w).internal))
// FS states of a group are owned by the current window of the group
if (!engine->match(Fullscreen::controller()->getFullscreenModes(w->m_group ? w->m_group->current() : w).internal))
return false;
break;
case RULE_PROP_FULLSCREENSTATE_CLIENT:
if (!engine->match(Fullscreen::controller()->getFullscreenModes(w).client))
// FS states of a group are owned by the current window of the group
if (!engine->match(Fullscreen::controller()->getFullscreenModes(w->m_group ? w->m_group->current() : w).client))
return false;
break;
case RULE_PROP_ON_WORKSPACE:
-1
View File
@@ -258,7 +258,6 @@ void CGroup::setCurrent(size_t idx) {
if (IS_FULLSCREEN) {
Fullscreen::controller()->setFullscreenMode(newWindow, FS_MODE_INTERNAL, std::nullopt, IS_LAYOUT_HANDLED);
newWindow->m_target->warpPositionSize();
oldWindow->m_target->setPositionGlobal(newWindow->m_target->position()); // TODO: this is a hack and sucks
}
if (WASFOCUS)
@@ -552,6 +552,7 @@ void SScrollingData::recalculate(bool forceInstant) {
Fullscreen::controller()->m_windowPosSettingQueued = true;
// must set pos of the highest level target (i.e. if target a part of a group, must set that group's pos which will set the pos of all member targets)
TDATA->target->setPositionGlobal(targetBoxWithGaps(TDATA->layoutBox, i, j, COL_HAS_FS_TARGET && TARGET_FS_MODE == Fullscreen::FSMODE_FULLSCREEN));
Fullscreen::controller()->m_windowPosSettingQueued = false;
}
if (forceInstant && TDATA->target)
@@ -45,7 +45,7 @@ bool CScrollingFullscreenHandler::isFullscreen(SP<Layout::ITarget> target, const
if (!target)
return false;
// A window group's FS modes are considered to be owned by its current window
// A window group's FS state is considered to be owned by its current window
if (const auto WINDOW_GROUP_TARGET = dc<Layout::CWindowGroupTarget*>(target.get()); WINDOW_GROUP_TARGET && target->type() == Layout::TARGET_TYPE_GROUP) {
if (WINDOW_GROUP_TARGET->getGroup() && WINDOW_GROUP_TARGET->getGroup()->current() && WINDOW_GROUP_TARGET->getGroup()->current()->m_target)
target = WINDOW_GROUP_TARGET->getGroup()->current()->m_target;
@@ -96,6 +96,7 @@ SFullscreenMode CScrollingFullscreenHandler::getFullscreenModes(SP<Layout::ITarg
if (!target)
return {};
// A window group's FS modes are considered to be owned by its current window
if (const auto WINDOW_GROUP_TARGET = dc<Layout::CWindowGroupTarget*>(target.get()); WINDOW_GROUP_TARGET && target->type() == Layout::TARGET_TYPE_GROUP) {
if (WINDOW_GROUP_TARGET->getGroup() && WINDOW_GROUP_TARGET->getGroup()->current() && WINDOW_GROUP_TARGET->getGroup()->current()->m_target)
target = WINDOW_GROUP_TARGET->getGroup()->current()->m_target;
@@ -288,9 +289,18 @@ void CScrollingFullscreenHandler::updateTargetRulesAndDecos(const SP<Layout::ITa
const auto MONITOR = target->workspace()->m_monitor.lock();
const auto WINDOW = target->window();
WINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT |
Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
WINDOW->updateDecorationValues();
// If window is in a group, we need to update these values for ALL members of the group.
if (WINDOW->m_group) {
for (const auto& gm : WINDOW->m_group->windows()) {
gm->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT |
Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
gm->updateDecorationValues();
}
} else {
WINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT |
Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
WINDOW->updateDecorationValues();
}
// Normally, FS controller's FS state setter's method of handling window rules should be used; but calling g_layoutManager->recalculateMonitor(MONITOR) and getSpace()->recalculate()
// here would lead to an inf recursion
@@ -565,7 +575,6 @@ eFullscreenHandler CScrollingFullscreenHandler::getFullscreenHandlerName() const
void CScrollingFullscreenHandler::sScrollingDataRecalculateHelper(const SP<Layout::Tiled::SScrollingTargetData> CURRENT_FS_TDATA, const PHLMONITOR MONITOR,
const bool TARGET_WORKSPACE_HAS_FS) {
// TODO Decouple FS logic from SScrollingData::recalculate() to avoid having to schedule a prop refresh: it has to be here and it's a mess because recalculate() handled scrolling
// onto/away from FS windows and this process doesn't call the controller's FS setters which are normally responsible for handling window rule checks.
+23 -18
View File
@@ -13,6 +13,7 @@
#include "../../state/MonitorState.hpp"
#include "../../desktop/Workspace.hpp"
#include "../../managers/fullscreen/FullscreenController.hpp"
#include "../../desktop/view/Group.hpp"
#include <hyprutils/utils/ScopeGuard.hpp>
@@ -44,6 +45,11 @@ void CWindowTarget::updatePos(uint8_t flags) {
if (!m_window)
return;
const auto effectiveWindow = [&]() {
// FS state of a window in a group is owned by the current window of that group.
return window()->m_group ? window()->m_group->current() : window();
};
g_pHyprRenderer->damageWindow(m_window.lock());
CScopeGuard x([this] { g_pHyprRenderer->damageWindow(m_window.lock()); });
@@ -67,7 +73,7 @@ void CWindowTarget::updatePos(uint8_t flags) {
}
// Non-FS Floating Windows
if (floating() && m_window && !Fullscreen::controller()->isFullscreen(m_window.lock())) {
if (floating() && m_window && !Fullscreen::controller()->isFullscreen(effectiveWindow())) {
m_window->setBox(m_box.logicalBox);
if (CONFIGURECLIENT)
@@ -79,17 +85,15 @@ void CWindowTarget::updatePos(uint8_t flags) {
/* FS Handling */
// prevent re-setting an FS window's pos after it is set by the FS calls
if (m_window && Fullscreen::controller()->isFullscreen(m_window.lock(), std::nullopt, true)) {
// prevent re-setting a covering FS window's pos after it is set by the FS calls
if (m_window && Fullscreen::controller()->isFullscreen(effectiveWindow(), std::nullopt, true)) {
if (!Fullscreen::controller()->m_windowPosSettingQueued)
return;
Fullscreen::controller()->m_windowPosSettingQueued = false;
}
// Default Handled FS (floating or tiling)
if (const auto FSMODES = Fullscreen::controller()->getFullscreenModes(m_window.lock());
FSMODES.internal != Fullscreen::FSMODE_NONE && !Fullscreen::controller()->layoutManagedFS(m_self->window())) {
if (const auto FSMODES = Fullscreen::controller()->getFullscreenModes(effectiveWindow());
FSMODES.internal != Fullscreen::FSMODE_NONE && !Fullscreen::controller()->layoutManagedFS(effectiveWindow())) {
if (FSMODES.internal == Fullscreen::FSMODE_FULLSCREEN) {
m_window->setBox(m_box.logicalBox);
@@ -100,7 +104,8 @@ void CWindowTarget::updatePos(uint8_t flags) {
visualBox.round();
// Reserved area must be updated before this is called
const auto RESERVED = m_window->getFullWindowReservedArea();
// Reserved area for all windows in a group are owned by the leading window. Other windows are hidden anyway so this simply ensures their sizes are uniform when FSed
const auto RESERVED = effectiveWindow()->getFullWindowReservedArea();
m_window->setBox({visualBox.pos() + RESERVED.topLeft, visualBox.size() - (RESERVED.topLeft + RESERVED.bottomRight)});
}
@@ -111,9 +116,9 @@ void CWindowTarget::updatePos(uint8_t flags) {
return;
}
// Layout handled FS
if (const auto FSMODES = Fullscreen::controller()->getFullscreenModes(m_window.lock());
FSMODES.internal != Fullscreen::FSMODE_NONE && Fullscreen::controller()->layoutManagedFS(m_self->window())) {
// Layout handled FS (Tiled Only)
if (const auto FSMODES = Fullscreen::controller()->getFullscreenModes(effectiveWindow());
FSMODES.internal != Fullscreen::FSMODE_NONE && Fullscreen::controller()->layoutManagedFS(effectiveWindow())) {
CBox nodeBox = m_box.logicalBox;
CBox visualBox = m_box.visualBox.empty() ? nodeBox : m_box.visualBox;
@@ -124,7 +129,8 @@ void CWindowTarget::updatePos(uint8_t flags) {
} else if (FSMODES.internal == Fullscreen::FSMODE_MAXIMIZED) {
// Reserved area must be updated before this is called
const auto RESERVED = m_window->getFullWindowReservedArea();
// Reserved area for all windows in a group are owned by the leading window. Other windows are hidden anyway so this simply ensures their sizes are uniform when FSed
const auto RESERVED = effectiveWindow()->getFullWindowReservedArea();
m_window->setBox({visualBox.pos() + RESERVED.topLeft, visualBox.size() - (RESERVED.topLeft + RESERVED.bottomRight)});
}
@@ -166,7 +172,7 @@ void CWindowTarget::updatePos(uint8_t flags) {
Vector2D ratioPadding;
if ((*REQUESTEDRATIO).y != 0 && m_space->algorithm()->tiledTargets() <= 1 && m_window && !Fullscreen::controller()->isFullscreen(m_window.lock())) {
if ((*REQUESTEDRATIO).y != 0 && m_space->algorithm()->tiledTargets() <= 1 && m_window) {
const Vector2D originalSize = MONITOR_WORKAREA.size();
const double requestedRatio = (*REQUESTEDRATIO).x / (*REQUESTEDRATIO).y;
@@ -194,7 +200,7 @@ void CWindowTarget::updatePos(uint8_t flags) {
calcSize = calcSize - GAPOFFSETTOPLEFT - GAPOFFSETBOTTOMRIGHT - ratioPadding;
}
if (isPseudo() && m_window && !Fullscreen::controller()->isFullscreen(m_window.lock())) {
if (isPseudo() && m_window) {
// Calculate pseudo
float scale = 1;
@@ -226,9 +232,8 @@ void CWindowTarget::updatePos(uint8_t flags) {
if (*PCLAMP_TILED) {
Vector2D minSize = m_window->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE});
Vector2D maxSize =
Fullscreen::controller()->isFullscreen(m_window.lock()) ? Vector2D{INFINITY, INFINITY} : m_window->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY});
calcSize = calcSize.clamp(minSize, maxSize);
Vector2D maxSize = m_window->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY});
calcSize = calcSize.clamp(minSize, maxSize);
calcPos += (availableSpace - calcSize) / 2.0;
@@ -236,7 +241,7 @@ void CWindowTarget::updatePos(uint8_t flags) {
calcPos.y = std::clamp(calcPos.y, MONITOR_WORKAREA.y, std::max(MONITOR_WORKAREA.y, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h - calcSize.y));
}
if (m_window->onSpecialWorkspace() && m_window && !Fullscreen::controller()->isFullscreen(m_window.lock())) {
if (m_window->onSpecialWorkspace() && m_window) {
// if special, we adjust the coords a bit
static auto PSCALEFACTOR = CConfigValue<Config::FLOAT>("dwindle:special_scale_factor");
@@ -38,7 +38,7 @@ bool IFullscreenHandler::isFullscreen(SP<Layout::ITarget> target, const std::opt
!isFullscreen(target, std::nullopt, covering);
}
// A window group's FS modes are considered to be owned by its current window
// A window group's FS state is considered to be owned by its current window
if (const auto WINDOW_GROUP_TARGET = dc<Layout::CWindowGroupTarget*>(target.get()); WINDOW_GROUP_TARGET && target->type() == Layout::TARGET_TYPE_GROUP) {
if (WINDOW_GROUP_TARGET->getGroup() && WINDOW_GROUP_TARGET->getGroup()->current() && WINDOW_GROUP_TARGET->getGroup()->current()->m_target)
target = WINDOW_GROUP_TARGET->getGroup()->current()->m_target;
@@ -70,6 +70,7 @@ SFullscreenMode IFullscreenHandler::getFullscreenModes(SP<Layout::ITarget> targe
if (!target)
return {};
// A window group's FS modes are considered to be owned by its current window
if (const auto WINDOW_GROUP_TARGET = dc<Layout::CWindowGroupTarget*>(target.get()); WINDOW_GROUP_TARGET && target->type() == Layout::TARGET_TYPE_GROUP) {
if (WINDOW_GROUP_TARGET->getGroup() && WINDOW_GROUP_TARGET->getGroup()->current() && WINDOW_GROUP_TARGET->getGroup()->current()->m_target)
target = WINDOW_GROUP_TARGET->getGroup()->current()->m_target;
@@ -165,9 +166,19 @@ void IFullscreenHandler::updateTargetRulesAndDecos(const SP<Layout::ITarget> tar
// Target must be a fullscreen window as considered by window/workspace rule matchers by now.
// update all the values necessary for FS windows to get correct window dimensions and pos
WINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT |
Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
WINDOW->updateDecorationValues();
// If window is in a group, we need to update these values for ALL members of the group.
if (WINDOW->m_group) {
for (const auto& gm : WINDOW->m_group->windows()) {
gm->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT |
Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
gm->updateDecorationValues();
}
} else {
WINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT |
Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
WINDOW->updateDecorationValues();
}
g_layoutManager->recalculateMonitor(MONITOR, Layout::CLayoutManager::RECALCULATE_MONITOR_REASON_TOGGLE_FULLSCREEN);
getSpace()->recalculate(Layout::RECALCULATE_REASON_TOGGLE_DEFAULT_HANDLED_FULLSCREEN);
}
@@ -197,6 +208,7 @@ void IFullscreenHandler::setTargetSizeAndPosition(const SP<Layout::ITarget> targ
Fullscreen::controller()->m_windowPosSettingQueued = true;
LAYOUT_TARGET->setPositionGlobal(WORKAREA);
}
Fullscreen::controller()->m_windowPosSettingQueued = false;
}
void IFullscreenHandler::syncTargetSizeAndPosition() {
@@ -253,6 +265,7 @@ void IFullscreenHandler::syncTargetSizeAndPosition() {
LAYOUT_TARGET->setPositionGlobal(WORKSPACE->m_space->workArea(FS_TARGET->floating()));
}
}
Fullscreen::controller()->m_windowPosSettingQueued = false;
}
void IFullscreenHandler::setNoMembersAboveFullscreen() {