Skip to content

Commit cd7fcad

Browse files
committed
fix: better handling for maximize on auto_tile
1 parent afadd99 commit cd7fcad

4 files changed

Lines changed: 207 additions & 24 deletions

File tree

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#include "../test.h"
2+
3+
// test that maximized toplevels are automatically unmaximized when new
4+
// toplevels are added
5+
// 1. Enable auto-tiling
6+
// 2. Open two toplevels (A and B) - they should tile side by side
7+
// 3. Maximize one (A)
8+
// 4. Open another toplevel (C)
9+
// Expected: A gets automatically unmaximized, and all three (A, B, C) are tiled
10+
// together
11+
int main() {
12+
DEFAULT(3);
13+
14+
// enable auto-tiling
15+
AWMSG("b r auto_tile");
16+
sleep(1);
17+
18+
// get initial workspace state
19+
AWMSG_J("w l", workspaces);
20+
json workspace = *workspaces.begin();
21+
ASSERT(workspace["auto_tile"] == true);
22+
23+
// open first toplevel (already opened by DEFAULT(3))
24+
sleep(1);
25+
26+
// verify we have 3 toplevels
27+
AWMSG_J("t l", toplevels);
28+
ASSERT(toplevels.size() == 3);
29+
30+
// get the toplevels
31+
auto it = toplevels.begin();
32+
json toplevel_a = *it++;
33+
json toplevel_b = *it++;
34+
json toplevel_c = *it++;
35+
36+
// verify none are maximized initially
37+
ASSERT(toplevel_a["maximized"] == false);
38+
ASSERT(toplevel_b["maximized"] == false);
39+
ASSERT(toplevel_c["maximized"] == false);
40+
41+
// verify all have reasonable dimensions (should be tiled in a grid)
42+
// with 3 toplevels in grid mode, they should not take full usable area
43+
AWMSG_J("o l", outputs);
44+
json output = *outputs.begin();
45+
int usable_width = output["usable"]["width"];
46+
int usable_height = output["usable"]["height"];
47+
48+
// each toplevel should be smaller than full usable area
49+
ASSERT(toplevel_a["width"] < usable_width);
50+
ASSERT(toplevel_b["width"] < usable_width);
51+
ASSERT(toplevel_c["width"] < usable_width);
52+
53+
// focus the first toplevel
54+
AWMSG("b r previous");
55+
AWMSG("b r previous");
56+
sleep(1);
57+
58+
// maximize the first toplevel
59+
AWMSG("b r maximize");
60+
sleep(1);
61+
62+
// verify it's maximized and takes full usable area
63+
AWMSG_J("t l", toplevels_max);
64+
auto it_max = toplevels_max.begin();
65+
json toplevel_a_max = *it_max++;
66+
json toplevel_b_after = *it_max++;
67+
json toplevel_c_after = *it_max++;
68+
69+
ASSERT(toplevel_a_max["maximized"] == true);
70+
ASSERT(toplevel_a_max["width"] == usable_width);
71+
ASSERT(toplevel_a_max["height"] == usable_height);
72+
73+
// the other two should not be maximized
74+
ASSERT(toplevel_b_after["maximized"] == false);
75+
ASSERT(toplevel_c_after["maximized"] == false);
76+
77+
std::cout << "Maximized toplevel verified. Opening new toplevel..."
78+
<< std::endl;
79+
80+
// open a new toplevel
81+
spawn("foot");
82+
sleep(2);
83+
84+
// verify we have 4 toplevels now
85+
AWMSG_J("t l", toplevels_final);
86+
ASSERT(toplevels_final.size() == 4);
87+
88+
// get all toplevels
89+
auto it_final = toplevels_final.begin();
90+
json tl0 = *it_final++;
91+
json tl1 = *it_final++;
92+
json tl2 = *it_final++;
93+
json tl3 = *it_final++;
94+
95+
// After adding a new toplevel, the previously maximized one should be
96+
// auto-unmaximized So all 4 toplevels should be tiled (none maximized)
97+
int maximized_count = 0;
98+
99+
for (json *tl : {&tl0, &tl1, &tl2, &tl3}) {
100+
if ((*tl)["maximized"] == true) {
101+
maximized_count++;
102+
}
103+
}
104+
105+
// there should be NO maximized toplevels after adding a new one
106+
ASSERT(maximized_count == 0);
107+
108+
// all 4 toplevels should be tiled (smaller than full usable area)
109+
for (json *tl : {&tl0, &tl1, &tl2, &tl3}) {
110+
int width = (*tl)["width"];
111+
int height = (*tl)["height"];
112+
113+
// each tiled window should be smaller than full area
114+
// (they should be in a grid or similar layout)
115+
ASSERT(width < usable_width || height < usable_height);
116+
117+
// make sure they're not hidden (width/height should be reasonable)
118+
ASSERT(width > 0);
119+
ASSERT(height > 0);
120+
}
121+
122+
std::cout << "Test passed: Previously maximized toplevel was "
123+
"auto-unmaximized when new toplevel was added"
124+
<< std::endl;
125+
126+
return 0;
127+
}

