i3
con.c
Go to the documentation of this file.
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  * con.c: Functions which deal with containers directly (creating containers,
8  * searching containers, getting specific properties from containers,
9  * …).
10  *
11  */
12 #include "all.h"
13 
14 char *colors[] = {
15  "#ff0000",
16  "#00FF00",
17  "#0000FF",
18  "#ff00ff",
19  "#00ffff",
20  "#ffff00",
21  "#aa0000",
22  "#00aa00",
23  "#0000aa",
24  "#aa00aa"
25 };
26 
27 static void con_on_remove_child(Con *con);
28 
29 /*
30  * Create a new container (and attach it to the given parent, if not NULL).
31  * This function initializes the data structures and creates the appropriate
32  * X11 IDs using x_con_init().
33  *
34  */
35 Con *con_new(Con *parent, i3Window *window) {
36  Con *new = scalloc(sizeof(Con));
37  new->on_remove_child = con_on_remove_child;
39  new->type = CT_CON;
40  new->window = window;
41  new->border_style = config.default_border;
42  static int cnt = 0;
43  DLOG("opening window %d\n", cnt);
44 
45  /* TODO: remove window coloring after test-phase */
46  DLOG("color %s\n", colors[cnt]);
47  new->name = strdup(colors[cnt]);
48  //uint32_t cp = get_colorpixel(colors[cnt]);
49  cnt++;
50  if ((cnt % (sizeof(colors) / sizeof(char*))) == 0)
51  cnt = 0;
52  if (window)
53  x_con_init(new, window->depth);
54  else
55  x_con_init(new, XCB_COPY_FROM_PARENT);
56 
57  TAILQ_INIT(&(new->floating_head));
58  TAILQ_INIT(&(new->nodes_head));
59  TAILQ_INIT(&(new->focus_head));
60  TAILQ_INIT(&(new->swallow_head));
61 
62  if (parent != NULL)
63  con_attach(new, parent, false);
64 
65  return new;
66 }
67 
68 /*
69  * Attaches the given container to the given parent. This happens when moving
70  * a container or when inserting a new container at a specific place in the
71  * tree.
72  *
73  * ignore_focus is to just insert the Con at the end (useful when creating a
74  * new split container *around* some containers, that is, detaching and
75  * attaching them in order without wanting to mess with the focus in between).
76  *
77  */
78 void con_attach(Con *con, Con *parent, bool ignore_focus) {
79  con->parent = parent;
80  Con *loop;
81  Con *current = NULL;
82  struct nodes_head *nodes_head = &(parent->nodes_head);
83  struct focus_head *focus_head = &(parent->focus_head);
84 
85  /* Workspaces are handled differently: they need to be inserted at the
86  * right position. */
87  if (con->type == CT_WORKSPACE) {
88  DLOG("it's a workspace. num = %d\n", con->num);
89  if (con->num == -1 || TAILQ_EMPTY(nodes_head)) {
90  TAILQ_INSERT_TAIL(nodes_head, con, nodes);
91  } else {
92  current = TAILQ_FIRST(nodes_head);
93  if (con->num < current->num) {
94  /* we need to insert the container at the beginning */
95  TAILQ_INSERT_HEAD(nodes_head, con, nodes);
96  } else {
97  while (current->num != -1 && con->num > current->num) {
98  current = TAILQ_NEXT(current, nodes);
99  if (current == TAILQ_END(nodes_head)) {
100  current = NULL;
101  break;
102  }
103  }
104  /* we need to insert con after current, if current is not NULL */
105  if (current)
106  TAILQ_INSERT_BEFORE(current, con, nodes);
107  else TAILQ_INSERT_TAIL(nodes_head, con, nodes);
108  }
109  }
110  goto add_to_focus_head;
111  }
112 
113  if (con->type == CT_FLOATING_CON) {
114  DLOG("Inserting into floating containers\n");
115  TAILQ_INSERT_TAIL(&(parent->floating_head), con, floating_windows);
116  } else {
117  if (!ignore_focus) {
118  /* Get the first tiling container in focus stack */
119  TAILQ_FOREACH(loop, &(parent->focus_head), focused) {
120  if (loop->type == CT_FLOATING_CON)
121  continue;
122  current = loop;
123  break;
124  }
125  }
126 
127  /* When the container is not a split container (but contains a window)
128  * and is attached to a workspace, we check if the user configured a
129  * workspace_layout. This is done in workspace_attach_to, which will
130  * provide us with the container to which we should attach (either the
131  * workspace or a new split container with the configured
132  * workspace_layout).
133  */
134  if (con->window != NULL &&
135  parent->type == CT_WORKSPACE &&
136  config.default_layout != L_DEFAULT) {
137  DLOG("Parent is a workspace. Applying default layout...\n");
138  Con *target = workspace_attach_to(parent);
139 
140  /* Attach the original con to this new split con instead */
141  nodes_head = &(target->nodes_head);
142  focus_head = &(target->focus_head);
143  con->parent = target;
144  current = NULL;
145 
146  DLOG("done\n");
147  }
148 
149  /* Insert the container after the tiling container, if found.
150  * When adding to a CT_OUTPUT, just append one after another. */
151  if (current && parent->type != CT_OUTPUT) {
152  DLOG("Inserting con = %p after last focused tiling con %p\n",
153  con, current);
154  TAILQ_INSERT_AFTER(nodes_head, current, con, nodes);
155  } else TAILQ_INSERT_TAIL(nodes_head, con, nodes);
156  }
157 
158 add_to_focus_head:
159  /* We insert to the TAIL because con_focus() will correct this.
160  * This way, we have the option to insert Cons without having
161  * to focus them. */
162  TAILQ_INSERT_TAIL(focus_head, con, focused);
163 }
164 
165 /*
166  * Detaches the given container from its current parent
167  *
168  */
169 void con_detach(Con *con) {
170  if (con->type == CT_FLOATING_CON) {
171  TAILQ_REMOVE(&(con->parent->floating_head), con, floating_windows);
172  TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
173  } else {
174  TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes);
175  TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
176  }
177 }
178 
179 /*
180  * Sets input focus to the given container. Will be updated in X11 in the next
181  * run of x_push_changes().
182  *
183  */
184 void con_focus(Con *con) {
185  assert(con != NULL);
186  DLOG("con_focus = %p\n", con);
187 
188  /* 1: set focused-pointer to the new con */
189  /* 2: exchange the position of the container in focus stack of the parent all the way up */
190  TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
191  TAILQ_INSERT_HEAD(&(con->parent->focus_head), con, focused);
192  if (con->parent->parent != NULL)
193  con_focus(con->parent);
194 
195  focused = con;
196  if (con->urgent) {
197  con->urgent = false;
199  }
200 }
201 
202 /*
203  * Returns true when this node is a leaf node (has no children)
204  *
205  */
206 bool con_is_leaf(Con *con) {
207  return TAILQ_EMPTY(&(con->nodes_head));
208 }
209 
210 /*
211  * Returns true if this node accepts a window (if the node swallows windows,
212  * it might already have swallowed enough and cannot hold any more).
213  *
214  */
216  /* 1: workspaces never accept direct windows */
217  if (con->type == CT_WORKSPACE)
218  return false;
219 
220  if (con->orientation != NO_ORIENTATION) {
221  DLOG("container %p does not accepts windows, orientation != NO_ORIENTATION\n", con);
222  return false;
223  }
224 
225  /* TODO: if this is a swallowing container, we need to check its max_clients */
226  return (con->window == NULL);
227 }
228 
229 /*
230  * Gets the output container (first container with CT_OUTPUT in hierarchy) this
231  * node is on.
232  *
233  */
235  Con *result = con;
236  while (result != NULL && result->type != CT_OUTPUT)
237  result = result->parent;
238  /* We must be able to get an output because focus can never be set higher
239  * in the tree (root node cannot be focused). */
240  assert(result != NULL);
241  return result;
242 }
243 
244 /*
245  * Gets the workspace container this node is on.
246  *
247  */
249  Con *result = con;
250  while (result != NULL && result->type != CT_WORKSPACE)
251  result = result->parent;
252  return result;
253 }
254 
255 /*
256  * Searches parenst of the given 'con' until it reaches one with the specified
257  * 'orientation'. Aborts when it comes across a floating_con.
258  *
259  */
261  DLOG("Searching for parent of Con %p with orientation %d\n", con, orientation);
262  Con *parent = con->parent;
263  if (parent->type == CT_FLOATING_CON)
264  return NULL;
265  while (con_orientation(parent) != orientation) {
266  DLOG("Need to go one level further up\n");
267  parent = parent->parent;
268  /* Abort when we reach a floating con */
269  if (parent && parent->type == CT_FLOATING_CON)
270  parent = NULL;
271  if (parent == NULL)
272  break;
273  }
274  DLOG("Result: %p\n", parent);
275  return parent;
276 }
277 
278 /*
279  * helper data structure for the breadth-first-search in
280  * con_get_fullscreen_con()
281  *
282  */
283 struct bfs_entry {
285 
286  TAILQ_ENTRY(bfs_entry) entries;
287 };
288 
289 /*
290  * Returns the first fullscreen node below this node.
291  *
292  */
293 Con *con_get_fullscreen_con(Con *con, int fullscreen_mode) {
294  Con *current, *child;
295 
296  /* TODO: is breadth-first-search really appropriate? (check as soon as
297  * fullscreen levels and fullscreen for containers is implemented) */
298  TAILQ_HEAD(bfs_head, bfs_entry) bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head);
299  struct bfs_entry *entry = smalloc(sizeof(struct bfs_entry));
300  entry->con = con;
301  TAILQ_INSERT_TAIL(&bfs_head, entry, entries);
302 
303  while (!TAILQ_EMPTY(&bfs_head)) {
304  entry = TAILQ_FIRST(&bfs_head);
305  current = entry->con;
306  if (current != con && current->fullscreen_mode == fullscreen_mode) {
307  /* empty the queue */
308  while (!TAILQ_EMPTY(&bfs_head)) {
309  entry = TAILQ_FIRST(&bfs_head);
310  TAILQ_REMOVE(&bfs_head, entry, entries);
311  free(entry);
312  }
313  return current;
314  }
315 
316  TAILQ_REMOVE(&bfs_head, entry, entries);
317  free(entry);
318 
319  TAILQ_FOREACH(child, &(current->nodes_head), nodes) {
320  entry = smalloc(sizeof(struct bfs_entry));
321  entry->con = child;
322  TAILQ_INSERT_TAIL(&bfs_head, entry, entries);
323  }
324 
325  TAILQ_FOREACH(child, &(current->floating_head), floating_windows) {
326  entry = smalloc(sizeof(struct bfs_entry));
327  entry->con = child;
328  TAILQ_INSERT_TAIL(&bfs_head, entry, entries);
329  }
330  }
331 
332  return NULL;
333 }
334 
335 /*
336  * Returns true if the node is floating.
337  *
338  */
340  assert(con != NULL);
341  DLOG("checking if con %p is floating\n", con);
342  return (con->floating >= FLOATING_AUTO_ON);
343 }
344 
345 /*
346  * Checks if the given container is either floating or inside some floating
347  * container. It returns the FLOATING_CON container.
348  *
349  */
351  assert(con != NULL);
352  if (con->type == CT_FLOATING_CON)
353  return con;
354 
355  if (con->floating >= FLOATING_AUTO_ON)
356  return con->parent;
357 
358  if (con->type == CT_WORKSPACE || con->type == CT_OUTPUT)
359  return NULL;
360 
361  return con_inside_floating(con->parent);
362 }
363 
364 /*
365  * Checks if the given container is inside a focused container.
366  *
367  */
369  if (con == focused)
370  return true;
371  if (!con->parent)
372  return false;
373  return con_inside_focused(con->parent);
374 }
375 
376 /*
377  * Returns the container with the given client window ID or NULL if no such
378  * container exists.
379  *
380  */
381 Con *con_by_window_id(xcb_window_t window) {
382  Con *con;
384  if (con->window != NULL && con->window->id == window)
385  return con;
386  return NULL;
387 }
388 
389 /*
390  * Returns the container with the given frame ID or NULL if no such container
391  * exists.
392  *
393  */
394 Con *con_by_frame_id(xcb_window_t frame) {
395  Con *con;
397  if (con->frame == frame)
398  return con;
399  return NULL;
400 }
401 
402 /*
403  * Returns the first container below 'con' which wants to swallow this window
404  * TODO: priority
405  *
406  */
407 Con *con_for_window(Con *con, i3Window *window, Match **store_match) {
408  Con *child;
409  Match *match;
410  //DLOG("searching con for window %p starting at con %p\n", window, con);
411  //DLOG("class == %s\n", window->class_class);
412 
413  TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
414  TAILQ_FOREACH(match, &(child->swallow_head), matches) {
415  if (!match_matches_window(match, window))
416  continue;
417  if (store_match != NULL)
418  *store_match = match;
419  return child;
420  }
421  Con *result = con_for_window(child, window, store_match);
422  if (result != NULL)
423  return result;
424  }
425 
426  TAILQ_FOREACH(child, &(con->floating_head), floating_windows) {
427  TAILQ_FOREACH(match, &(child->swallow_head), matches) {
428  if (!match_matches_window(match, window))
429  continue;
430  if (store_match != NULL)
431  *store_match = match;
432  return child;
433  }
434  Con *result = con_for_window(child, window, store_match);
435  if (result != NULL)
436  return result;
437  }
438 
439  return NULL;
440 }
441 
442 /*
443  * Returns the number of children of this container.
444  *
445  */
447  Con *child;
448  int children = 0;
449 
450  TAILQ_FOREACH(child, &(con->nodes_head), nodes)
451  children++;
452 
453  return children;
454 }
455 
456 /*
457  * Updates the percent attribute of the children of the given container. This
458  * function needs to be called when a window is added or removed from a
459  * container.
460  *
461  */
463  Con *child;
464  int children = con_num_children(con);
465 
466  // calculate how much we have distributed and how many containers
467  // with a percentage set we have
468  double total = 0.0;
469  int children_with_percent = 0;
470  TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
471  if (child->percent > 0.0) {
472  total += child->percent;
473  ++children_with_percent;
474  }
475  }
476 
477  // if there were children without a percentage set, set to a value that
478  // will make those children proportional to all others
479  if (children_with_percent != children) {
480  TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
481  if (child->percent <= 0.0) {
482  if (children_with_percent == 0)
483  total += (child->percent = 1.0);
484  else total += (child->percent = total / children_with_percent);
485  }
486  }
487  }
488 
489  // if we got a zero, just distribute the space equally, otherwise
490  // distribute according to the proportions we got
491  if (total == 0.0) {
492  TAILQ_FOREACH(child, &(con->nodes_head), nodes)
493  child->percent = 1.0 / children;
494  } else if (total != 1.0) {
495  TAILQ_FOREACH(child, &(con->nodes_head), nodes)
496  child->percent /= total;
497  }
498 }
499 
500 /*
501  * Toggles fullscreen mode for the given container. Fullscreen mode will not be
502  * entered when there already is a fullscreen container on this workspace.
503  *
504  */
505 void con_toggle_fullscreen(Con *con, int fullscreen_mode) {
506  Con *workspace, *fullscreen;
507 
508  if (con->type == CT_WORKSPACE) {
509  DLOG("You cannot make a workspace fullscreen.\n");
510  return;
511  }
512 
513  DLOG("toggling fullscreen for %p / %s\n", con, con->name);
514  if (con->fullscreen_mode == CF_NONE) {
515  /* 1: check if there already is a fullscreen con */
516  if (fullscreen_mode == CF_GLOBAL)
517  fullscreen = con_get_fullscreen_con(croot, CF_GLOBAL);
518  else {
519  workspace = con_get_workspace(con);
520  fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
521  }
522  if (fullscreen != NULL) {
523  /* Disable fullscreen for the currently fullscreened
524  * container and enable it for the one the user wants
525  * to have in fullscreen mode. */
526  LOG("Disabling fullscreen for (%p/%s) upon user request\n",
527  fullscreen, fullscreen->name);
528  fullscreen->fullscreen_mode = CF_NONE;
529  }
530 
531  /* 2: enable fullscreen */
532  con->fullscreen_mode = fullscreen_mode;
533  } else {
534  /* 1: disable fullscreen */
535  con->fullscreen_mode = CF_NONE;
536  }
537 
538  DLOG("mode now: %d\n", con->fullscreen_mode);
539 
540  /* update _NET_WM_STATE if this container has a window */
541  /* TODO: when a window is assigned to a container which is already
542  * fullscreened, this state needs to be pushed to the client, too */
543  if (con->window == NULL)
544  return;
545 
546  uint32_t values[1];
547  unsigned int num = 0;
548 
549  if (con->fullscreen_mode != CF_NONE)
550  values[num++] = A__NET_WM_STATE_FULLSCREEN;
551 
552  xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id,
553  A__NET_WM_STATE, XCB_ATOM_ATOM, 32, num, values);
554 }
555 
556 /*
557  * Moves the given container to the currently focused container on the given
558  * workspace.
559  *
560  * The fix_coordinates flag will translate the current coordinates (offset from
561  * the monitor position basically) to appropriate coordinates on the
562  * destination workspace.
563  * Not enabling this behaviour comes in handy when this function gets called by
564  * floating_maybe_reassign_ws, which will only "move" a floating window when it
565  * *already* changed its coordinates to a different output.
566  *
567  * The dont_warp flag disables pointer warping and will be set when this
568  * function is called while dragging a floating window.
569  *
570  * TODO: is there a better place for this function?
571  *
572  */
573 void con_move_to_workspace(Con *con, Con *workspace, bool fix_coordinates, bool dont_warp) {
574  if (con->type == CT_WORKSPACE) {
575  DLOG("Moving workspaces is not yet implemented.\n");
576  return;
577  }
578 
579  if (con_is_floating(con)) {
580  DLOG("Using FLOATINGCON instead\n");
581  con = con->parent;
582  }
583 
584  Con *source_output = con_get_output(con),
585  *dest_output = con_get_output(workspace);
586 
587  /* 1: save the container which is going to be focused after the current
588  * container is moved away */
589  Con *focus_next = con_next_focused(con);
590 
591  /* 2: get the focused container of this workspace */
592  Con *next = con_descend_focused(workspace);
593 
594  /* 3: we go up one level, but only when next is a normal container */
595  if (next->type != CT_WORKSPACE) {
596  DLOG("next originally = %p / %s / type %d\n", next, next->name, next->type);
597  next = next->parent;
598  }
599 
600  /* 4: if the target container is floating, we get the workspace instead.
601  * Only tiling windows need to get inserted next to the current container.
602  * */
603  Con *floatingcon = con_inside_floating(next);
604  if (floatingcon != NULL) {
605  DLOG("floatingcon, going up even further\n");
606  next = floatingcon->parent;
607  }
608 
609  if (con->type == CT_FLOATING_CON) {
610  Con *ws = con_get_workspace(next);
611  DLOG("This is a floating window, using workspace %p / %s\n", ws, ws->name);
612  next = ws;
613  }
614 
615  if (source_output != dest_output) {
616  /* Take the relative coordinates of the current output, then add them
617  * to the coordinate space of the correct output */
618  if (fix_coordinates && con->type == CT_FLOATING_CON) {
619  floating_fix_coordinates(con, &(source_output->rect), &(dest_output->rect));
620  } else DLOG("Not fixing coordinates, fix_coordinates flag = %d\n", fix_coordinates);
621 
622  /* If moving to a visible workspace, call show so it can be considered
623  * focused. Must do before attaching because workspace_show checks to see
624  * if focused container is in its area. */
625  if (workspace_is_visible(workspace)) {
626  workspace_show(workspace);
627 
628  /* Don’t warp if told so (when dragging floating windows with the
629  * mouse for example) */
630  if (dont_warp)
631  x_set_warp_to(NULL);
632  else
633  x_set_warp_to(&(con->rect));
634  }
635  }
636 
637  DLOG("Re-attaching container to %p / %s\n", next, next->name);
638  /* 5: re-attach the con to the parent of this focused container */
639  Con *parent = con->parent;
640  con_detach(con);
641  con_attach(con, next, false);
642 
643  /* 6: fix the percentages */
644  con_fix_percent(parent);
645  con->percent = 0.0;
646  con_fix_percent(next);
647 
648  /* 7: focus the con on the target workspace (the X focus is only updated by
649  * calling tree_render(), so for the "real" focus this is a no-op).
650  * We don’t focus the con for i3 pseudo workspaces like __i3_scratch and
651  * we don’t focus when there is a fullscreen con on that workspace. */
652  if ((workspace->name[0] != '_' || workspace->name[1] != '_') &&
653  con_get_fullscreen_con(workspace, CF_OUTPUT) == NULL)
655 
656  /* 8: when moving to a visible workspace on a different output, we keep the
657  * con focused. Otherwise, we leave the focus on the current workspace as we
658  * don’t want to focus invisible workspaces */
659  if (source_output != dest_output &&
660  workspace_is_visible(workspace)) {
661  DLOG("Moved to a different output, focusing target\n");
662  } else {
663  /* Descend focus stack in case focus_next is a workspace which can
664  * occur if we move to the same workspace. Also show current workspace
665  * to ensure it is focused. */
666  workspace_show(con_get_workspace(focus_next));
667  con_focus(con_descend_focused(focus_next));
668  }
669 
670  CALL(parent, on_remove_child);
671 }
672 
673 /*
674  * Returns the orientation of the given container (for stacked containers,
675  * vertical orientation is used regardless of the actual orientation of the
676  * container).
677  *
678  */
680  /* stacking containers behave like they are in vertical orientation */
681  if (con->layout == L_STACKED)
682  return VERT;
683 
684  if (con->layout == L_TABBED)
685  return HORIZ;
686 
687  return con->orientation;
688 }
689 
690 /*
691  * Returns the container which will be focused next when the given container
692  * is not available anymore. Called in tree_close and con_move_to_workspace
693  * to properly restore focus.
694  *
695  */
697  Con *next;
698  /* floating containers are attached to a workspace, so we focus either the
699  * next floating container (if any) or the workspace itself. */
700  if (con->type == CT_FLOATING_CON) {
701  DLOG("selecting next for CT_FLOATING_CON\n");
702  next = TAILQ_NEXT(con, floating_windows);
703  DLOG("next = %p\n", next);
704  if (!next) {
705  next = TAILQ_PREV(con, floating_head, floating_windows);
706  DLOG("using prev, next = %p\n", next);
707  }
708  if (!next) {
709  Con *ws = con_get_workspace(con);
710  next = ws;
711  DLOG("no more floating containers for next = %p, restoring workspace focus\n", next);
712  while (next != TAILQ_END(&(ws->focus_head)) && !TAILQ_EMPTY(&(next->focus_head))) {
713  next = TAILQ_FIRST(&(next->focus_head));
714  if (next == con) {
715  DLOG("skipping container itself, we want the next client\n");
716  next = TAILQ_NEXT(next, focused);
717  }
718  }
719  if (next == TAILQ_END(&(ws->focus_head))) {
720  DLOG("Focus list empty, returning ws\n");
721  next = ws;
722  }
723  } else {
724  /* Instead of returning the next CT_FLOATING_CON, we descend it to
725  * get an actual window to focus. */
726  next = con_descend_focused(next);
727  }
728  return next;
729  }
730 
731  /* dock clients cannot be focused, so we focus the workspace instead */
732  if (con->parent->type == CT_DOCKAREA) {
733  DLOG("selecting workspace for dock client\n");
735  }
736 
737  /* if 'con' is not the first entry in the focus stack, use the first one as
738  * it’s currently focused already */
739  Con *first = TAILQ_FIRST(&(con->parent->focus_head));
740  if (first != con) {
741  DLOG("Using first entry %p\n", first);
742  next = first;
743  } else {
744  /* try to focus the next container on the same level as this one or fall
745  * back to its parent */
746  if (!(next = TAILQ_NEXT(con, focused)))
747  next = con->parent;
748  }
749 
750  /* now go down the focus stack as far as
751  * possible, excluding the current container */
752  while (!TAILQ_EMPTY(&(next->focus_head)) &&
753  TAILQ_FIRST(&(next->focus_head)) != con)
754  next = TAILQ_FIRST(&(next->focus_head));
755 
756  return next;
757 }
758 
759 /*
760  * Get the next/previous container in the specified orientation. This may
761  * travel up until it finds a container with suitable orientation.
762  *
763  */
764 Con *con_get_next(Con *con, char way, orientation_t orientation) {
765  DLOG("con_get_next(way=%c, orientation=%d)\n", way, orientation);
766  /* 1: get the first parent with the same orientation */
767  Con *cur = con;
768  while (con_orientation(cur->parent) != orientation) {
769  DLOG("need to go one level further up\n");
770  if (cur->parent->type == CT_WORKSPACE) {
771  LOG("that's a workspace, we can't go further up\n");
772  return NULL;
773  }
774  cur = cur->parent;
775  }
776 
777  /* 2: chose next (or previous) */
778  Con *next;
779  if (way == 'n') {
780  next = TAILQ_NEXT(cur, nodes);
781  /* if we are at the end of the list, we need to wrap */
782  if (next == TAILQ_END(&(parent->nodes_head)))
783  return NULL;
784  } else {
785  next = TAILQ_PREV(cur, nodes_head, nodes);
786  /* if we are at the end of the list, we need to wrap */
787  if (next == TAILQ_END(&(cur->nodes_head)))
788  return NULL;
789  }
790  DLOG("next = %p\n", next);
791 
792  return next;
793 }
794 
795 /*
796  * Returns the focused con inside this client, descending the tree as far as
797  * possible. This comes in handy when attaching a con to a workspace at the
798  * currently focused position, for example.
799  *
800  */
802  Con *next = con;
803  while (next != focused && !TAILQ_EMPTY(&(next->focus_head)))
804  next = TAILQ_FIRST(&(next->focus_head));
805  return next;
806 }
807 
808 /*
809  * Returns the focused con inside this client, descending the tree as far as
810  * possible. This comes in handy when attaching a con to a workspace at the
811  * currently focused position, for example.
812  *
813  * Works like con_descend_focused but considers only tiling cons.
814  *
815  */
817  Con *next = con;
818  Con *before;
819  Con *child;
820  if (next == focused)
821  return next;
822  do {
823  before = next;
824  TAILQ_FOREACH(child, &(next->focus_head), focused) {
825  if (child->type == CT_FLOATING_CON)
826  continue;
827 
828  next = child;
829  break;
830  }
831  } while (before != next && next != focused);
832  return next;
833 }
834 
835 /*
836  * Returns the leftmost, rightmost, etc. container in sub-tree. For example, if
837  * direction is D_LEFT, then we return the rightmost container and if direction
838  * is D_RIGHT, we return the leftmost container. This is because if we are
839  * moving D_LEFT, and thus want the rightmost container.
840  *
841  */
843  Con *most = NULL;
844  int orientation = con_orientation(con);
845  DLOG("con_descend_direction(%p, orientation %d, direction %d)\n", con, orientation, direction);
846  if (direction == D_LEFT || direction == D_RIGHT) {
847  if (orientation == HORIZ) {
848  /* If the direction is horizontal, we can use either the first
849  * (D_RIGHT) or the last con (D_LEFT) */
850  if (direction == D_RIGHT)
851  most = TAILQ_FIRST(&(con->nodes_head));
852  else most = TAILQ_LAST(&(con->nodes_head), nodes_head);
853  } else if (orientation == VERT) {
854  /* Wrong orientation. We use the last focused con. Within that con,
855  * we recurse to chose the left/right con or at least the last
856  * focused one. */
857  most = TAILQ_FIRST(&(con->focus_head));
858  } else {
859  /* If the con has no orientation set, it’s not a split container
860  * but a container with a client window, so stop recursing */
861  return con;
862  }
863  }
864 
865  if (direction == D_UP || direction == D_DOWN) {
866  if (orientation == VERT) {
867  /* If the direction is vertical, we can use either the first
868  * (D_DOWN) or the last con (D_UP) */
869  if (direction == D_UP)
870  most = TAILQ_LAST(&(con->nodes_head), nodes_head);
871  else most = TAILQ_FIRST(&(con->nodes_head));
872  } else if (orientation == HORIZ) {
873  /* Wrong orientation. We use the last focused con. Within that con,
874  * we recurse to chose the top/bottom con or at least the last
875  * focused one. */
876  most = TAILQ_FIRST(&(con->focus_head));
877  } else {
878  /* If the con has no orientation set, it’s not a split container
879  * but a container with a client window, so stop recursing */
880  return con;
881  }
882  }
883 
884  if (!most)
885  return con;
886  return con_descend_direction(most, direction);
887 }
888 
889 /*
890  * Returns a "relative" Rect which contains the amount of pixels that need to
891  * be added to the original Rect to get the final position (obviously the
892  * amount of pixels for normal, 1pixel and borderless are different).
893  *
894  */
896  switch (con_border_style(con)) {
897  case BS_NORMAL:
898  return (Rect){2, 0, -(2 * 2), -2};
899 
900  case BS_1PIXEL:
901  return (Rect){1, 1, -2, -2};
902 
903  case BS_NONE:
904  return (Rect){0, 0, 0, 0};
905 
906  default:
907  assert(false);
908  }
909 }
910 
911 /*
912  * Use this function to get a container’s border style. This is important
913  * because when inside a stack, the border style is always BS_NORMAL.
914  * For tabbed mode, the same applies, with one exception: when the container is
915  * borderless and the only element in the tabbed container, the border is not
916  * rendered.
917  *
918  * For children of a CT_DOCKAREA, the border style is always none.
919  *
920  */
922  Con *fs = con_get_fullscreen_con(con->parent, CF_OUTPUT);
923  if (fs == con) {
924  DLOG("this one is fullscreen! overriding BS_NONE\n");
925  return BS_NONE;
926  }
927 
928  if (con->parent->layout == L_STACKED)
929  return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL);
930 
931  if (con->parent->layout == L_TABBED && con->border_style != BS_NORMAL)
932  return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL);
933 
934  if (con->parent->type == CT_DOCKAREA)
935  return BS_NONE;
936 
937  return con->border_style;
938 }
939 
940 /*
941  * Sets the given border style on con, correctly keeping the position/size of a
942  * floating window.
943  *
944  */
945 void con_set_border_style(Con *con, int border_style) {
946  /* Handle the simple case: non-floating containerns */
947  if (!con_is_floating(con)) {
948  con->border_style = border_style;
949  return;
950  }
951 
952  /* For floating containers, we want to keep the position/size of the
953  * *window* itself. We first add the border pixels to con->rect to make
954  * con->rect represent the absolute position of the window. Then, we change
955  * the border and subtract the new border pixels. Afterwards, we update
956  * parent->rect to contain con. */
957  DLOG("This is a floating container\n");
958 
959  Rect bsr = con_border_style_rect(con);
960  con->rect.x += bsr.x;
961  con->rect.y += bsr.y;
962  con->rect.width += bsr.width;
963  con->rect.height += bsr.height;
964 
965  /* Change the border style, get new border/decoration values. */
966  con->border_style = border_style;
967  bsr = con_border_style_rect(con);
968  int deco_height =
969  (con->border_style == BS_NORMAL ? config.font.height + 5 : 0);
970 
971  con->rect.x -= bsr.x;
972  con->rect.y -= bsr.y;
973  con->rect.width -= bsr.width;
974  con->rect.height -= bsr.height;
975 
976  Con *parent = con->parent;
977  parent->rect.x = con->rect.x;
978  parent->rect.y = con->rect.y - deco_height;
979  parent->rect.width = con->rect.width;
980  parent->rect.height = con->rect.height + deco_height;
981 }
982 
983 /*
984  * This function changes the layout of a given container. Use it to handle
985  * special cases like changing a whole workspace to stacked/tabbed (creates a
986  * new split container before).
987  *
988  */
989 void con_set_layout(Con *con, int layout) {
990  /* When the container type is CT_WORKSPACE, the user wants to change the
991  * whole workspace into stacked/tabbed mode. To do this and still allow
992  * intuitive operations (like level-up and then opening a new window), we
993  * need to create a new split container. */
994  if (con->type == CT_WORKSPACE) {
995  DLOG("Creating new split container\n");
996  /* 1: create a new split container */
997  Con *new = con_new(NULL, NULL);
998  new->parent = con;
999 
1000  /* 2: set the requested layout on the split con */
1001  new->layout = layout;
1002 
1003  /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs
1004  * to be set. Otherwise, this con will not be interpreted as a split
1005  * container. */
1007  new->orientation = (con->rect.height > con->rect.width) ? VERT : HORIZ;
1008  } else {
1009  new->orientation = config.default_orientation;
1010  }
1011 
1012  Con *old_focused = TAILQ_FIRST(&(con->focus_head));
1013  if (old_focused == TAILQ_END(&(con->focus_head)))
1014  old_focused = NULL;
1015 
1016  /* 4: move the existing cons of this workspace below the new con */
1017  DLOG("Moving cons\n");
1018  Con *child;
1019  while (!TAILQ_EMPTY(&(con->nodes_head))) {
1020  child = TAILQ_FIRST(&(con->nodes_head));
1021  con_detach(child);
1022  con_attach(child, new, true);
1023  }
1024 
1025  /* 4: attach the new split container to the workspace */
1026  DLOG("Attaching new split to ws\n");
1027  con_attach(new, con, false);
1028 
1029  if (old_focused)
1030  con_focus(old_focused);
1031 
1033 
1034  return;
1035  }
1036 
1037  con->layout = layout;
1038 }
1039 
1040 /*
1041  * Callback which will be called when removing a child from the given con.
1042  * Kills the container if it is empty and replaces it with the child if there
1043  * is exactly one child.
1044  *
1045  */
1046 static void con_on_remove_child(Con *con) {
1047  DLOG("on_remove_child\n");
1048 
1049  /* Every container 'above' (in the hierarchy) the workspace content should
1050  * not be closed when the last child was removed */
1051  if (con->type == CT_OUTPUT ||
1052  con->type == CT_ROOT ||
1053  con->type == CT_DOCKAREA) {
1054  DLOG("not handling, type = %d\n", con->type);
1055  return;
1056  }
1057 
1058  /* For workspaces, close them only if they're not visible anymore */
1059  if (con->type == CT_WORKSPACE) {
1060  if (TAILQ_EMPTY(&(con->focus_head)) && !workspace_is_visible(con)) {
1061  LOG("Closing old workspace (%p / %s), it is empty\n", con, con->name);
1062  tree_close(con, DONT_KILL_WINDOW, false, false);
1063  ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
1064  }
1065  return;
1066  }
1067 
1068  /* TODO: check if this container would swallow any other client and
1069  * don’t close it automatically. */
1070  int children = con_num_children(con);
1071  if (children == 0) {
1072  DLOG("Container empty, closing\n");
1073  tree_close(con, DONT_KILL_WINDOW, false, false);
1074  return;
1075  }
1076 }
1077 
1078 /*
1079  * Determines the minimum size of the given con by looking at its children (for
1080  * split/stacked/tabbed cons). Will be called when resizing floating cons
1081  *
1082  */
1084  DLOG("Determining minimum size for con %p\n", con);
1085 
1086  if (con_is_leaf(con)) {
1087  DLOG("leaf node, returning 75x50\n");
1088  return (Rect){ 0, 0, 75, 50 };
1089  }
1090 
1091  if (con->type == CT_FLOATING_CON) {
1092  DLOG("floating con\n");
1093  Con *child = TAILQ_FIRST(&(con->nodes_head));
1094  return con_minimum_size(child);
1095  }
1096 
1097  if (con->layout == L_STACKED || con->layout == L_TABBED) {
1098  uint32_t max_width = 0, max_height = 0, deco_height = 0;
1099  Con *child;
1100  TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
1101  Rect min = con_minimum_size(child);
1102  deco_height += child->deco_rect.height;
1103  max_width = max(max_width, min.width);
1104  max_height = max(max_height, min.height);
1105  }
1106  DLOG("stacked/tabbed now, returning %d x %d + deco_rect = %d\n",
1107  max_width, max_height, deco_height);
1108  return (Rect){ 0, 0, max_width, max_height + deco_height };
1109  }
1110 
1111  /* For horizontal/vertical split containers we sum up the width (h-split)
1112  * or height (v-split) and use the maximum of the height (h-split) or width
1113  * (v-split) as minimum size. */
1114  if (con->orientation == HORIZ || con->orientation == VERT) {
1115  uint32_t width = 0, height = 0;
1116  Con *child;
1117  TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
1118  Rect min = con_minimum_size(child);
1119  if (con->orientation == HORIZ) {
1120  width += min.width;
1121  height = max(height, min.height);
1122  } else {
1123  height += min.height;
1124  width = max(width, min.width);
1125  }
1126  }
1127  DLOG("split container, returning width = %d x height = %d\n", width, height);
1128  return (Rect){ 0, 0, width, height };
1129  }
1130 
1131  ELOG("Unhandled case, type = %d, layout = %d, orientation = %d\n",
1132  con->type, con->layout, con->orientation);
1133  assert(false);
1134 }