aboutsummaryrefslogtreecommitdiffstats
path: root/sway/tree
diff options
context:
space:
mode:
authorLibravatar Drew DeVault <sir@cmpwn.com>2017-11-11 11:00:18 -0500
committerLibravatar Drew DeVault <sir@cmpwn.com>2017-11-11 11:00:18 -0500
commit0ba6554c4f6c923274062862d895240eea4de350 (patch)
treee2b94dee4e8049f3a39365e82913559660f87bcd /sway/tree
parentEstablish sway input submodule (diff)
downloadsway-0ba6554c4f6c923274062862d895240eea4de350.tar.gz
sway-0ba6554c4f6c923274062862d895240eea4de350.tar.zst
sway-0ba6554c4f6c923274062862d895240eea4de350.zip
Move sway's internal tree code to sway/tree/
Diffstat (limited to 'sway/tree')
-rw-r--r--sway/tree/container.c1016
-rw-r--r--sway/tree/criteria.c451
-rw-r--r--sway/tree/focus.c278
-rw-r--r--sway/tree/layout.c1773
-rw-r--r--sway/tree/output.c277
-rw-r--r--sway/tree/workspace.c373
6 files changed, 4168 insertions, 0 deletions
diff --git a/sway/tree/container.c b/sway/tree/container.c
new file mode 100644
index 00000000..829fde69
--- /dev/null
+++ b/sway/tree/container.c
@@ -0,0 +1,1016 @@
1#define _XOPEN_SOURCE 500
2#include <ctype.h>
3#include <stdlib.h>
4#include <stdbool.h>
5#include <strings.h>
6#include <string.h>
7#include "sway/config.h"
8#include "sway/container.h"
9#include "sway/workspace.h"
10#include "sway/focus.h"
11#include "sway/border.h"
12#include "sway/layout.h"
13#include "sway/input_state.h"
14#include "sway/ipc-server.h"
15#include "sway/output.h"
16#include "log.h"
17#include "stringop.h"
18
19#define ASSERT_NONNULL(PTR) \
20 sway_assert (PTR, #PTR "must be non-null")
21
22static swayc_t *new_swayc(enum swayc_types type) {
23 // next id starts at 1 because 0 is assigned to root_container in layout.c
24 static size_t next_id = 1;
25 swayc_t *c = calloc(1, sizeof(swayc_t));
26 if (!c) {
27 return NULL;
28 }
29 c->id = next_id++;
30 c->handle = -1;
31 c->gaps = -1;
32 c->layout = L_NONE;
33 c->workspace_layout = L_NONE;
34 c->type = type;
35 c->nb_master = 1;
36 c->nb_slave_groups = 1;
37 if (type != C_VIEW) {
38 c->children = create_list();
39 }
40 return c;
41}
42
43static void free_swayc(swayc_t *cont) {
44 if (!ASSERT_NONNULL(cont)) {
45 return;
46 }
47 if (cont->children) {
48 // remove children until there are no more, free_swayc calls
49 // remove_child, which removes child from this container
50 while (cont->children->length) {
51 free_swayc(cont->children->items[0]);
52 }
53 list_free(cont->children);
54 }
55 if (cont->unmanaged) {
56 list_free(cont->unmanaged);
57 }
58 if (cont->floating) {
59 while (cont->floating->length) {
60 free_swayc(cont->floating->items[0]);
61 }
62 list_free(cont->floating);
63 }
64 if (cont->marks) {
65 list_foreach(cont->marks, free);
66 list_free(cont->marks);
67 }
68 if (cont->parent) {
69 remove_child(cont);
70 }
71 if (cont->name) {
72 free(cont->name);
73 }
74 if (cont->class) {
75 free(cont->class);
76 }
77 if (cont->instance) {
78 free(cont->instance);
79 }
80 if (cont->app_id) {
81 free(cont->app_id);
82 }
83 if (cont->bg_pid != 0) {
84 terminate_swaybg(cont->bg_pid);
85 }
86 if (cont->border) {
87 if (cont->border->buffer) {
88 free(cont->border->buffer);
89 }
90 free(cont->border);
91 }
92 free(cont);
93}
94
95static void update_root_geometry() {
96 int width = 0;
97 int height = 0;
98 swayc_t *child;
99 int child_width;
100 int child_height;
101
102 for (int i = 0; i < root_container.children->length; ++i) {
103 child = root_container.children->items[i];
104 child_width = child->width + child->x;
105 child_height = child->height + child->y;
106 if (child_width > width) {
107 width = child_width;
108 }
109
110 if (child_height > height) {
111 height = child_height;
112 }
113 }
114
115 root_container.width = width;
116 root_container.height = height;
117}
118
119// New containers
120
121swayc_t *new_output(wlc_handle handle) {
122 struct wlc_size size;
123 output_get_scaled_size(handle, &size);
124 const char *name = wlc_output_get_name(handle);
125 // Find current outputs to see if this already exists
126 {
127 int i, len = root_container.children->length;
128 for (i = 0; i < len; ++i) {
129 swayc_t *op = root_container.children->items[i];
130 const char *op_name = op->name;
131 if (op_name && name && strcmp(op_name, name) == 0) {
132 sway_log(L_DEBUG, "restoring output %" PRIuPTR ":%s", handle, op_name);
133 return op;
134 }
135 }
136 }
137
138 sway_log(L_DEBUG, "New output %" PRIuPTR ":%s", handle, name);
139
140 struct output_config *oc = NULL, *all = NULL;
141 int i;
142 for (i = 0; i < config->output_configs->length; ++i) {
143 struct output_config *cur = config->output_configs->items[i];
144 if (strcasecmp(name, cur->name) == 0) {
145 sway_log(L_DEBUG, "Matched output config for %s", name);
146 oc = cur;
147 }
148 if (strcasecmp("*", cur->name) == 0) {
149 sway_log(L_DEBUG, "Matched wildcard output config for %s", name);
150 all = cur;
151 }
152
153 if (oc && all) {
154 break;
155 }
156 }
157
158 if (!oc) {
159 oc = all;
160 }
161
162 if (oc && !oc->enabled) {
163 return NULL;
164 }
165
166 swayc_t *output = new_swayc(C_OUTPUT);
167 output->handle = handle;
168 output->name = name ? strdup(name) : NULL;
169 output->width = size.w;
170 output->height = size.h;
171 output->unmanaged = create_list();
172 output->bg_pid = 0;
173
174 apply_output_config(oc, output);
175 add_child(&root_container, output);
176 load_swaybars();
177
178 // Create workspace
179 char *ws_name = NULL;
180 swayc_t *ws = NULL;
181
182 if (name) {
183 for (i = 0; i < config->workspace_outputs->length; ++i) {
184 struct workspace_output *wso = config->workspace_outputs->items[i];
185 if (strcasecmp(wso->output, name) == 0) {
186 sway_log(L_DEBUG, "Matched workspace to output: %s for %s", wso->workspace, wso->output);
187 // Check if any other workspaces are using this name
188 if ((ws = workspace_by_name(wso->workspace))) {
189 // if yes, move those to this output, because they should be here
190 move_workspace_to(ws, output);
191 } else if (!ws_name) {
192 // set a workspace name in case we need to create a default one
193 ws_name = strdup(wso->workspace);
194 }
195 }
196 }
197 }
198
199 if (output->children->length == 0) {
200 if (!ws_name) {
201 ws_name = workspace_next_name(output->name);
202 }
203 // create and initialize default workspace
204 sway_log(L_DEBUG, "Creating default workspace %s", ws_name);
205 ws = new_workspace(output, ws_name);
206 ws->is_focused = true;
207 } else {
208 sort_workspaces(output);
209 set_focused_container(output->children->items[0]);
210 }
211
212 free(ws_name);
213 update_root_geometry();
214 return output;
215}
216
217swayc_t *new_workspace(swayc_t *output, const char *name) {
218 if (!ASSERT_NONNULL(output)) {
219 return NULL;
220 }
221 sway_log(L_DEBUG, "Added workspace %s for output %u", name, (unsigned int)output->handle);
222 swayc_t *workspace = new_swayc(C_WORKSPACE);
223
224 workspace->prev_layout = L_NONE;
225 workspace->layout = default_layout(output);
226 workspace->workspace_layout = default_layout(output);
227
228 workspace->x = output->x;
229 workspace->y = output->y;
230 workspace->width = output->width;
231 workspace->height = output->height;
232 workspace->name = !name ? NULL : strdup(name);
233 workspace->visible = false;
234 workspace->floating = create_list();
235
236 add_child(output, workspace);
237 sort_workspaces(output);
238
239 return workspace;
240}
241
242swayc_t *new_container(swayc_t *child, enum swayc_layouts layout) {
243 if (!ASSERT_NONNULL(child)
244 && !sway_assert(!child->is_floating, "cannot create container around floating window")) {
245 return NULL;
246 }
247 swayc_t *cont = new_swayc(C_CONTAINER);
248
249 sway_log(L_DEBUG, "creating container %p around %p", cont, child);
250
251 cont->prev_layout = L_NONE;
252 cont->layout = layout;
253 cont->width = child->width;
254 cont->height = child->height;
255 cont->x = child->x;
256 cont->y = child->y;
257 cont->visible = child->visible;
258 cont->cached_geometry = child->cached_geometry;
259 cont->gaps = child->gaps;
260
261 /* Container inherits all of workspaces children, layout and whatnot */
262 if (child->type == C_WORKSPACE) {
263 swayc_t *workspace = child;
264 // reorder focus
265 cont->focused = workspace->focused;
266 workspace->focused = cont;
267 // set all children focu to container
268 int i;
269 for (i = 0; i < workspace->children->length; ++i) {
270 ((swayc_t *)workspace->children->items[i])->parent = cont;
271 }
272 // Swap children
273 list_t *tmp_list = workspace->children;
274 workspace->children = cont->children;
275 cont->children = tmp_list;
276 // add container to workspace chidren
277 add_child(workspace, cont);
278 // give them proper layouts
279 cont->layout = workspace->workspace_layout;
280 cont->prev_layout = workspace->prev_layout;
281 /* TODO: might break shit in move_container!!! workspace->layout = layout; */
282 set_focused_container_for(workspace, get_focused_view(workspace));
283 } else { // Or is built around container
284 swayc_t *parent = replace_child(child, cont);
285 if (parent) {
286 add_child(cont, child);
287 }
288 }
289 return cont;
290}
291
292swayc_t *new_view(swayc_t *sibling, wlc_handle handle) {
293 if (!ASSERT_NONNULL(sibling)) {
294 return NULL;
295 }
296 const char *title = wlc_view_get_title(handle);
297 swayc_t *view = new_swayc(C_VIEW);
298 sway_log(L_DEBUG, "Adding new view %" PRIuPTR ":%s to container %p %d",
299 handle, title, sibling, sibling ? sibling->type : 0);
300 // Setup values
301 view->handle = handle;
302 view->name = title ? strdup(title) : NULL;
303 const char *class = wlc_view_get_class(handle);
304 view->class = class ? strdup(class) : NULL;
305 const char *instance = wlc_view_get_instance(handle);
306 view->instance = instance ? strdup(instance) : NULL;
307 const char *app_id = wlc_view_get_app_id(handle);
308 view->app_id = app_id ? strdup(app_id) : NULL;
309 view->visible = true;
310 view->is_focused = true;
311 view->sticky = false;
312 view->width = 0;
313 view->height = 0;
314 view->desired_width = -1;
315 view->desired_height = -1;
316 // setup border
317 view->border_type = config->border;
318 view->border_thickness = config->border_thickness;
319
320 view->is_floating = false;
321
322 if (sibling->type == C_WORKSPACE) {
323 // Case of focused workspace, just create as child of it
324 add_child(sibling, view);
325 } else {
326 // Regular case, create as sibling of current container
327 add_sibling(sibling, view);
328 }
329 return view;
330}
331
332swayc_t *new_floating_view(wlc_handle handle) {
333 if (swayc_active_workspace() == NULL) {
334 return NULL;
335 }
336 const char *title = wlc_view_get_title(handle);
337 swayc_t *view = new_swayc(C_VIEW);
338 sway_log(L_DEBUG, "Adding new view %" PRIuPTR ":%x:%s as a floating view",
339 handle, wlc_view_get_type(handle), title);
340 // Setup values
341 view->handle = handle;
342 view->name = title ? strdup(title) : NULL;
343 const char *class = wlc_view_get_class(handle);
344 view->class = class ? strdup(class) : NULL;
345 const char *instance = wlc_view_get_instance(handle);
346 view->instance = instance ? strdup(instance) : NULL;
347 const char *app_id = wlc_view_get_app_id(handle);
348 view->app_id = app_id ? strdup(app_id) : NULL;
349 view->visible = true;
350 view->sticky = false;
351
352 // Set the geometry of the floating view
353 const struct wlc_geometry *geometry = wlc_view_get_geometry(handle);
354
355 // give it requested geometry, but place in center if possible
356 // in top left otherwise
357 if (geometry->size.w != 0) {
358 view->x = (swayc_active_workspace()->width - geometry->size.w) / 2;
359 } else {
360 view->x = 0;
361 }
362 if (geometry->size.h != 0) {
363 view->y = (swayc_active_workspace()->height - geometry->size.h) / 2;
364 } else {
365 view->y = 0;
366 }
367
368 view->width = geometry->size.w;
369 view->height = geometry->size.h;
370
371 view->desired_width = view->width;
372 view->desired_height = view->height;
373
374 // setup border
375 view->border_type = config->floating_border;
376 view->border_thickness = config->floating_border_thickness;
377
378 view->is_floating = true;
379
380 // Case of focused workspace, just create as child of it
381 list_add(swayc_active_workspace()->floating, view);
382 view->parent = swayc_active_workspace();
383 if (swayc_active_workspace()->focused == NULL) {
384 set_focused_container_for(swayc_active_workspace(), view);
385 }
386 return view;
387}
388
389void floating_view_sane_size(swayc_t *view) {
390 // floating_minimum is used as sane value.
391 // floating_maximum has priority in case of conflict
392 // TODO: implement total_outputs_dimensions()
393 if (config->floating_minimum_height != -1 &&
394 view->desired_height < config->floating_minimum_height) {
395 view->desired_height = config->floating_minimum_height;
396 }
397 if (config->floating_minimum_width != -1 &&
398 view->desired_width < config->floating_minimum_width) {
399 view->desired_width = config->floating_minimum_width;
400 }
401
402 // if 0 do not resize, only enforce max value
403 if (config->floating_maximum_height == 0) {
404 // Missing total_outputs_dimensions() using swayc_active_workspace()
405 config->floating_maximum_height = swayc_active_workspace()->height;
406
407 } else if (config->floating_maximum_height != -1 &&
408 view->desired_height > config->floating_maximum_height) {
409 view->desired_height = config->floating_maximum_height;
410 }
411
412 // if 0 do not resize, only enforce max value
413 if (config->floating_maximum_width == 0) {
414 // Missing total_outputs_dimensions() using swayc_active_workspace()
415 config->floating_maximum_width = swayc_active_workspace()->width;
416
417 } else if (config->floating_maximum_width != -1 &&
418 view->desired_width > config->floating_maximum_width) {
419 view->desired_width = config->floating_maximum_width;
420 }
421
422 sway_log(L_DEBUG, "Sane values for view to %d x %d @ %.f, %.f",
423 view->desired_width, view->desired_height, view->x, view->y);
424
425 return;
426}
427
428
429// Destroy container
430
431swayc_t *destroy_output(swayc_t *output) {
432 if (!ASSERT_NONNULL(output)) {
433 return NULL;
434 }
435 if (output->children->length > 0) {
436 // TODO save workspaces when there are no outputs.
437 // TODO also check if there will ever be no outputs except for exiting
438 // program
439 if (root_container.children->length > 1) {
440 int p = root_container.children->items[0] == output;
441 // Move workspace from this output to another output
442 while (output->children->length) {
443 swayc_t *child = output->children->items[0];
444 remove_child(child);
445 add_child(root_container.children->items[p], child);
446 }
447 sort_workspaces(root_container.children->items[p]);
448 update_visibility(root_container.children->items[p]);
449 arrange_windows(root_container.children->items[p], -1, -1);
450 }
451 }
452 sway_log(L_DEBUG, "OUTPUT: Destroying output '%" PRIuPTR "'", output->handle);
453 free_swayc(output);
454 update_root_geometry();
455 return &root_container;
456}
457
458swayc_t *destroy_workspace(swayc_t *workspace) {
459 if (!ASSERT_NONNULL(workspace)) {
460 return NULL;
461 }
462
463 // Do not destroy this if it's the last workspace on this output
464 swayc_t *output = swayc_parent_by_type(workspace, C_OUTPUT);
465 if (output && output->children->length == 1) {
466 return NULL;
467 }
468
469 swayc_t *parent = workspace->parent;
470 // destroy the WS if there are no children
471 if (workspace->children->length == 0 && workspace->floating->length == 0) {
472 sway_log(L_DEBUG, "destroying workspace '%s'", workspace->name);
473 ipc_event_workspace(workspace, NULL, "empty");
474 } else {
475 // Move children to a different workspace on this output
476 swayc_t *new_workspace = NULL;
477 int i;
478 for(i = 0; i < output->children->length; i++) {
479 if(output->children->items[i] != workspace) {
480 break;
481 }
482 }
483 new_workspace = output->children->items[i];
484
485 sway_log(L_DEBUG, "moving children to different workspace '%s' -> '%s'",
486 workspace->name, new_workspace->name);
487
488 for(i = 0; i < workspace->children->length; i++) {
489 move_container_to(workspace->children->items[i], new_workspace);
490 }
491
492 for(i = 0; i < workspace->floating->length; i++) {
493 move_container_to(workspace->floating->items[i], new_workspace);
494 }
495 }
496
497 free_swayc(workspace);
498 return parent;
499}
500
501swayc_t *destroy_container(swayc_t *container) {
502 if (!ASSERT_NONNULL(container)) {
503 return NULL;
504 }
505 while (container->children->length == 0 && container->type == C_CONTAINER) {
506 sway_log(L_DEBUG, "Container: Destroying container '%p'", container);
507 swayc_t *parent = container->parent;
508 free_swayc(container);
509 container = parent;
510 }
511 return container;
512}
513
514swayc_t *destroy_view(swayc_t *view) {
515 if (!ASSERT_NONNULL(view)) {
516 return NULL;
517 }
518 sway_log(L_DEBUG, "Destroying view '%p'", view);
519 swayc_t *parent = view->parent;
520 free_swayc(view);
521
522 // Destroy empty containers
523 if (parent && parent->type == C_CONTAINER) {
524 return destroy_container(parent);
525 }
526 return parent;
527}
528
529// Container lookup
530
531
532swayc_t *swayc_by_test(swayc_t *container, bool (*test)(swayc_t *view, void *data), void *data) {
533 if (!container->children) {
534 return NULL;
535 }
536 // Special case for checking floating stuff
537 int i;
538 if (container->type == C_WORKSPACE) {
539 for (i = 0; i < container->floating->length; ++i) {
540 swayc_t *child = container->floating->items[i];
541 if (test(child, data)) {
542 return child;
543 }
544 }
545 }
546 for (i = 0; i < container->children->length; ++i) {
547 swayc_t *child = container->children->items[i];
548 if (test(child, data)) {
549 return child;
550 } else {
551 swayc_t *res = swayc_by_test(child, test, data);
552 if (res) {
553 return res;
554 }
555 }
556 }
557 return NULL;
558}
559
560static bool test_name(swayc_t *view, void *data) {
561 if (!view || !view->name) {
562 return false;
563 }
564 return strcmp(view->name, data) == 0;
565}
566
567swayc_t *swayc_by_name(const char *name) {
568 return swayc_by_test(&root_container, test_name, (void *)name);
569}
570
571swayc_t *swayc_parent_by_type(swayc_t *container, enum swayc_types type) {
572 if (!ASSERT_NONNULL(container)) {
573 return NULL;
574 }
575 if (!sway_assert(type < C_TYPES && type >= C_ROOT, "invalid type")) {
576 return NULL;
577 }
578 do {
579 container = container->parent;
580 } while (container && container->type != type);
581 return container;
582}
583
584swayc_t *swayc_parent_by_layout(swayc_t *container, enum swayc_layouts layout) {
585 if (!ASSERT_NONNULL(container)) {
586 return NULL;
587 }
588 if (!sway_assert(layout < L_LAYOUTS && layout >= L_NONE, "invalid layout")) {
589 return NULL;
590 }
591 do {
592 container = container->parent;
593 } while (container && container->layout != layout);
594 return container;
595}
596
597swayc_t *swayc_focus_by_type(swayc_t *container, enum swayc_types type) {
598 if (!ASSERT_NONNULL(container)) {
599 return NULL;
600 }
601 if (!sway_assert(type < C_TYPES && type >= C_ROOT, "invalid type")) {
602 return NULL;
603 }
604 do {
605 container = container->focused;
606 } while (container && container->type != type);
607 return container;
608}
609
610swayc_t *swayc_focus_by_layout(swayc_t *container, enum swayc_layouts layout) {
611 if (!ASSERT_NONNULL(container)) {
612 return NULL;
613 }
614 if (!sway_assert(layout < L_LAYOUTS && layout >= L_NONE, "invalid layout")) {
615 return NULL;
616 }
617 do {
618 container = container->focused;
619 } while (container && container->layout != layout);
620 return container;
621}
622
623
624static swayc_t *_swayc_by_handle_helper(wlc_handle handle, swayc_t *parent) {
625 if (!parent || !parent->children) {
626 return NULL;
627 }
628 int i, len;
629 swayc_t **child;
630 if (parent->type == C_WORKSPACE) {
631 len = parent->floating->length;
632 child = (swayc_t **)parent->floating->items;
633 for (i = 0; i < len; ++i, ++child) {
634 if ((*child)->handle == handle) {
635 return *child;
636 }
637 }
638 }
639
640 len = parent->children->length;
641 child = (swayc_t**)parent->children->items;
642 for (i = 0; i < len; ++i, ++child) {
643 if ((*child)->handle == handle) {
644 return *child;
645 } else {
646 swayc_t *res;
647 if ((res = _swayc_by_handle_helper(handle, *child))) {
648 return res;
649 }
650 }
651 }
652 return NULL;
653}
654
655swayc_t *swayc_by_handle(wlc_handle handle) {
656 return _swayc_by_handle_helper(handle, &root_container);
657}
658
659swayc_t *swayc_active_output(void) {
660 return root_container.focused;
661}
662
663swayc_t *swayc_active_workspace(void) {
664 return root_container.focused ? root_container.focused->focused : NULL;
665}
666
667swayc_t *swayc_active_workspace_for(swayc_t *cont) {
668 if (!cont) {
669 return NULL;
670 }
671 switch (cont->type) {
672 case C_ROOT:
673 cont = cont->focused;
674 /* Fallthrough */
675
676 case C_OUTPUT:
677 cont = cont ? cont->focused : NULL;
678 /* Fallthrough */
679
680 case C_WORKSPACE:
681 return cont;
682
683 default:
684 return swayc_parent_by_type(cont, C_WORKSPACE);
685 }
686}
687
688static bool pointer_test(swayc_t *view, void *_origin) {
689 const struct wlc_point *origin = _origin;
690 // Determine the output that the view is under
691 swayc_t *parent = swayc_parent_by_type(view, C_OUTPUT);
692 if (origin->x >= view->x && origin->y >= view->y
693 && origin->x < view->x + view->width && origin->y < view->y + view->height
694 && view->visible && parent == root_container.focused) {
695 return true;
696 }
697 return false;
698}
699
700swayc_t *container_under_pointer(void) {
701 // root.output->workspace
702 if (!root_container.focused) {
703 return NULL;
704 }
705 swayc_t *lookup = root_container.focused;
706 // Case of empty workspace
707 if (lookup->children && !lookup->unmanaged) {
708 return NULL;
709 }
710 double x, y;
711 wlc_pointer_get_position_v2(&x, &y);
712 struct wlc_point origin = { .x = x, .y = y };
713
714 while (lookup && lookup->type != C_VIEW) {
715 int i;
716 int len;
717 for (int _i = 0; lookup->unmanaged && _i < lookup->unmanaged->length; ++_i) {
718 wlc_handle *handle = lookup->unmanaged->items[_i];
719 const struct wlc_geometry *geo = wlc_view_get_geometry(*handle);
720 if (origin.x >= geo->origin.x && origin.y >= geo->origin.y
721 && origin.x < geo->origin.x + (int)geo->size.w
722 && origin.y < geo->origin.y + (int)geo->size.h) {
723 // Hack: we force focus upon unmanaged views here
724 wlc_view_focus(*handle);
725 return NULL;
726 }
727 }
728 // if tabbed/stacked go directly to focused container, otherwise search
729 // children
730 if (lookup->layout == L_TABBED || lookup->layout == L_STACKED) {
731 lookup = lookup->focused;
732 continue;
733 }
734 // if workspace, search floating
735 if (lookup->type == C_WORKSPACE) {
736 i = len = lookup->floating->length;
737 bool got_floating = false;
738 while (--i > -1) {
739 if (pointer_test(lookup->floating->items[i], &origin)) {
740 lookup = lookup->floating->items[i];
741 got_floating = true;
742 break;
743 }
744 }
745 if (got_floating) {
746 continue;
747 }
748 }
749 // search children
750 len = lookup->children->length;
751 for (i = 0; i < len; ++i) {
752 if (pointer_test(lookup->children->items[i], &origin)) {
753 lookup = lookup->children->items[i];
754 break;
755 }
756 }
757 // when border and titles are done, this could happen
758 if (i == len) {
759 break;
760 }
761 }
762 return lookup;
763}
764
765swayc_t *container_find(swayc_t *container, bool (*f)(swayc_t *, const void *), const void *data) {
766 if (container->children == NULL || container->children->length == 0) {
767 return NULL;
768 }
769
770 swayc_t *con;
771 if (container->type == C_WORKSPACE) {
772 for (int i = 0; i < container->floating->length; ++i) {
773 con = container->floating->items[i];
774 if (f(con, data)) {
775 return con;
776 }
777 con = container_find(con, f, data);
778 if (con != NULL) {
779 return con;
780 }
781 }
782 }
783
784 for (int i = 0; i < container->children->length; ++i) {
785 con = container->children->items[i];
786 if (f(con, data)) {
787 return con;
788 }
789
790 con = container_find(con, f, data);
791 if (con != NULL) {
792 return con;
793 }
794 }
795
796 return NULL;
797}
798
799// Container information
800
801bool swayc_is_fullscreen(swayc_t *view) {
802 return view && view->type == C_VIEW && (wlc_view_get_state(view->handle) & WLC_BIT_FULLSCREEN);
803}
804
805bool swayc_is_active(swayc_t *view) {
806 return view && view->type == C_VIEW && (wlc_view_get_state(view->handle) & WLC_BIT_ACTIVATED);
807}
808
809bool swayc_is_parent_of(swayc_t *parent, swayc_t *child) {
810 while (child != &root_container) {
811 child = child->parent;
812 if (child == parent) {
813 return true;
814 }
815 }
816 return false;
817}
818
819bool swayc_is_child_of(swayc_t *child, swayc_t *parent) {
820 return swayc_is_parent_of(parent, child);
821}
822
823bool swayc_is_empty_workspace(swayc_t *container) {
824 return container->type == C_WORKSPACE && container->children->length == 0;
825}
826
827int swayc_gap(swayc_t *container) {
828 if (container->type == C_VIEW || container->type == C_CONTAINER) {
829 return container->gaps >= 0 ? container->gaps : config->gaps_inner;
830 } else if (container->type == C_WORKSPACE) {
831 int base = container->gaps >= 0 ? container->gaps : config->gaps_outer;
832 if (config->edge_gaps && !(config->smart_gaps && container->children->length == 1)) {
833 // the inner gap is created via a margin around each window which
834 // is half the gap size, so the workspace also needs half a gap
835 // size to make the outermost gap the same size (excluding the
836 // actual "outer gap" size which is handled independently)
837 return base + config->gaps_inner / 2;
838 } else if (config->smart_gaps && container->children->length == 1) {
839 return 0;
840 } else {
841 return base;
842 }
843 } else {
844 return 0;
845 }
846}
847
848// Mapping
849
850void container_map(swayc_t *container, void (*f)(swayc_t *view, void *data), void *data) {
851 if (container) {
852 int i;
853 if (container->children) {
854 for (i = 0; i < container->children->length; ++i) {
855 swayc_t *child = container->children->items[i];
856 container_map(child, f, data);
857 }
858 }
859 if (container->floating) {
860 for (i = 0; i < container->floating->length; ++i) {
861 swayc_t *child = container->floating->items[i];
862 container_map(child, f, data);
863 }
864 }
865 f(container, data);
866 }
867}
868
869void update_visibility_output(swayc_t *container, wlc_handle output) {
870 // Inherit visibility
871 swayc_t *parent = container->parent;
872 container->visible = parent->visible;
873 // special cases where visibility depends on focus
874 if (parent->type == C_OUTPUT || parent->layout == L_TABBED ||
875 parent->layout == L_STACKED) {
876 container->visible = parent->focused == container && parent->visible;
877 }
878 // Set visibility and output for view
879 if (container->type == C_VIEW) {
880 wlc_view_set_output(container->handle, output);
881 wlc_view_set_mask(container->handle, container->visible ? VISIBLE : 0);
882 }
883 // Update visibility for children
884 else {
885 if (container->children) {
886 int i, len = container->children->length;
887 for (i = 0; i < len; ++i) {
888 update_visibility_output(container->children->items[i], output);
889 }
890 }
891 if (container->floating) {
892 int i, len = container->floating->length;
893 for (i = 0; i < len; ++i) {
894 update_visibility_output(container->floating->items[i], output);
895 }
896 }
897 }
898}
899
900void update_visibility(swayc_t *container) {
901 if (!container) return;
902 switch (container->type) {
903 case C_ROOT:
904 container->visible = true;
905 if (container->children) {
906 int i, len = container->children->length;
907 for (i = 0; i < len; ++i) {
908 update_visibility(container->children->items[i]);
909 }
910 }
911 return;
912
913 case C_OUTPUT:
914 container->visible = true;
915 if (container->children) {
916 int i, len = container->children->length;
917 for (i = 0; i < len; ++i) {
918 update_visibility_output(container->children->items[i], container->handle);
919 }
920 }
921 return;
922
923 default:
924 {
925 swayc_t *op = swayc_parent_by_type(container, C_OUTPUT);
926 update_visibility_output(container, op->handle);
927 }
928 }
929}
930
931void set_gaps(swayc_t *view, void *_data) {
932 int *data = _data;
933 if (!ASSERT_NONNULL(view)) {
934 return;
935 }
936 if (view->type == C_WORKSPACE || view->type == C_VIEW) {
937 view->gaps = *data;
938 }
939}
940
941void add_gaps(swayc_t *view, void *_data) {
942 int *data = _data;
943 if (!ASSERT_NONNULL(view)) {
944 return;
945 }
946 if (view->type == C_WORKSPACE || view->type == C_VIEW) {
947 if ((view->gaps += *data) < 0) {
948 view->gaps = 0;
949 }
950 }
951}
952
953static void close_view(swayc_t *container, void *data) {
954 if (container->type == C_VIEW) {
955 wlc_view_close(container->handle);
956 }
957}
958
959void close_views(swayc_t *container) {
960 container_map(container, close_view, NULL);
961}
962
963swayc_t *swayc_tabbed_stacked_ancestor(swayc_t *view) {
964 swayc_t *parent = NULL;
965 if (!ASSERT_NONNULL(view)) {
966 return NULL;
967 }
968 while (view->type != C_WORKSPACE && view->parent && view->parent->type != C_WORKSPACE) {
969 view = view->parent;
970 if (view->layout == L_TABBED || view->layout == L_STACKED) {
971 parent = view;
972 }
973 }
974
975 return parent;
976}
977
978swayc_t *swayc_tabbed_stacked_parent(swayc_t *con) {
979 if (!ASSERT_NONNULL(con)) {
980 return NULL;
981 }
982 if (con->parent && (con->parent->layout == L_TABBED || con->parent->layout == L_STACKED)) {
983 return con->parent;
984 }
985 return NULL;
986}
987
988swayc_t *swayc_change_layout(swayc_t *container, enum swayc_layouts layout) {
989 // if layout change modifies the auto layout's major axis, swap width and height
990 // to preserve current ratios.
991 if (is_auto_layout(layout) && is_auto_layout(container->layout)) {
992 enum swayc_layouts prev_major =
993 container->layout == L_AUTO_LEFT || container->layout == L_AUTO_RIGHT
994 ? L_HORIZ : L_VERT;
995 enum swayc_layouts new_major =
996 layout == L_AUTO_LEFT || layout == L_AUTO_RIGHT
997 ? L_HORIZ : L_VERT;
998 if (new_major != prev_major) {
999 for (int i = 0; i < container->children->length; ++i) {
1000 swayc_t *child = container->children->items[i];
1001 double h = child->height;
1002 child->height = child->width;
1003 child->width = h;
1004 }
1005 }
1006 }
1007 if (container->type == C_WORKSPACE) {
1008 container->workspace_layout = layout;
1009 if (layout == L_HORIZ || layout == L_VERT || is_auto_layout(layout)) {
1010 container->layout = layout;
1011 }
1012 } else {
1013 container->layout = layout;
1014 }
1015 return container;
1016}
diff --git a/sway/tree/criteria.c b/sway/tree/criteria.c
new file mode 100644
index 00000000..e8978ebe
--- /dev/null
+++ b/sway/tree/criteria.c
@@ -0,0 +1,451 @@
1#define _XOPEN_SOURCE 700
2#include <stdlib.h>
3#include <stdio.h>
4#include <stdbool.h>
5#include <pcre.h>
6#include "sway/criteria.h"
7#include "sway/container.h"
8#include "sway/config.h"
9#include "stringop.h"
10#include "list.h"
11#include "log.h"
12
13enum criteria_type { // *must* keep in sync with criteria_strings[]
14 CRIT_CLASS,
15 CRIT_CON_ID,
16 CRIT_CON_MARK,
17 CRIT_FLOATING,
18 CRIT_ID,
19 CRIT_INSTANCE,
20 CRIT_TILING,
21 CRIT_TITLE,
22 CRIT_URGENT,
23 CRIT_WINDOW_ROLE,
24 CRIT_WINDOW_TYPE,
25 CRIT_WORKSPACE,
26 CRIT_LAST
27};
28
29static const char * const criteria_strings[CRIT_LAST] = {
30 [CRIT_CLASS] = "class",
31 [CRIT_CON_ID] = "con_id",
32 [CRIT_CON_MARK] = "con_mark",
33 [CRIT_FLOATING] = "floating",
34 [CRIT_ID] = "id",
35 [CRIT_INSTANCE] = "instance",
36 [CRIT_TILING] = "tiling",
37 [CRIT_TITLE] = "title",
38 [CRIT_URGENT] = "urgent", // either "latest" or "oldest" ...
39 [CRIT_WINDOW_ROLE] = "window_role",
40 [CRIT_WINDOW_TYPE] = "window_type",
41 [CRIT_WORKSPACE] = "workspace"
42};
43
44/**
45 * A single criteria token (ie. value/regex pair),
46 * e.g. 'class="some class regex"'.
47 */
48struct crit_token {
49 enum criteria_type type;
50 pcre *regex;
51 char *raw;
52};
53
54static void free_crit_token(struct crit_token *crit) {
55 pcre_free(crit->regex);
56 free(crit->raw);
57 free(crit);
58}
59
60static void free_crit_tokens(list_t *crit_tokens) {
61 for (int i = 0; i < crit_tokens->length; i++) {
62 free_crit_token(crit_tokens->items[i]);
63 }
64 list_free(crit_tokens);
65}
66
67// Extracts criteria string from its brackets. Returns new (duplicate)
68// substring.
69static char *criteria_from(const char *arg) {
70 char *criteria = NULL;
71 if (*arg == '[') {
72 criteria = strdup(arg + 1);
73 } else {
74 criteria = strdup(arg);
75 }
76
77 int last = strlen(criteria) - 1;
78 if (criteria[last] == ']') {
79 criteria[last] = '\0';
80 }
81 return criteria;
82}
83
84// Return instances of c found in str.
85static int countchr(char *str, char c) {
86 int found = 0;
87 for (int i = 0; str[i]; i++) {
88 if (str[i] == c) {
89 ++found;
90 }
91 }
92 return found;
93}
94
95// criteria_str is e.g. '[class="some class regex" instance="instance name"]'.
96//
97// Will create array of pointers in buf, where first is duplicate of given
98// string (must be freed) and the rest are pointers to names and values in the
99// base string (every other, naturally). argc will be populated with the length
100// of buf.
101//
102// Returns error string or NULL if successful.
103static char *crit_tokens(int *argc, char ***buf, const char * const criteria_str) {
104 sway_log(L_DEBUG, "Parsing criteria: '%s'", criteria_str);
105 char *base = criteria_from(criteria_str);
106 char *head = base;
107 char *namep = head; // start of criteria name
108 char *valp = NULL; // start of value
109
110 // We're going to place EOS markers where we need to and fill up an array
111 // of pointers to the start of each token (either name or value).
112 int pairs = countchr(base, '=');
113 int max_tokens = pairs * 2 + 1; // this gives us at least enough slots
114
115 char **argv = *buf = calloc(max_tokens, sizeof(char*));
116 argv[0] = base; // this needs to be freed by caller
117 bool quoted = true;
118
119 *argc = 1; // uneven = name, even = value
120 while (*head && *argc < max_tokens) {
121 if (namep != head && *(head - 1) == '\\') {
122 // escaped character: don't try to parse this
123 } else if (*head == '=' && namep != head) {
124 if (*argc % 2 != 1) {
125 // we're not expecting a name
126 return strdup("Unable to parse criteria: "
127 "Found out of place equal sign");
128 } else {
129 // name ends here
130 char *end = head; // don't want to rewind the head
131 while (*(end - 1) == ' ') {
132 --end;
133 }
134 *end = '\0';
135 if (*(namep) == ' ') {
136 namep = strrchr(namep, ' ') + 1;
137 }
138 argv[*argc] = namep;
139 *argc += 1;
140 }
141 } else if (*head == '"') {
142 if (*argc % 2 != 0) {
143 // we're not expecting a value
144 return strdup("Unable to parse criteria: "
145 "Found quoted value where it was not expected");
146 } else if (!valp) { // value starts here
147 valp = head + 1;
148 quoted = true;
149 } else {
150 // value ends here
151 argv[*argc] = valp;
152 *argc += 1;
153 *head = '\0';
154 valp = NULL;
155 namep = head + 1;
156 }
157 } else if (*argc % 2 == 0 && *head != ' ') {
158 // parse unquoted values
159 if (!valp) {
160 quoted = false;
161 valp = head; // value starts here
162 }
163 } else if (valp && !quoted && *head == ' ') {
164 // value ends here
165 argv[*argc] = valp;
166 *argc += 1;
167 *head = '\0';
168 valp = NULL;
169 namep = head + 1;
170 }
171 head++;
172 }
173
174 // catch last unquoted value if needed
175 if (valp && !quoted && !*head) {
176 argv[*argc] = valp;
177 *argc += 1;
178 }
179
180 return NULL;
181}
182
183// Returns error string on failure or NULL otherwise.
184static char *parse_criteria_name(enum criteria_type *type, char *name) {
185 *type = CRIT_LAST;
186 for (int i = 0; i < CRIT_LAST; i++) {
187 if (strcmp(criteria_strings[i], name) == 0) {
188 *type = (enum criteria_type) i;
189 break;
190 }
191 }
192 if (*type == CRIT_LAST) {
193 const char *fmt = "Criteria type '%s' is invalid or unsupported.";
194 int len = strlen(name) + strlen(fmt) - 1;
195 char *error = malloc(len);
196 snprintf(error, len, fmt, name);
197 return error;
198 } else if (*type == CRIT_URGENT || *type == CRIT_WINDOW_ROLE ||
199 *type == CRIT_WINDOW_TYPE) {
200 // (we're just being helpful here)
201 const char *fmt = "\"%s\" criteria currently unsupported, "
202 "no window will match this";
203 int len = strlen(fmt) + strlen(name) - 1;
204 char *error = malloc(len);
205 snprintf(error, len, fmt, name);
206 return error;
207 }
208 return NULL;
209}
210
211// Returns error string on failure or NULL otherwise.
212static char *generate_regex(pcre **regex, char *value) {
213 const char *reg_err;
214 int offset;
215
216 *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, &reg_err, &offset, NULL);
217
218 if (!*regex) {
219 const char *fmt = "Regex compilation (for '%s') failed: %s";
220 int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3;
221 char *error = malloc(len);
222 snprintf(error, len, fmt, value, reg_err);
223 return error;
224 }
225 return NULL;
226}
227
228// Test whether the criterion corresponds to the currently focused window
229static bool crit_is_focused(const char *value) {
230 return !strcmp(value, "focused") || !strcmp(value, "__focused__");
231}
232
233// Populate list with crit_tokens extracted from criteria string, returns error
234// string or NULL if successful.
235char *extract_crit_tokens(list_t *tokens, const char * const criteria) {
236 int argc;
237 char **argv = NULL, *error = NULL;
238 if ((error = crit_tokens(&argc, &argv, criteria))) {
239 goto ect_cleanup;
240 }
241 for (int i = 1; i + 1 < argc; i += 2) {
242 char* name = argv[i], *value = argv[i + 1];
243 struct crit_token *token = calloc(1, sizeof(struct crit_token));
244 token->raw = strdup(value);
245
246 if ((error = parse_criteria_name(&token->type, name))) {
247 free_crit_token(token);
248 goto ect_cleanup;
249 } else if (token->type == CRIT_URGENT || crit_is_focused(value)) {
250 sway_log(L_DEBUG, "%s -> \"%s\"", name, value);
251 list_add(tokens, token);
252 } else if((error = generate_regex(&token->regex, value))) {
253 free_crit_token(token);
254 goto ect_cleanup;
255 } else {
256 sway_log(L_DEBUG, "%s -> /%s/", name, value);
257 list_add(tokens, token);
258 }
259 }
260ect_cleanup:
261 free(argv[0]); // base string
262 free(argv);
263 return error;
264}
265
266static int regex_cmp(const char *item, const pcre *regex) {
267 return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0);
268}
269
270// test a single view if it matches list of criteria tokens (all of them).
271static bool criteria_test(swayc_t *cont, list_t *tokens) {
272 if (cont->type != C_VIEW) {
273 return false;
274 }
275 int matches = 0;
276 for (int i = 0; i < tokens->length; i++) {
277 struct crit_token *crit = tokens->items[i];
278 switch (crit->type) {
279 case CRIT_CLASS:
280 if (!cont->class) {
281 // ignore
282 } else if (crit_is_focused(crit->raw)) {
283 swayc_t *focused = get_focused_view(&root_container);
284 if (focused->class && strcmp(cont->class, focused->class) == 0) {
285 matches++;
286 }
287 } else if (crit->regex && regex_cmp(cont->class, crit->regex) == 0) {
288 matches++;
289 }
290 break;
291 case CRIT_CON_ID: {
292 char *endptr;
293 size_t crit_id = strtoul(crit->raw, &endptr, 10);
294
295 if (*endptr == 0 && cont->id == crit_id) {
296 ++matches;
297 }
298 break;
299 }
300 case CRIT_CON_MARK:
301 if (crit->regex && cont->marks && (list_seq_find(cont->marks, (int (*)(const void *, const void *))regex_cmp, crit->regex) != -1)) {
302 // Make sure it isn't matching the NUL string
303 if ((strcmp(crit->raw, "") == 0) == (list_seq_find(cont->marks, (int (*)(const void *, const void *))strcmp, "") != -1)) {
304 ++matches;
305 }
306 }
307 break;
308 case CRIT_FLOATING:
309 if (cont->is_floating) {
310 matches++;
311 }
312 break;
313 case CRIT_ID:
314 if (!cont->app_id) {
315 // ignore
316 } else if (crit->regex && regex_cmp(cont->app_id, crit->regex) == 0) {
317 matches++;
318 }
319 break;
320 case CRIT_INSTANCE:
321 if (!cont->instance) {
322 // ignore
323 } else if (crit_is_focused(crit->raw)) {
324 swayc_t *focused = get_focused_view(&root_container);
325 if (focused->instance && strcmp(cont->instance, focused->instance) == 0) {
326 matches++;
327 }
328 } else if (crit->regex && regex_cmp(cont->instance, crit->regex) == 0) {
329 matches++;
330 }
331 break;
332 case CRIT_TILING:
333 if (!cont->is_floating) {
334 matches++;
335 }
336 break;
337 case CRIT_TITLE:
338 if (!cont->name) {
339 // ignore
340 } else if (crit_is_focused(crit->raw)) {
341 swayc_t *focused = get_focused_view(&root_container);
342 if (focused->name && strcmp(cont->name, focused->name) == 0) {
343 matches++;
344 }
345 } else if (crit->regex && regex_cmp(cont->name, crit->regex) == 0) {
346 matches++;
347 }
348 break;
349 case CRIT_URGENT: // "latest" or "oldest"
350 break;
351 case CRIT_WINDOW_ROLE:
352 break;
353 case CRIT_WINDOW_TYPE:
354 // TODO wlc indeed exposes this information
355 break;
356 case CRIT_WORKSPACE: ;
357 swayc_t *cont_ws = swayc_parent_by_type(cont, C_WORKSPACE);
358 if (!cont_ws || !cont_ws->name) {
359 // ignore
360 } else if (crit_is_focused(crit->raw)) {
361 swayc_t *focused_ws = swayc_active_workspace();
362 if (focused_ws->name && strcmp(cont_ws->name, focused_ws->name) == 0) {
363 matches++;
364 }
365 } else if (crit->regex && regex_cmp(cont_ws->name, crit->regex) == 0) {
366 matches++;
367 }
368 break;
369 default:
370 sway_abort("Invalid criteria type (%i)", crit->type);
371 break;
372 }
373 }
374 return matches == tokens->length;
375}
376
377int criteria_cmp(const void *a, const void *b) {
378 if (a == b) {
379 return 0;
380 } else if (!a) {
381 return -1;
382 } else if (!b) {
383 return 1;
384 }
385 const struct criteria *crit_a = a, *crit_b = b;
386 int cmp = lenient_strcmp(crit_a->cmdlist, crit_b->cmdlist);
387 if (cmp != 0) {
388 return cmp;
389 }
390 return lenient_strcmp(crit_a->crit_raw, crit_b->crit_raw);
391}
392
393void free_criteria(struct criteria *crit) {
394 if (crit->tokens) {
395 free_crit_tokens(crit->tokens);
396 }
397 if (crit->cmdlist) {
398 free(crit->cmdlist);
399 }
400 if (crit->crit_raw) {
401 free(crit->crit_raw);
402 }
403 free(crit);
404}
405
406bool criteria_any(swayc_t *cont, list_t *criteria) {
407 for (int i = 0; i < criteria->length; i++) {
408 struct criteria *bc = criteria->items[i];
409 if (criteria_test(cont, bc->tokens)) {
410 return true;
411 }
412 }
413 return false;
414}
415
416list_t *criteria_for(swayc_t *cont) {
417 list_t *criteria = config->criteria, *matches = create_list();
418 for (int i = 0; i < criteria->length; i++) {
419 struct criteria *bc = criteria->items[i];
420 if (criteria_test(cont, bc->tokens)) {
421 list_add(matches, bc);
422 }
423 }
424 return matches;
425}
426
427struct list_tokens {
428 list_t *list;
429 list_t *tokens;
430};
431
432static void container_match_add(swayc_t *container, struct list_tokens *list_tokens) {
433 if (criteria_test(container, list_tokens->tokens)) {
434 list_add(list_tokens->list, container);
435 }
436}
437
438list_t *container_for(list_t *tokens) {
439 struct list_tokens list_tokens = (struct list_tokens){create_list(), tokens};
440
441 container_map(&root_container, (void (*)(swayc_t *, void *))container_match_add, &list_tokens);
442
443 for (int i = 0; i < scratchpad->length; ++i) {
444 swayc_t *c = scratchpad->items[i];
445 if (criteria_test(c, tokens)) {
446 list_add(list_tokens.list, c);
447 }
448 }
449
450 return list_tokens.list;
451}
diff --git a/sway/tree/focus.c b/sway/tree/focus.c
new file mode 100644
index 00000000..66f7ee17
--- /dev/null
+++ b/sway/tree/focus.c
@@ -0,0 +1,278 @@
1#include "stdbool.h"
2#include <wlc/wlc.h>
3#include "sway/focus.h"
4#include "sway/workspace.h"
5#include "sway/layout.h"
6#include "sway/config.h"
7#include "sway/input_state.h"
8#include "sway/ipc-server.h"
9#include "sway/border.h"
10#include "log.h"
11
12bool locked_container_focus = false;
13bool suspend_workspace_cleanup = false;
14
15// switches parent focus to c. will switch it accordingly
16static void update_focus(swayc_t *c) {
17 // Handle if focus switches
18 swayc_t *parent = c->parent;
19 if (!parent) return;
20 if (parent->focused != c) {
21 // Get previous focus
22 swayc_t *prev = parent->focused;
23 // Set new focus
24 parent->focused = c;
25
26 switch (c->type) {
27 // Shouldn't happen
28 case C_ROOT: return;
29
30 // Case where output changes
31 case C_OUTPUT:
32 wlc_output_focus(c->handle);
33 break;
34
35 // Case where workspace changes
36 case C_WORKSPACE:
37 if (prev) {
38 ipc_event_workspace(prev, c, "focus");
39
40 // if the old workspace has no children, destroy it
41 if(prev->children->length == 0 && prev->floating->length == 0 && !suspend_workspace_cleanup) {
42 destroy_workspace(prev);
43 } else {
44 // update visibility of old workspace
45 update_visibility(prev);
46 }
47 }
48 // Update visibility of newly focused workspace
49 update_visibility(c);
50 break;
51
52 default:
53 case C_VIEW:
54 case C_CONTAINER:
55 break;
56 }
57 }
58}
59
60bool move_focus(enum movement_direction direction) {
61 swayc_t *old_view = get_focused_container(&root_container);
62 swayc_t *new_view = get_swayc_in_direction(old_view, direction);
63 if (!new_view) {
64 return false;
65 } else if (new_view->type == C_ROOT) {
66 sway_log(L_DEBUG, "Not setting focus above the workspace level");
67 return false;
68 } else if (new_view->type == C_OUTPUT) {
69 return set_focused_container(swayc_active_workspace_for(new_view));
70 } else if (direction == MOVE_PARENT || direction == MOVE_CHILD) {
71 return set_focused_container(new_view);
72 } else if (config->mouse_warping) {
73 swayc_t *old_op = old_view->type == C_OUTPUT ?
74 old_view : swayc_parent_by_type(old_view, C_OUTPUT);
75 swayc_t *focused = get_focused_view(new_view);
76 if (set_focused_container(focused)) {
77 if (old_op != swayc_active_output() && focused && focused->type == C_VIEW) {
78 center_pointer_on(focused);
79 }
80 return true;
81 }
82 } else {
83 return set_focused_container(get_focused_view(new_view));
84 }
85 return false;
86}
87
88swayc_t *get_focused_container(swayc_t *parent) {
89 if (!parent) {
90 return swayc_active_workspace();
91 }
92 while (!parent->is_focused && parent->focused) {
93 parent = parent->focused;
94 }
95 return parent;
96}
97
98bool set_focused_container(swayc_t *c) {
99 if (locked_container_focus || !c || !c->parent) {
100 return false;
101 }
102
103 // current ("old") workspace for sending workspace change event later
104 swayc_t *old_ws = swayc_active_workspace();
105 // keep track of child count so we can determine if it gets destroyed
106 int old_ws_child_count = 0;
107 if (old_ws) {
108 old_ws_child_count = old_ws->children->length + old_ws->floating->length;
109 }
110
111 // current ("old") focused container
112 swayc_t *old_focus = get_focused_container(&root_container);
113 // if old_focus is a workspace, then it's the same workspace as
114 // old_ws, and we'll need to null its pointer too, since it will
115 // be destroyed in the update_focus() call
116 bool old_focus_was_ws = (old_focus->type == C_WORKSPACE);
117
118 // workspace of new focused container
119 swayc_t *workspace = swayc_active_workspace_for(c);
120
121 if (swayc_is_fullscreen(get_focused_container(workspace))) {
122 // if switching to a workspace with a fullscreen view,
123 // focus on the fullscreen view
124 c = get_focused_container(workspace);
125 }
126
127 swayc_log(L_DEBUG, c, "Setting focus to %p:%" PRIuPTR, c, c->handle);
128
129 if (c->type == C_VIEW) {
130 // dispatch a window event
131 ipc_event_window(c, "focus");
132 }
133
134 // update the global pointer
135 current_focus = c;
136
137 // update container focus from here to root, making necessary changes along
138 // the way
139 swayc_t *p = c;
140 if (p->type != C_OUTPUT && p->type != C_ROOT) {
141 p->is_focused = true;
142 }
143 while (p != &root_container) {
144 update_focus(p);
145 p = p->parent;
146 p->is_focused = false;
147 }
148
149 if (old_focus_was_ws && old_ws_child_count == 0) {
150 // this workspace was destroyed in update_focus(), so null the pointers
151 old_focus = NULL;
152 old_ws = NULL;
153 }
154
155 if (!(wlc_view_get_type(p->handle) & WLC_BIT_POPUP)) {
156 if (old_focus) {
157 if (old_focus->type == C_VIEW) {
158 wlc_view_set_state(old_focus->handle, WLC_BIT_ACTIVATED, false);
159 }
160 update_container_border(old_focus);
161 }
162 if (c->type == C_VIEW) {
163 wlc_view_set_state(c->handle, WLC_BIT_ACTIVATED, true);
164 }
165 /* TODO WLR
166 if (!desktop_shell.is_locked) {
167 // If the system is locked, we do everything _but_ actually setting
168 // focus. This includes making our internals think that this view is
169 // focused.
170 wlc_view_focus(c->handle);
171 }
172 */
173 if (c->parent->layout != L_TABBED && c->parent->layout != L_STACKED) {
174 update_container_border(c);
175 }
176
177 swayc_t *parent = swayc_tabbed_stacked_ancestor(c);
178 if (parent != NULL) {
179 arrange_backgrounds();
180 arrange_windows(parent, -1, -1);
181 }
182 }
183
184 if (old_ws != workspace) {
185 // old_ws might be NULL here but that's ok
186 ipc_event_workspace(old_ws, workspace, "focus");
187 }
188
189 return true;
190}
191
192bool set_focused_container_for(swayc_t *a, swayc_t *c) {
193 if (locked_container_focus || !c) {
194 return false;
195 }
196 swayc_t *find = c;
197 while (find != a && (find = find->parent)) {
198 if (find == &root_container) {
199 return false;
200 }
201 }
202
203 // Get workspace for c, get that workspaces current focused container.
204 swayc_t *workspace = swayc_active_workspace_for(c);
205 swayc_t *focused = get_focused_view(workspace);
206 // if the workspace we are changing focus to has a fullscreen view return
207 if (swayc_is_fullscreen(focused) && c != focused) {
208 return false;
209 }
210
211 // Check if we are changing a parent container that will see change
212 bool effective = true;
213 while (find != &root_container) {
214 if (find->parent->focused != find) {
215 effective = false;
216 }
217 find = find->parent;
218 }
219 if (effective) {
220 // Go to set_focused_container
221 return set_focused_container(c);
222 }
223
224 sway_log(L_DEBUG, "Setting focus for %p:%" PRIuPTR " to %p:%" PRIuPTR,
225 a, a->handle, c, c->handle);
226
227 c->is_focused = true;
228 swayc_t *p = c;
229 while (p != a) {
230 update_focus(p);
231 p = p->parent;
232 p->is_focused = false;
233 }
234 return true;
235}
236
237swayc_t *get_focused_view(swayc_t *parent) {
238 swayc_t *c = parent;
239 while (c && c->type != C_VIEW) {
240 if (c->type == C_WORKSPACE && c->focused == NULL) {
241 return c;
242 }
243 c = c->focused;
244 }
245 if (c == NULL) {
246 c = swayc_active_workspace_for(parent);
247 }
248 return c;
249}
250
251swayc_t *get_focused_float(swayc_t *ws) {
252 if(!sway_assert(ws->type == C_WORKSPACE, "must be of workspace type")) {
253 ws = swayc_active_workspace();
254 }
255 if (ws->floating->length) {
256 return ws->floating->items[ws->floating->length - 1];
257 }
258 return NULL;
259}
260
261swayc_t *get_focused_view_include_floating(swayc_t *parent) {
262 swayc_t *c = parent;
263 swayc_t *f = NULL;
264
265 while (c && c->type != C_VIEW) {
266 if (c->type == C_WORKSPACE && c->focused == NULL) {
267 return ((f = get_focused_float(c))) ? f : c;
268 }
269
270 c = c->focused;
271 }
272
273 if (c == NULL) {
274 c = swayc_active_workspace_for(parent);
275 }
276
277 return c;
278}
diff --git a/sway/tree/layout.c b/sway/tree/layout.c
new file mode 100644
index 00000000..22f81688
--- /dev/null
+++ b/sway/tree/layout.c
@@ -0,0 +1,1773 @@
1#define _XOPEN_SOURCE 500
2#include <stdlib.h>
3#include <stdbool.h>
4#include <math.h>
5#include <wlc/wlc.h>
6#include "sway/config.h"
7#include "sway/container.h"
8#include "sway/workspace.h"
9#include "sway/focus.h"
10#include "sway/output.h"
11#include "sway/ipc-server.h"
12#include "sway/border.h"
13#include "sway/layout.h"
14#include "list.h"
15#include "log.h"
16
17swayc_t root_container;
18swayc_t *current_focus;
19list_t *scratchpad;
20
21int min_sane_h = 60;
22int min_sane_w = 100;
23
24void init_layout(void) {
25 root_container.id = 0; // normally assigned in new_swayc()
26 root_container.type = C_ROOT;
27 root_container.layout = L_NONE;
28 root_container.name = strdup("root");
29 root_container.children = create_list();
30 root_container.handle = -1;
31 root_container.visible = true;
32 current_focus = &root_container;
33 scratchpad = create_list();
34}
35
36int index_child(const swayc_t *child) {
37 swayc_t *parent = child->parent;
38 int i, len;
39 if (!child->is_floating) {
40 len = parent->children->length;
41 for (i = 0; i < len; ++i) {
42 if (parent->children->items[i] == child) {
43 break;
44 }
45 }
46 } else {
47 len = parent->floating->length;
48 for (i = 0; i < len; ++i) {
49 if (parent->floating->items[i] == child) {
50 break;
51 }
52 }
53 }
54 if (!sway_assert(i < len, "Stray container")) {
55 return -1;
56 }
57 return i;
58}
59
60void add_child(swayc_t *parent, swayc_t *child) {
61 sway_log(L_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)", child, child->type,
62 child->width, child->height, parent, parent->type, parent->width, parent->height);
63 list_add(parent->children, child);
64 child->parent = parent;
65 // set focus for this container
66 if (!parent->focused) {
67 parent->focused = child;
68 }
69 if (parent->type == C_WORKSPACE && child->type == C_VIEW && (parent->workspace_layout == L_TABBED || parent->workspace_layout == L_STACKED)) {
70 child = new_container(child, parent->workspace_layout);
71 }
72}
73
74static double *get_height(swayc_t *cont) {
75 return &cont->height;
76}
77
78static double *get_width(swayc_t *cont) {
79 return &cont->width;
80}
81
82void insert_child(swayc_t *parent, swayc_t *child, int index) {
83 if (index > parent->children->length) {
84 index = parent->children->length;
85 }
86 if (index < 0) {
87 index = 0;
88 }
89 list_insert(parent->children, index, child);
90 child->parent = parent;
91 if (!parent->focused) {
92 parent->focused = child;
93 }
94 if (parent->type == C_WORKSPACE && child->type == C_VIEW && (parent->workspace_layout == L_TABBED || parent->workspace_layout == L_STACKED)) {
95 child = new_container(child, parent->workspace_layout);
96 }
97 if (is_auto_layout(parent->layout)) {
98 /* go through each group, adjust the size of the first child of each group */
99 double *(*get_maj_dim)(swayc_t *cont);
100 double *(*get_min_dim)(swayc_t *cont);
101 if (parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT) {
102 get_maj_dim = get_width;
103 get_min_dim = get_height;
104 } else {
105 get_maj_dim = get_height;
106 get_min_dim = get_width;
107 }
108 for (int i = index; i < parent->children->length;) {
109 int start = auto_group_start_index(parent, i);
110 int end = auto_group_end_index(parent, i);
111 swayc_t *first = parent->children->items[start];
112 if (start + 1 < parent->children->length) {
113 /* preserve the group's dimension along major axis */
114 *get_maj_dim(first) = *get_maj_dim(parent->children->items[start + 1]);
115 } else {
116 /* new group, let the apply_layout handle it */
117 first->height = first->width = 0;
118 break;
119 }
120 double remaining = *get_min_dim(parent);
121 for (int j = end - 1; j > start; --j) {
122 swayc_t *sibling = parent->children->items[j];
123 if (sibling == child) {
124 /* the inserted child won't yet have its minor
125 dimension set */
126 remaining -= *get_min_dim(parent) / (end - start);
127 } else {
128 remaining -= *get_min_dim(sibling);
129 }
130 }
131 *get_min_dim(first) = remaining;
132 i = end;
133 }
134 }
135}
136
137void add_floating(swayc_t *ws, swayc_t *child) {
138 sway_log(L_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)", child, child->type,
139 child->width, child->height, ws, ws->type, ws->width, ws->height);
140 if (!sway_assert(ws->type == C_WORKSPACE, "Must be of workspace type")) {
141 return;
142 }
143 list_add(ws->floating, child);
144 child->parent = ws;
145 child->is_floating = true;
146 if (!ws->focused) {
147 ws->focused = child;
148 }
149 ipc_event_window(child, "floating");
150}
151
152swayc_t *add_sibling(swayc_t *fixed, swayc_t *active) {
153 swayc_t *parent = fixed->parent;
154 if (fixed->is_floating) {
155 if (active->is_floating) {
156 int i = index_child(fixed);
157 list_insert(parent->floating, i + 1, active);
158 } else {
159 list_add(parent->children, active);
160 }
161 } else {
162 if (active->is_floating) {
163 list_add(parent->floating, active);
164 } else {
165 int i = index_child(fixed);
166 if (is_auto_layout(parent->layout)) {
167 list_add(parent->children, active);
168 } else {
169 list_insert(parent->children, i + 1, active);
170 }
171 }
172 }
173 active->parent = parent;
174 // focus new child
175 parent->focused = active;
176 return active->parent;
177}
178
179swayc_t *replace_child(swayc_t *child, swayc_t *new_child) {
180 swayc_t *parent = child->parent;
181 if (parent == NULL) {
182 return NULL;
183 }
184 int i = index_child(child);
185 if (child->is_floating) {
186 parent->floating->items[i] = new_child;
187 } else {
188 parent->children->items[i] = new_child;
189 }
190 // Set parent and focus for new_child
191 new_child->parent = child->parent;
192 if (child->parent->focused == child) {
193 child->parent->focused = new_child;
194 }
195 child->parent = NULL;
196
197 // Set geometry for new child
198 new_child->x = child->x;
199 new_child->y = child->y;
200 new_child->width = child->width;
201 new_child->height = child->height;
202
203 // reset geometry for child
204 child->width = 0;
205 child->height = 0;
206
207 // deactivate child
208 if (child->type == C_VIEW) {
209 wlc_view_set_state(child->handle, WLC_BIT_ACTIVATED, false);
210 }
211 return parent;
212}
213
214swayc_t *remove_child(swayc_t *child) {
215 int i;
216 swayc_t *parent = child->parent;
217 if (child->is_floating) {
218 // Special case for floating views
219 for (i = 0; i < parent->floating->length; ++i) {
220 if (parent->floating->items[i] == child) {
221 list_del(parent->floating, i);
222 break;
223 }
224 }
225 i = 0;
226 } else {
227 for (i = 0; i < parent->children->length; ++i) {
228 if (parent->children->items[i] == child) {
229 list_del(parent->children, i);
230 break;
231 }
232 }
233 if (is_auto_layout(parent->layout) && parent->children->length) {
234 /* go through each group, adjust the size of the last child of each group */
235 double *(*get_maj_dim)(swayc_t *cont);
236 double *(*get_min_dim)(swayc_t *cont);
237 if (parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT) {
238 get_maj_dim = get_width;
239 get_min_dim = get_height;
240 } else {
241 get_maj_dim = get_height;
242 get_min_dim = get_width;
243 }
244 for (int j = parent->children->length - 1; j >= i;) {
245 int start = auto_group_start_index(parent, j);
246 int end = auto_group_end_index(parent, j);
247 swayc_t *first = parent->children->items[start];
248 if (i == start) {
249 /* removed element was first child in the current group,
250 use its size along the major axis */
251 *get_maj_dim(first) = *get_maj_dim(child);
252 } else if (start > i) {
253 /* preserve the group's dimension along major axis */
254 *get_maj_dim(first) = *get_maj_dim(parent->children->items[start - 1]);
255 }
256 if (end != parent->children->length) {
257 double remaining = *get_min_dim(parent);
258 for (int k = start; k < end - 1; ++k) {
259 swayc_t *sibling = parent->children->items[k];
260 remaining -= *get_min_dim(sibling);
261 }
262 /* last element of the group gets remaining size, elements
263 that don't change groups keep their ratio */
264 *get_min_dim((swayc_t *) parent->children->items[end - 1]) = remaining;
265 } /* else last group, let apply_layout handle it */
266 j = start - 1;
267 }
268 }
269 }
270 // Set focused to new container
271 if (parent->focused == child) {
272 if (parent->children->length > 0) {
273 parent->focused = parent->children->items[i ? i-1:0];
274 } else if (parent->floating && parent->floating->length) {
275 parent->focused = parent->floating->items[parent->floating->length - 1];
276 } else {
277 parent->focused = NULL;
278 }
279 }
280 child->parent = NULL;
281 // deactivate view
282 if (child->type == C_VIEW) {
283 wlc_view_set_state(child->handle, WLC_BIT_ACTIVATED, false);
284 }
285 return parent;
286}
287
288void swap_container(swayc_t *a, swayc_t *b) {
289 if (!sway_assert(a&&b, "parameters must be non null") ||
290 !sway_assert(a->parent && b->parent, "containers must have parents")) {
291 return;
292 }
293 size_t a_index = index_child(a);
294 size_t b_index = index_child(b);
295 swayc_t *a_parent = a->parent;
296 swayc_t *b_parent = b->parent;
297 // Swap the pointers
298 if (a->is_floating) {
299 a_parent->floating->items[a_index] = b;
300 } else {
301 a_parent->children->items[a_index] = b;
302 }
303 if (b->is_floating) {
304 b_parent->floating->items[b_index] = a;
305 } else {
306 b_parent->children->items[b_index] = a;
307 }
308 a->parent = b_parent;
309 b->parent = a_parent;
310 if (a_parent->focused == a) {
311 a_parent->focused = b;
312 }
313 // don't want to double switch
314 if (b_parent->focused == b && a_parent != b_parent) {
315 b_parent->focused = a;
316 }
317}
318
319void swap_geometry(swayc_t *a, swayc_t *b) {
320 double x = a->x;
321 double y = a->y;
322 double w = a->width;
323 double h = a->height;
324 a->x = b->x;
325 a->y = b->y;
326 a->width = b->width;
327 a->height = b->height;
328 b->x = x;
329 b->y = y;
330 b->width = w;
331 b->height = h;
332}
333
334static void swap_children(swayc_t *container, int a, int b) {
335 if (a >= 0 && b >= 0 && a < container->children->length
336 && b < container->children->length
337 && a != b) {
338 swayc_t *pa = (swayc_t *)container->children->items[a];
339 swayc_t *pb = (swayc_t *)container->children->items[b];
340 container->children->items[a] = container->children->items[b];
341 container->children->items[b] = pa;
342 if (is_auto_layout(container->layout)) {
343 size_t ga = auto_group_index(container, a);
344 size_t gb = auto_group_index(container, b);
345 if (ga != gb) {
346 swap_geometry(pa, pb);
347 }
348 }
349 }
350}
351
352void move_container(swayc_t *container, enum movement_direction dir, int move_amt) {
353 enum swayc_layouts layout = L_NONE;
354 swayc_t *parent = container->parent;
355 if (container->is_floating) {
356 swayc_t *output = swayc_parent_by_type(container, C_OUTPUT);
357 switch(dir) {
358 case MOVE_LEFT:
359 container->x = MAX(0, container->x - move_amt);
360 break;
361 case MOVE_RIGHT:
362 container->x = MIN(output->width - container->width, container->x + move_amt);
363 break;
364 case MOVE_UP:
365 container->y = MAX(0, container->y - move_amt);
366 break;
367 case MOVE_DOWN:
368 container->y = MIN(output->height - container->height, container->y + move_amt);
369 break;
370 default:
371 break;
372 }
373 update_geometry(container);
374 return;
375 }
376 if (container->type != C_VIEW && container->type != C_CONTAINER) {
377 return;
378 }
379 if (dir == MOVE_UP || dir == MOVE_DOWN) {
380 layout = L_VERT;
381 } else if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
382 layout = L_HORIZ;
383 } else if (dir == MOVE_FIRST) {
384 // swap first child in auto layout with currently focused child
385 if (is_auto_layout(parent->layout)) {
386 int focused_idx = index_child(container);
387 swayc_t *first = parent->children->items[0];
388 if (focused_idx > 0) {
389 list_swap(parent->children, 0, focused_idx);
390 swap_geometry(first, container);
391 }
392 arrange_windows(parent->parent, -1, -1);
393 ipc_event_window(container, "move");
394 set_focused_container_for(parent->parent, container);
395 }
396 return;
397 } else if (! (dir == MOVE_NEXT || dir == MOVE_PREV)) {
398 return;
399 }
400 swayc_t *child = container;
401 bool ascended = false;
402
403 // View is wrapped in intermediate container which is needed for displaying
404 // the titlebar. Moving only the view outside of its parent container would just
405 // wrap it again under worspace. There would effectively be no movement,
406 // just a change of wrapping container.
407 if (child->type == C_VIEW &&
408 parent->type == C_CONTAINER &&
409 parent->children->length == 1 &&
410 parent->parent->type == C_WORKSPACE) {
411 child = parent;
412 parent = parent->parent;
413 }
414
415 while (true) {
416 sway_log(L_DEBUG, "container:%p, parent:%p, child %p,",
417 container,parent,child);
418 if (parent->layout == layout
419 || (layout == L_NONE && (parent->type == C_CONTAINER || parent->type == C_WORKSPACE)) /* accept any layout for next/prev direction */
420 || (parent->layout == L_TABBED && layout == L_HORIZ)
421 || (parent->layout == L_STACKED && layout == L_VERT)
422 || is_auto_layout(parent->layout)) {
423 int diff;
424 // If it has ascended (parent has moved up), no container is removed
425 // so insert it at index, or index+1.
426 // if it has not, the moved container is removed, so it needs to be
427 // inserted at index-1, or index+1
428 if (ascended) {
429 diff = dir == MOVE_LEFT || dir == MOVE_UP || dir == MOVE_PREV ? 0 : 1;
430 } else {
431 diff = dir == MOVE_LEFT || dir == MOVE_UP || dir == MOVE_PREV ? -1 : 1;
432 }
433 int idx = index_child(child);
434 int desired = idx + diff;
435 if (dir == MOVE_NEXT || dir == MOVE_PREV) {
436 // Next/Prev always wrap.
437 if (desired < 0) {
438 desired += parent->children->length;
439 } else if (desired >= parent->children->length) {
440 desired = 0;
441 }
442 }
443 // when it has ascended, legal insertion position is 0:len
444 // when it has not, legal insertion position is 0:len-1
445 if (desired >= 0 && desired - ascended < parent->children->length) {
446 if (!ascended) {
447 child = parent->children->items[desired];
448 // Move container into sibling container
449 if (child->type == C_CONTAINER) {
450 parent = child;
451 // Insert it in first/last if matching layout, otherwise
452 // insert it next to focused container
453 if (parent->layout == layout
454 || (parent->layout == L_TABBED && layout == L_HORIZ)
455 || (parent->layout == L_STACKED && layout == L_VERT)
456 || is_auto_layout(parent->layout)) {
457 desired = (diff < 0) * parent->children->length;
458 } else {
459 desired = index_child(child->focused) + 1;
460 }
461 //reset geometry
462 container->width = container->height = 0;
463 }
464 }
465 if (container->parent == parent) {
466 swap_children(parent, idx, desired);
467 } else {
468 swayc_t *old_parent = remove_child(container);
469 insert_child(parent, container, desired);
470 destroy_container(old_parent);
471 sway_log(L_DEBUG,"Moving to %p %d", parent, desired);
472 }
473 break;
474 }
475 }
476 // Change parent layout if we need to
477 if (parent->children->length == 1 && parent->layout != layout && layout != L_NONE) {
478 /* swayc_change_layout(parent, layout); */
479 parent->layout = layout;
480 continue;
481 }
482 if (parent->type == C_WORKSPACE) {
483 // If moving to an adjacent output we need a starting position (since this
484 // output might border to multiple outputs).
485 struct wlc_point abs_pos;
486 get_absolute_center_position(container, &abs_pos);
487
488 swayc_t *output = swayc_adjacent_output(parent->parent, dir, &abs_pos, true);
489
490 if (output) {
491 sway_log(L_DEBUG, "Moving between outputs");
492 swayc_t *old_parent = remove_child(container);
493 destroy_container(old_parent);
494
495 swayc_t *dest = output->focused;
496 switch (dir) {
497 case MOVE_LEFT:
498 case MOVE_UP:
499 // reset container geometry
500 container->width = container->height = 0;
501 add_child(dest, container);
502 break;
503 case MOVE_RIGHT:
504 case MOVE_DOWN:
505 // reset container geometry
506 container->width = container->height = 0;
507 insert_child(dest, container, 0);
508 break;
509 default:
510 break;
511 }
512 // arrange new workspace
513 arrange_windows(dest, -1, -1);
514 set_focused_container(container);
515 break;
516 }
517
518 // We simply cannot move any further.
519 if (parent->layout == layout) {
520 break;
521 }
522 // Create container around workspace to insert child into
523 parent = new_container(parent, layout);
524 // Previous line set the resulting container's layout to
525 // workspace_layout. It should have been just layout.
526 parent->layout = parent->parent->layout;
527 }
528 ascended = true;
529 child = parent;
530 parent = child->parent;
531 }
532 arrange_windows(parent->parent, -1, -1);
533 ipc_event_window(container, "move");
534 set_focused_container_for(parent->parent, container);
535}
536
537void move_container_to(swayc_t* container, swayc_t* destination) {
538 if (container == destination || swayc_is_parent_of(container, destination)) {
539 return;
540 }
541 swayc_t *parent = remove_child(container);
542 // Send to new destination
543 if (container->is_floating) {
544 swayc_t *ws = swayc_active_workspace_for(destination);
545 add_floating(ws, container);
546
547 // If the workspace only has one child after adding one, it
548 // means that the workspace was just initialized.
549 if (ws->children->length + ws->floating->length == 1) {
550 ipc_event_workspace(NULL, ws, "init");
551 }
552 } else if (destination->type == C_WORKSPACE) {
553 // reset container geometry
554 container->width = container->height = 0;
555 add_child(destination, container);
556
557 // If the workspace only has one child after adding one, it
558 // means that the workspace was just initialized.
559 if (destination->children->length + destination->floating->length == 1) {
560 ipc_event_workspace(NULL, destination, "init");
561 }
562 } else {
563 // reset container geometry
564 container->width = container->height = 0;
565 add_sibling(destination, container);
566 }
567 // Destroy old container if we need to
568 parent = destroy_container(parent);
569 // Refocus
570 swayc_t *op1 = swayc_parent_by_type(destination, C_OUTPUT);
571 swayc_t *op2 = swayc_parent_by_type(parent, C_OUTPUT);
572 set_focused_container(get_focused_view(op1));
573 arrange_windows(op1, -1, -1);
574 update_visibility(op1);
575 if (op1 != op2) {
576 set_focused_container(get_focused_view(op2));
577 arrange_windows(op2, -1, -1);
578 update_visibility(op2);
579 }
580}
581
582void move_workspace_to(swayc_t* workspace, swayc_t* destination) {
583 if (workspace == destination || swayc_is_parent_of(workspace, destination)) {
584 return;
585 }
586 swayc_t *src_op = remove_child(workspace);
587 // reset container geometry
588 workspace->width = workspace->height = 0;
589 add_child(destination, workspace);
590 sort_workspaces(destination);
591 // Refocus destination (change to new workspace)
592 set_focused_container(get_focused_view(workspace));
593 arrange_windows(destination, -1, -1);
594 update_visibility(destination);
595
596 // make sure source output has a workspace
597 if (src_op->children->length == 0) {
598 char *ws_name = workspace_next_name(src_op->name);
599 swayc_t *ws = new_workspace(src_op, ws_name);
600 ws->is_focused = true;
601 free(ws_name);
602 }
603 set_focused_container(get_focused_view(src_op));
604 update_visibility(src_op);
605}
606
607static void adjust_border_geometry(swayc_t *c, struct wlc_geometry *g,
608 const struct wlc_size *res, int left, int right, int top, int bottom) {
609
610 g->size.w += left + right;
611 if (g->origin.x - left < 0) {
612 g->size.w += g->origin.x - left;
613 } else if (g->origin.x + g->size.w - right > res->w) {
614 g->size.w = res->w - g->origin.x + right;
615 }
616
617 g->size.h += top + bottom;
618 if (g->origin.y - top < 0) {
619 g->size.h += g->origin.y - top;
620 } else if (g->origin.y + g->size.h - top > res->h) {
621 g->size.h = res->h - g->origin.y + top;
622 }
623
624 g->origin.x = MIN((uint32_t)MAX(g->origin.x - left, 0), res->w);
625 g->origin.y = MIN((uint32_t)MAX(g->origin.y - top, 0), res->h);
626
627}
628
629static void update_border_geometry_floating(swayc_t *c, struct wlc_geometry *geometry) {
630 struct wlc_geometry g = *geometry;
631 c->actual_geometry = g;
632
633 swayc_t *output = swayc_parent_by_type(c, C_OUTPUT);
634 struct wlc_size res;
635 output_get_scaled_size(output->handle, &res);
636
637 switch (c->border_type) {
638 case B_NONE:
639 break;
640 case B_PIXEL:
641 adjust_border_geometry(c, &g, &res, c->border_thickness,
642 c->border_thickness, c->border_thickness, c->border_thickness);
643 break;
644 case B_NORMAL:
645 {
646 int title_bar_height = config->font_height + 4; // borders + padding
647
648 adjust_border_geometry(c, &g, &res, c->border_thickness,
649 c->border_thickness, title_bar_height, c->border_thickness);
650
651 struct wlc_geometry title_bar = {
652 .origin = {
653 .x = c->actual_geometry.origin.x - c->border_thickness,
654 .y = c->actual_geometry.origin.y - title_bar_height
655 },
656 .size = {
657 .w = c->actual_geometry.size.w + (2 * c->border_thickness),
658 .h = title_bar_height
659 }
660 };
661 c->title_bar_geometry = title_bar;
662 break;
663 }
664 }
665
666 c->border_geometry = g;
667 *geometry = c->actual_geometry;
668
669 update_container_border(c);
670}
671
672void update_layout_geometry(swayc_t *parent, enum swayc_layouts prev_layout) {
673 switch (parent->layout) {
674 case L_TABBED:
675 case L_STACKED:
676 if (prev_layout != L_TABBED && prev_layout != L_STACKED) {
677 // cache current geometry for all non-float children
678 int i;
679 for (i = 0; i < parent->children->length; ++i) {
680 swayc_t *child = parent->children->items[i];
681 child->cached_geometry.origin.x = child->x;
682 child->cached_geometry.origin.y = child->y;
683 child->cached_geometry.size.w = child->width;
684 child->cached_geometry.size.h = child->height;
685 }
686 }
687 break;
688 default:
689 if (prev_layout == L_TABBED || prev_layout == L_STACKED) {
690 // recover cached geometry for all non-float children
691 int i;
692 for (i = 0; i < parent->children->length; ++i) {
693 swayc_t *child = parent->children->items[i];
694 // only recoverer cached geometry if non-zero
695 if (!wlc_geometry_equals(&child->cached_geometry, &wlc_geometry_zero)) {
696 child->x = child->cached_geometry.origin.x;
697 child->y = child->cached_geometry.origin.y;
698 child->width = child->cached_geometry.size.w;
699 child->height = child->cached_geometry.size.h;
700 }
701 }
702 }
703 break;
704 }
705}
706
707static int update_gap_geometry(swayc_t *container, struct wlc_geometry *g) {
708 swayc_t *ws = swayc_parent_by_type(container, C_WORKSPACE);
709 swayc_t *op = ws->parent;
710 int gap = container->is_floating ? 0 : swayc_gap(container);
711 if (gap % 2 != 0) {
712 // because gaps are implemented as "half sized margins" it's currently
713 // not possible to align views properly with odd sized gaps.
714 gap -= 1;
715 }
716
717 g->origin.x = container->x + gap/2 < op->width ? container->x + gap/2 : op->width-1;
718 g->origin.y = container->y + gap/2 < op->height ? container->y + gap/2 : op->height-1;
719 g->size.w = container->width > gap ? container->width - gap : 1;
720 g->size.h = container->height > gap ? container->height - gap : 1;
721
722 if ((!config->edge_gaps && gap > 0) || (config->smart_gaps && ws->children->length == 1)) {
723 // Remove gap against the workspace edges. Because a pixel is not
724 // divisable, depending on gap size and the number of siblings our view
725 // might be at the workspace edge without being exactly so (thus test
726 // with gap, and align correctly).
727 if (container->x - gap <= ws->x) {
728 g->origin.x = ws->x;
729 g->size.w = container->width - gap/2;
730 }
731 if (container->y - gap <= ws->y) {
732 g->origin.y = ws->y;
733 g->size.h = container->height - gap/2;
734 }
735 if (container->x + container->width + gap >= ws->x + ws->width) {
736 g->size.w = ws->x + ws->width - g->origin.x;
737 }
738 if (container->y + container->height + gap >= ws->y + ws->height) {
739 g->size.h = ws->y + ws->height - g->origin.y;
740 }
741 }
742
743 return gap;
744}
745
746void update_geometry(swayc_t *container) {
747 if (container->type != C_VIEW && container->type != C_CONTAINER) {
748 return;
749 }
750
751 swayc_t *workspace = swayc_parent_by_type(container, C_WORKSPACE);
752 swayc_t *op = workspace->parent;
753 swayc_t *parent = container->parent;
754
755 struct wlc_geometry geometry = {
756 .origin = {
757 .x = container->x < op->width ? container->x : op->width-1,
758 .y = container->y < op->height ? container->y : op->height-1
759 },
760 .size = {
761 .w = container->width,
762 .h = container->height,
763 }
764 };
765
766 int gap = 0;
767
768 // apply inner gaps to non-tabbed/stacked containers
769 swayc_t *p = swayc_tabbed_stacked_ancestor(container);
770 if (p == NULL) {
771 gap = update_gap_geometry(container, &geometry);
772 }
773
774 swayc_t *output = swayc_parent_by_type(container, C_OUTPUT);
775 struct wlc_size size;
776 output_get_scaled_size(output->handle, &size);
777
778 if (swayc_is_fullscreen(container)) {
779 geometry.origin.x = 0;
780 geometry.origin.y = 0;
781 geometry.size.w = size.w;
782 geometry.size.h = size.h;
783 if (op->focused == workspace) {
784 wlc_view_bring_to_front(container->handle);
785 }
786
787 container->border_geometry = wlc_geometry_zero;
788 container->title_bar_geometry = wlc_geometry_zero;
789 border_clear(container->border);
790 } else if (container->is_floating) { // allocate border for floating window
791 update_border_geometry_floating(container, &geometry);
792 } else if (!container->is_floating) { // allocate border for titled window
793 container->border_geometry = geometry;
794
795 int border_top = container->border_thickness;
796 int border_bottom = container->border_thickness;
797 int border_left = container->border_thickness;
798 int border_right = container->border_thickness;
799
800 // handle hide_edge_borders
801 if (config->hide_edge_borders != E_NONE && (gap <= 0 || (config->smart_gaps && workspace->children->length == 1))) {
802 if (config->hide_edge_borders == E_VERTICAL || config->hide_edge_borders == E_BOTH) {
803 if (geometry.origin.x == workspace->x) {
804 border_left = 0;
805 }
806
807 if (geometry.origin.x + geometry.size.w == workspace->x + workspace->width) {
808 border_right = 0;
809 }
810 }
811
812 if (config->hide_edge_borders == E_HORIZONTAL || config->hide_edge_borders == E_BOTH) {
813 if (geometry.origin.y == workspace->y || should_hide_top_border(container, geometry.origin.y)) {
814 border_top = 0;
815 }
816
817 if (geometry.origin.y + geometry.size.h == workspace->y + workspace->height) {
818 border_bottom = 0;
819 }
820 }
821
822 if (config->hide_edge_borders == E_SMART && workspace->children->length == 1) {
823 border_top = 0;
824 border_bottom = 0;
825 border_left = 0;
826 border_right = 0;
827 }
828 }
829
830 int title_bar_height = config->font_height + 4; //borders + padding
831
832 if (parent->layout == L_TABBED && parent->children->length > 1) {
833 int i, x = 0, w, l, r;
834 l = parent->children->length;
835 w = geometry.size.w / l;
836 r = geometry.size.w % l;
837 for (i = 0; i < parent->children->length; ++i) {
838 swayc_t *view = parent->children->items[i];
839 if (view == container) {
840 x = w * i;
841 if (i == l - 1) {
842 w += r;
843 }
844 break;
845 }
846 }
847
848 struct wlc_geometry title_bar = {
849 .origin = {
850 .x = container->border_geometry.origin.x + x,
851 .y = container->border_geometry.origin.y
852 },
853 .size = {
854 .w = w,
855 .h = title_bar_height
856 }
857 };
858 geometry.origin.x += border_left;
859 geometry.origin.y += title_bar.size.h;
860 geometry.size.w -= (border_left + border_right);
861 geometry.size.h -= (border_bottom + title_bar.size.h);
862 container->title_bar_geometry = title_bar;
863 } else if (parent->layout == L_STACKED && parent->children->length > 1) {
864 int i, y = 0;
865 for (i = 0; i < parent->children->length; ++i) {
866 swayc_t *view = parent->children->items[i];
867 if (view == container) {
868 y = title_bar_height * i;
869 }
870 }
871
872 struct wlc_geometry title_bar = {
873 .origin = {
874 .x = container->border_geometry.origin.x,
875 .y = container->border_geometry.origin.y + y
876 },
877 .size = {
878 .w = container->border_geometry.size.w,
879 .h = title_bar_height
880 }
881 };
882 title_bar_height = title_bar_height * parent->children->length;
883 geometry.origin.x += border_left;
884 geometry.origin.y += title_bar_height;
885 geometry.size.w -= (border_left + border_right);
886 geometry.size.h -= (border_bottom + title_bar_height);
887 container->title_bar_geometry = title_bar;
888 } else {
889 switch (container->border_type) {
890 case B_NONE:
891 break;
892 case B_PIXEL:
893 geometry.origin.x += border_left;
894 geometry.origin.y += border_top;
895 geometry.size.w -= (border_left + border_right);
896 geometry.size.h -= (border_top + border_bottom);
897 break;
898 case B_NORMAL:
899 {
900 struct wlc_geometry title_bar = {
901 .origin = {
902 .x = container->border_geometry.origin.x,
903 .y = container->border_geometry.origin.y
904 },
905 .size = {
906 .w = container->border_geometry.size.w,
907 .h = title_bar_height
908 }
909 };
910 geometry.origin.x += border_left;
911 geometry.origin.y += title_bar.size.h;
912 geometry.size.w -= (border_left + border_right);
913 geometry.size.h -= (border_bottom + title_bar.size.h);
914 container->title_bar_geometry = title_bar;
915 break;
916 }
917 }
918 }
919
920 container->actual_geometry = geometry;
921
922 if (container->type == C_VIEW) {
923 update_container_border(container);
924 }
925 }
926
927 if (container->type == C_VIEW) {
928 wlc_view_set_geometry(container->handle, 0, &geometry);
929 }
930}
931
932/**
933 * Layout application prototypes
934 */
935static void apply_horiz_layout(swayc_t *container, const double x,
936 const double y, const double width,
937 const double height, const int start,
938 const int end);
939static void apply_vert_layout(swayc_t *container, const double x,
940 const double y, const double width,
941 const double height, const int start,
942 const int end);
943static void apply_tabbed_or_stacked_layout(swayc_t *container, double x,
944 double y, double width,
945 double height);
946
947static void apply_auto_layout(swayc_t *container, const double x, const double y,
948 const double width, const double height,
949 enum swayc_layouts group_layout,
950 bool master_first);
951
952static void arrange_windows_r(swayc_t *container, double width, double height) {
953 int i;
954 if (width == -1 || height == -1) {
955 swayc_log(L_DEBUG, container, "Arranging layout for %p", container);
956 width = container->width;
957 height = container->height;
958 }
959 // pixels are indivisible. if we don't round the pixels, then the view
960 // calculations will be off (e.g. 50.5 + 50.5 = 101, but in reality it's
961 // 50 + 50 = 100). doing it here cascades properly to all width/height/x/y.
962 width = floor(width);
963 height = floor(height);
964
965 sway_log(L_DEBUG, "Arranging layout for %p %s %fx%f+%f,%f", container,
966 container->name, container->width, container->height, container->x,
967 container->y);
968
969 double x = 0, y = 0;
970 switch (container->type) {
971 case C_ROOT:
972 for (i = 0; i < container->children->length; ++i) {
973 swayc_t *output = container->children->items[i];
974 sway_log(L_DEBUG, "Arranging output '%s' at %f,%f", output->name, output->x, output->y);
975 arrange_windows_r(output, -1, -1);
976 }
977 return;
978 case C_OUTPUT:
979 {
980 struct wlc_size resolution;
981 output_get_scaled_size(container->handle, &resolution);
982 width = resolution.w; height = resolution.h;
983 // output must have correct size due to e.g. seamless mouse,
984 // but a workspace might be smaller depending on panels.
985 container->width = width;
986 container->height = height;
987 }
988 // arrange all workspaces:
989 for (i = 0; i < container->children->length; ++i) {
990 swayc_t *child = container->children->items[i];
991 arrange_windows_r(child, -1, -1);
992 }
993 // Bring all unmanaged views to the front
994 for (i = 0; i < container->unmanaged->length; ++i) {
995 wlc_handle *handle = container->unmanaged->items[i];
996 wlc_view_bring_to_front(*handle);
997 }
998 return;
999 case C_WORKSPACE:
1000 {
1001 swayc_t *output = swayc_parent_by_type(container, C_OUTPUT);
1002 width = output->width, height = output->height;
1003 /* TODO WLR
1004 for (i = 0; i < desktop_shell.panels->length; ++i) {
1005 struct panel_config *config = desktop_shell.panels->items[i];
1006 if (config->output == output->handle) {
1007 struct wlc_size size = *wlc_surface_get_size(config->surface);
1008 sway_log(L_DEBUG, "-> Found panel for this workspace: %ux%u, position: %u", size.w, size.h, config->panel_position);
1009 switch (config->panel_position) {
1010 case DESKTOP_SHELL_PANEL_POSITION_TOP:
1011 y += size.h; height -= size.h;
1012 break;
1013 case DESKTOP_SHELL_PANEL_POSITION_BOTTOM:
1014 height -= size.h;
1015 break;
1016 case DESKTOP_SHELL_PANEL_POSITION_LEFT:
1017 x += size.w; width -= size.w;
1018 break;
1019 case DESKTOP_SHELL_PANEL_POSITION_RIGHT:
1020 width -= size.w;
1021 break;
1022 }
1023 }
1024 }
1025 */
1026 int gap = swayc_gap(container);
1027 x = container->x = x + gap;
1028 y = container->y = y + gap;
1029 width = container->width = width - gap * 2;
1030 height = container->height = height - gap * 2;
1031 sway_log(L_DEBUG, "Arranging workspace '%s' at %f, %f", container->name, container->x, container->y);
1032 }
1033 // children are properly handled below
1034 break;
1035 case C_VIEW:
1036 {
1037 container->width = width;
1038 container->height = height;
1039 update_geometry(container);
1040 sway_log(L_DEBUG, "Set view to %.f x %.f @ %.f, %.f", container->width,
1041 container->height, container->x, container->y);
1042 }
1043 return;
1044 default:
1045 container->width = width;
1046 container->height = height;
1047 x = container->x;
1048 y = container->y;
1049
1050 // add gaps to top level tapped/stacked container
1051 if (container->parent->type == C_WORKSPACE &&
1052 (container->layout == L_TABBED || container->layout == L_STACKED)) {
1053 update_geometry(container);
1054 width = container->border_geometry.size.w;
1055 height = container->border_geometry.size.h;
1056 x = container->border_geometry.origin.x;
1057 y = container->border_geometry.origin.y;
1058 }
1059
1060 // update container size if it's a direct child in a tabbed/stacked layout
1061 // if parent is a workspace, its actual_geometry won't be initialized
1062 if (swayc_tabbed_stacked_parent(container) != NULL &&
1063 container->parent->type != C_WORKSPACE) {
1064 // Use parent actual_geometry as a base for calculating
1065 // container geometry
1066 container->width = container->parent->actual_geometry.size.w;
1067 container->height = container->parent->actual_geometry.size.h;
1068 container->x = container->parent->actual_geometry.origin.x;
1069 container->y = container->parent->actual_geometry.origin.y;
1070
1071 update_geometry(container);
1072 width = container->width = container->actual_geometry.size.w;
1073 height = container->height = container->actual_geometry.size.h;
1074 x = container->x = container->actual_geometry.origin.x;
1075 y = container->y = container->actual_geometry.origin.y;
1076 }
1077
1078 break;
1079 }
1080
1081 switch (container->layout) {
1082 case L_HORIZ:
1083 default:
1084 apply_horiz_layout(container, x, y, width, height, 0,
1085 container->children->length);
1086 break;
1087 case L_VERT:
1088 apply_vert_layout(container, x, y, width, height, 0,
1089 container->children->length);
1090 break;
1091 case L_TABBED:
1092 case L_STACKED:
1093 apply_tabbed_or_stacked_layout(container, x, y, width, height);
1094 break;
1095 case L_AUTO_LEFT:
1096 apply_auto_layout(container, x, y, width, height, L_VERT, true);
1097 break;
1098 case L_AUTO_RIGHT:
1099 apply_auto_layout(container, x, y, width, height, L_VERT, false);
1100 break;
1101 case L_AUTO_TOP:
1102 apply_auto_layout(container, x, y, width, height, L_HORIZ, true);
1103 break;
1104 case L_AUTO_BOTTOM:
1105 apply_auto_layout(container, x, y, width, height, L_HORIZ, false);
1106 break;
1107 }
1108
1109 // Arrage floating layouts for workspaces last
1110 if (container->type == C_WORKSPACE) {
1111 for (int i = 0; i < container->floating->length; ++i) {
1112 swayc_t *view = container->floating->items[i];
1113 if (view->type == C_VIEW) {
1114 update_geometry(view);
1115 sway_log(L_DEBUG, "Set floating view to %.f x %.f @ %.f, %.f",
1116 view->width, view->height, view->x, view->y);
1117 if (swayc_is_fullscreen(view)) {
1118 wlc_view_bring_to_front(view->handle);
1119 } else if (!container->focused ||
1120 !swayc_is_fullscreen(container->focused)) {
1121 wlc_view_bring_to_front(view->handle);
1122 }
1123 }
1124 }
1125 }
1126}
1127
1128void apply_horiz_layout(swayc_t *container, const double x, const double y,
1129 const double width, const double height,
1130 const int start, const int end) {
1131 double scale = 0;
1132 // Calculate total width
1133 for (int i = start; i < end; ++i) {
1134 double *old_width = &((swayc_t *)container->children->items[i])->width;
1135 if (*old_width <= 0) {
1136 if (end - start > 1) {
1137 *old_width = width / (end - start - 1);
1138 } else {
1139 *old_width = width;
1140 }
1141 }
1142 scale += *old_width;
1143 }
1144 scale = width / scale;
1145
1146 // Resize windows
1147 double child_x = x;
1148 if (scale > 0.1) {
1149 sway_log(L_DEBUG, "Arranging %p horizontally", container);
1150 swayc_t *focused = NULL;
1151 for (int i = start; i < end; ++i) {
1152 swayc_t *child = container->children->items[i];
1153 sway_log(L_DEBUG,
1154 "Calculating arrangement for %p:%d (will scale %f by %f)", child,
1155 child->type, width, scale);
1156 child->x = child_x;
1157 child->y = y;
1158
1159 if (child == container->focused) {
1160 focused = child;
1161 }
1162
1163 if (i == end - 1) {
1164 double remaining_width = x + width - child_x;
1165 arrange_windows_r(child, remaining_width, height);
1166 } else {
1167 arrange_windows_r(child, child->width * scale, height);
1168 }
1169 child_x += child->width;
1170 }
1171
1172 // update focused view border last because it may
1173 // depend on the title bar geometry of its siblings.
1174 if (focused && container->children->length > 1) {
1175 update_container_border(focused);
1176 }
1177 }
1178}
1179
1180void apply_vert_layout(swayc_t *container, const double x, const double y,
1181 const double width, const double height, const int start,
1182 const int end) {
1183 int i;
1184 double scale = 0;
1185 // Calculate total height
1186 for (i = start; i < end; ++i) {
1187 double *old_height = &((swayc_t *)container->children->items[i])->height;
1188 if (*old_height <= 0) {
1189 if (end - start > 1) {
1190 *old_height = height / (end - start - 1);
1191 } else {
1192 *old_height = height;
1193 }
1194 }
1195 scale += *old_height;
1196 }
1197 scale = height / scale;
1198
1199 // Resize
1200 double child_y = y;
1201 if (scale > 0.1) {
1202 sway_log(L_DEBUG, "Arranging %p vertically", container);
1203 swayc_t *focused = NULL;
1204 for (i = start; i < end; ++i) {
1205 swayc_t *child = container->children->items[i];
1206 sway_log(L_DEBUG,
1207 "Calculating arrangement for %p:%d (will scale %f by %f)", child,
1208 child->type, height, scale);
1209 child->x = x;
1210 child->y = child_y;
1211
1212 if (child == container->focused) {
1213 focused = child;
1214 }
1215
1216 if (i == end - 1) {
1217 double remaining_height = y + height - child_y;
1218 arrange_windows_r(child, width, remaining_height);
1219 } else {
1220 arrange_windows_r(child, width, child->height * scale);
1221 }
1222 child_y += child->height;
1223 }
1224
1225 // update focused view border last because it may
1226 // depend on the title bar geometry of its siblings.
1227 if (focused && container->children->length > 1) {
1228 update_container_border(focused);
1229 }
1230 }
1231}
1232
1233void apply_tabbed_or_stacked_layout(swayc_t *container, double x, double y,
1234 double width, double height) {
1235 int i;
1236 swayc_t *focused = NULL;
1237 for (i = 0; i < container->children->length; ++i) {
1238 swayc_t *child = container->children->items[i];
1239 child->x = x;
1240 child->y = y;
1241 if (child == container->focused) {
1242 focused = child;
1243 } else {
1244 arrange_windows_r(child, width, height);
1245 }
1246 }
1247
1248 if (focused) {
1249 arrange_windows_r(focused, width, height);
1250 }
1251}
1252
1253void apply_auto_layout(swayc_t *container, const double x, const double y,
1254 const double width, const double height,
1255 enum swayc_layouts group_layout,
1256 bool master_first) {
1257 // Auto layout "container" in width x height @ x, y
1258 // using "group_layout" for each of the groups in the container.
1259 // There is one "master" group, plus container->nb_slave_groups.
1260 // Each group is layed out side by side following the "major" axis.
1261 // The direction of the layout used for groups is the "minor" axis.
1262 // Example:
1263 //
1264 // ---- major axis -->
1265 // +---------+-----------+
1266 // | | | |
1267 // | master | slave 1 | |
1268 // | +-----------+ | minor axis (direction of group_layout)
1269 // | | | |
1270 // | | slave 2 | V
1271 // +---------+-----------+
1272 //
1273 // container with three children (one master and two slaves) and
1274 // a single slave group (containing slave 1 and 2). The master
1275 // group and slave group are layed out using L_VERT.
1276
1277 size_t nb_groups = auto_group_count(container);
1278
1279 // the target dimension of the container along the "major" axis, each
1280 // group in the container will be layed out using "group_layout" along
1281 // the "minor" axis.
1282 double dim_maj;
1283 double pos_maj;
1284
1285 // x and y coords for the next group to be laid out.
1286 const double *group_x, *group_y;
1287
1288 // pos of the next group to layout along the major axis
1289 double pos;
1290
1291 // size of the next group along the major axis.
1292 double group_dim;
1293
1294 // height and width of next group to be laid out.
1295 const double *group_h, *group_w;
1296
1297 switch (group_layout) {
1298 default:
1299 sway_log(L_DEBUG, "Unknown layout type (%d) used in %s()",
1300 group_layout, __func__);
1301 /* fall through */
1302 case L_VERT:
1303 dim_maj = width;
1304 pos_maj = x;
1305
1306 group_x = &pos;
1307 group_y = &y;
1308 group_w = &group_dim;
1309 group_h = &height;
1310 break;
1311 case L_HORIZ:
1312 dim_maj = height;
1313 pos_maj = y;
1314
1315 group_x = &x;
1316 group_y = &pos;
1317 group_w = &width;
1318 group_h = &group_dim;
1319 break;
1320 }
1321
1322 /* Determine the dimension of each of the groups in the layout.
1323 * Dimension will be width for a VERT layout and height for a HORIZ
1324 * layout. */
1325 double old_group_dim[nb_groups];
1326 double old_dim = 0;
1327 for (size_t group = 0; group < nb_groups; ++group) {
1328 int idx;
1329 if (auto_group_bounds(container, group, &idx, NULL)) {
1330 swayc_t *child = container->children->items[idx];
1331 double *dim = group_layout == L_HORIZ ? &child->height : &child->width;
1332 if (*dim <= 0) {
1333 // New child with uninitialized dimension
1334 *dim = dim_maj;
1335 if (nb_groups > 1) {
1336 // child gets a dimension proportional to existing groups,
1337 // it will be later scaled based on to the available size
1338 // in the major axis.
1339 *dim /= (nb_groups - 1);
1340 }
1341 }
1342 old_dim += *dim;
1343 old_group_dim[group] = *dim;
1344 }
1345 }
1346 double scale = dim_maj / old_dim;
1347
1348 /* Apply layout to each group */
1349 pos = pos_maj;
1350
1351 for (size_t group = 0; group < nb_groups; ++group) {
1352 int start, end; // index of first (inclusive) and last (exclusive) child in the group
1353 if (auto_group_bounds(container, group, &start, &end)) {
1354 // adjusted size of the group
1355 group_dim = old_group_dim[group] * scale;
1356 if (group == nb_groups - 1) {
1357 group_dim = pos_maj + dim_maj - pos; // remaining width
1358 }
1359 sway_log(L_DEBUG, "Arranging container %p column %zu, children [%d,%d[ (%fx%f+%f,%f)",
1360 container, group, start, end, *group_w, *group_h, *group_x, *group_y);
1361 switch (group_layout) {
1362 default:
1363 case L_VERT:
1364 apply_vert_layout(container, *group_x, *group_y, *group_w, *group_h, start, end);
1365 break;
1366 case L_HORIZ:
1367 apply_horiz_layout(container, *group_x, *group_y, *group_w, *group_h, start, end);
1368 break;
1369 }
1370
1371 /* update position for next group */
1372 pos += group_dim;
1373 }
1374 }
1375}
1376
1377void arrange_windows(swayc_t *container, double width, double height) {
1378 update_visibility(container);
1379 arrange_windows_r(container, width, height);
1380 layout_log(&root_container, 0);
1381}
1382
1383void arrange_backgrounds(void) {
1384 /* TODO WLR
1385 struct background_config *bg;
1386 for (int i = 0; i < desktop_shell.backgrounds->length; ++i) {
1387 bg = desktop_shell.backgrounds->items[i];
1388 wlc_view_send_to_back(bg->handle);
1389 }
1390 */
1391}
1392
1393/**
1394 * Get swayc in the direction of newly entered output.
1395 */
1396static swayc_t *get_swayc_in_output_direction(swayc_t *output, enum movement_direction dir) {
1397 if (!output) {
1398 return NULL;
1399 }
1400
1401 swayc_t *ws = swayc_focus_by_type(output, C_WORKSPACE);
1402 if (ws && ws->children->length > 0) {
1403 switch (dir) {
1404 case MOVE_LEFT:
1405 // get most right child of new output
1406 return ws->children->items[ws->children->length-1];
1407 case MOVE_RIGHT:
1408 // get most left child of new output
1409 return ws->children->items[0];
1410 case MOVE_UP:
1411 case MOVE_DOWN:
1412 {
1413 swayc_t *focused_view = swayc_focus_by_type(ws, C_VIEW);
1414 if (focused_view && focused_view->parent) {
1415 swayc_t *parent = focused_view->parent;
1416 if (parent->layout == L_VERT) {
1417 if (dir == MOVE_UP) {
1418 // get child furthest down on new output
1419 return parent->children->items[parent->children->length-1];
1420 } else if (dir == MOVE_DOWN) {
1421 // get child furthest up on new output
1422 return parent->children->items[0];
1423 }
1424 }
1425 return focused_view;
1426 }
1427 break;
1428 }
1429 default:
1430 break;
1431 }
1432 }
1433
1434 return output;
1435}
1436
1437swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_direction dir, swayc_t *limit) {
1438 if (dir == MOVE_CHILD) {
1439 return container->focused;
1440 }
1441
1442 swayc_t *parent = container->parent;
1443 if (dir == MOVE_PARENT) {
1444 if (parent->type == C_OUTPUT) {
1445 return NULL;
1446 } else {
1447 return parent;
1448 }
1449 }
1450
1451 if (dir == MOVE_PREV || dir == MOVE_NEXT) {
1452 int focused_idx = index_child(container);
1453 if (focused_idx == -1) {
1454 return NULL;
1455 } else {
1456 int desired = (focused_idx + (dir == MOVE_NEXT ? 1 : -1)) %
1457 parent->children->length;
1458 if (desired < 0) {
1459 desired += parent->children->length;
1460 }
1461 return parent->children->items[desired];
1462 }
1463 }
1464
1465 // If moving to an adjacent output we need a starting position (since this
1466 // output might border to multiple outputs).
1467 struct wlc_point abs_pos;
1468 get_absolute_center_position(container, &abs_pos);
1469
1470 if (container->type == C_VIEW && swayc_is_fullscreen(container)) {
1471 sway_log(L_DEBUG, "Moving from fullscreen view, skipping to output");
1472 container = swayc_parent_by_type(container, C_OUTPUT);
1473 get_absolute_center_position(container, &abs_pos);
1474 swayc_t *output = swayc_adjacent_output(container, dir, &abs_pos, true);
1475 return get_swayc_in_output_direction(output, dir);
1476 }
1477
1478 if (container->type == C_WORKSPACE && container->fullscreen) {
1479 sway_log(L_DEBUG, "Moving to fullscreen view");
1480 return container->fullscreen;
1481 }
1482
1483 swayc_t *wrap_candidate = NULL;
1484 while (true) {
1485 // Test if we can even make a difference here
1486 bool can_move = false;
1487 int desired;
1488 int idx = index_child(container);
1489 if (parent->type == C_ROOT) {
1490 swayc_t *output = swayc_adjacent_output(container, dir, &abs_pos, true);
1491 if (!output || output == container) {
1492 return wrap_candidate;
1493 }
1494 sway_log(L_DEBUG, "Moving between outputs");
1495 return get_swayc_in_output_direction(output, dir);
1496 } else {
1497 if (is_auto_layout(parent->layout)) {
1498 bool is_major = parent->layout == L_AUTO_LEFT || parent->layout == L_AUTO_RIGHT
1499 ? dir == MOVE_LEFT || dir == MOVE_RIGHT
1500 : dir == MOVE_DOWN || dir == MOVE_UP;
1501 size_t gidx = auto_group_index(parent, idx);
1502 if (is_major) {
1503 size_t desired_grp = gidx + (dir == MOVE_RIGHT || dir == MOVE_DOWN ? 1 : -1);
1504 can_move = auto_group_bounds(parent, desired_grp, &desired, NULL);
1505 } else {
1506 desired = idx + (dir == MOVE_RIGHT || dir == MOVE_DOWN ? 1 : -1);
1507 int start, end;
1508 can_move = auto_group_bounds(parent, gidx, &start, &end)
1509 && desired >= start && desired < end;
1510 }
1511 } else {
1512 if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
1513 if (parent->layout == L_HORIZ || parent->layout == L_TABBED) {
1514 can_move = true;
1515 desired = idx + (dir == MOVE_LEFT ? -1 : 1);
1516 }
1517 } else {
1518 if (parent->layout == L_VERT || parent->layout == L_STACKED) {
1519 can_move = true;
1520 desired = idx + (dir == MOVE_UP ? -1 : 1);
1521 }
1522 }
1523 }
1524 }
1525
1526 if (can_move) {
1527 if (container->is_floating) {
1528 if (desired < 0) {
1529 wrap_candidate = parent->floating->items[parent->floating->length-1];
1530 } else if (desired >= parent->floating->length){
1531 wrap_candidate = parent->floating->items[0];
1532 } else {
1533 wrap_candidate = parent->floating->items[desired];
1534 }
1535 if (wrap_candidate) {
1536 wlc_view_bring_to_front(wrap_candidate->handle);
1537 }
1538 return wrap_candidate;
1539 } else if (desired < 0 || desired >= parent->children->length) {
1540 can_move = false;
1541 int len = parent->children->length;
1542 if (!wrap_candidate && len > 1) {
1543 if (desired < 0) {
1544 wrap_candidate = parent->children->items[len-1];
1545 } else {
1546 wrap_candidate = parent->children->items[0];
1547 }
1548 if (config->force_focus_wrapping) {
1549 return wrap_candidate;
1550 }
1551 }
1552 } else {
1553 sway_log(L_DEBUG, "%s cont %d-%p dir %i sibling %d: %p", __func__,
1554 idx, container, dir, desired, parent->children->items[desired]);
1555 return parent->children->items[desired];
1556 }
1557 }
1558 if (!can_move) {
1559 container = parent;
1560 parent = parent->parent;
1561 if (!parent || container == limit) {
1562 // wrapping is the last chance
1563 return wrap_candidate;
1564 }
1565 }
1566 }
1567}
1568
1569swayc_t *get_swayc_in_direction(swayc_t *container, enum movement_direction dir) {
1570 return get_swayc_in_direction_under(container, dir, NULL);
1571}
1572
1573void recursive_resize(swayc_t *container, double amount, enum wlc_resize_edge edge) {
1574 int i;
1575 bool layout_match = true;
1576 sway_log(L_DEBUG, "Resizing %p with amount: %f", container, amount);
1577 if (edge == WLC_RESIZE_EDGE_LEFT || edge == WLC_RESIZE_EDGE_RIGHT) {
1578 container->width += amount;
1579 layout_match = container->layout == L_HORIZ;
1580 } else if (edge == WLC_RESIZE_EDGE_TOP || edge == WLC_RESIZE_EDGE_BOTTOM) {
1581 container->height += amount;
1582 layout_match = container->layout == L_VERT;
1583 }
1584 if (container->type == C_VIEW) {
1585 update_geometry(container);
1586 return;
1587 }
1588 if (layout_match) {
1589 for (i = 0; i < container->children->length; i++) {
1590 recursive_resize(container->children->items[i], amount/container->children->length, edge);
1591 }
1592 } else {
1593 for (i = 0; i < container->children->length; i++) {
1594 recursive_resize(container->children->items[i], amount, edge);
1595 }
1596 }
1597}
1598
1599enum swayc_layouts default_layout(swayc_t *output) {
1600 if (config->default_layout != L_NONE) {
1601 return config->default_layout;
1602 } else if (config->default_orientation != L_NONE) {
1603 return config->default_orientation;
1604 } else if (output->width >= output->height) {
1605 return L_HORIZ;
1606 } else {
1607 return L_VERT;
1608 }
1609}
1610
1611bool is_auto_layout(enum swayc_layouts layout) {
1612 return (layout >= L_AUTO_FIRST) && (layout <= L_AUTO_LAST);
1613}
1614
1615/**
1616 * Return the number of master elements in a container
1617 */
1618static inline size_t auto_master_count(const swayc_t *container) {
1619 sway_assert(container->children->length >= 0, "Container %p has (negative) children %d",
1620 container, container->children->length);
1621 return MIN(container->nb_master, (size_t)container->children->length);
1622}
1623
1624/**
1625 * Return the number of children in the slave groups. This corresponds to the children
1626 * that are not members of the master group.
1627 */
1628static inline size_t auto_slave_count(const swayc_t *container) {
1629 return container->children->length - auto_master_count(container);
1630}
1631
1632/**
1633 * Return the number of slave groups in the container.
1634 */
1635size_t auto_slave_group_count(const swayc_t *container) {
1636 return MIN(container->nb_slave_groups, auto_slave_count(container));
1637}
1638
1639/**
1640 * Return the combined number of master and slave groups in the container.
1641 */
1642size_t auto_group_count(const swayc_t *container) {
1643 return auto_slave_group_count(container)
1644 + (container->children->length && container->nb_master ? 1 : 0);
1645}
1646
1647/**
1648 * given the index of a container's child, return the index of the first child of the group
1649 * which index is a member of.
1650 */
1651int auto_group_start_index(const swayc_t *container, int index) {
1652 if (index < 0 || ! is_auto_layout(container->layout)
1653 || (size_t)index < container->nb_master) {
1654 return 0;
1655 } else {
1656 size_t nb_slaves = auto_slave_count(container);
1657 size_t nb_slave_grp = auto_slave_group_count(container);
1658 size_t grp_sz = nb_slaves / nb_slave_grp;
1659 size_t remainder = nb_slaves % nb_slave_grp;
1660 int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master;
1661 int start_idx;
1662 if (index < idx2) {
1663 start_idx = ((index - container->nb_master) / grp_sz) * grp_sz + container->nb_master;
1664 } else {
1665 start_idx = idx2 + ((index - idx2) / (grp_sz + 1)) * (grp_sz + 1);
1666 }
1667 return MIN(start_idx, container->children->length);
1668 }
1669}
1670
1671/**
1672 * given the index of a container's child, return the index of the first child of the group
1673 * that follows the one which index is a member of.
1674 * This makes the function usable to walk through the groups in a container.
1675 */
1676int auto_group_end_index(const swayc_t *container, int index) {
1677 if (index < 0 || ! is_auto_layout(container->layout)) {
1678 return container->children->length;
1679 } else {
1680 int nxt_idx;
1681 if ((size_t)index < container->nb_master) {
1682 nxt_idx = auto_master_count(container);
1683 } else {
1684 size_t nb_slaves = auto_slave_count(container);
1685 size_t nb_slave_grp = auto_slave_group_count(container);
1686 size_t grp_sz = nb_slaves / nb_slave_grp;
1687 size_t remainder = nb_slaves % nb_slave_grp;
1688 int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master;
1689 if (index < idx2) {
1690 nxt_idx = ((index - container->nb_master) / grp_sz + 1) * grp_sz + container->nb_master;
1691 } else {
1692 nxt_idx = idx2 + ((index - idx2) / (grp_sz + 1) + 1) * (grp_sz + 1);
1693 }
1694 }
1695 return MIN(nxt_idx, container->children->length);
1696 }
1697}
1698
1699/**
1700 * return the index of the Group containing <index>th child of <container>.
1701 * The index is the order of the group along the container's major axis (starting at 0).
1702 */
1703size_t auto_group_index(const swayc_t *container, int index) {
1704 if (index < 0) {
1705 return 0;
1706 }
1707 bool master_first = (container->layout == L_AUTO_LEFT || container->layout == L_AUTO_TOP);
1708 size_t nb_slaves = auto_slave_count(container);
1709 if ((size_t)index < container->nb_master) {
1710 if (master_first || nb_slaves <= 0) {
1711 return 0;
1712 } else {
1713 return auto_slave_group_count(container);
1714 }
1715 } else {
1716 size_t nb_slave_grp = auto_slave_group_count(container);
1717 size_t grp_sz = nb_slaves / nb_slave_grp;
1718 size_t remainder = nb_slaves % nb_slave_grp;
1719 int idx2 = (nb_slave_grp - remainder) * grp_sz + container->nb_master;
1720 size_t grp_idx;
1721 if (index < idx2) {
1722 grp_idx = (index - container->nb_master) / grp_sz;
1723 } else {
1724 grp_idx = (nb_slave_grp - remainder) + (index - idx2) / (grp_sz + 1) ;
1725 }
1726 return grp_idx + (master_first && container-> nb_master ? 1 : 0);
1727 }
1728}
1729
1730/**
1731 * Return the first index (inclusive) and last index (exclusive) of the elements of a group in
1732 * an auto layout.
1733 * If the bounds of the given group can be calculated, they are returned in the start/end
1734 * parameters (int pointers) and the return value will be true.
1735 * The indexes are passed by reference and can be NULL.
1736 */
1737bool auto_group_bounds(const swayc_t *container, size_t group_index, int *start, int *end) {
1738 size_t nb_grp = auto_group_count(container);
1739 if (group_index >= nb_grp) {
1740 return false;
1741 }
1742 bool master_first = (container->layout == L_AUTO_LEFT || container->layout == L_AUTO_TOP);
1743 size_t nb_master = auto_master_count(container);
1744 size_t nb_slave_grp = auto_slave_group_count(container);
1745 int g_start, g_end;
1746 if (nb_master && (master_first ? group_index == 0 : group_index == nb_grp - 1)) {
1747 g_start = 0;
1748 g_end = nb_master;
1749 } else {
1750 size_t nb_slaves = auto_slave_count(container);
1751 size_t grp_sz = nb_slaves / nb_slave_grp;
1752 size_t remainder = nb_slaves % nb_slave_grp;
1753 size_t g0 = master_first && container->nb_master ? 1 : 0;
1754 size_t g1 = g0 + nb_slave_grp - remainder;
1755 if (group_index < g1) {
1756 g_start = container->nb_master + (group_index - g0) * grp_sz;
1757 g_end = g_start + grp_sz;
1758 } else {
1759 size_t g2 = group_index - g1;
1760 g_start = container->nb_master
1761 + (nb_slave_grp - remainder) * grp_sz
1762 + g2 * (grp_sz + 1);
1763 g_end = g_start + grp_sz + 1;
1764 }
1765 }
1766 if (start) {
1767 *start = g_start;
1768 }
1769 if (end) {
1770 *end = g_end;
1771 }
1772 return true;
1773}
diff --git a/sway/tree/output.c b/sway/tree/output.c
new file mode 100644
index 00000000..c0f29c5a
--- /dev/null
+++ b/sway/tree/output.c
@@ -0,0 +1,277 @@
1#include <strings.h>
2#include <ctype.h>
3#include <stdlib.h>
4#include "sway/output.h"
5#include "log.h"
6#include "list.h"
7
8void output_get_scaled_size(wlc_handle handle, struct wlc_size *size) {
9 *size = *wlc_output_get_resolution(handle);
10 uint32_t scale = wlc_output_get_scale(handle);
11 size->w /= scale;
12 size->h /= scale;
13}
14
15swayc_t *output_by_name(const char* name, const struct wlc_point *abs_pos) {
16 swayc_t *output = NULL;
17 // If there is no output directly next to the current one, use
18 // swayc_opposite_output to wrap.
19 if (strcasecmp(name, "left") == 0) {
20 output = swayc_adjacent_output(NULL, MOVE_LEFT, abs_pos, true);
21 if (!output) {
22 output = swayc_opposite_output(MOVE_RIGHT, abs_pos);
23 }
24 } else if (strcasecmp(name, "right") == 0) {
25 output = swayc_adjacent_output(NULL, MOVE_RIGHT, abs_pos, true);
26 if (!output) {
27 output = swayc_opposite_output(MOVE_LEFT, abs_pos);
28 }
29 } else if (strcasecmp(name, "up") == 0) {
30 output = swayc_adjacent_output(NULL, MOVE_UP, abs_pos, true);
31 if (!output) {
32 output = swayc_opposite_output(MOVE_DOWN, abs_pos);
33 }
34 } else if (strcasecmp(name, "down") == 0) {
35 output = swayc_adjacent_output(NULL, MOVE_DOWN, abs_pos, true);
36 if (!output) {
37 output = swayc_opposite_output(MOVE_UP, abs_pos);
38 }
39 } else {
40 for(int i = 0; i < root_container.children->length; ++i) {
41 swayc_t *c = root_container.children->items[i];
42 if (c->type == C_OUTPUT && strcasecmp(c->name, name) == 0) {
43 return c;
44 }
45 }
46 }
47 return output;
48}
49
50swayc_t *swayc_opposite_output(enum movement_direction dir,
51 const struct wlc_point *abs_pos) {
52
53 // Search through all the outputs and pick the output whose edge covers the
54 // given position, and is at leftmost/rightmost/upmost/downmost side of the
55 // screen (decided by the direction given).
56 swayc_t *opposite = NULL;
57 char *dir_text = NULL;
58 switch(dir) {
59 case MOVE_LEFT:
60 case MOVE_RIGHT: ;
61 for (int i = 0; i < root_container.children->length; ++i) {
62 swayc_t *c = root_container.children->items[i];
63 if (abs_pos->y >= c->y && abs_pos->y <= c->y + c->height) {
64 if (!opposite) {
65 opposite = c;
66 } else if ((dir == MOVE_LEFT && c->x < opposite->x)
67 || (dir == MOVE_RIGHT && c->x > opposite->x)) {
68 opposite = c;
69 }
70 }
71 }
72 dir_text = dir == MOVE_LEFT ? "leftmost" : "rightmost";
73 break;
74 case MOVE_UP:
75 case MOVE_DOWN: ;
76 for (int i = 0; i < root_container.children->length; ++i) {
77 swayc_t *c = root_container.children->items[i];
78 if (abs_pos->x >= c->x && abs_pos->x <= c->x + c->width) {
79 if (!opposite) {
80 opposite = c;
81 } else if ((dir == MOVE_UP && c->y < opposite->y)
82 || (dir == MOVE_DOWN && c->y > opposite->y)) {
83 opposite = c;
84 }
85 }
86 }
87 dir_text = dir == MOVE_UP ? "upmost" : "downmost";
88 break;
89 default:
90 sway_abort("Function called with invalid argument.");
91 break;
92 }
93 if (opposite) {
94 sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s from y-position %i",
95 opposite->name, opposite->width, opposite->height, opposite->x, opposite->y,
96 dir_text, abs_pos->y);
97 }
98 return opposite;
99}
100
101// Position is where on the edge (as absolute position) the adjacent output should be searched for.
102swayc_t *swayc_adjacent_output(swayc_t *output, enum movement_direction dir,
103 const struct wlc_point *abs_pos, bool pick_closest) {
104
105 if (!output) {
106 output = swayc_active_output();
107 }
108 // In order to find adjacent outputs we need to test that the outputs are
109 // aligned on one axis (decided by the direction given) and that the given
110 // position is within the edge of the adjacent output. If no such output
111 // exists we pick the adjacent output within the edge that is closest to
112 // the given position, if any.
113 swayc_t *adjacent = NULL;
114 char *dir_text = NULL;
115 switch(dir) {
116 case MOVE_LEFT:
117 case MOVE_RIGHT: ;
118 double delta_y = 0;
119 for(int i = 0; i < root_container.children->length; ++i) {
120 swayc_t *c = root_container.children->items[i];
121 if (c == output || c->type != C_OUTPUT) {
122 continue;
123 }
124 bool x_aligned = dir == MOVE_LEFT ?
125 c->x + c->width == output->x :
126 c->x == output->x + output->width;
127 if (!x_aligned) {
128 continue;
129 }
130 if (abs_pos->y >= c->y && abs_pos->y <= c->y + c->height) {
131 delta_y = 0;
132 adjacent = c;
133 break;
134 } else if (pick_closest) {
135 // track closest adjacent output
136 double top_y = c->y, bottom_y = c->y + c->height;
137 if (top_y >= output->y && top_y <= output->y + output->height) {
138 double delta = top_y - abs_pos->y;
139 if (delta < 0) delta = -delta;
140 if (delta < delta_y || !adjacent) {
141 delta_y = delta;
142 adjacent = c;
143 }
144 }
145 // we check both points and pick the closest
146 if (bottom_y >= output->y && bottom_y <= output->y + output->height) {
147 double delta = bottom_y - abs_pos->y;
148 if (delta < 0) delta = -delta;
149 if (delta < delta_y || !adjacent) {
150 delta_y = delta;
151 adjacent = c;
152 }
153 }
154 }
155 }
156 dir_text = dir == MOVE_LEFT ? "left of" : "right of";
157 if (adjacent && delta_y == 0) {
158 sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (y-position %i)",
159 adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y,
160 dir_text, output->name, abs_pos->y);
161 } else if (adjacent) {
162 // so we end up picking the closest adjacent output because
163 // there is no directly adjacent to the given position
164 sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (y-position %i, delta: %.0f)",
165 adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y,
166 dir_text, output->name, abs_pos->y, delta_y);
167 }
168 break;
169 case MOVE_UP:
170 case MOVE_DOWN: ;
171 double delta_x = 0;
172 for(int i = 0; i < root_container.children->length; ++i) {
173 swayc_t *c = root_container.children->items[i];
174 if (c == output || c->type != C_OUTPUT) {
175 continue;
176 }
177 bool y_aligned = dir == MOVE_UP ?
178 c->y + c->height == output->y :
179 c->y == output->y + output->height;
180 if (!y_aligned) {
181 continue;
182 }
183 if (abs_pos->x >= c->x && abs_pos->x <= c->x + c->width) {
184 delta_x = 0;
185 adjacent = c;
186 break;
187 } else if (pick_closest) {
188 // track closest adjacent output
189 double left_x = c->x, right_x = c->x + c->width;
190 if (left_x >= output->x && left_x <= output->x + output->width) {
191 double delta = left_x - abs_pos->x;
192 if (delta < 0) delta = -delta;
193 if (delta < delta_x || !adjacent) {
194 delta_x = delta;
195 adjacent = c;
196 }
197 }
198 // we check both points and pick the closest
199 if (right_x >= output->x && right_x <= output->x + output->width) {
200 double delta = right_x - abs_pos->x;
201 if (delta < 0) delta = -delta;
202 if (delta < delta_x || !adjacent) {
203 delta_x = delta;
204 adjacent = c;
205 }
206 }
207 }
208 }
209 dir_text = dir == MOVE_UP ? "above" : "below";
210 if (adjacent && delta_x == 0) {
211 sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (x-position %i)",
212 adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y,
213 dir_text, output->name, abs_pos->x);
214 } else if (adjacent) {
215 // so we end up picking the closest adjacent output because
216 // there is no directly adjacent to the given position
217 sway_log(L_DEBUG, "%s (%.0fx%.0f+%.0f+%.0f) is %s current output %s (x-position %i, delta: %.0f)",
218 adjacent->name, adjacent->width, adjacent->height, adjacent->x, adjacent->y,
219 dir_text, output->name, abs_pos->x, delta_x);
220 }
221 break;
222 default:
223 sway_abort("Function called with invalid argument.");
224 break;
225 }
226 return adjacent;
227}
228
229void get_absolute_position(swayc_t *container, struct wlc_point *point) {
230 if (!container || !point)
231 sway_abort("Need container and wlc_point (was %p, %p).", container, point);
232
233 if (container->type == C_OUTPUT) {
234 // Coordinates are already absolute.
235 point->x = container->x;
236 point->y = container->y;
237 } else {
238 swayc_t *output = swayc_parent_by_type(container, C_OUTPUT);
239 if (container->type == C_WORKSPACE) {
240 // Workspace coordinates are actually wrong/arbitrary, but should
241 // be same as output.
242 point->x = output->x;
243 point->y = output->y;
244 } else {
245 point->x = output->x + container->x;
246 point->y = output->y + container->y;
247 }
248 }
249}
250
251void get_absolute_center_position(swayc_t *container, struct wlc_point *point) {
252 get_absolute_position(container, point);
253 point->x += container->width/2;
254 point->y += container->height/2;
255}
256
257static int sort_workspace_cmp_qsort(const void *_a, const void *_b) {
258 swayc_t *a = *(void **)_a;
259 swayc_t *b = *(void **)_b;
260 int retval = 0;
261
262 if (isdigit(a->name[0]) && isdigit(b->name[0])) {
263 int a_num = strtol(a->name, NULL, 10);
264 int b_num = strtol(b->name, NULL, 10);
265 retval = (a_num < b_num) ? -1 : (a_num > b_num);
266 } else if (isdigit(a->name[0])) {
267 retval = -1;
268 } else if (isdigit(b->name[0])) {
269 retval = 1;
270 }
271
272 return retval;
273}
274
275void sort_workspaces(swayc_t *output) {
276 list_stable_sort(output->children, sort_workspace_cmp_qsort);
277}
diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c
new file mode 100644
index 00000000..14cde146
--- /dev/null
+++ b/sway/tree/workspace.c
@@ -0,0 +1,373 @@
1#define _XOPEN_SOURCE 500
2#include <stdlib.h>
3#include <stdbool.h>
4#include <limits.h>
5#include <ctype.h>
6#include <wlc/wlc.h>
7#include <string.h>
8#include <strings.h>
9#include <sys/types.h>
10#include "sway/ipc-server.h"
11#include "sway/workspace.h"
12#include "sway/layout.h"
13#include "sway/container.h"
14#include "sway/handlers.h"
15#include "sway/config.h"
16#include "sway/focus.h"
17#include "stringop.h"
18#include "util.h"
19#include "list.h"
20#include "log.h"
21#include "ipc.h"
22
23char *prev_workspace_name = NULL;
24struct workspace_by_number_data {
25 int len;
26 const char *cset;
27 const char *name;
28};
29
30static bool workspace_valid_on_output(const char *output_name, const char *ws_name) {
31 int i;
32 for (i = 0; i < config->workspace_outputs->length; ++i) {
33 struct workspace_output *wso = config->workspace_outputs->items[i];
34 if (strcasecmp(wso->workspace, ws_name) == 0) {
35 if (strcasecmp(wso->output, output_name) != 0) {
36 return false;
37 }
38 }
39 }
40
41 return true;
42}
43
44char *workspace_next_name(const char *output_name) {
45 sway_log(L_DEBUG, "Workspace: Generating new workspace name for output %s", output_name);
46 int i;
47 int l = 1;
48 // Scan all workspace bindings to find the next available workspace name,
49 // if none are found/available then default to a number
50 struct sway_mode *mode = config->current_mode;
51
52 int order = INT_MAX;
53 char *target = NULL;
54 for (i = 0; i < mode->bindings->length; ++i) {
55 struct sway_binding *binding = mode->bindings->items[i];
56 char *cmdlist = strdup(binding->command);
57 char *dup = cmdlist;
58 char *name = NULL;
59
60 // workspace n
61 char *cmd = argsep(&cmdlist, " ");
62 if (cmdlist) {
63 name = argsep(&cmdlist, ",;");
64 }
65
66 if (strcmp("workspace", cmd) == 0 && name) {
67 sway_log(L_DEBUG, "Got valid workspace command for target: '%s'", name);
68 char *_target = strdup(name);
69 strip_quotes(_target);
70 while (isspace(*_target))
71 _target++;
72
73 // Make sure that the command references an actual workspace
74 // not a command about workspaces
75 if (strcmp(_target, "next") == 0 ||
76 strcmp(_target, "prev") == 0 ||
77 strcmp(_target, "next_on_output") == 0 ||
78 strcmp(_target, "prev_on_output") == 0 ||
79 strcmp(_target, "number") == 0 ||
80 strcmp(_target, "back_and_forth") == 0 ||
81 strcmp(_target, "current") == 0)
82 {
83 free(_target);
84 free(dup);
85 continue;
86 }
87
88 // Make sure that the workspace doesn't already exist
89 if (workspace_by_name(_target)) {
90 free(_target);
91 free(dup);
92 continue;
93 }
94
95 // make sure that the workspace can appear on the given
96 // output
97 if (!workspace_valid_on_output(output_name, _target)) {
98 free(_target);
99 free(dup);
100 continue;
101 }
102
103 if (binding->order < order) {
104 order = binding->order;
105 free(target);
106 target = _target;
107 sway_log(L_DEBUG, "Workspace: Found free name %s", _target);
108 }
109 }
110 free(dup);
111 }
112 if (target != NULL) {
113 return target;
114 }
115 // As a fall back, get the current number of active workspaces
116 // and return that + 1 for the next workspace's name
117 int ws_num = root_container.children->length;
118 if (ws_num >= 10) {
119 l = 2;
120 } else if (ws_num >= 100) {
121 l = 3;
122 }
123 char *name = malloc(l + 1);
124 if (!name) {
125 sway_log(L_ERROR, "Could not allocate workspace name");
126 return NULL;
127 }
128 sprintf(name, "%d", ws_num++);
129 return name;
130}
131
132swayc_t *workspace_create(const char* name) {
133 swayc_t *parent;
134 // Search for workspace<->output pair
135 int i, e = config->workspace_outputs->length;
136 for (i = 0; i < e; ++i) {
137 struct workspace_output *wso = config->workspace_outputs->items[i];
138 if (strcasecmp(wso->workspace, name) == 0)
139 {
140 // Find output to use if it exists
141 e = root_container.children->length;
142 for (i = 0; i < e; ++i) {
143 parent = root_container.children->items[i];
144 if (strcmp(parent->name, wso->output) == 0) {
145 return new_workspace(parent, name);
146 }
147 }
148 break;
149 }
150 }
151 // Otherwise create a new one
152 parent = get_focused_container(&root_container);
153 parent = swayc_parent_by_type(parent, C_OUTPUT);
154 return new_workspace(parent, name);
155}
156
157static bool _workspace_by_name(swayc_t *view, void *data) {
158 return (view->type == C_WORKSPACE) &&
159 (strcasecmp(view->name, (char *) data) == 0);
160}
161
162swayc_t *workspace_by_name(const char* name) {
163 if (strcmp(name, "prev") == 0) {
164 return workspace_prev();
165 }
166 else if (strcmp(name, "prev_on_output") == 0) {
167 return workspace_output_prev();
168 }
169 else if (strcmp(name, "next") == 0) {
170 return workspace_next();
171 }
172 else if (strcmp(name, "next_on_output") == 0) {
173 return workspace_output_next();
174 }
175 else if (strcmp(name, "current") == 0) {
176 return swayc_active_workspace();
177 }
178 else {
179 return swayc_by_test(&root_container, _workspace_by_name, (void *) name);
180 }
181}
182
183static bool _workspace_by_number(swayc_t *view, void *data) {
184 if (view->type != C_WORKSPACE) {
185 return false;
186 }
187 struct workspace_by_number_data *wbnd = data;
188 int a = strspn(view->name, wbnd->cset);
189 return a == wbnd->len && strncmp(view->name, wbnd->name, a) == 0;
190}
191swayc_t *workspace_by_number(const char* name) {
192 struct workspace_by_number_data wbnd = {0, "1234567890", name};
193 wbnd.len = strspn(name, wbnd.cset);
194 if (wbnd.len <= 0) {
195 return NULL;
196 }
197 return swayc_by_test(&root_container, _workspace_by_number, (void *) &wbnd);
198}
199
200/**
201 * Get the previous or next workspace on the specified output.
202 * Wraps around at the end and beginning.
203 * If next is false, the previous workspace is returned, otherwise the next one is returned.
204 */
205swayc_t *workspace_output_prev_next_impl(swayc_t *output, bool next) {
206 if (!sway_assert(output->type == C_OUTPUT, "Argument must be an output, is %d", output->type)) {
207 return NULL;
208 }
209
210 int i;
211 for (i = 0; i < output->children->length; i++) {
212 if (output->children->items[i] == output->focused) {
213 return output->children->items[wrap(i + (next ? 1 : -1), output->children->length)];
214 }
215 }
216
217 // Doesn't happen, at worst the for loop returns the previously active workspace
218 return NULL;
219}
220
221/**
222 * Get the previous or next workspace. If the first/last workspace on an output is active,
223 * proceed to the previous/next output's previous/next workspace.
224 * If next is false, the previous workspace is returned, otherwise the next one is returned.
225 */
226swayc_t *workspace_prev_next_impl(swayc_t *workspace, bool next) {
227 if (!sway_assert(workspace->type == C_WORKSPACE, "Argument must be a workspace, is %d", workspace->type)) {
228 return NULL;
229 }
230
231 swayc_t *current_output = workspace->parent;
232 int offset = next ? 1 : -1;
233 int start = next ? 0 : 1;
234 int end = next ? (current_output->children->length) - 1 : current_output->children->length;
235 int i;
236 for (i = start; i < end; i++) {
237 if (current_output->children->items[i] == workspace) {
238 return current_output->children->items[i + offset];
239 }
240 }
241
242 // Given workspace is the first/last on the output, jump to the previous/next output
243 int num_outputs = root_container.children->length;
244 for (i = 0; i < num_outputs; i++) {
245 if (root_container.children->items[i] == current_output) {
246 swayc_t *next_output = root_container.children->items[wrap(i + offset, num_outputs)];
247 return workspace_output_prev_next_impl(next_output, next);
248 }
249 }
250
251 // Doesn't happen, at worst the for loop returns the previously active workspace on the active output
252 return NULL;
253}
254
255swayc_t *workspace_output_next() {
256 return workspace_output_prev_next_impl(swayc_active_output(), true);
257}
258
259swayc_t *workspace_next() {
260 return workspace_prev_next_impl(swayc_active_workspace(), true);
261}
262
263swayc_t *workspace_output_prev() {
264 return workspace_output_prev_next_impl(swayc_active_output(), false);
265}
266
267swayc_t *workspace_prev() {
268 return workspace_prev_next_impl(swayc_active_workspace(), false);
269}
270
271bool workspace_switch(swayc_t *workspace) {
272 if (!workspace) {
273 return false;
274 }
275 swayc_t *active_ws = swayc_active_workspace();
276 if (config->auto_back_and_forth && active_ws == workspace && prev_workspace_name) {
277 swayc_t *new_ws = workspace_by_name(prev_workspace_name);
278 workspace = new_ws ? new_ws : workspace_create(prev_workspace_name);
279 }
280
281 if (!prev_workspace_name
282 || (strcmp(prev_workspace_name, active_ws->name)
283 && active_ws != workspace)) {
284 free(prev_workspace_name);
285 prev_workspace_name = malloc(strlen(active_ws->name) + 1);
286 if (!prev_workspace_name) {
287 sway_log(L_ERROR, "Unable to allocate previous workspace name");
288 return false;
289 }
290 strcpy(prev_workspace_name, active_ws->name);
291 }
292
293 // move sticky containers
294 if (swayc_parent_by_type(active_ws, C_OUTPUT) == swayc_parent_by_type(workspace, C_OUTPUT)) {
295 // don't change list while traversing it, use intermediate list instead
296 list_t *stickies = create_list();
297 for (int i = 0; i < active_ws->floating->length; i++) {
298 swayc_t *cont = active_ws->floating->items[i];
299 if (cont->sticky) {
300 list_add(stickies, cont);
301 }
302 }
303 for (int i = 0; i < stickies->length; i++) {
304 swayc_t *cont = stickies->items[i];
305 sway_log(L_DEBUG, "Moving sticky container %p to %p:%s",
306 cont, workspace, workspace->name);
307 swayc_t *parent = remove_child(cont);
308 add_floating(workspace, cont);
309 // Destroy old container if we need to
310 destroy_container(parent);
311 }
312 list_free(stickies);
313 }
314 sway_log(L_DEBUG, "Switching to workspace %p:%s", workspace, workspace->name);
315 if (!set_focused_container(get_focused_view(workspace))) {
316 return false;
317 }
318 swayc_t *output = swayc_parent_by_type(workspace, C_OUTPUT);
319 arrange_backgrounds();
320 arrange_windows(output, -1, -1);
321 return true;
322}
323
324swayc_t *workspace_for_pid(pid_t pid) {
325 int i;
326 swayc_t *ws = NULL;
327 struct pid_workspace *pw = NULL;
328
329 sway_log(L_DEBUG, "looking for workspace for pid %d", pid);
330
331 // leaving this here as it's useful for debugging
332 // sway_log(L_DEBUG, "all pid_workspaces");
333 // for (int k = 0; k < config->pid_workspaces->length; k++) {
334 // pw = config->pid_workspaces->items[k];
335 // sway_log(L_DEBUG, "pid %d workspace %s time_added %li", *pw->pid, pw->workspace, *pw->time_added);
336 // }
337
338 do {
339 for (i = 0; i < config->pid_workspaces->length; i++) {
340 pw = config->pid_workspaces->items[i];
341 pid_t *pw_pid = pw->pid;
342
343 if (pid == *pw_pid) {
344 sway_log(L_DEBUG, "found pid_workspace for pid %d, workspace %s", pid, pw->workspace);
345 break; // out of for loop
346 }
347
348 pw = NULL;
349 }
350
351 if (pw) {
352 break; // out of do-while loop
353 }
354
355 pid = get_parent_pid(pid);
356 // no sense in looking for matches for pid 0.
357 // also, if pid == getpid(), that is the compositor's
358 // pid, which definitely isn't helpful
359 } while (pid > 0 && pid != getpid());
360
361 if (pw) {
362 ws = workspace_by_name(pw->workspace);
363
364 if (!ws) {
365 sway_log(L_DEBUG, "Creating workspace %s for pid %d because it disappeared", pw->workspace, pid);
366 ws = workspace_create(pw->workspace);
367 }
368
369 list_del(config->pid_workspaces, i);
370 }
371
372 return ws;
373}