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