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, 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");
121 text_color = get_colorpixel(conn, client, client->frame, "#ffffff");
122 border_color = get_colorpixel(conn, client, client->frame, "#4c7899");
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");
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_void_cookie_t text_cookie = xcb_image_text_8_checked(conn, strlen(label), drawable,
158 gc, 3 /* X */, offset + font->height /* Y = baseline of font */, label);
159 check_error(conn, text_cookie, "Could not draw client's title");
164 * Pushes the client’s x and y coordinates to X11
167 static void reposition_client(xcb_connection_t *conn, Client *client) {
168 printf("frame needs to be pushed to %dx%d\n", client->rect.x, client->rect.y);
169 /* Note: We can use a pointer to client->x like an array of uint32_ts
170 because it is followed by client->y by definition */
171 xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
175 * Pushes the client’s width/height to X11 and resizes the child window
178 static void resize_client(xcb_connection_t *conn, Client *client) {
179 i3Font *font = load_font(conn, config.font);
181 printf("resizing client to %d x %d\n", client->rect.width, client->rect.height);
182 xcb_configure_window(conn, client->frame,
183 XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
184 &(client->rect.width));
186 /* Adjust the position of the child inside its frame.
187 * The coordinates of the child are relative to its frame, we
188 * add a border of 2 pixel to each value */
189 uint32_t mask = XCB_CONFIG_WINDOW_X |
190 XCB_CONFIG_WINDOW_Y |
191 XCB_CONFIG_WINDOW_WIDTH |
192 XCB_CONFIG_WINDOW_HEIGHT;
193 Rect *rect = &(client->child_rect);
194 switch (client->container->mode) {
198 rect->width = client->rect.width - (2 + 2);
199 rect->height = client->rect.height - 2;
202 if (client->titlebar_position == TITLEBAR_OFF) {
205 rect->width = client->rect.width;
206 rect->height = client->rect.height;
209 rect->y = font->height + 2 + 2;
210 rect->width = client->rect.width - (2 + 2);
211 rect->height = client->rect.height - ((font->height + 2 + 2) + 2);
216 printf("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
218 xcb_configure_window(conn, client->child, mask, &(rect->x));
220 /* After configuring a child window we need to fake a configure_notify_event according
221 to ICCCM 4.2.3. This seems rather broken, especially since X sends exactly the same
222 configure_notify_event automatically according to xtrace. Anyone knows details? */
223 xcb_configure_notify_event_t event;
225 event.event = client->child;
226 event.window = client->child;
227 event.response_type = XCB_CONFIGURE_NOTIFY;
231 event.width = rect->width;
232 event.height = rect->height;
234 event.border_width = 0;
235 event.above_sibling = XCB_NONE;
236 event.override_redirect = false;
238 xcb_send_event(conn, false, client->child, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&event);
242 * Renders the given container. Is called by render_layout() or individually (for example
243 * when focus changes in a stacking container)
246 void render_container(xcb_connection_t *conn, Container *container) {
248 int num_clients = 0, current_client = 0;
250 if (container->currently_focused == NULL)
253 CIRCLEQ_FOREACH(client, &(container->clients), clients)
256 if (container->mode == MODE_DEFAULT) {
257 printf("got %d clients in this default container.\n", num_clients);
258 CIRCLEQ_FOREACH(client, &(container->clients), clients) {
259 /* Check if we changed client->x or client->y by updating it.
260 * Note the bitwise OR instead of logical OR to force evaluation of both statements */
261 if (client->force_reconfigure |
262 HAS_CHANGED(old_value_1, client->rect.x, container->x) |
263 HAS_CHANGED(old_value_2, client->rect.y, container->y +
264 (container->height / num_clients) * current_client))
265 reposition_client(conn, client);
267 /* TODO: vertical default layout */
268 if (client->force_reconfigure |
269 HAS_CHANGED(old_value_1, client->rect.width, container->width) |
270 HAS_CHANGED(old_value_2, client->rect.height, container->height / num_clients))
271 resize_client(conn, client);
273 client->force_reconfigure = false;
278 i3Font *font = load_font(conn, config.font);
279 int decoration_height = (font->height + 2 + 2);
280 struct Stack_Window *stack_win = &(container->stack_win);
282 /* Check if we need to remap our stack title window, it gets unmapped when the container
283 is empty in src/handlers.c:unmap_notify() */
284 if (stack_win->rect.height == 0)
285 xcb_map_window(conn, stack_win->window);
287 /* Check if we need to reconfigure our stack title window */
288 if (HAS_CHANGED(old_value_1, stack_win->rect.x, container->x) |
289 HAS_CHANGED(old_value_2, stack_win->rect.y, container->y) |
290 HAS_CHANGED(old_value_3, stack_win->rect.width, container->width) |
291 HAS_CHANGED(old_value_4, stack_win->rect.height, decoration_height * num_clients)) {
293 uint32_t values[] = { stack_win->rect.x, stack_win->rect.y,
294 stack_win->rect.width, stack_win->rect.height,
295 XCB_STACK_MODE_ABOVE };
297 xcb_configure_window(conn, stack_win->window,
298 XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
299 XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
300 XCB_CONFIG_WINDOW_STACK_MODE,
304 /* Reconfigure the currently focused client, if necessary. It is the only visible one */
305 client = container->currently_focused;
307 if (container->workspace->fullscreen_client == NULL) {
308 uint32_t values[] = { XCB_STACK_MODE_ABOVE };
309 xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
312 /* Render the decorations of all clients */
313 CIRCLEQ_FOREACH(client, &(container->clients), clients) {
314 /* Check if we changed client->x or client->y by updating it.
315 * Note the bitwise OR instead of logical OR to force evaluation of both statements */
316 if (client->force_reconfigure |
317 HAS_CHANGED(old_value_1, client->rect.x, container->x) |
318 HAS_CHANGED(old_value_2, client->rect.y, container->y + (decoration_height * num_clients)))
319 reposition_client(conn, client);
321 if (client->force_reconfigure |
322 HAS_CHANGED(old_value_1, client->rect.width, container->width) |
323 HAS_CHANGED(old_value_2, client->rect.height, container->height - (decoration_height * num_clients)))
324 resize_client(conn, client);
326 client->force_reconfigure = false;
328 decorate_window(conn, client, stack_win->window, stack_win->gc,
329 current_client++ * decoration_height);
334 static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) {
336 SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) {
337 if (client->force_reconfigure |
338 HAS_CHANGED(old_value_1, client->rect.x, 0) |
339 HAS_CHANGED(old_value_2, client->rect.y, *height))
340 reposition_client(conn, client);
342 if (client->force_reconfigure |
343 HAS_CHANGED(old_value_1, client->rect.width, width) |
344 HAS_CHANGED(old_value_2, client->rect.height, client->desired_height))
345 resize_client(conn, client);
347 client->force_reconfigure = false;
348 *height += client->desired_height;
352 static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) {
353 printf("Rendering internal bar\n");
354 i3Font *font = load_font(conn, config.font);
355 i3Screen *screen = r_ws->screen;
356 enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
357 uint32_t background_color[2],
363 black = get_colorpixel(conn, NULL, screen->bar, "#000000");
365 background_color[SET_NORMAL] = get_colorpixel(conn, NULL, screen->bar, "#222222");
366 text_color[SET_NORMAL] = get_colorpixel(conn, NULL, screen->bar, "#888888");
367 border_color[SET_NORMAL] = get_colorpixel(conn, NULL, screen->bar, "#333333");
369 background_color[SET_FOCUSED] = get_colorpixel(conn, NULL, screen->bar, "#285577");
370 text_color[SET_FOCUSED] = get_colorpixel(conn, NULL, screen->bar, "#ffffff");
371 border_color[SET_FOCUSED] = get_colorpixel(conn, NULL, screen->bar, "#4c7899");
373 /* Fill the whole bar in black */
374 xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, black);
375 xcb_rectangle_t rect = {0, 0, width, height};
376 xcb_poly_fill_rectangle(conn, screen->bar, screen->bargc, 1, &rect);
379 xcb_change_gc_single(conn, screen->bargc, XCB_GC_FONT, font->id);
382 for (int c = 0; c < 10; c++) {
383 if (workspaces[c].screen == screen) {
384 int set = (screen->current_workspace == c ? SET_FOCUSED : SET_NORMAL);
386 xcb_draw_rect(conn, screen->bar, screen->bargc, border_color[set],
387 drawn * height, 1, height - 2, height - 2);
388 xcb_draw_rect(conn, screen->bar, screen->bargc, background_color[set],
389 drawn * height + 1, 2, height - 4, height - 4);
391 snprintf(label, sizeof(label), "%d", c+1);
392 xcb_change_gc_single(conn, screen->bargc, XCB_GC_FOREGROUND, text_color[set]);
393 xcb_change_gc_single(conn, screen->bargc, XCB_GC_BACKGROUND, background_color[set]);
394 xcb_void_cookie_t text_cookie = xcb_image_text_8_checked(conn, strlen(label), screen->bar,
395 screen->bargc, drawn * height + 5 /* X */,
396 font->height + 1 /* Y = baseline of font */, label);
397 check_error(conn, text_cookie, "Could not draw workspace title");
402 printf("done rendering internal\n");
405 void render_layout(xcb_connection_t *conn) {
407 i3Font *font = load_font(conn, config.font);
409 TAILQ_FOREACH(screen, virtual_screens, screens) {
410 /* r_ws (rendering workspace) is just a shortcut to the Workspace being currently rendered */
411 Workspace *r_ws = &(workspaces[screen->current_workspace]);
413 printf("Rendering screen %d\n", screen->num);
414 if (r_ws->fullscreen_client != NULL)
415 /* This is easy: A client has entered fullscreen mode, so we don’t render at all */
418 int width = r_ws->rect.width;
419 int height = r_ws->rect.height;
421 /* Reserve space for dock clients */
423 SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients)
424 height -= client->desired_height;
426 /* Space for the internal bar */
427 height -= (font->height + 6);
429 printf("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
431 int xoffset[r_ws->rows];
432 int yoffset[r_ws->cols];
433 /* Initialize offsets */
434 for (int cols = 0; cols < r_ws->cols; cols++)
435 yoffset[cols] = r_ws->rect.y;
436 for (int rows = 0; rows < r_ws->rows; rows++)
437 xoffset[rows] = r_ws->rect.x;
439 /* Go through the whole table and render what’s necessary */
440 for (int cols = 0; cols < r_ws->cols; cols++)
441 for (int rows = 0; rows < r_ws->rows; rows++) {
442 Container *container = r_ws->table[cols][rows];
443 printf("\n========\ncontainer has %d colspan, %d rowspan\n",
444 container->colspan, container->rowspan);
445 printf("container at %d, %d\n", xoffset[rows], yoffset[cols]);
446 /* Update position of the container */
447 container->row = rows;
448 container->col = cols;
449 container->x = xoffset[rows];
450 container->y = yoffset[cols];
452 if (container->width_factor == 0)
453 container->width = (width / r_ws->cols);
454 else container->width = get_unoccupied_x(r_ws, rows) * container->width_factor;
455 container->width *= container->colspan;
457 if (container->height_factor == 0)
458 container->height = (height / r_ws->rows);
459 else container->height = get_unoccupied_y(r_ws, cols) * container->height_factor;
460 container->height *= container->rowspan;
462 /* Render the container if it is not empty */
463 render_container(conn, container);
465 xoffset[rows] += container->width;
466 yoffset[cols] += container->height;
467 printf("==========\n");
470 render_bars(conn, r_ws, width, &height);
471 render_internal_bar(conn, r_ws, width, 18);