]> git.sur5r.net Git - i3/i3/blob - src/workspace.c
Introduce splith/splitv layouts, remove orientation
[i3/i3] / src / workspace.c
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
6  *
7  * workspace.c: Modifying workspaces, accessing them, moving containers to
8  *              workspaces.
9  *
10  */
11 #include "all.h"
12
13 /* Stores a copy of the name of the last used workspace for the workspace
14  * back-and-forth switching. */
15 static char *previous_workspace_name = NULL;
16
17 /*
18  * Sets ws->layout to splith/splitv if default_orientation was specified in the
19  * configfile. Otherwise, it uses splith/splitv depending on whether the output
20  * is higher than wide.
21  *
22  */
23 static void _workspace_apply_default_orientation(Con *ws) {
24     /* If default_orientation is set to NO_ORIENTATION we determine
25      * orientation depending on output resolution. */
26     if (config.default_orientation == NO_ORIENTATION) {
27         Con *output = con_get_output(ws);
28         ws->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH;
29         DLOG("Auto orientation. Workspace size set to (%d,%d), setting layout to %d.\n",
30              output->rect.width, output->rect.height, ws->layout);
31     } else {
32         ws->layout = (config.default_orientation == HORIZ) ? L_SPLITH : L_SPLITV;
33     }
34 }
35
36 /*
37  * Returns a pointer to the workspace with the given number (starting at 0),
38  * creating the workspace if necessary (by allocating the necessary amount of
39  * memory and initializing the data structures correctly).
40  *
41  */
42 Con *workspace_get(const char *num, bool *created) {
43     Con *output, *workspace = NULL;
44
45     TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
46         GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num));
47
48     if (workspace == NULL) {
49         LOG("Creating new workspace \"%s\"\n", num);
50         /* unless an assignment is found, we will create this workspace on the current output */
51         output = con_get_output(focused);
52         /* look for assignments */
53         struct Workspace_Assignment *assignment;
54         TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
55             if (strcmp(assignment->name, num) != 0)
56                 continue;
57
58             LOG("Found workspace assignment to output \"%s\"\n", assignment->output);
59             GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
60             break;
61         }
62         Con *content = output_get_content(output);
63         LOG("got output %p with content %p\n", output, content);
64         /* We need to attach this container after setting its type. con_attach
65          * will handle CT_WORKSPACEs differently */
66         workspace = con_new(NULL, NULL);
67         char *name;
68         sasprintf(&name, "[i3 con] workspace %s", num);
69         x_set_name(workspace, name);
70         free(name);
71         workspace->type = CT_WORKSPACE;
72         FREE(workspace->name);
73         workspace->name = sstrdup(num);
74         /* We set ->num to the number if this workspace’s name begins with a
75          * positive number. Otherwise it’s a named ws and num will be -1. */
76         char *endptr = NULL;
77         long parsed_num = strtol(num, &endptr, 10);
78         if (parsed_num == LONG_MIN ||
79             parsed_num == LONG_MAX ||
80             parsed_num < 0 ||
81             endptr == num)
82             workspace->num = -1;
83         else workspace->num = parsed_num;
84         LOG("num = %d\n", workspace->num);
85
86         workspace->parent = content;
87         _workspace_apply_default_orientation(workspace);
88
89         con_attach(workspace, content, false);
90
91         ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
92         if (created != NULL)
93             *created = true;
94     }
95     else if (created != NULL) {
96         *created = false;
97     }
98
99     return workspace;
100 }
101
102 /*
103  * Returns a pointer to a new workspace in the given output. The workspace
104  * is created attached to the tree hierarchy through the given content
105  * container.
106  *
107  */
108 Con *create_workspace_on_output(Output *output, Con *content) {
109     /* add a workspace to this output */
110     Con *out, *current;
111     char *name;
112     bool exists = true;
113     Con *ws = con_new(NULL, NULL);
114     ws->type = CT_WORKSPACE;
115
116     /* try the configured workspace bindings first to find a free name */
117     Binding *bind;
118     TAILQ_FOREACH(bind, bindings, bindings) {
119         DLOG("binding with command %s\n", bind->command);
120         if (strlen(bind->command) < strlen("workspace ") ||
121             strncasecmp(bind->command, "workspace", strlen("workspace")) != 0)
122             continue;
123         DLOG("relevant command = %s\n", bind->command);
124         char *target = bind->command + strlen("workspace ");
125         /* We check if this is the workspace
126          * next/prev/next_on_output/prev_on_output/back_and_forth/number command.
127          * Beware: The workspace names "next", "prev", "next_on_output",
128          * "prev_on_output", "number", "back_and_forth" and "current" are OK,
129          * so we check before stripping the double quotes */
130         if (strncasecmp(target, "next", strlen("next")) == 0 ||
131             strncasecmp(target, "prev", strlen("prev")) == 0 ||
132             strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 ||
133             strncasecmp(target, "prev_on_output", strlen("prev_on_output")) == 0 ||
134             strncasecmp(target, "number", strlen("number")) == 0 ||
135             strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 ||
136             strncasecmp(target, "current", strlen("current")) == 0)
137             continue;
138         if (*target == '"')
139             target++;
140         FREE(ws->name);
141         ws->name = strdup(target);
142         if (ws->name[strlen(ws->name)-1] == '"')
143             ws->name[strlen(ws->name)-1] = '\0';
144         DLOG("trying name *%s*\n", ws->name);
145
146         /* Ensure that this workspace is not assigned to a different output —
147          * otherwise we would create it, then move it over to its output, then
148          * find a new workspace, etc… */
149         bool assigned = false;
150         struct Workspace_Assignment *assignment;
151         TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
152             if (strcmp(assignment->name, ws->name) != 0 ||
153                 strcmp(assignment->output, output->name) == 0)
154                 continue;
155
156             assigned = true;
157             break;
158         }
159
160         if (assigned)
161             continue;
162
163         current = NULL;
164         TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
165             GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name));
166
167         exists = (current != NULL);
168         if (!exists) {
169             /* Set ->num to the number of the workspace, if the name actually
170              * is a number or starts with a number */
171             char *endptr = NULL;
172             long parsed_num = strtol(ws->name, &endptr, 10);
173             if (parsed_num == LONG_MIN ||
174                 parsed_num == LONG_MAX ||
175                 parsed_num < 0 ||
176                 endptr == ws->name)
177                 ws->num = -1;
178             else ws->num = parsed_num;
179             LOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
180
181             break;
182         }
183     }
184
185     if (exists) {
186         /* get the next unused workspace number */
187         DLOG("Getting next unused workspace by number\n");
188         int c = 0;
189         while (exists) {
190             c++;
191
192             FREE(ws->name);
193             sasprintf(&(ws->name), "%d", c);
194
195             current = NULL;
196             TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
197                 GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name));
198             exists = (current != NULL);
199
200             DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists);
201         }
202         ws->num = c;
203     }
204     con_attach(ws, content, false);
205
206     sasprintf(&name, "[i3 con] workspace %s", ws->name);
207     x_set_name(ws, name);
208     free(name);
209
210     ws->fullscreen_mode = CF_OUTPUT;
211
212     _workspace_apply_default_orientation(ws);
213
214     return ws;
215 }
216
217
218 /*
219  * Returns true if the workspace is currently visible. Especially important for
220  * multi-monitor environments, as they can have multiple currenlty active
221  * workspaces.
222  *
223  */
224 bool workspace_is_visible(Con *ws) {
225     Con *output = con_get_output(ws);
226     if (output == NULL)
227         return false;
228     Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
229     LOG("workspace visible? fs = %p, ws = %p\n", fs, ws);
230     return (fs == ws);
231 }
232
233 /*
234  * XXX: we need to clean up all this recursive walking code.
235  *
236  */
237 Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) {
238     Con *current;
239
240     TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
241         if (current != exclude &&
242             current->sticky_group != NULL &&
243             current->window != NULL &&
244             strcmp(current->sticky_group, sticky_group) == 0)
245             return current;
246
247         Con *recurse = _get_sticky(current, sticky_group, exclude);
248         if (recurse != NULL)
249             return recurse;
250     }
251
252     TAILQ_FOREACH(current, &(con->floating_head), floating_windows) {
253         if (current != exclude &&
254             current->sticky_group != NULL &&
255             current->window != NULL &&
256             strcmp(current->sticky_group, sticky_group) == 0)
257             return current;
258
259         Con *recurse = _get_sticky(current, sticky_group, exclude);
260         if (recurse != NULL)
261             return recurse;
262     }
263
264     return NULL;
265 }
266
267 /*
268  * Reassigns all child windows in sticky containers. Called when the user
269  * changes workspaces.
270  *
271  * XXX: what about sticky containers which contain containers?
272  *
273  */
274 static void workspace_reassign_sticky(Con *con) {
275     Con *current;
276     /* 1: go through all containers */
277
278     /* handle all children and floating windows of this node */
279     TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
280         if (current->sticky_group == NULL) {
281             workspace_reassign_sticky(current);
282             continue;
283         }
284
285         LOG("Ah, this one is sticky: %s / %p\n", current->name, current);
286         /* 2: find a window which we can re-assign */
287         Con *output = con_get_output(current);
288         Con *src = _get_sticky(output, current->sticky_group, current);
289
290         if (src == NULL) {
291             LOG("No window found for this sticky group\n");
292             workspace_reassign_sticky(current);
293             continue;
294         }
295
296         x_move_win(src, current);
297         current->window = src->window;
298         current->mapped = true;
299         src->window = NULL;
300         src->mapped = false;
301
302         x_reparent_child(current, src);
303
304         LOG("re-assigned window from src %p to dest %p\n", src, current);
305     }
306
307     TAILQ_FOREACH(current, &(con->floating_head), floating_windows)
308         workspace_reassign_sticky(current);
309 }
310
311
312 static void _workspace_show(Con *workspace) {
313     Con *current, *old = NULL;
314
315     /* safe-guard against showing i3-internal workspaces like __i3_scratch */
316     if (workspace->name[0] == '_' && workspace->name[1] == '_')
317         return;
318
319     /* disable fullscreen for the other workspaces and get the workspace we are
320      * currently on. */
321     TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) {
322         if (current->fullscreen_mode == CF_OUTPUT)
323             old = current;
324         current->fullscreen_mode = CF_NONE;
325     }
326
327     /* enable fullscreen for the target workspace. If it happens to be the
328      * same one we are currently on anyways, we can stop here. */
329     workspace->fullscreen_mode = CF_OUTPUT;
330     current = con_get_workspace(focused);
331     if (workspace == current) {
332         DLOG("Not switching, already there.\n");
333         return;
334     }
335
336     /* Remember currently focused workspace for switching back to it later with
337      * the 'workspace back_and_forth' command.
338      * NOTE: We have to duplicate the name as the original will be freed when
339      * the corresponding workspace is cleaned up. */
340
341     FREE(previous_workspace_name);
342     if (current)
343         previous_workspace_name = sstrdup(current->name);
344
345     workspace_reassign_sticky(workspace);
346
347     LOG("switching to %p\n", workspace);
348     Con *next = con_descend_focused(workspace);
349
350     if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
351         /* check if this workspace is currently visible */
352         if (!workspace_is_visible(old)) {
353             LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
354             tree_close(old, DONT_KILL_WINDOW, false, false);
355             ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
356         }
357     }
358
359     /* Memorize current output */
360     Con *old_output = con_get_output(focused);
361
362     con_focus(next);
363     workspace->fullscreen_mode = CF_OUTPUT;
364     LOG("focused now = %p / %s\n", focused, focused->name);
365
366     /* Set mouse pointer */
367     Con *new_output = con_get_output(focused);
368     if (old_output != new_output) {
369         x_set_warp_to(&next->rect);
370     }
371
372     /* Update the EWMH hints */
373     ewmh_update_current_desktop();
374
375     ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
376 }
377
378 /*
379  * Switches to the given workspace
380  *
381  */
382 void workspace_show(Con *workspace) {
383     _workspace_show(workspace);
384 }
385
386 /*
387  * Looks up the workspace by name and switches to it.
388  *
389  */
390 void workspace_show_by_name(const char *num) {
391     Con *workspace;
392     bool changed_num_workspaces;
393     workspace = workspace_get(num, &changed_num_workspaces);
394     _workspace_show(workspace);
395 }
396
397 /*
398  * Focuses the next workspace.
399  *
400  */
401 Con* workspace_next(void) {
402     Con *current = con_get_workspace(focused);
403     Con *next = NULL;
404     Con *output;
405
406     if (current->num == -1) {
407         /* If currently a named workspace, find next named workspace. */
408         next = TAILQ_NEXT(current, nodes);
409     } else {
410         /* If currently a numbered workspace, find next numbered workspace. */
411         TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
412             /* Skip outputs starting with __, they are internal. */
413             if (output->name[0] == '_' && output->name[1] == '_')
414                 continue;
415             NODES_FOREACH(output_get_content(output)) {
416                 if (child->type != CT_WORKSPACE)
417                     continue;
418                 if (child->num == -1)
419                     break;
420                 /* Need to check child against current and next because we are
421                  * traversing multiple lists and thus are not guaranteed the
422                  * relative order between the list of workspaces. */
423                 if (current->num < child->num && (!next || child->num < next->num))
424                     next = child;
425             }
426         }
427     }
428
429     /* Find next named workspace. */
430     if (!next) {
431         bool found_current = false;
432         TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
433             /* Skip outputs starting with __, they are internal. */
434             if (output->name[0] == '_' && output->name[1] == '_')
435                 continue;
436             NODES_FOREACH(output_get_content(output)) {
437                 if (child->type != CT_WORKSPACE)
438                     continue;
439                 if (child == current) {
440                     found_current = 1;
441                 } else if (child->num == -1 && (current->num != -1 || found_current)) {
442                     next = child;
443                     goto workspace_next_end;
444                 }
445             }
446         }
447     }
448
449     /* Find first workspace. */
450     if (!next) {
451         TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
452             /* Skip outputs starting with __, they are internal. */
453             if (output->name[0] == '_' && output->name[1] == '_')
454                 continue;
455             NODES_FOREACH(output_get_content(output)) {
456                 if (child->type != CT_WORKSPACE)
457                     continue;
458                 if (!next || (child->num != -1 && child->num < next->num))
459                     next = child;
460             }
461         }
462     }
463 workspace_next_end:
464     return next;
465 }
466
467 /*
468  * Focuses the previous workspace.
469  *
470  */
471 Con* workspace_prev(void) {
472     Con *current = con_get_workspace(focused);
473     Con *prev = NULL;
474     Con *output;
475
476     if (current->num == -1) {
477         /* If named workspace, find previous named workspace. */
478         prev = TAILQ_PREV(current, nodes_head, nodes);
479         if (prev && prev->num != -1)
480             prev = NULL;
481     } else {
482         /* If numbered workspace, find previous numbered workspace. */
483         TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
484             /* Skip outputs starting with __, they are internal. */
485             if (output->name[0] == '_' && output->name[1] == '_')
486                 continue;
487             NODES_FOREACH_REVERSE(output_get_content(output)) {
488                 if (child->type != CT_WORKSPACE || child->num == -1)
489                     continue;
490                 /* Need to check child against current and previous because we
491                  * are traversing multiple lists and thus are not guaranteed
492                  * the relative order between the list of workspaces. */
493                 if (current->num > child->num && (!prev || child->num > prev->num))
494                     prev = child;
495             }
496         }
497     }
498
499     /* Find previous named workspace. */
500     if (!prev) {
501         bool found_current = false;
502         TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
503             /* Skip outputs starting with __, they are internal. */
504             if (output->name[0] == '_' && output->name[1] == '_')
505                 continue;
506             NODES_FOREACH_REVERSE(output_get_content(output)) {
507                 if (child->type != CT_WORKSPACE)
508                     continue;
509                 if (child == current) {
510                     found_current = true;
511                 } else if (child->num == -1 && (current->num != -1 || found_current)) {
512                     prev = child;
513                     goto workspace_prev_end;
514                 }
515             }
516         }
517     }
518
519     /* Find last workspace. */
520     if (!prev) {
521         TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
522             /* Skip outputs starting with __, they are internal. */
523             if (output->name[0] == '_' && output->name[1] == '_')
524                 continue;
525             NODES_FOREACH_REVERSE(output_get_content(output)) {
526                 if (child->type != CT_WORKSPACE)
527                     continue;
528                 if (!prev || child->num > prev->num)
529                     prev = child;
530             }
531         }
532     }
533
534 workspace_prev_end:
535     return prev;
536 }
537
538
539 /*
540  * Focuses the next workspace on the same output.
541  *
542  */
543 Con* workspace_next_on_output(void) {
544     Con *current = con_get_workspace(focused);
545     Con *next = NULL;
546     Con *output  = con_get_output(focused);
547
548     if (current->num == -1) {
549         /* If currently a named workspace, find next named workspace. */
550         next = TAILQ_NEXT(current, nodes);
551     } else {
552         /* If currently a numbered workspace, find next numbered workspace. */
553         NODES_FOREACH(output_get_content(output)) {
554             if (child->type != CT_WORKSPACE)
555                 continue;
556             if (child->num == -1)
557                 break;
558             /* Need to check child against current and next because we are
559              * traversing multiple lists and thus are not guaranteed the
560              * relative order between the list of workspaces. */
561             if (current->num < child->num && (!next || child->num < next->num))
562                 next = child;
563             }
564         }
565
566     /* Find next named workspace. */
567     if (!next) {
568         bool found_current = false;
569         NODES_FOREACH(output_get_content(output)) {
570             if (child->type != CT_WORKSPACE)
571                 continue;
572             if (child == current) {
573                 found_current = 1;
574             } else if (child->num == -1 && (current->num != -1 || found_current)) {
575                 next = child;
576                 goto workspace_next_on_output_end;
577             }
578         }
579     }
580
581     /* Find first workspace. */
582     if (!next) {
583         NODES_FOREACH(output_get_content(output)) {
584             if (child->type != CT_WORKSPACE)
585                 continue;
586             if (!next || (child->num != -1 && child->num < next->num))
587                 next = child;
588         }
589     }
590 workspace_next_on_output_end:
591     return next;
592 }
593
594 /*
595  * Focuses the previous workspace on same output.
596  *
597  */
598 Con* workspace_prev_on_output(void) {
599     Con *current = con_get_workspace(focused);
600     Con *prev = NULL;
601     Con *output  = con_get_output(focused);
602     DLOG("output = %s\n", output->name);
603
604     if (current->num == -1) {
605         /* If named workspace, find previous named workspace. */
606         prev = TAILQ_PREV(current, nodes_head, nodes);
607         if (prev && prev->num != -1)
608             prev = NULL;
609     } else {
610         /* If numbered workspace, find previous numbered workspace. */
611         NODES_FOREACH_REVERSE(output_get_content(output)) {
612             if (child->type != CT_WORKSPACE || child->num == -1)
613                 continue;
614              /* Need to check child against current and previous because we
615              * are traversing multiple lists and thus are not guaranteed
616              * the relative order between the list of workspaces. */
617             if (current->num > child->num && (!prev || child->num > prev->num))
618                 prev = child;
619         }
620     }
621
622     /* Find previous named workspace. */
623     if (!prev) {
624         bool found_current = false;
625         NODES_FOREACH_REVERSE(output_get_content(output)) {
626             if (child->type != CT_WORKSPACE)
627                 continue;
628             if (child == current) {
629                 found_current = true;
630             } else if (child->num == -1 && (current->num != -1 || found_current)) {
631                 prev = child;
632                 goto workspace_prev_on_output_end;
633             }
634         }
635     }
636
637     /* Find last workspace. */
638     if (!prev) {
639         NODES_FOREACH_REVERSE(output_get_content(output)) {
640             if (child->type != CT_WORKSPACE)
641                 continue;
642             if (!prev || child->num > prev->num)
643                 prev = child;
644         }
645     }
646
647 workspace_prev_on_output_end:
648     return prev;
649 }
650
651 /*
652  * Focuses the previously focused workspace.
653  *
654  */
655 void workspace_back_and_forth(void) {
656     if (!previous_workspace_name) {
657         DLOG("No previous workspace name set. Not switching.");
658         return;
659     }
660
661     workspace_show_by_name(previous_workspace_name);
662 }
663
664 static bool get_urgency_flag(Con *con) {
665     Con *child;
666     TAILQ_FOREACH(child, &(con->nodes_head), nodes)
667         if (child->urgent || get_urgency_flag(child))
668             return true;
669
670     TAILQ_FOREACH(child, &(con->floating_head), floating_windows)
671         if (child->urgent || get_urgency_flag(child))
672             return true;
673
674     return false;
675 }
676
677 /*
678  * Goes through all clients on the given workspace and updates the workspace’s
679  * urgent flag accordingly.
680  *
681  */
682 void workspace_update_urgent_flag(Con *ws) {
683     bool old_flag = ws->urgent;
684     ws->urgent = get_urgency_flag(ws);
685     DLOG("Workspace urgency flag changed from %d to %d\n", old_flag, ws->urgent);
686
687     if (old_flag != ws->urgent)
688         ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}");
689 }
690
691 /*
692  * 'Forces' workspace orientation by moving all cons into a new split-con with
693  * the same layout as the workspace and then changing the workspace layout.
694  *
695  */
696 void ws_force_orientation(Con *ws, orientation_t orientation) {
697     /* 1: create a new split container */
698     Con *split = con_new(NULL, NULL);
699     split->parent = ws;
700
701     /* 2: copy layout from workspace */
702     split->layout = ws->layout;
703
704     Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
705
706     /* 3: move the existing cons of this workspace below the new con */
707     DLOG("Moving cons\n");
708     while (!TAILQ_EMPTY(&(ws->nodes_head))) {
709         Con *child = TAILQ_FIRST(&(ws->nodes_head));
710         con_detach(child);
711         con_attach(child, split, true);
712     }
713
714     /* 4: switch workspace layout */
715     ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
716
717     /* 5: attach the new split container to the workspace */
718     DLOG("Attaching new split to ws\n");
719     con_attach(split, ws, false);
720
721     /* 6: fix the percentages */
722     con_fix_percent(ws);
723
724     if (old_focused)
725         con_focus(old_focused);
726 }
727
728 /*
729  * Called when a new con (with a window, not an empty or split con) should be
730  * attached to the workspace (for example when managing a new window or when
731  * moving an existing window to the workspace level).
732  *
733  * Depending on the workspace_layout setting, this function either returns the
734  * workspace itself (default layout) or creates a new stacked/tabbed con and
735  * returns that.
736  *
737  */
738 Con *workspace_attach_to(Con *ws) {
739     DLOG("Attaching a window to workspace %p / %s\n", ws, ws->name);
740
741     if (config.default_layout == L_DEFAULT) {
742         DLOG("Default layout, just attaching it to the workspace itself.\n");
743         return ws;
744     }
745
746     DLOG("Non-default layout, creating a new split container\n");
747     /* 1: create a new split container */
748     Con *new = con_new(NULL, NULL);
749     new->parent = ws;
750     new->split = true;
751
752     /* 2: set the requested layout on the split con */
753     new->layout = config.default_layout;
754
755     /* 4: attach the new split container to the workspace */
756     DLOG("Attaching new split %p to workspace %p\n", new, ws);
757     con_attach(new, ws, false);
758
759     return new;
760 }