]> git.sur5r.net Git - i3/i3/blob - src/layout.c
ac3ee36149917dc02e6f942312062d3df466bf72
[i3/i3] / src / layout.c
1 /*
2  * vim:ts=8:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  *
6  * © 2009 Michael Stapelberg and contributors
7  *
8  * See file LICENSE for license information.
9  *
10  * layout.c: Functions handling layout/drawing of window decorations
11  *
12  */
13 #include <stdio.h>
14 #include <string.h>
15 #include <stdlib.h>
16 #include <xcb/xcb.h>
17 #include <assert.h>
18
19 #include "config.h"
20 #include "i3.h"
21 #include "xcb.h"
22 #include "table.h"
23 #include "util.h"
24 #include "xinerama.h"
25 #include "layout.h"
26
27 /* This macro copies the old value of the given variable, changes the variable to contain
28    the new one and returns true if it changed.
29    Note that when combining multiple HAS_CHANGED statements, you need to use different variables.
30    If someone by chance knows why this is necessary (order of expressions in gcc?) and/or can
31    come up with a fix, please mail me. */
32 #define HAS_CHANGED(temp, value, new) (temp = value, temp != (value = new))
33
34 static int old_value_1;
35 static int old_value_2;
36 static int old_value_3;
37 static int old_value_4;
38
39 /*
40  * Gets the unoccupied space (= space which is available for windows which were resized by the user)
41  * for the given row. This is necessary to render both, customly resized windows and never touched
42  * windows correctly, meaning that the aspect ratio will be maintained when opening new windows.
43  *
44  */
45 int get_unoccupied_x(Workspace *workspace, int row) {
46         int unoccupied = workspace->rect.width;
47         float default_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width;
48
49         printf("get_unoccupied_x(), starting with %d, default_factor = %f\n", unoccupied, default_factor);
50
51         for (int cols = 0; cols < workspace->cols;) {
52                 Container *con = workspace->table[cols][row];
53                 printf("width_factor[%d][%d] = %f, colspan = %d\n", cols, row, con->width_factor, con->colspan);
54                 if (con->width_factor == 0)
55                         unoccupied -= workspace->rect.width * default_factor * con->colspan;
56                 cols += con->colspan;
57         }
58
59         assert(unoccupied != 0);
60
61         printf("unoccupied space: %d\n", unoccupied);
62         return unoccupied;
63 }
64
65 /* See get_unoccupied_x() */
66 int get_unoccupied_y(Workspace *workspace, int col) {
67         int unoccupied = workspace->rect.height;
68         float default_factor = ((float)workspace->rect.height / workspace->rows) / workspace->rect.height;
69
70         printf("get_unoccupied_y(), starting with %d, default_factor = %f\n", unoccupied, default_factor);
71
72         for (int rows = 0; rows < workspace->rows;) {
73                 Container *con = workspace->table[col][rows];
74                 printf("height_factor[%d][%d] = %f, rowspan %d\n", col, rows, con->height_factor, con->rowspan);
75                 if (con->height_factor == 0)
76                         unoccupied -= workspace->rect.height * default_factor * con->rowspan;
77                 rows += con->rowspan;
78         }
79
80         assert(unoccupied != 0);
81
82         printf("unoccupied space: %d\n", unoccupied);
83         return unoccupied;
84 }
85
86 /*
87  * Redecorates the given client correctly by checking if it’s in a stacking container and
88  * re-rendering the stack window or just calling decorate_window if it’s not in a stacking
89  * container.
90  *
91  */
92 void redecorate_window(xcb_connection_t *conn, Client *client) {
93         if (client->container->mode == MODE_STACK) {
94                 render_container(conn, client->container);
95                 xcb_flush(conn);
96         } else decorate_window(conn, client, client->frame, client->titlegc, 0);
97 }
98
99 /*
100  * (Re-)draws window decorations for a given Client onto the given drawable/graphic context.
101  * When in stacking mode, the window decorations are drawn onto an own window.
102  *
103  */
104 void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable, xcb_gcontext_t gc, int offset) {
105         i3Font *font = load_font(conn, config.font);
106         uint32_t background_color,
107                  text_color,
108                  border_color;
109
110         /* Clients without a container (docks) won’t get decorated */
111         if (client->container == NULL)
112                 return;
113
114         if (client->container->currently_focused == client) {
115                 /* Distinguish if the window is currently focused… */
116                 if (CUR_CELL->currently_focused == client)
117                         background_color = get_colorpixel(conn, client, client->frame, "#285577");
118                 /* …or if it is the focused window in a not focused container */
119                 else background_color = get_colorpixel(conn, client, client->frame, "#555555");
120
121                 text_color = get_colorpixel(conn, client, client->frame, "#ffffff");
122                 border_color = get_colorpixel(conn, client, client->frame, "#4c7899");
123         } else {
124                 background_color = get_colorpixel(conn, client, client->frame, "#222222");
125                 text_color = get_colorpixel(conn, client, client->frame, "#888888");
126                 border_color = get_colorpixel(conn, client, client->frame, "#333333");
127         }
128
129         /* Our plan is the following:
130            - Draw a rect around the whole client in background_color
131            - Draw two lines in a lighter color
132            - Draw the window’s title
133          */
134
135         /* Draw a rectangle in background color around the window */
136         xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, background_color);
137
138         /* We need to use the container’s width because it is the more recent value - when
139            in stacking mode, clients get reconfigured only on demand (the not active client
140            is not reconfigured), so the client’s rect.width would be wrong */
141         xcb_rectangle_t rect = {0, offset, client->container->width, offset + client->rect.height};
142         xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
143
144         /* Draw the lines */
145         xcb_draw_line(conn, drawable, gc, border_color, 2, offset, client->rect.width, offset);
146         xcb_draw_line(conn, drawable, gc, border_color, 2, offset + font->height + 3,
147                       2 + client->rect.width, offset + font->height + 3);
148
149         /* Draw the font */
150         uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
151         uint32_t values[] = { text_color, background_color, font->id };
152         xcb_change_gc(conn, gc, mask, values);
153
154         /* TODO: utf8? */
155         char *label;
156         asprintf(&label, "%.*s", client->name_len, client->name);
157         xcb_void_cookie_t text_cookie = xcb_image_text_8_checked(conn, strlen(label), drawable,
158                                         gc, 3 /* X */, offset + font->height /* Y = baseline of font */, label);
159         check_error(conn, text_cookie, "Could not draw client's title");
160         free(label);
161 }
162
163 /*
164  * Pushes the client’s x and y coordinates to X11
165  *
166  */
167 static void reposition_client(xcb_connection_t *conn, Client *client) {
168         printf("frame needs to be pushed to %dx%d\n", client->rect.x, client->rect.y);
169         /* Note: We can use a pointer to client->x like an array of uint32_ts
170            because it is followed by client->y by definition */
171         xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
172 }
173
174 /*
175  * Pushes the client’s width/height to X11 and resizes the child window
176  *
177  */
178 static void resize_client(xcb_connection_t *conn, Client *client) {
179         i3Font *font = load_font(conn, config.font);
180
181         printf("resizing client to %d x %d\n", client->rect.width, client->rect.height);
182         xcb_configure_window(conn, client->frame,
183                         XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
184                         &(client->rect.width));
185
186         /* Adjust the position of the child inside its frame.
187          * The coordinates of the child are relative to its frame, we
188          * add a border of 2 pixel to each value */
189         uint32_t mask = XCB_CONFIG_WINDOW_X |
190                         XCB_CONFIG_WINDOW_Y |
191                         XCB_CONFIG_WINDOW_WIDTH |
192                         XCB_CONFIG_WINDOW_HEIGHT;
193         Rect *rect = &(client->child_rect);
194         switch (client->container->mode) {
195                 case MODE_STACK:
196                         rect->x = 2;
197                         rect->y = 0;
198                         rect->width = client->rect.width - (2 + 2);
199                         rect->height = client->rect.height - 2;
200                         break;
201                 default:
202                         if (client->titlebar_position == TITLEBAR_OFF) {
203                                 rect->x = 0;
204                                 rect->y = 0;
205                                 rect->width = client->rect.width;
206                                 rect->height = client->rect.height;
207                         } else {
208                                 rect->x = 2;
209                                 rect->y = font->height + 2 + 2;
210                                 rect->width = client->rect.width - (2 + 2);
211                                 rect->height = client->rect.height - ((font->height + 2 + 2) + 2);
212                         }
213                         break;
214         }
215
216         printf("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
217
218         xcb_configure_window(conn, client->child, mask, &(rect->x));
219
220         /* After configuring a child window we need to fake a configure_notify_event according
221            to ICCCM 4.2.3. This seems rather broken, especially since X sends exactly the same
222            configure_notify_event automatically according to xtrace. Anyone knows details? */
223         xcb_configure_notify_event_t event;
224
225         event.event = client->child;
226         event.window = client->child;
227         event.response_type = XCB_CONFIGURE_NOTIFY;
228
229         event.x = rect->x;
230         event.y = rect->y;
231         event.width = rect->width;
232         event.height = rect->height;
233
234         event.border_width = 0;
235         event.above_sibling = XCB_NONE;
236         event.override_redirect = false;
237
238         xcb_send_event(conn, false, client->child, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&event);
239 }
240
241 /*
242  * Renders the given container. Is called by render_layout() or individually (for example
243  * when focus changes in a stacking container)
244  *
245  */
246 void render_container(xcb_connection_t *conn, Container *container) {
247         Client *client;
248         int num_clients = 0, current_client = 0;
249
250         if (container->currently_focused == NULL)
251                 return;
252
253         CIRCLEQ_FOREACH(client, &(container->clients), clients)
254                 num_clients++;
255
256         if (container->mode == MODE_DEFAULT) {
257                 printf("got %d clients in this default container.\n", num_clients);
258                 CIRCLEQ_FOREACH(client, &(container->clients), clients) {
259                         /* Check if we changed client->x or client->y by updating it.
260                          * Note the bitwise OR instead of logical OR to force evaluation of both statements */
261                         if (client->force_reconfigure |
262                             HAS_CHANGED(old_value_1, client->rect.x, container->x) |
263                             HAS_CHANGED(old_value_2, client->rect.y, container->y +
264                                         (container->height / num_clients) * current_client))
265                                 reposition_client(conn, client);
266
267                         /* TODO: vertical default layout */
268                         if (client->force_reconfigure |
269                             HAS_CHANGED(old_value_1, client->rect.width, container->width) |
270                             HAS_CHANGED(old_value_2, client->rect.height, container->height / num_clients))
271                                 resize_client(conn, client);
272
273                         client->force_reconfigure = false;
274
275                         current_client++;
276                 }
277         } else {
278                 i3Font *font = load_font(conn, config.font);
279                 int decoration_height = (font->height + 2 + 2);
280                 struct Stack_Window *stack_win = &(container->stack_win);
281
282                 /* Check if we need to remap our stack title window, it gets unmapped when the container
283                    is empty in src/handlers.c:unmap_notify() */
284                 if (stack_win->rect.height == 0)
285                         xcb_map_window(conn, stack_win->window);
286
287                 /* Check if we need to reconfigure our stack title window */
288                 if (HAS_CHANGED(old_value_1, stack_win->rect.x, container->x) |
289                     HAS_CHANGED(old_value_2, stack_win->rect.y, container->y) |
290                     HAS_CHANGED(old_value_3, stack_win->rect.width, container->width) |
291                     HAS_CHANGED(old_value_4, stack_win->rect.height, decoration_height * num_clients)) {
292
293                         uint32_t values[] = { stack_win->rect.x, stack_win->rect.y,
294                                               stack_win->rect.width, stack_win->rect.height,
295                                               XCB_STACK_MODE_ABOVE };
296
297                         xcb_configure_window(conn, stack_win->window,
298                                              XCB_CONFIG_WINDOW_X     | XCB_CONFIG_WINDOW_Y      |
299                                              XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
300                                              XCB_CONFIG_WINDOW_STACK_MODE,
301                                              values);
302                 }
303
304                 /* Reconfigure the currently focused client, if necessary. It is the only visible one */
305                 client = container->currently_focused;
306
307                 if (container->workspace->fullscreen_client == NULL) {
308                         uint32_t values[] = { XCB_STACK_MODE_ABOVE };
309                         xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
310                 }
311
312                 /* Render the decorations of all clients */
313                 CIRCLEQ_FOREACH(client, &(container->clients), clients) {
314                         /* Check if we changed client->x or client->y by updating it.
315                          * Note the bitwise OR instead of logical OR to force evaluation of both statements */
316                         if (client->force_reconfigure |
317                             HAS_CHANGED(old_value_1, client->rect.x, container->x) |
318                             HAS_CHANGED(old_value_2, client->rect.y, container->y + (decoration_height * num_clients)))
319                                 reposition_client(conn, client);
320
321                         if (client->force_reconfigure |
322                             HAS_CHANGED(old_value_1, client->rect.width, container->width) |
323                             HAS_CHANGED(old_value_2, client->rect.height, container->height - (decoration_height * num_clients)))
324                                 resize_client(conn, client);
325
326                         client->force_reconfigure = false;
327
328                         decorate_window(conn, client, stack_win->window, stack_win->gc,
329                                         current_client++ * decoration_height);
330                 }
331         }
332 }
333
334 static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) {
335         Client *client;
336         SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) {
337                 if (client->force_reconfigure |
338                     HAS_CHANGED(old_value_1, client->rect.x, 0) |
339                     HAS_CHANGED(old_value_2, client->rect.y, *height))
340                         reposition_client(conn, client);
341
342                 if (client->force_reconfigure |
343                     HAS_CHANGED(old_value_1, client->rect.width, width) |
344                     HAS_CHANGED(old_value_2, client->rect.height, client->desired_height))
345                         resize_client(conn, client);
346
347                 client->force_reconfigure = false;
348                 *height += client->desired_height;
349         }
350 }
351
352 static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) {
353         printf("Rendering internal bar\n");
354         i3Font *font = load_font(conn, config.font);
355         i3Screen *screen = r_ws->screen;
356         enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
357         uint32_t background_color[2],
358                  text_color[2],
359                  border_color[2],
360                  black;
361         char label[3];
362
363         black = get_colorpixel(conn, NULL, screen->bar, "#000000");
364
365         background_color[SET_NORMAL] = get_colorpixel(conn, NULL, screen->bar, "#222222");
366         text_color[SET_NORMAL] = get_colorpixel(conn, NULL, screen->bar, "#888888");
367         border_color[SET_NORMAL] = get_colorpixel(conn, NULL, screen->bar, "#333333");
368
369         background_color[SET_FOCUSED] = get_colorpixel(conn, NULL, screen->bar, "#285577");
370         text_color[SET_FOCUSED] = get_colorpixel(conn, NULL, screen->bar, "#ffffff");
371         border_color[SET_FOCUSED] = get_colorpixel(conn, NULL, screen->bar, "#4c7899");
372
373         /* Fill the whole bar in black */
374         xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, black);
375         xcb_rectangle_t rect = {0, 0, width, height};
376         xcb_poly_fill_rectangle(conn, screen->bar, screen->bargc, 1, &rect);
377
378         /* Set font */
379         xcb_change_gc_single(conn, screen->bargc, XCB_GC_FONT, font->id);
380
381         int drawn = 0;
382         for (int c = 0; c < 10; c++) {
383                 if (workspaces[c].screen == screen) {
384                         int set = (screen->current_workspace == c ? SET_FOCUSED : SET_NORMAL);
385
386                         xcb_draw_rect(conn, screen->bar, screen->bargc, border_color[set],
387                                       drawn * height, 1, height - 2, height - 2);
388                         xcb_draw_rect(conn, screen->bar, screen->bargc, background_color[set],
389                                       drawn * height + 1, 2, height - 4, height - 4);
390
391                         snprintf(label, sizeof(label), "%d", c+1);
392                         xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, text_color[set]);
393                         xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, background_color[set]);
394                         xcb_void_cookie_t text_cookie = xcb_image_text_8_checked(conn, strlen(label), screen->bar,
395                                                         screen->bargc, drawn * height + 5 /* X */,
396                                                         font->height + 1 /* Y = baseline of font */, label);
397                         check_error(conn, text_cookie, "Could not draw workspace title");
398                         drawn++;
399                 }
400         }
401
402         printf("done rendering internal\n");
403 }
404
405 void render_layout(xcb_connection_t *conn) {
406         i3Screen *screen;
407         i3Font *font = load_font(conn, config.font);
408
409         TAILQ_FOREACH(screen, virtual_screens, screens) {
410                 /* r_ws (rendering workspace) is just a shortcut to the Workspace being currently rendered */
411                 Workspace *r_ws = &(workspaces[screen->current_workspace]);
412
413                 printf("Rendering screen %d\n", screen->num);
414                 if (r_ws->fullscreen_client != NULL)
415                         /* This is easy: A client has entered fullscreen mode, so we don’t render at all */
416                         continue;
417
418                 int width = r_ws->rect.width;
419                 int height = r_ws->rect.height;
420
421                 /* Reserve space for dock clients */
422                 Client *client;
423                 SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients)
424                         height -= client->desired_height;
425
426                 /* Space for the internal bar */
427                 height -= (font->height + 6);
428
429                 printf("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
430
431                 int xoffset[r_ws->rows];
432                 int yoffset[r_ws->cols];
433                 /* Initialize offsets */
434                 for (int cols = 0; cols < r_ws->cols; cols++)
435                         yoffset[cols] = r_ws->rect.y;
436                 for (int rows = 0; rows < r_ws->rows; rows++)
437                         xoffset[rows] = r_ws->rect.x;
438
439                 /* Go through the whole table and render what’s necessary */
440                 for (int cols = 0; cols < r_ws->cols; cols++)
441                         for (int rows = 0; rows < r_ws->rows; rows++) {
442                                 Container *container = r_ws->table[cols][rows];
443                                 printf("\n========\ncontainer has %d colspan, %d rowspan\n",
444                                                 container->colspan, container->rowspan);
445                                 printf("container at %d, %d\n", xoffset[rows], yoffset[cols]);
446                                 /* Update position of the container */
447                                 container->row = rows;
448                                 container->col = cols;
449                                 container->x = xoffset[rows];
450                                 container->y = yoffset[cols];
451
452                                 if (container->width_factor == 0)
453                                         container->width = (width / r_ws->cols);
454                                 else container->width = get_unoccupied_x(r_ws, rows) * container->width_factor;
455                                 container->width *= container->colspan;
456
457                                 if (container->height_factor == 0)
458                                         container->height = (height / r_ws->rows);
459                                 else container->height = get_unoccupied_y(r_ws, cols) * container->height_factor;
460                                 container->height *= container->rowspan;
461
462                                 /* Render the container if it is not empty */
463                                 render_container(conn, container);
464
465                                 xoffset[rows] += container->width;
466                                 yoffset[cols] += container->height;
467                                 printf("==========\n");
468                         }
469
470                 render_bars(conn, r_ws, width, &height);
471                 render_internal_bar(conn, r_ws, width, 18);
472         }
473
474         xcb_flush(conn);
475 }