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