4 * i3 - an improved dynamic tiling window manager
6 * © 2009 Michael Stapelberg and contributors
8 * See file LICENSE for license information.
10 * layout.c: Functions handling layout/drawing of window decorations
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))
34 static int old_value_1;
35 static int old_value_2;
36 static int old_value_3;
37 static int old_value_4;
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.
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;
49 printf("get_unoccupied_x(), starting with %d, default_factor = %f\n", unoccupied, default_factor);
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;
59 assert(unoccupied != 0);
61 printf("unoccupied space: %d\n", unoccupied);
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;
70 printf("get_unoccupied_y(), starting with %d, default_factor = %f\n", unoccupied, default_factor);
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;
80 assert(unoccupied != 0);
82 printf("unoccupied space: %d\n", unoccupied);
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
92 void redecorate_window(xcb_connection_t *conn, Client *client) {
93 if (client->container->mode == MODE_STACK) {
94 render_container(conn, client->container);
96 } else decorate_window(conn, client, client->frame, client->titlegc, 0);
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.
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,
110 /* Clients without a container (docks) won’t get decorated */
111 if (client->container == NULL)
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");
121 text_color = get_colorpixel(conn, "#ffffff");
122 border_color = get_colorpixel(conn, "#4c7899");
124 background_color = get_colorpixel(conn, "#222222");
125 text_color = get_colorpixel(conn, "#888888");
126 border_color = get_colorpixel(conn, "#333333");
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
135 /* Draw a rectangle in background color around the window */
136 xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, background_color);
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);
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);
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);
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);
162 * Pushes the client’s x and y coordinates to X11
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));
173 * Pushes the client’s width/height to X11 and resizes the child window
176 static void resize_client(xcb_connection_t *conn, Client *client) {
177 i3Font *font = load_font(conn, config.font);
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));
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) {
196 rect->width = client->rect.width - (2 + 2);
197 rect->height = client->rect.height - 2;
200 if (client->titlebar_position == TITLEBAR_OFF) {
203 rect->width = client->rect.width;
204 rect->height = client->rect.height;
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);
214 printf("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
216 xcb_configure_window(conn, client->child, mask, &(rect->x));
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;
223 event.event = client->child;
224 event.window = client->child;
225 event.response_type = XCB_CONFIGURE_NOTIFY;
229 event.width = rect->width;
230 event.height = rect->height;
232 event.border_width = 0;
233 event.above_sibling = XCB_NONE;
234 event.override_redirect = false;
236 xcb_send_event(conn, false, client->child, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&event);
240 * Renders the given container. Is called by render_layout() or individually (for example
241 * when focus changes in a stacking container)
244 void render_container(xcb_connection_t *conn, Container *container) {
246 int num_clients = 0, current_client = 0;
248 if (container->currently_focused == NULL)
251 CIRCLEQ_FOREACH(client, &(container->clients), clients)
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);
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);
271 client->force_reconfigure = false;
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);
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);
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)) {
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 };
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,
302 /* Reconfigure the currently focused client, if necessary. It is the only visible one */
303 client = container->currently_focused;
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);
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);
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);
324 client->force_reconfigure = false;
326 decorate_window(conn, client, stack_win->window, stack_win->gc,
327 current_client++ * decoration_height);
332 static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) {
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);
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);
345 client->force_reconfigure = false;
346 *height += client->desired_height;
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],
361 black = get_colorpixel(conn, "#000000");
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");
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");
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);
377 xcb_change_gc_single(conn, screen->bargc, XCB_GC_FONT, font->id);
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);
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);
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);
398 printf("done rendering internal\n");
401 void render_layout(xcb_connection_t *conn) {
403 i3Font *font = load_font(conn, config.font);
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]);
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 */
414 int width = r_ws->rect.width;
415 int height = r_ws->rect.height;
417 /* Reserve space for dock clients */
419 SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients)
420 height -= client->desired_height;
422 /* Space for the internal bar */
423 height -= (font->height + 6);
425 printf("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
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;
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];
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;
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;
458 /* Render the container if it is not empty */
459 render_container(conn, container);
461 xoffset[rows] += container->width;
462 yoffset[cols] += container->height;
463 printf("==========\n");
466 render_bars(conn, r_ws, width, &height);
467 render_internal_bar(conn, r_ws, width, 18);