]> git.sur5r.net Git - i3/i3/blob - src/render.c
Move precalculating the container sizes into a separate function
[i3/i3] / src / render.c
1 #undef I3__FILE__
2 #define I3__FILE__ "render.c"
3 /*
4  * vim:ts=4:sw=4:expandtab
5  *
6  * i3 - an improved dynamic tiling window manager
7  * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
8  *
9  * render.c: Renders (determines position/sizes) the layout tree, updating the
10  *           various rects. Needs to be pushed to X11 (see x.c) to be visible.
11  *
12  */
13 #include "all.h"
14
15 /* Forward declarations */
16 static int *precalculate_sizes(Con *con, render_params *p);
17 static void render_con_split(Con *con, Con *child, render_params *p, int i);
18 static void render_con_stacked(Con *con, Con *child, render_params *p, int i);
19 static void render_con_tabbed(Con *con, Con *child, render_params *p, int i);
20 static void render_con_dockarea(Con *con, Con *child, render_params *p);
21
22 /* change this to 'true' if you want to have additional borders around every
23  * container (for debugging purposes) */
24 static bool show_debug_borders = false;
25
26 /*
27  * Returns the height for the decorations
28  */
29 int render_deco_height(void) {
30     int deco_height = config.font.height + 4;
31     if (config.font.height & 0x01)
32         ++deco_height;
33     return deco_height;
34 }
35
36 /*
37  * Renders a container with layout L_OUTPUT. In this layout, all CT_DOCKAREAs
38  * get the height of their content and the remaining CT_CON gets the rest.
39  *
40  */
41 static void render_l_output(Con *con) {
42     Con *child, *dockchild;
43
44     int x = con->rect.x;
45     int y = con->rect.y;
46     int height = con->rect.height;
47
48     /* Find the content container and ensure that there is exactly one. Also
49      * check for any non-CT_DOCKAREA clients. */
50     Con *content = NULL;
51     TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
52         if (child->type == CT_CON) {
53             if (content != NULL) {
54                 DLOG("More than one CT_CON on output container\n");
55                 assert(false);
56             }
57             content = child;
58         } else if (child->type != CT_DOCKAREA) {
59             DLOG("Child %p of type %d is inside the OUTPUT con\n", child, child->type);
60             assert(false);
61         }
62     }
63
64     if (content == NULL) {
65         DLOG("Skipping this output because it is currently being destroyed.\n");
66         return;
67     }
68
69     /* We need to find out if there is a fullscreen con on the current workspace
70      * and take the short-cut to render it directly (the user does not want to
71      * see the dockareas in that case) */
72     Con *ws = con_get_fullscreen_con(content, CF_OUTPUT);
73     if (!ws) {
74         DLOG("Skipping this output because it is currently being destroyed.\n");
75         return;
76     }
77     Con *fullscreen = con_get_fullscreen_con(ws, CF_OUTPUT);
78     if (fullscreen) {
79         fullscreen->rect = con->rect;
80         x_raise_con(fullscreen);
81         render_con(fullscreen, true);
82         return;
83     }
84
85     /* First pass: determine the height of all CT_DOCKAREAs (the sum of their
86      * children) and figure out how many pixels we have left for the rest */
87     TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
88         if (child->type != CT_DOCKAREA)
89             continue;
90
91         child->rect.height = 0;
92         TAILQ_FOREACH(dockchild, &(child->nodes_head), nodes)
93         child->rect.height += dockchild->geometry.height;
94
95         height -= child->rect.height;
96     }
97
98     /* Second pass: Set the widths/heights */
99     TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
100         if (child->type == CT_CON) {
101             child->rect.x = x;
102             child->rect.y = y;
103             child->rect.width = con->rect.width;
104             child->rect.height = height;
105         }
106
107         child->rect.x = x;
108         child->rect.y = y;
109         child->rect.width = con->rect.width;
110
111         child->deco_rect.x = 0;
112         child->deco_rect.y = 0;
113         child->deco_rect.width = 0;
114         child->deco_rect.height = 0;
115
116         y += child->rect.height;
117
118         DLOG("child at (%d, %d) with (%d x %d)\n",
119              child->rect.x, child->rect.y, child->rect.width, child->rect.height);
120         x_raise_con(child);
121         render_con(child, false);
122     }
123 }
124
125 /*
126  * "Renders" the given container (and its children), meaning that all rects are
127  * updated correctly. Note that this function does not call any xcb_*
128  * functions, so the changes are completely done in memory only (and
129  * side-effect free). As soon as you call x_push_changes(), the changes will be
130  * updated in X11.
131  *
132  */
133 void render_con(Con *con, bool render_fullscreen) {
134     render_params params = {
135         .rect = con->rect,
136         .x = con->rect.x,
137         .y = con->rect.y,
138         .children = con_num_children(con)};
139
140     DLOG("Rendering %snode %p / %s / layout %d / children %d\n",
141          (render_fullscreen ? "fullscreen " : ""), con, con->name, con->layout,
142          params.children);
143
144     /* Display a border if this is a leaf node. For container nodes, we don’t
145      * draw borders (except when in debug mode) */
146     if (show_debug_borders) {
147         params.rect.x += 2;
148         params.rect.y += 2;
149         params.rect.width -= 2 * 2;
150         params.rect.height -= 2 * 2;
151     }
152
153     int i = 0;
154     con->mapped = true;
155
156     /* if this container contains a window, set the coordinates */
157     if (con->window) {
158         /* depending on the border style, the rect of the child window
159          * needs to be smaller */
160         Rect *inset = &(con->window_rect);
161         *inset = (Rect){0, 0, con->rect.width, con->rect.height};
162         if (!render_fullscreen)
163             *inset = rect_add(*inset, con_border_style_rect(con));
164
165         /* Obey x11 border */
166         inset->width -= (2 * con->border_width);
167         inset->height -= (2 * con->border_width);
168
169         /* Obey the aspect ratio, if any, unless we are in fullscreen mode.
170          *
171          * The spec isn’t explicit on whether the aspect ratio hints should be
172          * respected during fullscreen mode. Other WMs such as Openbox don’t do
173          * that, and this post suggests that this is the correct way to do it:
174          * http://mail.gnome.org/archives/wm-spec-list/2003-May/msg00007.html
175          *
176          * Ignoring aspect ratio during fullscreen was necessary to fix MPlayer
177          * subtitle rendering, see http://bugs.i3wm.org/594 */
178         if (!render_fullscreen &&
179             con->window->aspect_ratio > 0.0) {
180             DLOG("aspect_ratio = %f, current width/height are %d/%d\n",
181                  con->window->aspect_ratio, inset->width, inset->height);
182             double new_height = inset->height + 1;
183             int new_width = inset->width;
184
185             while (new_height > inset->height) {
186                 new_height = (1.0 / con->window->aspect_ratio) * new_width;
187
188                 if (new_height > inset->height)
189                     new_width--;
190             }
191             /* Center the window */
192             inset->y += ceil(inset->height / 2) - floor((new_height + .5) / 2);
193             inset->x += ceil(inset->width / 2) - floor(new_width / 2);
194
195             inset->height = new_height + .5;
196             inset->width = new_width;
197         }
198
199         /* NB: We used to respect resize increment size hints for tiling
200          * windows up until commit 0db93d9 here. However, since all terminal
201          * emulators cope with ignoring the size hints in a better way than we
202          * can (by providing their fake-transparency or background color), this
203          * code was removed. See also http://bugs.i3wm.org/540 */
204
205         DLOG("child will be at %dx%d with size %dx%d\n", inset->x, inset->y, inset->width, inset->height);
206     }
207
208     /* Check for fullscreen nodes */
209     Con *fullscreen = NULL;
210     if (con->type != CT_OUTPUT) {
211         fullscreen = con_get_fullscreen_con(con, (con->type == CT_ROOT ? CF_GLOBAL : CF_OUTPUT));
212     }
213     if (fullscreen) {
214         fullscreen->rect = params.rect;
215         x_raise_con(fullscreen);
216         render_con(fullscreen, true);
217         /* Fullscreen containers are either global (underneath the CT_ROOT
218          * container) or per-output (underneath the CT_CONTENT container). For
219          * global fullscreen containers, we cannot abort rendering here yet,
220          * because the floating windows (with popup_during_fullscreen smart)
221          * have not yet been rendered (see the CT_ROOT code path below). See
222          * also http://bugs.i3wm.org/1393 */
223         if (con->type != CT_ROOT) {
224             return;
225         }
226     }
227
228     /* find the height for the decorations */
229     params.deco_height = render_deco_height();
230
231     /* precalculate the sizes to be able to correct rounding errors */
232     params.sizes = precalculate_sizes(con, &params);
233
234     if (con->layout == L_OUTPUT) {
235         /* Skip i3-internal outputs */
236         if (con_is_internal(con))
237             goto free_params;
238         render_l_output(con);
239     } else if (con->type == CT_ROOT) {
240         Con *output;
241         if (!fullscreen) {
242             TAILQ_FOREACH(output, &(con->nodes_head), nodes) {
243                 render_con(output, false);
244             }
245         }
246
247         /* We need to render floating windows after rendering all outputs’
248          * tiling windows because they need to be on top of *every* output at
249          * all times. This is important when the user places floating
250          * windows/containers so that they overlap on another output. */
251         DLOG("Rendering floating windows:\n");
252         TAILQ_FOREACH(output, &(con->nodes_head), nodes) {
253             if (con_is_internal(output))
254                 continue;
255             /* Get the active workspace of that output */
256             Con *content = output_get_content(output);
257             if (!content || TAILQ_EMPTY(&(content->focus_head))) {
258                 DLOG("Skipping this output because it is currently being destroyed.\n");
259                 continue;
260             }
261             Con *workspace = TAILQ_FIRST(&(content->focus_head));
262             Con *fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
263             Con *child;
264             TAILQ_FOREACH(child, &(workspace->floating_head), floating_windows) {
265                 /* Don’t render floating windows when there is a fullscreen window
266                  * on that workspace. Necessary to make floating fullscreen work
267                  * correctly (ticket #564). */
268                 /* If there is no fullscreen->window, this cannot be a
269                  * transient window, so we _know_ we need to skip it. This
270                  * happens during restarts where the container already exists,
271                  * but the window was not yet associated. */
272                 if (fullscreen != NULL && fullscreen->window == NULL)
273                     continue;
274                 if (fullscreen != NULL && fullscreen->window != NULL) {
275                     Con *floating_child = con_descend_focused(child);
276                     Con *transient_con = floating_child;
277                     bool is_transient_for = false;
278                     /* Exception to the above rule: smart
279                      * popup_during_fullscreen handling (popups belonging to
280                      * the fullscreen app will be rendered). */
281                     while (transient_con != NULL &&
282                            transient_con->window != NULL &&
283                            transient_con->window->transient_for != XCB_NONE) {
284                         DLOG("transient_con = 0x%08x, transient_con->window->transient_for = 0x%08x, fullscreen_id = 0x%08x\n",
285                              transient_con->window->id, transient_con->window->transient_for, fullscreen->window->id);
286                         if (transient_con->window->transient_for == fullscreen->window->id) {
287                             is_transient_for = true;
288                             break;
289                         }
290                         Con *next_transient = con_by_window_id(transient_con->window->transient_for);
291                         if (next_transient == NULL)
292                             break;
293                         /* Some clients (e.g. x11-ssh-askpass) actually set
294                          * WM_TRANSIENT_FOR to their own window id, so break instead of
295                          * looping endlessly. */
296                         if (transient_con == next_transient)
297                             break;
298                         transient_con = next_transient;
299                     }
300
301                     if (!is_transient_for)
302                         continue;
303                     else {
304                         DLOG("Rendering floating child even though in fullscreen mode: "
305                              "floating->transient_for (0x%08x) --> fullscreen->id (0x%08x)\n",
306                              floating_child->window->transient_for, fullscreen->window->id);
307                     }
308                 }
309                 DLOG("floating child at (%d,%d) with %d x %d\n",
310                      child->rect.x, child->rect.y, child->rect.width, child->rect.height);
311                 x_raise_con(child);
312                 render_con(child, false);
313             }
314         }
315
316     } else {
317         Con *child;
318         TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
319             assert(params.children > 0);
320
321             if (con->layout == L_SPLITH || con->layout == L_SPLITV) {
322                 render_con_split(con, child, &params, i);
323             } else if (con->layout == L_STACKED) {
324                 render_con_stacked(con, child, &params, i);
325             } else if (con->layout == L_TABBED) {
326                 render_con_tabbed(con, child, &params, i);
327             } else if (con->layout == L_DOCKAREA) {
328                 render_con_dockarea(con, child, &params);
329             }
330
331             DLOG("child at (%d, %d) with (%d x %d)\n",
332                  child->rect.x, child->rect.y, child->rect.width, child->rect.height);
333             x_raise_con(child);
334             render_con(child, false);
335             i++;
336         }
337
338         /* in a stacking or tabbed container, we ensure the focused client is raised */
339         if (con->layout == L_STACKED || con->layout == L_TABBED) {
340             TAILQ_FOREACH_REVERSE(child, &(con->focus_head), focus_head, focused)
341             x_raise_con(child);
342             if ((child = TAILQ_FIRST(&(con->focus_head)))) {
343                 /* By rendering the stacked container again, we handle the case
344              * that we have a non-leaf-container inside the stack. In that
345              * case, the children of the non-leaf-container need to be raised
346              * aswell. */
347                 render_con(child, false);
348             }
349
350             if (params.children != 1)
351                 /* Raise the stack con itself. This will put the stack decoration on
352              * top of every stack window. That way, when a new window is opened in
353              * the stack, the old window will not obscure part of the decoration
354              * (it’s unmapped afterwards). */
355                 x_raise_con(con);
356         }
357     }
358
359 free_params:
360     FREE(params.sizes);
361 }
362
363 static int *precalculate_sizes(Con *con, render_params *p) {
364     int *sizes = smalloc(p->children * sizeof(int));
365     if ((con->layout == L_SPLITH || con->layout == L_SPLITV) && p->children > 0) {
366         assert(!TAILQ_EMPTY(&con->nodes_head));
367
368         Con *child;
369         int i = 0, assigned = 0;
370         int total = con_orientation(con) == HORIZ ? p->rect.width : p->rect.height;
371         TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
372             double percentage = child->percent > 0.0 ? child->percent : 1.0 / p->children;
373             assigned += sizes[i++] = percentage * total;
374         }
375         assert(assigned == total ||
376                 (assigned > total && assigned - total <= p->children * 2) ||
377                 (assigned < total && total - assigned <= p->children * 2));
378         int signal = assigned < total ? 1 : -1;
379         while (assigned != total) {
380             for (i = 0; i < p->children && assigned != total; ++i) {
381                 sizes[i] += signal;
382                 assigned += signal;
383             }
384         }
385     }
386
387     return sizes;
388 }
389
390 static void render_con_split(Con *con, Con *child, render_params *p, int i) {
391     assert(con->layout == L_SPLITH || con->layout == L_SPLITV);
392
393     if (con->layout == L_SPLITH) {
394         child->rect.x = p->x;
395         child->rect.y = p->y;
396         child->rect.width = p->sizes[i];
397         child->rect.height = p->rect.height;
398         p->x += child->rect.width;
399     } else {
400         child->rect.x = p->x;
401         child->rect.y = p->y;
402         child->rect.width = p->rect.width;
403         child->rect.height = p->sizes[i];
404         p->y += child->rect.height;
405     }
406
407     /* first we have the decoration, if this is a leaf node */
408     if (con_is_leaf(child)) {
409         if (child->border_style == BS_NORMAL) {
410             /* TODO: make a function for relative coords? */
411             child->deco_rect.x = child->rect.x - con->rect.x;
412             child->deco_rect.y = child->rect.y - con->rect.y;
413
414             child->rect.y += p->deco_height;
415             child->rect.height -= p->deco_height;
416
417             child->deco_rect.width = child->rect.width;
418             child->deco_rect.height = p->deco_height;
419         } else {
420             child->deco_rect.x = 0;
421             child->deco_rect.y = 0;
422             child->deco_rect.width = 0;
423             child->deco_rect.height = 0;
424         }
425     }
426 }
427
428 static void render_con_stacked(Con *con, Con *child, render_params *p, int i) {
429     assert(con->layout == L_STACKED);
430
431     child->rect.x = p->x;
432     child->rect.y = p->y;
433     child->rect.width = p->rect.width;
434     child->rect.height = p->rect.height;
435
436     child->deco_rect.x = p->x - con->rect.x;
437     child->deco_rect.y = p->y - con->rect.y + (i * p->deco_height);
438     child->deco_rect.width = child->rect.width;
439     child->deco_rect.height = p->deco_height;
440
441     if (p->children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) {
442         child->rect.y += (p->deco_height * p->children);
443         child->rect.height -= (p->deco_height * p->children);
444     }
445 }
446
447 static void render_con_tabbed(Con *con, Con *child, render_params *p, int i) {
448     assert(con->layout == L_TABBED);
449
450     child->rect.x = p->x;
451     child->rect.y = p->y;
452     child->rect.width = p->rect.width;
453     child->rect.height = p->rect.height;
454
455     child->deco_rect.width = floor((float)child->rect.width / p->children);
456     child->deco_rect.x = p->x - con->rect.x + i * child->deco_rect.width;
457     child->deco_rect.y = p->y - con->rect.y;
458
459     /* Since the tab width may be something like 31,6 px per tab, we
460      * let the last tab have all the extra space (0,6 * children). */
461     if (i == (p->children - 1)) {
462         child->deco_rect.width += (child->rect.width - (child->deco_rect.x + child->deco_rect.width));
463     }
464
465     if (p->children > 1 || (child->border_style != BS_PIXEL && child->border_style != BS_NONE)) {
466         child->rect.y += p->deco_height;
467         child->rect.height -= p->deco_height;
468         child->deco_rect.height = p->deco_height;
469     } else {
470         child->deco_rect.height = (child->border_style == BS_PIXEL ? 1 : 0);
471     }
472 }
473
474 static void render_con_dockarea(Con *con, Con *child, render_params *p) {
475     assert(con->layout == L_DOCKAREA);
476
477     child->rect.x = p->x;
478     child->rect.y = p->y;
479     child->rect.width = p->rect.width;
480     child->rect.height = child->geometry.height;
481
482     child->deco_rect.x = 0;
483     child->deco_rect.y = 0;
484     child->deco_rect.width = 0;
485     child->deco_rect.height = 0;
486     p->y += child->rect.height;
487 }