diff options
Diffstat (limited to 'sway/tree/container.c')
-rw-r--r-- | sway/tree/container.c | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/sway/tree/container.c b/sway/tree/container.c new file mode 100644 index 00000000..ea1c93bb --- /dev/null +++ b/sway/tree/container.c | |||
@@ -0,0 +1,534 @@ | |||
1 | #define _POSIX_C_SOURCE 200809L | ||
2 | #include <assert.h> | ||
3 | #include <stdint.h> | ||
4 | #include <stdlib.h> | ||
5 | #include <string.h> | ||
6 | #include <strings.h> | ||
7 | #include <wayland-server.h> | ||
8 | #include <wlr/types/wlr_output_layout.h> | ||
9 | #include <wlr/types/wlr_wl_shell.h> | ||
10 | #include "sway/config.h" | ||
11 | #include "sway/input/input-manager.h" | ||
12 | #include "sway/input/seat.h" | ||
13 | #include "sway/ipc-server.h" | ||
14 | #include "sway/output.h" | ||
15 | #include "sway/server.h" | ||
16 | #include "sway/tree/layout.h" | ||
17 | #include "sway/tree/view.h" | ||
18 | #include "sway/tree/workspace.h" | ||
19 | #include "log.h" | ||
20 | |||
21 | static list_t *bfs_queue; | ||
22 | |||
23 | static list_t *get_bfs_queue() { | ||
24 | if (!bfs_queue) { | ||
25 | bfs_queue = create_list(); | ||
26 | if (!bfs_queue) { | ||
27 | wlr_log(L_ERROR, "could not allocate list for bfs queue"); | ||
28 | return NULL; | ||
29 | } | ||
30 | } | ||
31 | bfs_queue->length = 0; | ||
32 | |||
33 | return bfs_queue; | ||
34 | } | ||
35 | |||
36 | const char *container_type_to_str(enum sway_container_type type) { | ||
37 | switch (type) { | ||
38 | case C_ROOT: | ||
39 | return "C_ROOT"; | ||
40 | case C_OUTPUT: | ||
41 | return "C_OUTPUT"; | ||
42 | case C_WORKSPACE: | ||
43 | return "C_WORKSPACE"; | ||
44 | case C_CONTAINER: | ||
45 | return "C_CONTAINER"; | ||
46 | case C_VIEW: | ||
47 | return "C_VIEW"; | ||
48 | default: | ||
49 | return "C_UNKNOWN"; | ||
50 | } | ||
51 | } | ||
52 | |||
53 | void container_create_notify(struct sway_container *container) { | ||
54 | // TODO send ipc event type based on the container type | ||
55 | wl_signal_emit(&root_container.sway_root->events.new_container, container); | ||
56 | |||
57 | if (container->type == C_VIEW || container->type == C_CONTAINER) { | ||
58 | ipc_event_window(container, "new"); | ||
59 | } | ||
60 | } | ||
61 | |||
62 | static void container_close_notify(struct sway_container *container) { | ||
63 | if (container == NULL) { | ||
64 | return; | ||
65 | } | ||
66 | // TODO send ipc event type based on the container type | ||
67 | if (container->type == C_VIEW || container->type == C_WORKSPACE) { | ||
68 | ipc_event_window(container, "close"); | ||
69 | } | ||
70 | } | ||
71 | |||
72 | struct sway_container *container_create(enum sway_container_type type) { | ||
73 | // next id starts at 1 because 0 is assigned to root_container in layout.c | ||
74 | static size_t next_id = 1; | ||
75 | struct sway_container *c = calloc(1, sizeof(struct sway_container)); | ||
76 | if (!c) { | ||
77 | return NULL; | ||
78 | } | ||
79 | c->id = next_id++; | ||
80 | c->layout = L_NONE; | ||
81 | c->workspace_layout = L_NONE; | ||
82 | c->type = type; | ||
83 | c->alpha = 1.0f; | ||
84 | |||
85 | if (type != C_VIEW) { | ||
86 | c->children = create_list(); | ||
87 | } | ||
88 | |||
89 | wl_signal_init(&c->events.destroy); | ||
90 | wl_signal_init(&c->events.reparent); | ||
91 | |||
92 | return c; | ||
93 | } | ||
94 | |||
95 | static void _container_destroy(struct sway_container *cont) { | ||
96 | if (cont == NULL) { | ||
97 | return; | ||
98 | } | ||
99 | |||
100 | wl_signal_emit(&cont->events.destroy, cont); | ||
101 | container_close_notify(cont); | ||
102 | |||
103 | struct sway_container *parent = cont->parent; | ||
104 | if (cont->children != NULL && cont->children->length) { | ||
105 | // remove children until there are no more, container_destroy calls | ||
106 | // container_remove_child, which removes child from this container | ||
107 | while (cont->children != NULL) { | ||
108 | struct sway_container *child = cont->children->items[0]; | ||
109 | container_remove_child(child); | ||
110 | _container_destroy(child); | ||
111 | } | ||
112 | } | ||
113 | if (cont->marks) { | ||
114 | list_foreach(cont->marks, free); | ||
115 | list_free(cont->marks); | ||
116 | } | ||
117 | if (parent) { | ||
118 | parent = container_remove_child(cont); | ||
119 | } | ||
120 | if (cont->name) { | ||
121 | free(cont->name); | ||
122 | } | ||
123 | list_free(cont->children); | ||
124 | cont->children = NULL; | ||
125 | free(cont); | ||
126 | } | ||
127 | |||
128 | static struct sway_container *container_output_destroy( | ||
129 | struct sway_container *output) { | ||
130 | if (!sway_assert(output, "cannot destroy null output")) { | ||
131 | return NULL; | ||
132 | } | ||
133 | |||
134 | if (output->children->length > 0) { | ||
135 | // TODO save workspaces when there are no outputs. | ||
136 | // TODO also check if there will ever be no outputs except for exiting | ||
137 | // program | ||
138 | if (root_container.children->length > 1) { | ||
139 | int p = root_container.children->items[0] == output; | ||
140 | // Move workspace from this output to another output | ||
141 | while (output->children->length) { | ||
142 | struct sway_container *child = output->children->items[0]; | ||
143 | container_remove_child(child); | ||
144 | container_add_child(root_container.children->items[p], child); | ||
145 | } | ||
146 | container_sort_workspaces(root_container.children->items[p]); | ||
147 | arrange_windows(root_container.children->items[p], | ||
148 | -1, -1); | ||
149 | } | ||
150 | } | ||
151 | |||
152 | wl_list_remove(&output->sway_output->destroy.link); | ||
153 | wl_list_remove(&output->sway_output->mode.link); | ||
154 | wl_list_remove(&output->sway_output->transform.link); | ||
155 | wl_list_remove(&output->sway_output->scale.link); | ||
156 | |||
157 | wl_list_remove(&output->sway_output->damage_destroy.link); | ||
158 | wl_list_remove(&output->sway_output->damage_frame.link); | ||
159 | |||
160 | wlr_log(L_DEBUG, "OUTPUT: Destroying output '%s'", output->name); | ||
161 | _container_destroy(output); | ||
162 | return &root_container; | ||
163 | } | ||
164 | |||
165 | static struct sway_container *container_workspace_destroy( | ||
166 | struct sway_container *workspace) { | ||
167 | if (!sway_assert(workspace, "cannot destroy null workspace")) { | ||
168 | return NULL; | ||
169 | } | ||
170 | |||
171 | // Do not destroy this if it's the last workspace on this output | ||
172 | struct sway_container *output = container_parent(workspace, C_OUTPUT); | ||
173 | if (output && output->children->length == 1) { | ||
174 | return NULL; | ||
175 | } | ||
176 | |||
177 | struct sway_container *parent = workspace->parent; | ||
178 | if (workspace->children->length == 0) { | ||
179 | // destroy the WS if there are no children (TODO check for floating) | ||
180 | wlr_log(L_DEBUG, "destroying workspace '%s'", workspace->name); | ||
181 | ipc_event_workspace(workspace, NULL, "empty"); | ||
182 | } else { | ||
183 | // Move children to a different workspace on this output | ||
184 | struct sway_container *new_workspace = NULL; | ||
185 | // TODO move floating | ||
186 | for (int i = 0; i < output->children->length; i++) { | ||
187 | if (output->children->items[i] != workspace) { | ||
188 | new_workspace = output->children->items[i]; | ||
189 | break; | ||
190 | } | ||
191 | } | ||
192 | |||
193 | wlr_log(L_DEBUG, "moving children to different workspace '%s' -> '%s'", | ||
194 | workspace->name, new_workspace->name); | ||
195 | for (int i = 0; i < workspace->children->length; i++) { | ||
196 | container_move_to(workspace->children->items[i], new_workspace); | ||
197 | } | ||
198 | } | ||
199 | |||
200 | _container_destroy(workspace); | ||
201 | |||
202 | output_damage_whole(output->sway_output); | ||
203 | |||
204 | return parent; | ||
205 | } | ||
206 | |||
207 | static void container_root_finish(struct sway_container *con) { | ||
208 | wlr_log(L_ERROR, "TODO: destroy the root container"); | ||
209 | } | ||
210 | |||
211 | bool container_reap_empty(struct sway_container *con) { | ||
212 | switch (con->type) { | ||
213 | case C_ROOT: | ||
214 | case C_OUTPUT: | ||
215 | // dont reap these | ||
216 | break; | ||
217 | case C_WORKSPACE: | ||
218 | if (!workspace_is_visible(con) && con->children->length == 0) { | ||
219 | wlr_log(L_DEBUG, "Destroying workspace via reaper"); | ||
220 | container_workspace_destroy(con); | ||
221 | return true; | ||
222 | } | ||
223 | break; | ||
224 | case C_CONTAINER: | ||
225 | if (con->children->length == 0) { | ||
226 | _container_destroy(con); | ||
227 | return true; | ||
228 | } | ||
229 | case C_VIEW: | ||
230 | break; | ||
231 | case C_TYPES: | ||
232 | sway_assert(false, "container_reap_empty called on an invalid " | ||
233 | "container"); | ||
234 | break; | ||
235 | } | ||
236 | |||
237 | return false; | ||
238 | } | ||
239 | |||
240 | struct sway_container *container_reap_empty_recursive( | ||
241 | struct sway_container *con) { | ||
242 | while (con) { | ||
243 | struct sway_container *next = con->parent; | ||
244 | if (!container_reap_empty(con)) { | ||
245 | break; | ||
246 | } | ||
247 | con = next; | ||
248 | } | ||
249 | return con; | ||
250 | } | ||
251 | |||
252 | struct sway_container *container_flatten(struct sway_container *container) { | ||
253 | while (container->type == C_CONTAINER && container->children->length == 1) { | ||
254 | struct sway_container *child = container->children->items[0]; | ||
255 | struct sway_container *parent = container->parent; | ||
256 | container_replace_child(container, child); | ||
257 | container_destroy(container); | ||
258 | container = parent; | ||
259 | } | ||
260 | return container; | ||
261 | } | ||
262 | |||
263 | struct sway_container *container_destroy(struct sway_container *con) { | ||
264 | if (con == NULL) { | ||
265 | return NULL; | ||
266 | } | ||
267 | |||
268 | struct sway_container *parent = con->parent; | ||
269 | |||
270 | switch (con->type) { | ||
271 | case C_ROOT: | ||
272 | container_root_finish(con); | ||
273 | break; | ||
274 | case C_OUTPUT: | ||
275 | // dont try to reap the root after this | ||
276 | container_output_destroy(con); | ||
277 | break; | ||
278 | case C_WORKSPACE: | ||
279 | // dont try to reap the output after this | ||
280 | container_workspace_destroy(con); | ||
281 | break; | ||
282 | case C_CONTAINER: | ||
283 | if (con->children->length) { | ||
284 | for (int i = 0; i < con->children->length; ++i) { | ||
285 | struct sway_container *child = con->children->items[0]; | ||
286 | container_remove_child(child); | ||
287 | container_add_child(parent, child); | ||
288 | } | ||
289 | } | ||
290 | _container_destroy(con); | ||
291 | break; | ||
292 | case C_VIEW: | ||
293 | _container_destroy(con); | ||
294 | break; | ||
295 | case C_TYPES: | ||
296 | wlr_log(L_ERROR, "container_destroy called on an invalid " | ||
297 | "container"); | ||
298 | break; | ||
299 | } | ||
300 | |||
301 | return container_reap_empty_recursive(parent); | ||
302 | } | ||
303 | |||
304 | static void container_close_func(struct sway_container *container, void *data) { | ||
305 | if (container->type == C_VIEW) { | ||
306 | view_close(container->sway_view); | ||
307 | } | ||
308 | } | ||
309 | |||
310 | struct sway_container *container_close(struct sway_container *con) { | ||
311 | if (!sway_assert(con != NULL, | ||
312 | "container_close called with a NULL container")) { | ||
313 | return NULL; | ||
314 | } | ||
315 | |||
316 | struct sway_container *parent = con->parent; | ||
317 | |||
318 | if (con->type == C_VIEW) { | ||
319 | view_close(con->sway_view); | ||
320 | } else { | ||
321 | container_for_each_descendant_dfs(con, container_close_func, NULL); | ||
322 | } | ||
323 | |||
324 | return parent; | ||
325 | } | ||
326 | |||
327 | struct sway_container *container_view_create(struct sway_container *sibling, | ||
328 | struct sway_view *sway_view) { | ||
329 | if (!sway_assert(sibling, | ||
330 | "container_view_create called with NULL sibling/parent")) { | ||
331 | return NULL; | ||
332 | } | ||
333 | const char *title = view_get_title(sway_view); | ||
334 | struct sway_container *swayc = container_create(C_VIEW); | ||
335 | wlr_log(L_DEBUG, "Adding new view %p:%s to container %p %d %s", | ||
336 | swayc, title, sibling, sibling ? sibling->type : 0, sibling->name); | ||
337 | // Setup values | ||
338 | swayc->sway_view = sway_view; | ||
339 | swayc->name = title ? strdup(title) : NULL; | ||
340 | swayc->width = 0; | ||
341 | swayc->height = 0; | ||
342 | |||
343 | if (sibling->type == C_WORKSPACE) { | ||
344 | // Case of focused workspace, just create as child of it | ||
345 | container_add_child(sibling, swayc); | ||
346 | } else { | ||
347 | // Regular case, create as sibling of current container | ||
348 | container_add_sibling(sibling, swayc); | ||
349 | } | ||
350 | container_create_notify(swayc); | ||
351 | return swayc; | ||
352 | } | ||
353 | |||
354 | void container_descendants(struct sway_container *root, | ||
355 | enum sway_container_type type, | ||
356 | void (*func)(struct sway_container *item, void *data), void *data) { | ||
357 | for (int i = 0; i < root->children->length; ++i) { | ||
358 | struct sway_container *item = root->children->items[i]; | ||
359 | if (item->type == type) { | ||
360 | func(item, data); | ||
361 | } | ||
362 | if (item->children && item->children->length) { | ||
363 | container_descendants(item, type, func, data); | ||
364 | } | ||
365 | } | ||
366 | } | ||
367 | |||
368 | struct sway_container *container_find(struct sway_container *container, | ||
369 | bool (*test)(struct sway_container *view, void *data), void *data) { | ||
370 | if (!container->children) { | ||
371 | return NULL; | ||
372 | } | ||
373 | // TODO: floating windows | ||
374 | for (int i = 0; i < container->children->length; ++i) { | ||
375 | struct sway_container *child = container->children->items[i]; | ||
376 | if (test(child, data)) { | ||
377 | return child; | ||
378 | } else { | ||
379 | struct sway_container *res = container_find(child, test, data); | ||
380 | if (res) { | ||
381 | return res; | ||
382 | } | ||
383 | } | ||
384 | } | ||
385 | return NULL; | ||
386 | } | ||
387 | |||
388 | struct sway_container *container_parent(struct sway_container *container, | ||
389 | enum sway_container_type type) { | ||
390 | if (!sway_assert(container, "container is NULL")) { | ||
391 | return NULL; | ||
392 | } | ||
393 | if (!sway_assert(type < C_TYPES && type >= C_ROOT, "invalid type")) { | ||
394 | return NULL; | ||
395 | } | ||
396 | do { | ||
397 | container = container->parent; | ||
398 | } while (container && container->type != type); | ||
399 | return container; | ||
400 | } | ||
401 | |||
402 | struct sway_container *container_at(struct sway_container *parent, | ||
403 | double lx, double ly, | ||
404 | struct wlr_surface **surface, double *sx, double *sy) { | ||
405 | list_t *queue = get_bfs_queue(); | ||
406 | if (!queue) { | ||
407 | return NULL; | ||
408 | } | ||
409 | |||
410 | list_add(queue, parent); | ||
411 | |||
412 | struct sway_container *swayc = NULL; | ||
413 | while (queue->length) { | ||
414 | swayc = queue->items[0]; | ||
415 | list_del(queue, 0); | ||
416 | if (swayc->type == C_VIEW) { | ||
417 | struct sway_view *sview = swayc->sway_view; | ||
418 | struct sway_container *soutput = container_parent(swayc, C_OUTPUT); | ||
419 | struct wlr_box *output_box = | ||
420 | wlr_output_layout_get_box( | ||
421 | root_container.sway_root->output_layout, | ||
422 | soutput->sway_output->wlr_output); | ||
423 | double ox = lx - output_box->x; | ||
424 | double oy = ly - output_box->y; | ||
425 | double view_sx = ox - swayc->x; | ||
426 | double view_sy = oy - swayc->y; | ||
427 | |||
428 | double _sx, _sy; | ||
429 | struct wlr_surface *_surface; | ||
430 | switch (sview->type) { | ||
431 | case SWAY_VIEW_XWAYLAND: | ||
432 | _surface = wlr_surface_surface_at(sview->surface, | ||
433 | view_sx, view_sy, &_sx, &_sy); | ||
434 | break; | ||
435 | case SWAY_VIEW_WL_SHELL: | ||
436 | _surface = wlr_wl_shell_surface_surface_at( | ||
437 | sview->wlr_wl_shell_surface, | ||
438 | view_sx, view_sy, &_sx, &_sy); | ||
439 | break; | ||
440 | case SWAY_VIEW_XDG_SHELL_V6: | ||
441 | // the top left corner of the sway container is the | ||
442 | // coordinate of the top left corner of the window geometry | ||
443 | view_sx += sview->wlr_xdg_surface_v6->geometry.x; | ||
444 | view_sy += sview->wlr_xdg_surface_v6->geometry.y; | ||
445 | |||
446 | _surface = wlr_xdg_surface_v6_surface_at( | ||
447 | sview->wlr_xdg_surface_v6, | ||
448 | view_sx, view_sy, &_sx, &_sy); | ||
449 | break; | ||
450 | } | ||
451 | if (_surface) { | ||
452 | *sx = _sx; | ||
453 | *sy = _sy; | ||
454 | *surface = _surface; | ||
455 | return swayc; | ||
456 | } | ||
457 | } else { | ||
458 | list_cat(queue, swayc->children); | ||
459 | } | ||
460 | } | ||
461 | |||
462 | return NULL; | ||
463 | } | ||
464 | |||
465 | void container_for_each_descendant_dfs(struct sway_container *container, | ||
466 | void (*f)(struct sway_container *container, void *data), | ||
467 | void *data) { | ||
468 | if (container) { | ||
469 | if (container->children) { | ||
470 | for (int i = 0; i < container->children->length; ++i) { | ||
471 | struct sway_container *child = | ||
472 | container->children->items[i]; | ||
473 | container_for_each_descendant_dfs(child, f, data); | ||
474 | } | ||
475 | } | ||
476 | f(container, data); | ||
477 | } | ||
478 | } | ||
479 | |||
480 | void container_for_each_descendant_bfs(struct sway_container *con, | ||
481 | void (*f)(struct sway_container *con, void *data), void *data) { | ||
482 | list_t *queue = get_bfs_queue(); | ||
483 | if (!queue) { | ||
484 | return; | ||
485 | } | ||
486 | |||
487 | if (queue == NULL) { | ||
488 | wlr_log(L_ERROR, "could not allocate list"); | ||
489 | return; | ||
490 | } | ||
491 | |||
492 | list_add(queue, con); | ||
493 | |||
494 | struct sway_container *current = NULL; | ||
495 | while (queue->length) { | ||
496 | current = queue->items[0]; | ||
497 | list_del(queue, 0); | ||
498 | f(current, data); | ||
499 | // TODO floating containers | ||
500 | list_cat(queue, current->children); | ||
501 | } | ||
502 | } | ||
503 | |||
504 | bool container_has_anscestor(struct sway_container *descendant, | ||
505 | struct sway_container *anscestor) { | ||
506 | while (descendant->type != C_ROOT) { | ||
507 | descendant = descendant->parent; | ||
508 | if (descendant == anscestor) { | ||
509 | return true; | ||
510 | } | ||
511 | } | ||
512 | return false; | ||
513 | } | ||
514 | |||
515 | static bool find_child_func(struct sway_container *con, void *data) { | ||
516 | struct sway_container *child = data; | ||
517 | return con == child; | ||
518 | } | ||
519 | |||
520 | bool container_has_child(struct sway_container *con, | ||
521 | struct sway_container *child) { | ||
522 | if (con == NULL || con->type == C_VIEW || con->children->length == 0) { | ||
523 | return false; | ||
524 | } | ||
525 | return container_find(con, find_child_func, child); | ||
526 | } | ||
527 | |||
528 | void container_damage_whole(struct sway_container *con) { | ||
529 | struct sway_container *output = con; | ||
530 | if (output->type != C_OUTPUT) { | ||
531 | output = container_parent(output, C_OUTPUT); | ||
532 | } | ||
533 | output_damage_whole_container(output->sway_output, con); | ||
534 | } | ||