]> git.sur5r.net Git - i3/i3/blob - src/layout.c
8b3f6e98aa5c9e79cfda018ea855ef7c897358ff
[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 void render_layout(xcb_connection_t *connection) {
342         i3Screen *screen;
343
344         TAILQ_FOREACH(screen, virtual_screens, screens) {
345                 /* r_ws (rendering workspace) is just a shortcut to the Workspace being currently rendered */
346                 Workspace *r_ws = &(workspaces[screen->current_workspace]);
347
348                 printf("Rendering screen %d\n", screen->num);
349                 if (r_ws->fullscreen_client != NULL)
350                         /* This is easy: A client has entered fullscreen mode, so we don’t render at all */
351                         continue;
352
353                 int width = r_ws->rect.width;
354                 int height = r_ws->rect.height;
355
356                 /* Reserve space for dock clients */
357                 Client *client;
358                 SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients)
359                         height -= client->desired_height;
360
361                 printf("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
362
363                 int xoffset[r_ws->rows];
364                 int yoffset[r_ws->cols];
365                 /* Initialize offsets */
366                 for (int cols = 0; cols < r_ws->cols; cols++)
367                         yoffset[cols] = r_ws->rect.y;
368                 for (int rows = 0; rows < r_ws->rows; rows++)
369                         xoffset[rows] = r_ws->rect.x;
370
371                 /* Go through the whole table and render what’s necessary */
372                 for (int cols = 0; cols < r_ws->cols; cols++)
373                         for (int rows = 0; rows < r_ws->rows; rows++) {
374                                 Container *container = r_ws->table[cols][rows];
375                                 printf("\n========\ncontainer has %d colspan, %d rowspan\n",
376                                                 container->colspan, container->rowspan);
377                                 printf("container at %d, %d\n", xoffset[rows], yoffset[cols]);
378                                 /* Update position of the container */
379                                 container->row = rows;
380                                 container->col = cols;
381                                 container->x = xoffset[rows];
382                                 container->y = yoffset[cols];
383
384                                 if (container->width_factor == 0)
385                                         container->width = (width / r_ws->cols);
386                                 else container->width = get_unoccupied_x(r_ws, rows) * container->width_factor;
387                                 container->width *= container->colspan;
388
389                                 if (container->height_factor == 0)
390                                         container->height = (height / r_ws->rows);
391                                 else container->height = get_unoccupied_y(r_ws, cols) * container->height_factor;
392                                 container->height *= container->rowspan;
393
394                                 /* Render the container if it is not empty */
395                                 render_container(connection, container);
396
397                                 xoffset[rows] += container->width;
398                                 yoffset[cols] += container->height;
399                                 printf("==========\n");
400                         }
401
402                 render_bars(connection, r_ws, width, height);
403         }
404
405         xcb_flush(connection);
406 }