]> git.sur5r.net Git - i3/i3/blob - src/layout.c
6a441e8c32542d05643bf0937d6c36981f31efd5
[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, "#285577");
118                 /* …or if it is the focused window in a not focused container */
119                 else background_color = get_colorpixel(conn, "#555555");
120
121                 text_color = get_colorpixel(conn, "#ffffff");
122                 border_color = get_colorpixel(conn, "#4c7899");
123         } else {
124                 background_color = get_colorpixel(conn, "#222222");
125                 text_color = get_colorpixel(conn, "#888888");
126                 border_color = get_colorpixel(conn, "#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_image_text_8(conn, strlen(label), drawable, gc, 3 /* X */, offset + font->height /* Y = baseline of font */, label);
158         free(label);
159 }
160
161 /*
162  * Pushes the client’s x and y coordinates to X11
163  *
164  */
165 static void reposition_client(xcb_connection_t *conn, Client *client) {
166         printf("frame needs to be pushed to %dx%d\n", client->rect.x, client->rect.y);
167         /* Note: We can use a pointer to client->x like an array of uint32_ts
168            because it is followed by client->y by definition */
169         xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
170 }
171
172 /*
173  * Pushes the client’s width/height to X11 and resizes the child window
174  *
175  */
176 static void resize_client(xcb_connection_t *conn, Client *client) {
177         i3Font *font = load_font(conn, config.font);
178
179         printf("resizing client to %d x %d\n", client->rect.width, client->rect.height);
180         xcb_configure_window(conn, client->frame,
181                         XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
182                         &(client->rect.width));
183
184         /* Adjust the position of the child inside its frame.
185          * The coordinates of the child are relative to its frame, we
186          * add a border of 2 pixel to each value */
187         uint32_t mask = XCB_CONFIG_WINDOW_X |
188                         XCB_CONFIG_WINDOW_Y |
189                         XCB_CONFIG_WINDOW_WIDTH |
190                         XCB_CONFIG_WINDOW_HEIGHT;
191         Rect *rect = &(client->child_rect);
192         switch (client->container->mode) {
193                 case MODE_STACK:
194                         rect->x = 2;
195                         rect->y = 0;
196                         rect->width = client->rect.width - (2 + 2);
197                         rect->height = client->rect.height - 2;
198                         break;
199                 default:
200                         if (client->titlebar_position == TITLEBAR_OFF) {
201                                 rect->x = 0;
202                                 rect->y = 0;
203                                 rect->width = client->rect.width;
204                                 rect->height = client->rect.height;
205                         } else {
206                                 rect->x = 2;
207                                 rect->y = font->height + 2 + 2;
208                                 rect->width = client->rect.width - (2 + 2);
209                                 rect->height = client->rect.height - ((font->height + 2 + 2) + 2);
210                         }
211                         break;
212         }
213
214         printf("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
215
216         xcb_configure_window(conn, client->child, mask, &(rect->x));
217
218         /* After configuring a child window we need to fake a configure_notify_event according
219            to ICCCM 4.2.3. This seems rather broken, especially since X sends exactly the same
220            configure_notify_event automatically according to xtrace. Anyone knows details? */
221         xcb_configure_notify_event_t event;
222
223         event.event = client->child;
224         event.window = client->child;
225         event.response_type = XCB_CONFIGURE_NOTIFY;
226
227         event.x = rect->x;
228         event.y = rect->y;
229         event.width = rect->width;
230         event.height = rect->height;
231
232         event.border_width = 0;
233         event.above_sibling = XCB_NONE;
234         event.override_redirect = false;
235
236         xcb_send_event(conn, false, client->child, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&event);
237 }
238
239 /*
240  * Renders the given container. Is called by render_layout() or individually (for example
241  * when focus changes in a stacking container)
242  *
243  */
244 void render_container(xcb_connection_t *conn, Container *container) {
245         Client *client;
246         int num_clients = 0, current_client = 0;
247
248         if (container->currently_focused == NULL)
249                 return;
250
251         CIRCLEQ_FOREACH(client, &(container->clients), clients)
252                 num_clients++;
253
254         if (container->mode == MODE_DEFAULT) {
255                 printf("got %d clients in this default container.\n", num_clients);
256                 CIRCLEQ_FOREACH(client, &(container->clients), clients) {
257                         /* Check if we changed client->x or client->y by updating it.
258                          * Note the bitwise OR instead of logical OR to force evaluation of both statements */
259                         if (client->force_reconfigure |
260                             HAS_CHANGED(old_value_1, client->rect.x, container->x) |
261                             HAS_CHANGED(old_value_2, client->rect.y, container->y +
262                                         (container->height / num_clients) * current_client))
263                                 reposition_client(conn, client);
264
265                         /* TODO: vertical default layout */
266                         if (client->force_reconfigure |
267                             HAS_CHANGED(old_value_1, client->rect.width, container->width) |
268                             HAS_CHANGED(old_value_2, client->rect.height, container->height / num_clients))
269                                 resize_client(conn, client);
270
271                         client->force_reconfigure = false;
272
273                         current_client++;
274                 }
275         } else {
276                 i3Font *font = load_font(conn, config.font);
277                 int decoration_height = (font->height + 2 + 2);
278                 struct Stack_Window *stack_win = &(container->stack_win);
279
280                 /* Check if we need to remap our stack title window, it gets unmapped when the container
281                    is empty in src/handlers.c:unmap_notify() */
282                 if (stack_win->rect.height == 0)
283                         xcb_map_window(conn, stack_win->window);
284
285                 /* Check if we need to reconfigure our stack title window */
286                 if (HAS_CHANGED(old_value_1, stack_win->rect.x, container->x) |
287                     HAS_CHANGED(old_value_2, stack_win->rect.y, container->y) |
288                     HAS_CHANGED(old_value_3, stack_win->rect.width, container->width) |
289                     HAS_CHANGED(old_value_4, stack_win->rect.height, decoration_height * num_clients)) {
290
291                         uint32_t values[] = { stack_win->rect.x, stack_win->rect.y,
292                                               stack_win->rect.width, stack_win->rect.height,
293                                               XCB_STACK_MODE_ABOVE };
294
295                         xcb_configure_window(conn, stack_win->window,
296                                              XCB_CONFIG_WINDOW_X     | XCB_CONFIG_WINDOW_Y      |
297                                              XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
298                                              XCB_CONFIG_WINDOW_STACK_MODE,
299                                              values);
300                 }
301
302                 /* Reconfigure the currently focused client, if necessary. It is the only visible one */
303                 client = container->currently_focused;
304
305                 if (container->workspace->fullscreen_client == NULL) {
306                         uint32_t values[] = { XCB_STACK_MODE_ABOVE };
307                         xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
308                 }
309
310                 /* Render the decorations of all clients */
311                 CIRCLEQ_FOREACH(client, &(container->clients), clients) {
312                         /* Check if we changed client->x or client->y by updating it.
313                          * Note the bitwise OR instead of logical OR to force evaluation of both statements */
314                         if (client->force_reconfigure |
315                             HAS_CHANGED(old_value_1, client->rect.x, container->x) |
316                             HAS_CHANGED(old_value_2, client->rect.y, container->y + (decoration_height * num_clients)))
317                                 reposition_client(conn, client);
318
319                         if (client->force_reconfigure |
320                             HAS_CHANGED(old_value_1, client->rect.width, container->width) |
321                             HAS_CHANGED(old_value_2, client->rect.height, container->height - (decoration_height * num_clients)))
322                                 resize_client(conn, client);
323
324                         client->force_reconfigure = false;
325
326                         decorate_window(conn, client, stack_win->window, stack_win->gc,
327                                         current_client++ * decoration_height);
328                 }
329         }
330 }
331
332 static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) {
333         Client *client;
334         SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) {
335                 if (client->force_reconfigure |
336                     HAS_CHANGED(old_value_1, client->rect.x, 0) |
337                     HAS_CHANGED(old_value_2, client->rect.y, *height))
338                         reposition_client(conn, client);
339
340                 if (client->force_reconfigure |
341                     HAS_CHANGED(old_value_1, client->rect.width, width) |
342                     HAS_CHANGED(old_value_2, client->rect.height, client->desired_height))
343                         resize_client(conn, client);
344
345                 client->force_reconfigure = false;
346                 *height += client->desired_height;
347         }
348 }
349
350 static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) {
351         printf("Rendering internal bar\n");
352         i3Font *font = load_font(conn, config.font);
353         i3Screen *screen = r_ws->screen;
354         enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
355         uint32_t background_color[2],
356                  text_color[2],
357                  border_color[2],
358                  black;
359         char label[3];
360
361         black = get_colorpixel(conn, "#000000");
362
363         background_color[SET_NORMAL] = get_colorpixel(conn, "#222222");
364         text_color[SET_NORMAL] = get_colorpixel(conn, "#888888");
365         border_color[SET_NORMAL] = get_colorpixel(conn, "#333333");
366
367         background_color[SET_FOCUSED] = get_colorpixel(conn, "#285577");
368         text_color[SET_FOCUSED] = get_colorpixel(conn, "#ffffff");
369         border_color[SET_FOCUSED] = get_colorpixel(conn, "#4c7899");
370
371         /* Fill the whole bar in black */
372         xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, black);
373         xcb_rectangle_t rect = {0, 0, width, height};
374         xcb_poly_fill_rectangle(conn, screen->bar, screen->bargc, 1, &rect);
375
376         /* Set font */
377         xcb_change_gc_single(conn, screen->bargc, XCB_GC_FONT, font->id);
378
379         int drawn = 0;
380         for (int c = 0; c < 10; c++) {
381                 if (workspaces[c].screen == screen) {
382                         int set = (screen->current_workspace == c ? SET_FOCUSED : SET_NORMAL);
383
384                         xcb_draw_rect(conn, screen->bar, screen->bargc, border_color[set],
385                                       drawn * height, 1, height - 2, height - 2);
386                         xcb_draw_rect(conn, screen->bar, screen->bargc, background_color[set],
387                                       drawn * height + 1, 2, height - 4, height - 4);
388
389                         snprintf(label, sizeof(label), "%d", c+1);
390                         xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, text_color[set]);
391                         xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, background_color[set]);
392                         xcb_image_text_8(conn, strlen(label), screen->bar, screen->bargc, drawn * height + 5 /* X */,
393                                                         font->height + 1 /* Y = baseline of font */, label);
394                         drawn++;
395                 }
396         }
397
398         printf("done rendering internal\n");
399 }
400
401 void render_layout(xcb_connection_t *conn) {
402         i3Screen *screen;
403         i3Font *font = load_font(conn, config.font);
404
405         TAILQ_FOREACH(screen, virtual_screens, screens) {
406                 /* r_ws (rendering workspace) is just a shortcut to the Workspace being currently rendered */
407                 Workspace *r_ws = &(workspaces[screen->current_workspace]);
408
409                 printf("Rendering screen %d\n", screen->num);
410                 if (r_ws->fullscreen_client != NULL)
411                         /* This is easy: A client has entered fullscreen mode, so we don’t render at all */
412                         continue;
413
414                 int width = r_ws->rect.width;
415                 int height = r_ws->rect.height;
416
417                 /* Reserve space for dock clients */
418                 Client *client;
419                 SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients)
420                         height -= client->desired_height;
421
422                 /* Space for the internal bar */
423                 height -= (font->height + 6);
424
425                 printf("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
426
427                 int xoffset[r_ws->rows];
428                 int yoffset[r_ws->cols];
429                 /* Initialize offsets */
430                 for (int cols = 0; cols < r_ws->cols; cols++)
431                         yoffset[cols] = r_ws->rect.y;
432                 for (int rows = 0; rows < r_ws->rows; rows++)
433                         xoffset[rows] = r_ws->rect.x;
434
435                 /* Go through the whole table and render what’s necessary */
436                 for (int cols = 0; cols < r_ws->cols; cols++)
437                         for (int rows = 0; rows < r_ws->rows; rows++) {
438                                 Container *container = r_ws->table[cols][rows];
439                                 printf("\n========\ncontainer has %d colspan, %d rowspan\n",
440                                                 container->colspan, container->rowspan);
441                                 printf("container at %d, %d\n", xoffset[rows], yoffset[cols]);
442                                 /* Update position of the container */
443                                 container->row = rows;
444                                 container->col = cols;
445                                 container->x = xoffset[rows];
446                                 container->y = yoffset[cols];
447
448                                 if (container->width_factor == 0)
449                                         container->width = (width / r_ws->cols);
450                                 else container->width = get_unoccupied_x(r_ws, rows) * container->width_factor;
451                                 container->width *= container->colspan;
452
453                                 if (container->height_factor == 0)
454                                         container->height = (height / r_ws->rows);
455                                 else container->height = get_unoccupied_y(r_ws, cols) * container->height_factor;
456                                 container->height *= container->rowspan;
457
458                                 /* Render the container if it is not empty */
459                                 render_container(conn, container);
460
461                                 xoffset[rows] += container->width;
462                                 yoffset[cols] += container->height;
463                                 printf("==========\n");
464                         }
465
466                 render_bars(conn, r_ws, width, &height);
467                 render_internal_bar(conn, r_ws, width, 18);
468         }
469
470         xcb_flush(conn);
471 }