src/BSPTree.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ void BSPTree::rebuild(std::vector<Toplevel *> toplevels) {
258258
clear();
259259

260260
for (Toplevel *tl : toplevels)
261-
if (tl && !tl->fullscreen())
261+
if (tl && !tl->fullscreen() && !tl->maximized())
262262
insert(tl);
263263
}
264264

@@ -268,10 +268,10 @@ void BSPTree::rebuild_grid(std::vector<Toplevel *> toplevels) {
268268
if (toplevels.empty())
269269
return;
270270

271-
// filter out fullscreen toplevels
271+
// filter out fullscreen and maximized toplevels
272272
std::vector<Toplevel *> tiled;
273273
for (Toplevel *tl : toplevels)
274-
if (tl && !tl->fullscreen())
274+
if (tl && !tl->fullscreen() && !tl->maximized())
275275
tiled.push_back(tl);
276276

277277
if (tiled.empty())
@@ -359,10 +359,10 @@ void BSPTree::rebuild_dwindle(std::vector<Toplevel *> toplevels) {
359359
if (toplevels.empty())
360360
return;
361361

362-
// filter out fullscreen toplevels
362+
// filter out fullscreen and maximized toplevels
363363
std::vector<Toplevel *> tiled;
364364
for (Toplevel *tl : toplevels)
365-
if (tl && !tl->fullscreen())
365+
if (tl && !tl->fullscreen() && !tl->maximized())
366366
tiled.push_back(tl);
367367

368368
if (tiled.empty())

src/Toplevel.cpp

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
#include "Server.h"
1010
#include "WindowRule.h"
1111
#include "Workspace.h"
12-
#include "wlr/types/wlr_xdg_decoration_v1.h"
1312

1413
void Toplevel::map_notify(wl_listener *listener, [[maybe_unused]] void *data) {
1514
// on map or display
@@ -1143,7 +1142,8 @@ void Toplevel::set_maximized(const bool maximized) {
11431142
// get output from toplevel's current workspace, fallback to
11441143
// focused output
11451144
const Output *output = nullptr;
1146-
if (Workspace *workspace = server->get_workspace(this))
1145+
Workspace *workspace = server->get_workspace(this);
1146+
if (workspace)
11471147
output = workspace->output;
11481148
else
11491149
output = server->focused_output();
@@ -1177,16 +1177,62 @@ void Toplevel::set_maximized(const bool maximized) {
11771177
set_position_size(usable_area.x + output_box.x,
11781178
usable_area.y + output_box.y, usable_area.width,
11791179
usable_area.height);
1180+
1181+
// re-tile remaining windows if in auto-tile workspace
1182+
if (workspace && workspace->auto_tile && workspace->bsp_tree) {
1183+
// remove this toplevel from BSP tree
1184+
workspace->bsp_tree->remove(this);
1185+
1186+
// apply layout to remaining toplevels
1187+
if (workspace->pending_layout_idle)
1188+
wl_event_source_remove(workspace->pending_layout_idle);
1189+
1190+
workspace->pending_layout_idle = wl_event_loop_add_idle(
1191+
wl_display_get_event_loop(server->display),
1192+
[](void *data) {
1193+
Workspace *ws = static_cast<Workspace *>(data);
1194+
ws->pending_layout_idle = nullptr;
1195+
if (ws->bsp_tree)
1196+
ws->bsp_tree->apply_layout(ws->output->usable_area);
1197+
},
1198+
workspace);
1199+
}
11801200
} else {
1181-
// handles edge case where toplevel starts maximized
1182-
if (saved_geometry.width && saved_geometry.height)
1183-
// set back to saved geometry
1184-
set_position_size(saved_geometry.x, saved_geometry.y,
1185-
saved_geometry.width, saved_geometry.height);
1186-
else
1187-
// use half of output geometry
1188-
set_position_size(output_box.x, output_box.y, output_box.width / 2,
1189-
output_box.height / 2);
1201+
// check if we need to handle auto-tiling
1202+
bool should_auto_tile = workspace && workspace->auto_tile;
1203+
1204+
if (should_auto_tile && workspace->bsp_tree) {
1205+
// re-insert into BSP tree if it's not already there
1206+
if (!workspace->bsp_tree->find_node(this))
1207+
workspace->bsp_tree->insert(this);
1208+
1209+
// apply BSP tree layout
1210+
wlr_box new_geometry;
1211+
if (workspace->bsp_tree->get_toplevel_geometry(
1212+
this, output->usable_area, new_geometry)) {
1213+
set_position_size(new_geometry);
1214+
} else {
1215+
// fallback to saved geometry if BSP tree fails
1216+
if (saved_geometry.width && saved_geometry.height)
1217+
set_position_size(saved_geometry.x, saved_geometry.y,
1218+
saved_geometry.width,
1219+
saved_geometry.height);
1220+
else
1221+
set_position_size(output_box.x, output_box.y,
1222+
output_box.width / 2,
1223+
output_box.height / 2);
1224+
}
1225+
} else {
1226+
// handles edge case where toplevel starts maximized
1227+
if (saved_geometry.width && saved_geometry.height)
1228+
// set back to saved geometry
1229+
set_position_size(saved_geometry.x, saved_geometry.y,
1230+
saved_geometry.width, saved_geometry.height);
1231+
else
1232+
// use half of output geometry
1233+
set_position_size(output_box.x, output_box.y,
1234+
output_box.width / 2, output_box.height / 2);
1235+
}
11901236
}
11911237
}
11921238

src/Workspace.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,15 @@ void Workspace::add_toplevel(Toplevel *toplevel, const bool focus) {
8181
}
8282

8383
// automatically tile if auto_tile is enabled and toplevel is not floating
84-
if (auto_tile && !toplevel->is_floating &&
84+
if (auto_tile && !toplevel->is_floating && !toplevel->fullscreen() &&
85+
!toplevel->maximized() &&
8586
!(toplevel->geometry.width <= 1 && toplevel->geometry.height <= 1)) {
87+
// unmaximize any maximized toplevels when adding a new one
88+
Toplevel *tl, *tmp;
89+
wl_list_for_each_safe(tl, tmp, &toplevels,
90+
link) if (tl != toplevel && tl->maximized())
91+
tl->set_maximized(false);
92+
8693
if (bsp_tree) {
8794
TileMethod method = output->server->config->tiling.method;
8895

@@ -182,6 +189,7 @@ void Workspace::close(Toplevel *toplevel) {
182189
wl_list_for_each_safe(tl, tmp, &toplevels,
183190
link) if (tl != toplevel &&
184191
!tl->fullscreen() &&
192+
!tl->maximized() &&
185193
!tl->is_floating) tls.push_back(tl);
186194

187195
bsp_tree->rebuild_grid(tls);
@@ -269,6 +277,7 @@ bool Workspace::move_to(Toplevel *toplevel, Workspace *workspace) {
269277
wl_list_for_each_safe(tl, tmp, &toplevels,
270278
link) if (tl != toplevel &&
271279
!tl->fullscreen() &&
280+
!tl->maximized() &&
272281
!tl->is_floating) tls.push_back(tl);
273282

274283
bsp_tree->rebuild_grid(tls);
@@ -321,7 +330,7 @@ void Workspace::swap(Toplevel *a, Toplevel *b) const {
321330
return;
322331

323332
// Use transaction for atomic swap
324-
[[maybe_unused]] Transaction *txn = output->server->transaction_manager->begin();
333+
output->server->transaction_manager->begin();
325334

326335
if (auto_tile && bsp_tree) {
327336
BSPNode *node_a = bsp_tree->find_node(a);
@@ -525,11 +534,12 @@ void Workspace::tile(std::vector<Toplevel *> sans_toplevels) {
525534

526535
int toplevel_count = wl_list_length(&toplevels);
527536

528-
// do not tile fullscreen or floating toplevels
537+
// do not tile fullscreen, maximized, or floating toplevels
529538
Toplevel *toplevel, *tmp;
530539
std::vector<Toplevel *> tiled;
531540
wl_list_for_each_safe(toplevel, tmp, &toplevels, link) {
532-
if (toplevel->fullscreen() || toplevel->is_floating)
541+
if (toplevel->fullscreen() || toplevel->maximized() ||
542+
toplevel->is_floating)
533543
--toplevel_count;
534544
else
535545
tiled.push_back(toplevel);
@@ -759,10 +769,10 @@ void Workspace::toggle_auto_tile() {
759769

760770
std::vector<Toplevel *> tls;
761771
Toplevel *toplevel, *tmp;
762-
wl_list_for_each_safe(toplevel, tmp, &toplevels,
763-
link) if (!toplevel->fullscreen() &&
764-
!toplevel->is_floating)
765-
tls.push_back(toplevel);
772+
wl_list_for_each_safe(
773+
toplevel, tmp, &toplevels,
774+
link) if (!toplevel->fullscreen() && !toplevel->maximized() &&
775+
!toplevel->is_floating) tls.push_back(toplevel);
766776

767777
if (method == TILE_GRID)
768778
bsp_tree->rebuild_grid(tls);

0 commit comments

Comments
 (0)