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