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
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))
35 static int old_value_1;
36 static int old_value_2;
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.
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;
48 printf("get_unoccupied_x(), starting with %d, default_factor = %f\n", unoccupied, default_factor);
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;
58 assert(unoccupied != 0);
60 printf("unoccupied space: %d\n", unoccupied);
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;
69 printf("get_unoccupied_y(), starting with %d, default_factor = %f\n", unoccupied, default_factor);
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;
79 assert(unoccupied != 0);
81 printf("unoccupied space: %d\n", unoccupied);
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
91 void redecorate_window(xcb_connection_t *conn, Client *client) {
92 if (client->container->mode == MODE_STACK) {
93 render_container(conn, client->container);
95 } else decorate_window(conn, client, client->frame, client->titlegc, 0);
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.
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,
109 /* Clients without a container (docks) won’t get decorated */
110 if (client->container == NULL)
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");
120 text_color = get_colorpixel(conn, client, client->frame, "#ffffff");
121 border_color = get_colorpixel(conn, client, client->frame, "#4c7899");
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");
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
134 /* Draw a rectangle in background color around the window */
135 xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, background_color);
137 xcb_rectangle_t rect = {0, offset, client->rect.width, offset + client->rect.height};
138 xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
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);
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);
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");
160 * Pushes the client’s x and y coordinates to X11
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));
172 * Pushes the client’s width/height to X11 and resizes the child window
175 static void resize_client(xcb_connection_t *connection, Client *client) {
176 i3Font *font = load_font(connection, config.font);
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));
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) {
195 rect->width = client->rect.width - (2 + 2);
196 rect->height = client->rect.height - 2;
199 if (client->titlebar_position == TITLEBAR_OFF) {
202 rect->width = client->rect.width;
203 rect->height = client->rect.height;
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);
213 printf("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
215 xcb_configure_window(connection, client->child, mask, &(rect->x));
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;
222 event.event = client->child;
223 event.window = client->child;
224 event.response_type = XCB_CONFIGURE_NOTIFY;
228 event.width = rect->width;
229 event.height = rect->height;
231 event.border_width = 0;
232 event.above_sibling = XCB_NONE;
233 event.override_redirect = false;
235 xcb_send_event(connection, false, client->child, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&event);
239 * Renders the given container. Is called by render_layout() or individually (for example
240 * when focus changes in a stacking container)
243 void render_container(xcb_connection_t *connection, Container *container) {
245 int num_clients = 0, current_client = 0;
247 if (container->currently_focused == NULL)
250 CIRCLEQ_FOREACH(client, &(container->clients), clients)
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);
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);
270 client->force_reconfigure = false;
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);
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);
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));
290 uint32_t values[] = { XCB_STACK_MODE_ABOVE };
291 xcb_configure_window(connection, stack_win->window, XCB_CONFIG_WINDOW_STACK_MODE, values);
294 /* Reconfigure the currently focused client, if necessary. It is the only visible one */
295 client = container->currently_focused;
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);
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);
309 client->force_reconfigure = false;
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);
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);
323 static void render_bars(xcb_connection_t *connection, Workspace *r_ws, int width, int *height) {
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);
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);
336 client->force_reconfigure = false;
337 *height += client->desired_height;
341 static void render_internal_bar(xcb_connection_t *connection, Workspace *r_ws, int width, int height) {
342 printf("Rendering internal bar\n");
343 i3Font *font = load_font(connection, config.font);
344 i3Screen *screen = r_ws->screen;
345 enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
346 uint32_t background_color[2],
352 black = get_colorpixel(connection, NULL, screen->bar, "#000000");
354 background_color[SET_NORMAL] = get_colorpixel(connection, NULL, screen->bar, "#222222");
355 text_color[SET_NORMAL] = get_colorpixel(connection, NULL, screen->bar, "#888888");
356 border_color[SET_NORMAL] = get_colorpixel(connection, NULL, screen->bar, "#333333");
358 background_color[SET_FOCUSED] = get_colorpixel(connection, NULL, screen->bar, "#285577");
359 text_color[SET_FOCUSED] = get_colorpixel(connection, NULL, screen->bar, "#ffffff");
360 border_color[SET_FOCUSED] = get_colorpixel(connection, NULL, screen->bar, "#4c7899");
362 /* Fill the whole bar in black */
363 xcb_change_gc_single(connection, screen->bargc, XCB_GC_FOREGROUND, black);
364 xcb_rectangle_t rect = {0, 0, width, height};
365 xcb_poly_fill_rectangle(connection, screen->bar, screen->bargc, 1, &rect);
368 xcb_change_gc_single(connection, screen->bargc, XCB_GC_FONT, font->id);
371 for (int c = 0; c < 10; c++) {
372 if (workspaces[c].screen == screen) {
373 int set = (screen->current_workspace == c ? SET_FOCUSED : SET_NORMAL);
375 xcb_draw_rect(connection, screen->bar, screen->bargc, border_color[set],
376 drawn * height, 1, height - 2, height - 2);
377 xcb_draw_rect(connection, screen->bar, screen->bargc, background_color[set],
378 drawn * height + 1, 2, height - 4, height - 4);
380 snprintf(label, sizeof(label), "%d", c+1);
381 xcb_change_gc_single(connection, screen->bargc, XCB_GC_FOREGROUND, text_color[set]);
382 xcb_change_gc_single(connection, screen->bargc, XCB_GC_BACKGROUND, background_color[set]);
383 xcb_void_cookie_t text_cookie = xcb_image_text_8_checked(connection, strlen(label), screen->bar,
384 screen->bargc, drawn * height + 5 /* X */,
385 font->height + 1 /* Y = baseline of font */, label);
386 check_error(connection, text_cookie, "Could not draw workspace title");
391 printf("done rendering internal\n");
394 void render_layout(xcb_connection_t *connection) {
396 i3Font *font = load_font(connection, config.font);
398 TAILQ_FOREACH(screen, virtual_screens, screens) {
399 /* r_ws (rendering workspace) is just a shortcut to the Workspace being currently rendered */
400 Workspace *r_ws = &(workspaces[screen->current_workspace]);
402 printf("Rendering screen %d\n", screen->num);
403 if (r_ws->fullscreen_client != NULL)
404 /* This is easy: A client has entered fullscreen mode, so we don’t render at all */
407 int width = r_ws->rect.width;
408 int height = r_ws->rect.height;
410 /* Reserve space for dock clients */
412 SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients)
413 height -= client->desired_height;
415 /* Space for the internal bar */
416 height -= (font->height + 6);
418 printf("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
420 int xoffset[r_ws->rows];
421 int yoffset[r_ws->cols];
422 /* Initialize offsets */
423 for (int cols = 0; cols < r_ws->cols; cols++)
424 yoffset[cols] = r_ws->rect.y;
425 for (int rows = 0; rows < r_ws->rows; rows++)
426 xoffset[rows] = r_ws->rect.x;
428 /* Go through the whole table and render what’s necessary */
429 for (int cols = 0; cols < r_ws->cols; cols++)
430 for (int rows = 0; rows < r_ws->rows; rows++) {
431 Container *container = r_ws->table[cols][rows];
432 printf("\n========\ncontainer has %d colspan, %d rowspan\n",
433 container->colspan, container->rowspan);
434 printf("container at %d, %d\n", xoffset[rows], yoffset[cols]);
435 /* Update position of the container */
436 container->row = rows;
437 container->col = cols;
438 container->x = xoffset[rows];
439 container->y = yoffset[cols];
441 if (container->width_factor == 0)
442 container->width = (width / r_ws->cols);
443 else container->width = get_unoccupied_x(r_ws, rows) * container->width_factor;
444 container->width *= container->colspan;
446 if (container->height_factor == 0)
447 container->height = (height / r_ws->rows);
448 else container->height = get_unoccupied_y(r_ws, cols) * container->height_factor;
449 container->height *= container->rowspan;
451 /* Render the container if it is not empty */
452 render_container(connection, container);
454 xoffset[rows] += container->width;
455 yoffset[cols] += container->height;
456 printf("==========\n");
459 render_bars(connection, r_ws, width, &height);
460 render_internal_bar(connection, r_ws, width, 18);
463 xcb_flush(connection);