]> git.sur5r.net Git - i3/i3/blob - src/layout.c
ad18f936e6c4b7542005b8987c4c37cfc89d3d8f
[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 /*
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         xcb_rectangle_t rect = {0, offset, client->rect.width, offset + client->rect.height};
139         xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
140
141         /* Draw the lines */
142         xcb_draw_line(conn, drawable, gc, border_color, 2, offset, client->rect.width, offset);
143         xcb_draw_line(conn, drawable, gc, border_color, 2, offset + font->height + 3,
144                       2 + client->rect.width, offset + font->height + 3);
145
146         /* Draw the font */
147         uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
148         uint32_t values[] = { text_color, background_color, font->id };
149         xcb_change_gc(conn, gc, mask, values);
150
151         /* TODO: utf8? */
152         char *label;
153         asprintf(&label, "(%08x) %.*s", client->frame, client->name_len, client->name);
154         xcb_void_cookie_t text_cookie = xcb_image_text_8_checked(conn, strlen(label), drawable,
155                                         gc, 3 /* X */, offset + font->height /* Y = baseline of font */, label);
156         check_error(conn, text_cookie, "Could not draw client's title");
157         free(label);
158 }
159
160 /*
161  * Pushes the client’s x and y coordinates to X11
162  *
163  */
164 static void reposition_client(xcb_connection_t *connection, Client *client) {
165         printf("frame needs to be pushed to %dx%d\n", client->rect.x, client->rect.y);
166         /* Note: We can use a pointer to client->x like an array of uint32_ts
167            because it is followed by client->y by definition */
168         xcb_configure_window(connection, client->frame,
169                         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 *connection, Client *client) {
177         i3Font *font = load_font(connection, config.font);
178
179         printf("resizing client to %d x %d\n", client->rect.width, client->rect.height);
180         xcb_configure_window(connection, 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(connection, 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(connection, 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 *connection, 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(connection, 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(connection, client);
270
271                         client->force_reconfigure = false;
272
273                         current_client++;
274                 }
275         } else {
276                 i3Font *font = load_font(connection, 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->height == 0)
283                         xcb_map_window(connection, stack_win->window);
284
285                 /* Check if we need to reconfigure our stack title window */
286                 if (HAS_CHANGED(old_value_1, stack_win->width, container->width) |
287                     HAS_CHANGED(old_value_2, stack_win->height, decoration_height * num_clients)) {
288                         xcb_configure_window(connection, stack_win->window,
289                                 XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, &(stack_win->width));
290
291                         uint32_t values[] = { XCB_STACK_MODE_ABOVE };
292                         xcb_configure_window(connection, stack_win->window, XCB_CONFIG_WINDOW_STACK_MODE, values);
293                 }
294
295                 /* Reconfigure the currently focused client, if necessary. It is the only visible one */
296                 client = container->currently_focused;
297
298                 /* Check if we changed client->x or client->y by updating it.
299                  * Note the bitwise OR instead of logical OR to force evaluation of both statements */
300                 if (client->force_reconfigure |
301                     HAS_CHANGED(old_value_1, client->rect.x, container->x) |
302                     HAS_CHANGED(old_value_2, client->rect.y, container->y + (decoration_height * num_clients)))
303                         reposition_client(connection, client);
304
305                 if (client->force_reconfigure |
306                     HAS_CHANGED(old_value_1, client->rect.width, container->width) |
307                     HAS_CHANGED(old_value_2, client->rect.height, container->height - (decoration_height * num_clients)))
308                         resize_client(connection, client);
309
310                 client->force_reconfigure = false;
311
312                 uint32_t values[] = { XCB_STACK_MODE_ABOVE };
313                 xcb_configure_window(connection, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
314
315                 /* Render the decorations of all clients */
316                 CIRCLEQ_FOREACH(client, &(container->clients), clients)
317                         decorate_window(connection, client, stack_win->window, stack_win->gc,
318                                         current_client++ * decoration_height);
319         }
320 }
321
322 static void render_bars(xcb_connection_t *connection, Workspace *r_ws, int width, int height) {
323         Client *client;
324         SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) {
325                 if (client->force_reconfigure |
326                     HAS_CHANGED(old_value_1, client->rect.x, 0) |
327                     HAS_CHANGED(old_value_2, client->rect.y, height))
328                         reposition_client(connection, client);
329
330                 if (client->force_reconfigure |
331                     HAS_CHANGED(old_value_1, client->rect.width, width) |
332                     HAS_CHANGED(old_value_2, client->rect.height, client->desired_height))
333                         resize_client(connection, client);
334
335                 client->force_reconfigure = false;
336                 height += client->desired_height;
337         }
338 }
339
340 void render_layout(xcb_connection_t *connection) {
341         i3Screen *screen;
342
343         TAILQ_FOREACH(screen, &virtual_screens, screens) {
344                 /* r_ws (rendering workspace) is just a shortcut to the Workspace being currently rendered */
345                 Workspace *r_ws = &(workspaces[screen->current_workspace]);
346
347                 printf("Rendering screen %d\n", screen->num);
348                 if (r_ws->fullscreen_client != NULL)
349                         /* This is easy: A client has entered fullscreen mode, so we don’t render at all */
350                         continue;
351
352                 int width = r_ws->rect.width;
353                 int height = r_ws->rect.height;
354
355                 /* Reserve space for dock clients */
356                 Client *client;
357                 SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients)
358                         height -= client->desired_height;
359
360                 printf("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
361
362                 int xoffset[r_ws->rows];
363                 int yoffset[r_ws->cols];
364                 /* Initialize offsets */
365                 for (int cols = 0; cols < r_ws->cols; cols++)
366                         yoffset[cols] = r_ws->rect.y;
367                 for (int rows = 0; rows < r_ws->rows; rows++)
368                         xoffset[rows] = r_ws->rect.x;
369
370                 /* Go through the whole table and render what’s necessary */
371                 for (int cols = 0; cols < r_ws->cols; cols++)
372                         for (int rows = 0; rows < r_ws->rows; rows++) {
373                                 Container *container = r_ws->table[cols][rows];
374                                 printf("\n========\ncontainer has %d colspan, %d rowspan\n",
375                                                 container->colspan, container->rowspan);
376                                 printf("container at %d, %d\n", xoffset[rows], yoffset[cols]);
377                                 /* Update position of the container */
378                                 container->row = rows;
379                                 container->col = cols;
380                                 container->x = xoffset[rows];
381                                 container->y = yoffset[cols];
382
383                                 if (container->width_factor == 0)
384                                         container->width = (width / r_ws->cols);
385                                 else container->width = get_unoccupied_x(r_ws, rows) * container->width_factor;
386                                 container->width *= container->colspan;
387
388                                 if (container->height_factor == 0)
389                                         container->height = (height / r_ws->rows);
390                                 else container->height = get_unoccupied_y(r_ws, cols) * container->height_factor;
391                                 container->height *= container->rowspan;
392
393                                 /* Render the container if it is not empty */
394                                 render_container(connection, container);
395
396                                 xoffset[rows] += container->width;
397                                 yoffset[cols] += container->height;
398                                 printf("==========\n");
399                         }
400
401                 render_bars(connection, r_ws, width, height);
402         }
403
404         xcb_flush(connection);
405 }