summaryrefslogtreecommitdiffstats
path: root/sway/tree/layout.c
diff options
context:
space:
mode:
Diffstat (limited to 'sway/tree/layout.c')
-rw-r--r--sway/tree/layout.c1030
1 files changed, 1030 insertions, 0 deletions
diff --git a/sway/tree/layout.c b/sway/tree/layout.c
new file mode 100644
index 00000000..ae76ca26
--- /dev/null
+++ b/sway/tree/layout.c
@@ -0,0 +1,1030 @@
1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h>
3#include <math.h>
4#include <stdbool.h>
5#include <stdlib.h>
6#include <string.h>
7#include <wlr/types/wlr_output.h>
8#include <wlr/types/wlr_output_layout.h>
9#include "sway/debug.h"
10#include "sway/tree/container.h"
11#include "sway/tree/layout.h"
12#include "sway/output.h"
13#include "sway/tree/workspace.h"
14#include "sway/tree/view.h"
15#include "sway/input/seat.h"
16#include "sway/ipc-server.h"
17#include "list.h"
18#include "log.h"
19
20struct sway_container root_container;
21
22static void output_layout_handle_change(struct wl_listener *listener,
23 void *data) {
24 struct wlr_output_layout *output_layout =
25 root_container.sway_root->output_layout;
26 const struct wlr_box *layout_box =
27 wlr_output_layout_get_box(output_layout, NULL);
28 root_container.x = layout_box->x;
29 root_container.y = layout_box->y;
30 root_container.width = layout_box->width;
31 root_container.height = layout_box->height;
32
33 arrange_windows(&root_container, layout_box->width, layout_box->height);
34}
35
36struct sway_container *container_set_layout(struct sway_container *container,
37 enum sway_container_layout layout) {
38 if (container->type == C_WORKSPACE) {
39 container->workspace_layout = layout;
40 if (layout == L_HORIZ || layout == L_VERT) {
41 container->layout = layout;
42 }
43 } else {
44 container->layout = layout;
45 }
46 return container;
47}
48
49void layout_init(void) {
50 root_container.id = 0; // normally assigned in new_swayc()
51 root_container.type = C_ROOT;
52 root_container.layout = L_NONE;
53 root_container.name = strdup("root");
54 root_container.children = create_list();
55 wl_signal_init(&root_container.events.destroy);
56
57 root_container.sway_root = calloc(1, sizeof(*root_container.sway_root));
58 root_container.sway_root->output_layout = wlr_output_layout_create();
59 wl_list_init(&root_container.sway_root->xwayland_unmanaged);
60 wl_signal_init(&root_container.sway_root->events.new_container);
61
62 root_container.sway_root->output_layout_change.notify =
63 output_layout_handle_change;
64 wl_signal_add(&root_container.sway_root->output_layout->events.change,
65 &root_container.sway_root->output_layout_change);
66}
67
68static int index_child(const struct sway_container *child) {
69 // TODO handle floating
70 struct sway_container *parent = child->parent;
71 int i, len;
72 len = parent->children->length;
73 for (i = 0; i < len; ++i) {
74 if (parent->children->items[i] == child) {
75 break;
76 }
77 }
78
79 if (!sway_assert(i < len, "Stray container")) {
80 return -1;
81 }
82 return i;
83}
84
85void container_insert_child(struct sway_container *parent,
86 struct sway_container *child, int i) {
87 struct sway_container *old_parent = child->parent;
88 if (old_parent) {
89 container_remove_child(child);
90 }
91 wlr_log(L_DEBUG, "Inserting id:%zd at index %d", child->id, i);
92 list_insert(parent->children, i, child);
93 child->parent = parent;
94 wl_signal_emit(&child->events.reparent, old_parent);
95}
96
97struct sway_container *container_add_sibling(struct sway_container *fixed,
98 struct sway_container *active) {
99 // TODO handle floating
100 struct sway_container *old_parent = NULL;
101 if (active->parent) {
102 old_parent = active->parent;
103 container_remove_child(active);
104 }
105 struct sway_container *parent = fixed->parent;
106 int i = index_child(fixed);
107 list_insert(parent->children, i + 1, active);
108 active->parent = parent;
109 wl_signal_emit(&active->events.reparent, old_parent);
110 return active->parent;
111}
112
113void container_add_child(struct sway_container *parent,
114 struct sway_container *child) {
115 wlr_log(L_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)",
116 child, child->type, child->width, child->height,
117 parent, parent->type, parent->width, parent->height);
118 list_add(parent->children, child);
119 child->parent = parent;
120}
121
122struct sway_container *container_remove_child(struct sway_container *child) {
123 struct sway_container *parent = child->parent;
124 for (int i = 0; i < parent->children->length; ++i) {
125 if (parent->children->items[i] == child) {
126 list_del(parent->children, i);
127 break;
128 }
129 }
130 child->parent = NULL;
131 return parent;
132}
133
134void container_move_to(struct sway_container *container,
135 struct sway_container *destination) {
136 if (container == destination
137 || container_has_anscestor(container, destination)) {
138 return;
139 }
140 struct sway_container *old_parent = container_remove_child(container);
141 container->width = container->height = 0;
142 struct sway_container *new_parent;
143 if (destination->type == C_VIEW) {
144 new_parent = container_add_sibling(destination, container);
145 } else {
146 new_parent = destination;
147 container_add_child(destination, container);
148 }
149 wl_signal_emit(&container->events.reparent, old_parent);
150 if (container->type == C_WORKSPACE) {
151 struct sway_seat *seat = input_manager_get_default_seat(
152 input_manager);
153 if (old_parent->children->length == 0) {
154 char *ws_name = workspace_next_name(old_parent->name);
155 struct sway_container *ws =
156 workspace_create(old_parent, ws_name);
157 free(ws_name);
158 seat_set_focus(seat, ws);
159 }
160 container_sort_workspaces(new_parent);
161 seat_set_focus(seat, new_parent);
162 }
163 if (old_parent) {
164 arrange_windows(old_parent, -1, -1);
165 }
166 arrange_windows(new_parent, -1, -1);
167}
168
169static bool sway_dir_to_wlr(enum movement_direction dir,
170 enum wlr_direction *out) {
171 switch (dir) {
172 case MOVE_UP:
173 *out = WLR_DIRECTION_UP;
174 break;
175 case MOVE_DOWN:
176 *out = WLR_DIRECTION_DOWN;
177 break;
178 case MOVE_LEFT:
179 *out = WLR_DIRECTION_LEFT;
180 break;
181 case MOVE_RIGHT:
182 *out = WLR_DIRECTION_RIGHT;
183 break;
184 default:
185 return false;
186 }
187
188 return true;
189}
190
191static bool is_parallel(enum sway_container_layout layout,
192 enum movement_direction dir) {
193 switch (layout) {
194 case L_TABBED:
195 case L_STACKED:
196 case L_HORIZ:
197 return dir == MOVE_LEFT || dir == MOVE_RIGHT;
198 case L_VERT:
199 return dir == MOVE_UP || dir == MOVE_DOWN;
200 default:
201 return false;
202 }
203}
204
205static enum movement_direction invert_movement(enum movement_direction dir) {
206 switch (dir) {
207 case MOVE_LEFT:
208 return MOVE_RIGHT;
209 case MOVE_RIGHT:
210 return MOVE_LEFT;
211 case MOVE_UP:
212 return MOVE_DOWN;
213 case MOVE_DOWN:
214 return MOVE_UP;
215 default:
216 sway_assert(0, "This function expects left|right|up|down");
217 return MOVE_LEFT;
218 }
219}
220
221static int move_offs(enum movement_direction move_dir) {
222 return move_dir == MOVE_LEFT || move_dir == MOVE_UP ? -1 : 1;
223}
224
225/* Gets the index of the most extreme member based on the movement offset */
226static int container_limit(struct sway_container *container,
227 enum movement_direction move_dir) {
228 return move_offs(move_dir) < 0 ? 0 : container->children->length;
229}
230
231/* Takes one child, sets it aside, wraps the rest of the children in a new
232 * container, switches the layout of the workspace, and drops the child back in.
233 * In other words, rejigger it. */
234static void workspace_rejigger(struct sway_container *ws,
235 struct sway_container *child, enum movement_direction move_dir) {
236 struct sway_container *original_parent = child->parent;
237 struct sway_container *new_parent =
238 container_split(ws, ws->layout);
239
240 container_remove_child(child);
241 for (int i = 0; i < ws->children->length; ++i) {
242 struct sway_container *_child = ws->children->items[i];
243 container_move_to(new_parent, _child);
244 }
245
246 int index = move_offs(move_dir);
247 container_insert_child(ws, child, index < 0 ? 0 : 1);
248 container_set_layout(ws,
249 move_dir == MOVE_LEFT || move_dir == MOVE_RIGHT ? L_HORIZ : L_VERT);
250
251 container_flatten(ws);
252 container_reap_empty_recursive(original_parent);
253 wl_signal_emit(&child->events.reparent, original_parent);
254 container_create_notify(new_parent);
255 arrange_windows(ws, -1, -1);
256}
257
258void container_move(struct sway_container *container,
259 enum movement_direction move_dir, int move_amt) {
260 if (!sway_assert(
261 container->type != C_CONTAINER || container->type != C_VIEW,
262 "Can only move containers and views")) {
263 return;
264 }
265 int offs = move_offs(move_dir);
266
267 struct sway_container *sibling = NULL;
268 struct sway_container *current = container;
269 struct sway_container *parent = current->parent;
270
271 if (parent != container_flatten(parent)) {
272 // Special case: we were the last one in this container, so flatten it
273 // and leave
274 update_debug_tree();
275 return;
276 }
277
278 while (!sibling) {
279 if (current->type == C_ROOT) {
280 return;
281 }
282
283 parent = current->parent;
284 wlr_log(L_DEBUG, "Visiting %p %s '%s'", current,
285 container_type_to_str(current->type), current->name);
286
287 int index = index_child(current);
288
289 switch (current->type) {
290 case C_OUTPUT: {
291 enum wlr_direction wlr_dir;
292 sway_dir_to_wlr(move_dir, &wlr_dir);
293 double ref_lx = current->x + current->width / 2;
294 double ref_ly = current->y + current->height / 2;
295 struct wlr_output *next = wlr_output_layout_adjacent_output(
296 root_container.sway_root->output_layout, wlr_dir,
297 current->sway_output->wlr_output, ref_lx, ref_ly);
298 if (!next) {
299 wlr_log(L_DEBUG, "Hit edge of output, nowhere else to go");
300 return;
301 }
302 struct sway_output *next_output = next->data;
303 current = next_output->swayc;
304 wlr_log(L_DEBUG, "Selected next output (%s)", current->name);
305 // Select workspace and get outta here
306 current = seat_get_focus_inactive(
307 config->handler_context.seat, current);
308 if (current->type != C_WORKSPACE) {
309 current = container_parent(current, C_WORKSPACE);
310 }
311 sibling = current;
312 break;
313 }
314 case C_WORKSPACE:
315 if (!is_parallel(current->layout, move_dir)) {
316 if (current->children->length > 2) {
317 wlr_log(L_DEBUG, "Rejiggering the workspace (%d kiddos)",
318 current->children->length);
319 workspace_rejigger(current, container, move_dir);
320 } else if (current->children->length == 2) {
321 wlr_log(L_DEBUG, "Changing workspace layout");
322 container_set_layout(current,
323 move_dir == MOVE_LEFT || move_dir == MOVE_RIGHT ?
324 L_HORIZ : L_VERT);
325 container_insert_child(current, container, offs < 0 ? 0 : 1);
326 arrange_windows(current, -1, -1);
327 }
328 return;
329 } else {
330 wlr_log(L_DEBUG, "Selecting output");
331 current = current->parent;
332 }
333 break;
334 case C_CONTAINER:
335 case C_VIEW:
336 if (is_parallel(parent->layout, move_dir)) {
337 if ((index == parent->children->length - 1 && offs > 0)
338 || (index == 0 && offs < 0)) {
339 if (current->parent == container->parent) {
340 wlr_log(L_DEBUG, "Hit limit, selecting parent");
341 current = current->parent;
342 } else {
343 wlr_log(L_DEBUG, "Hit limit, "
344 "promoting descendant to sibling");
345 // Special case
346 struct sway_container *old_parent = container->parent;
347 container_insert_child(current->parent, container,
348 index + (offs < 0 ? 0 : 1));
349 container->width = container->height = 0;
350 arrange_windows(current->parent, -1, -1);
351 arrange_windows(old_parent, -1, -1);
352 return;
353 }
354 } else {
355 sibling = parent->children->items[index + offs];
356 wlr_log(L_DEBUG, "Selecting sibling id:%zd", sibling->id);
357 }
358 } else {
359 wlr_log(L_DEBUG, "Moving up to find a parallel container");
360 current = current->parent;
361 }
362 break;
363 default:
364 sway_assert(0, "Not expecting to see container of type %s here",
365 container_type_to_str(current->type));
366 return;
367 }
368 }
369
370 // Part two: move stuff around
371 int index = index_child(container);
372 struct sway_container *old_parent = container->parent;
373
374 while (sibling) {
375 switch (sibling->type) {
376 case C_VIEW:
377 if (sibling->parent == container->parent) {
378 wlr_log(L_DEBUG, "Swapping siblings");
379 sibling->parent->children->items[index + offs] = container;
380 sibling->parent->children->items[index] = sibling;
381 arrange_windows(sibling->parent, -1, -1);
382 } else {
383 wlr_log(L_DEBUG, "Promoting to sibling of cousin");
384 container_insert_child(sibling->parent, container,
385 index_child(sibling) + (offs > 0 ? 0 : 1));
386 container->width = container->height = 0;
387 arrange_windows(sibling->parent, -1, -1);
388 arrange_windows(old_parent, -1, -1);
389 }
390 sibling = NULL;
391 break;
392 case C_WORKSPACE: // Note: only in the case of moving between outputs
393 case C_CONTAINER:
394 if (is_parallel(sibling->layout, move_dir)) {
395 int limit = container_limit(sibling, invert_movement(move_dir));
396 wlr_log(L_DEBUG, "limit: %d", limit);
397 wlr_log(L_DEBUG,
398 "Reparenting container (parallel) to index %d "
399 "(move dir: %d)", limit, move_dir);
400 container_insert_child(sibling, container, limit);
401 container->width = container->height = 0;
402 arrange_windows(sibling, -1, -1);
403 arrange_windows(old_parent, -1, -1);
404 sibling = NULL;
405 } else {
406 wlr_log(L_DEBUG, "Reparenting container (perpendicular)");
407 container_remove_child(container);
408 struct sway_container *focus_inactive = seat_get_focus_inactive(
409 config->handler_context.seat, sibling);
410 if (focus_inactive) {
411 while (focus_inactive->parent != sibling) {
412 focus_inactive = focus_inactive->parent;
413 }
414 wlr_log(L_DEBUG, "Focus inactive: id:%zd",
415 focus_inactive->id);
416 sibling = focus_inactive;
417 continue;
418 } else if (sibling->children->length) {
419 wlr_log(L_DEBUG, "No focus-inactive, adding arbitrarily");
420 container_add_sibling(sibling->children->items[0], container);
421 } else {
422 wlr_log(L_DEBUG, "No kiddos, adding container alone");
423 container_add_child(sibling, container);
424 }
425 container->width = container->height = 0;
426 arrange_windows(sibling, -1, -1);
427 arrange_windows(old_parent, -1, -1);
428 sibling = NULL;
429 }
430 break;
431 default:
432 sway_assert(0, "Not expecting to see container of type %s here",
433 container_type_to_str(sibling->type));
434 return;
435 }
436 }
437
438 if (old_parent) {
439 seat_set_focus(config->handler_context.seat, old_parent);
440 seat_set_focus(config->handler_context.seat, container);
441 }
442
443 struct sway_container *last_ws = old_parent;
444 struct sway_container *next_ws = container->parent;
445 if (last_ws && last_ws->type != C_WORKSPACE) {
446 last_ws = container_parent(last_ws, C_WORKSPACE);
447 }
448 if (next_ws && next_ws->type != C_WORKSPACE) {
449 next_ws = container_parent(next_ws, C_WORKSPACE);
450 }
451 if (last_ws && next_ws && last_ws != next_ws) {
452 ipc_event_workspace(last_ws, container, "focus");
453 }
454}
455
456enum sway_container_layout container_get_default_layout(
457 struct sway_container *con) {
458 if (con->type != C_OUTPUT) {
459 con = container_parent(con, C_OUTPUT);
460 }
461
462 if (!sway_assert(con != NULL,
463 "container_get_default_layout must be called on an attached"
464 " container below the root container")) {
465 return 0;
466 }
467
468 if (config->default_layout != L_NONE) {
469 return config->default_layout;
470 } else if (config->default_orientation != L_NONE) {
471 return config->default_orientation;
472 } else if (con->width >= con->height) {
473 return L_HORIZ;
474 } else {
475 return L_VERT;
476 }
477}
478
479static int sort_workspace_cmp_qsort(const void *_a, const void *_b) {
480 struct sway_container *a = *(void **)_a;
481 struct sway_container *b = *(void **)_b;
482 int retval = 0;
483
484 if (isdigit(a->name[0]) && isdigit(b->name[0])) {
485 int a_num = strtol(a->name, NULL, 10);
486 int b_num = strtol(b->name, NULL, 10);
487 retval = (a_num < b_num) ? -1 : (a_num > b_num);
488 } else if (isdigit(a->name[0])) {
489 retval = -1;
490 } else if (isdigit(b->name[0])) {
491 retval = 1;
492 }
493
494 return retval;
495}
496
497void container_sort_workspaces(struct sway_container *output) {
498 list_stable_sort(output->children, sort_workspace_cmp_qsort);
499}
500
501static void apply_horiz_layout(struct sway_container *container, const double x,
502 const double y, const double width,
503 const double height, const int start,
504 const int end);
505
506static void apply_vert_layout(struct sway_container *container, const double x,
507 const double y, const double width,
508 const double height, const int start,
509 const int end);
510
511void arrange_windows(struct sway_container *container,
512 double width, double height) {
513 if (config->reloading) {
514 return;
515 }
516 int i;
517 if (width == -1 || height == -1) {
518 width = container->width;
519 height = container->height;
520 }
521 // pixels are indivisible. if we don't round the pixels, then the view
522 // calculations will be off (e.g. 50.5 + 50.5 = 101, but in reality it's
523 // 50 + 50 = 100). doing it here cascades properly to all width/height/x/y.
524 width = floor(width);
525 height = floor(height);
526
527 wlr_log(L_DEBUG, "Arranging layout for %p %s %fx%f+%f,%f", container,
528 container->name, container->width, container->height, container->x,
529 container->y);
530
531 double x = 0, y = 0;
532 switch (container->type) {
533 case C_ROOT:
534 for (i = 0; i < container->children->length; ++i) {
535 struct sway_container *output = container->children->items[i];
536 const struct wlr_box *output_box = wlr_output_layout_get_box(
537 container->sway_root->output_layout,
538 output->sway_output->wlr_output);
539 output->x = output_box->x;
540 output->y = output_box->y;
541 output->width = output_box->width;
542 output->height = output_box->height;
543 wlr_log(L_DEBUG, "Arranging output '%s' at %f,%f",
544 output->name, output->x, output->y);
545 arrange_windows(output, output_box->width, output_box->height);
546 }
547 return;
548 case C_OUTPUT:
549 // arrange all workspaces:
550 for (i = 0; i < container->children->length; ++i) {
551 struct sway_container *child = container->children->items[i];
552 arrange_windows(child, -1, -1);
553 }
554 return;
555 case C_WORKSPACE:
556 {
557 struct sway_container *output =
558 container_parent(container, C_OUTPUT);
559 struct wlr_box *area = &output->sway_output->usable_area;
560 wlr_log(L_DEBUG, "Usable area for ws: %dx%d@%d,%d",
561 area->width, area->height, area->x, area->y);
562 container->width = width = area->width;
563 container->height = height = area->height;
564 container->x = x = area->x;
565 container->y = y = area->y;
566 wlr_log(L_DEBUG, "Arranging workspace '%s' at %f, %f",
567 container->name, container->x, container->y);
568 }
569 // children are properly handled below
570 break;
571 case C_VIEW:
572 {
573 container->width = width;
574 container->height = height;
575 view_configure(container->sway_view, container->x, container->y,
576 container->width, container->height);
577 wlr_log(L_DEBUG, "Set view to %.f x %.f @ %.f, %.f",
578 container->width, container->height,
579 container->x, container->y);
580 }
581 return;
582 default:
583 container->width = width;
584 container->height = height;
585 x = container->x;
586 y = container->y;
587 break;
588 }
589
590 switch (container->layout) {
591 case L_HORIZ:
592 apply_horiz_layout(container, x, y, width, height, 0,
593 container->children->length);
594 break;
595 case L_VERT:
596 apply_vert_layout(container, x, y, width, height, 0,
597 container->children->length);
598 break;
599 default:
600 wlr_log(L_DEBUG, "TODO: arrange layout type %d", container->layout);
601 apply_horiz_layout(container, x, y, width, height, 0,
602 container->children->length);
603 break;
604 }
605 container_damage_whole(container);
606 // TODO: Make this less shitty
607 update_debug_tree();
608}
609
610static void apply_horiz_layout(struct sway_container *container,
611 const double x, const double y,
612 const double width, const double height,
613 const int start, const int end) {
614 double scale = 0;
615 // Calculate total width
616 for (int i = start; i < end; ++i) {
617 double *old_width =
618 &((struct sway_container *)container->children->items[i])->width;
619 if (*old_width <= 0) {
620 if (end - start > 1) {
621 *old_width = width / (end - start - 1);
622 } else {
623 *old_width = width;
624 }
625 }
626 scale += *old_width;
627 }
628 scale = width / scale;
629
630 // Resize windows
631 double child_x = x;
632 if (scale > 0.1) {
633 wlr_log(L_DEBUG, "Arranging %p horizontally", container);
634 for (int i = start; i < end; ++i) {
635 struct sway_container *child = container->children->items[i];
636 wlr_log(L_DEBUG,
637 "Calculating arrangement for %p:%d (will scale %f by %f)",
638 child, child->type, width, scale);
639
640 if (child->type == C_VIEW) {
641 view_configure(child->sway_view, child_x, y, child->width,
642 child->height);
643 } else {
644 child->x = child_x;
645 child->y = y;
646 }
647
648 if (i == end - 1) {
649 double remaining_width = x + width - child_x;
650 arrange_windows(child, remaining_width, height);
651 } else {
652 arrange_windows(child, child->width * scale, height);
653 }
654 child_x += child->width;
655 }
656
657 // update focused view border last because it may
658 // depend on the title bar geometry of its siblings.
659 /* TODO WLR
660 if (focused && container->children->length > 1) {
661 update_container_border(focused);
662 }
663 */
664 }
665}
666
667void apply_vert_layout(struct sway_container *container,
668 const double x, const double y,
669 const double width, const double height, const int start,
670 const int end) {
671 int i;
672 double scale = 0;
673 // Calculate total height
674 for (i = start; i < end; ++i) {
675 double *old_height =
676 &((struct sway_container *)container->children->items[i])->height;
677 if (*old_height <= 0) {
678 if (end - start > 1) {
679 *old_height = height / (end - start - 1);
680 } else {
681 *old_height = height;
682 }
683 }
684 scale += *old_height;
685 }
686 scale = height / scale;
687
688 // Resize
689 double child_y = y;
690 if (scale > 0.1) {
691 wlr_log(L_DEBUG, "Arranging %p vertically", container);
692 for (i = start; i < end; ++i) {
693 struct sway_container *child = container->children->items[i];
694 wlr_log(L_DEBUG,
695 "Calculating arrangement for %p:%d (will scale %f by %f)",
696 child, child->type, height, scale);
697 if (child->type == C_VIEW) {
698 view_configure(child->sway_view, x, child_y, child->width,
699 child->height);
700 } else {
701 child->x = x;
702 child->y = child_y;
703 }
704
705 if (i == end - 1) {
706 double remaining_height = y + height - child_y;
707 arrange_windows(child, width, remaining_height);
708 } else {
709 arrange_windows(child, width, child->height * scale);
710 }
711 child_y += child->height;
712 }
713
714 // update focused view border last because it may
715 // depend on the title bar geometry of its siblings.
716 /* TODO WLR
717 if (focused && container->children->length > 1) {
718 update_container_border(focused);
719 }
720 */
721 }
722}
723
724/**
725 * Get swayc in the direction of newly entered output.
726 */
727static struct sway_container *get_swayc_in_output_direction(
728 struct sway_container *output, enum movement_direction dir,
729 struct sway_seat *seat) {
730 if (!output) {
731 return NULL;
732 }
733
734 struct sway_container *ws = seat_get_focus_inactive(seat, output);
735 if (ws->type != C_WORKSPACE) {
736 ws = container_parent(ws, C_WORKSPACE);
737 }
738
739 if (ws == NULL) {
740 wlr_log(L_ERROR, "got an output without a workspace");
741 return NULL;
742 }
743
744 if (ws->children->length > 0) {
745 switch (dir) {
746 case MOVE_LEFT:
747 // get most right child of new output
748 return ws->children->items[ws->children->length-1];
749 case MOVE_RIGHT:
750 // get most left child of new output
751 return ws->children->items[0];
752 case MOVE_UP:
753 case MOVE_DOWN: {
754 struct sway_container *focused =
755 seat_get_focus_inactive(seat, ws);
756 if (focused && focused->parent) {
757 struct sway_container *parent = focused->parent;
758 if (parent->layout == L_VERT) {
759 if (dir == MOVE_UP) {
760 // get child furthest down on new output
761 int idx = parent->children->length - 1;
762 return parent->children->items[idx];
763 } else if (dir == MOVE_DOWN) {
764 // get child furthest up on new output
765 return parent->children->items[0];
766 }
767 }
768 return focused;
769 }
770 break;
771 }
772 default:
773 break;
774 }
775 }
776
777 return ws;
778}
779
780static void get_layout_center_position(struct sway_container *container,
781 int *x, int *y) {
782 // FIXME view coords are inconsistently referred to in layout/output systems
783 if (container->type == C_OUTPUT) {
784 *x = container->x + container->width/2;
785 *y = container->y + container->height/2;
786 } else {
787 struct sway_container *output = container_parent(container, C_OUTPUT);
788 if (container->type == C_WORKSPACE) {
789 // Workspace coordinates are actually wrong/arbitrary, but should
790 // be same as output.
791 *x = output->x;
792 *y = output->y;
793 } else {
794 *x = output->x + container->x;
795 *y = output->y + container->y;
796 }
797 }
798}
799
800static struct sway_container *sway_output_from_wlr(struct wlr_output *output) {
801 if (output == NULL) {
802 return NULL;
803 }
804 for (int i = 0; i < root_container.children->length; ++i) {
805 struct sway_container *o = root_container.children->items[i];
806 if (o->type == C_OUTPUT && o->sway_output->wlr_output == output) {
807 return o;
808 }
809 }
810 return NULL;
811}
812
813struct sway_container *container_get_in_direction(
814 struct sway_container *container, struct sway_seat *seat,
815 enum movement_direction dir) {
816 if (dir == MOVE_CHILD) {
817 return seat_get_focus_inactive(seat, container);
818 }
819
820 struct sway_container *parent = container->parent;
821 if (dir == MOVE_PARENT) {
822 if (parent->type == C_OUTPUT) {
823 return NULL;
824 } else {
825 return parent;
826 }
827 }
828
829 // TODO WLR fullscreen
830 /*
831 if (container->type == C_VIEW && swayc_is_fullscreen(container)) {
832 wlr_log(L_DEBUG, "Moving from fullscreen view, skipping to output");
833 container = container_parent(container, C_OUTPUT);
834 get_layout_center_position(container, &abs_pos);
835 struct sway_container *output =
836 swayc_adjacent_output(container, dir, &abs_pos, true);
837 return get_swayc_in_output_direction(output, dir);
838 }
839 if (container->type == C_WORKSPACE && container->fullscreen) {
840 sway_log(L_DEBUG, "Moving to fullscreen view");
841 return container->fullscreen;
842 }
843 */
844
845 struct sway_container *wrap_candidate = NULL;
846 while (true) {
847 bool can_move = false;
848 int desired;
849 int idx = index_child(container);
850 if (parent->type == C_ROOT) {
851 enum wlr_direction wlr_dir = 0;
852 if (!sway_assert(sway_dir_to_wlr(dir, &wlr_dir),
853 "got invalid direction: %d", dir)) {
854 return NULL;
855 }
856 int lx, ly;
857 get_layout_center_position(container, &lx, &ly);
858 struct wlr_output_layout *layout =
859 root_container.sway_root->output_layout;
860 struct wlr_output *wlr_adjacent =
861 wlr_output_layout_adjacent_output(layout, wlr_dir,
862 container->sway_output->wlr_output, lx, ly);
863 struct sway_container *adjacent =
864 sway_output_from_wlr(wlr_adjacent);
865
866 if (!adjacent || adjacent == container) {
867 return wrap_candidate;
868 }
869 struct sway_container *next =
870 get_swayc_in_output_direction(adjacent, dir, seat);
871 if (next == NULL) {
872 return NULL;
873 }
874 if (next->children && next->children->length) {
875 // TODO consider floating children as well
876 return seat_get_focus_inactive_view(seat, next);
877 } else {
878 return next;
879 }
880 } else {
881 if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
882 if (parent->layout == L_HORIZ || parent->layout == L_TABBED) {
883 can_move = true;
884 desired = idx + (dir == MOVE_LEFT ? -1 : 1);
885 }
886 } else {
887 if (parent->layout == L_VERT || parent->layout == L_STACKED) {
888 can_move = true;
889 desired = idx + (dir == MOVE_UP ? -1 : 1);
890 }
891 }
892 }
893
894 if (can_move) {
895 // TODO handle floating
896 if (desired < 0 || desired >= parent->children->length) {
897 can_move = false;
898 int len = parent->children->length;
899 if (!wrap_candidate && len > 1) {
900 if (desired < 0) {
901 wrap_candidate = parent->children->items[len-1];
902 } else {
903 wrap_candidate = parent->children->items[0];
904 }
905 if (config->force_focus_wrapping) {
906 return wrap_candidate;
907 }
908 }
909 } else {
910 struct sway_container *desired_con = parent->children->items[desired];
911 wlr_log(L_DEBUG,
912 "cont %d-%p dir %i sibling %d: %p", idx,
913 container, dir, desired, desired_con);
914 struct sway_container *next = seat_get_focus_inactive_view(seat, desired_con);
915 return next;
916 }
917 }
918
919 if (!can_move) {
920 container = parent;
921 parent = parent->parent;
922 if (!parent) {
923 // wrapping is the last chance
924 return wrap_candidate;
925 }
926 }
927 }
928}
929
930struct sway_container *container_replace_child(struct sway_container *child,
931 struct sway_container *new_child) {
932 struct sway_container *parent = child->parent;
933 if (parent == NULL) {
934 return NULL;
935 }
936 int i = index_child(child);
937
938 // TODO floating
939 if (new_child->parent) {
940 container_remove_child(new_child);
941 }
942 parent->children->items[i] = new_child;
943 new_child->parent = parent;
944 child->parent = NULL;
945
946 // Set geometry for new child
947 new_child->x = child->x;
948 new_child->y = child->y;
949 new_child->width = child->width;
950 new_child->height = child->height;
951
952 // reset geometry for child
953 child->width = 0;
954 child->height = 0;
955
956 return parent;
957}
958
959struct sway_container *container_split(struct sway_container *child,
960 enum sway_container_layout layout) {
961 // TODO floating: cannot split a floating container
962 if (!sway_assert(child, "child cannot be null")) {
963 return NULL;
964 }
965 if (child->type == C_WORKSPACE && child->children->length == 0) {
966 // Special case: this just behaves like splitt
967 child->prev_layout = child->layout;
968 child->layout = layout;
969 arrange_windows(child, -1, -1);
970 return child;
971 }
972
973 struct sway_container *cont = container_create(C_CONTAINER);
974
975 wlr_log(L_DEBUG, "creating container %p around %p", cont, child);
976
977 cont->prev_layout = L_NONE;
978 cont->width = child->width;
979 cont->height = child->height;
980 cont->x = child->x;
981 cont->y = child->y;
982
983 if (child->type == C_WORKSPACE) {
984 struct sway_seat *seat = input_manager_get_default_seat(input_manager);
985 struct sway_container *workspace = child;
986 bool set_focus = (seat_get_focus(seat) == workspace);
987
988 while (workspace->children->length) {
989 struct sway_container *ws_child = workspace->children->items[0];
990 container_remove_child(ws_child);
991 container_add_child(cont, ws_child);
992 }
993
994 container_add_child(workspace, cont);
995 enum sway_container_layout old_layout = workspace->layout;
996 container_set_layout(workspace, layout);
997 cont->layout = old_layout;
998
999 if (set_focus) {
1000 seat_set_focus(seat, cont);
1001 }
1002 } else {
1003 cont->layout = layout;
1004 container_replace_child(child, cont);
1005 container_add_child(cont, child);
1006 }
1007
1008 return cont;
1009}
1010
1011void container_recursive_resize(struct sway_container *container,
1012 double amount, enum resize_edge edge) {
1013 bool layout_match = true;
1014 wlr_log(L_DEBUG, "Resizing %p with amount: %f", container, amount);
1015 if (edge == RESIZE_EDGE_LEFT || edge == RESIZE_EDGE_RIGHT) {
1016 container->width += amount;
1017 layout_match = container->layout == L_HORIZ;
1018 } else if (edge == RESIZE_EDGE_TOP || edge == RESIZE_EDGE_BOTTOM) {
1019 container->height += amount;
1020 layout_match = container->layout == L_VERT;
1021 }
1022 if (container->children) {
1023 for (int i = 0; i < container->children->length; i++) {
1024 struct sway_container *child = container->children->items[i];
1025 double amt = layout_match ?
1026 amount / container->children->length : amount;
1027 container_recursive_resize(child, amt, edge);
1028 }
1029 }
1030}