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;
38 * Gets the unoccupied space (= space which is available for windows which were resized by the user)
39 * for the given row. This is necessary to render both, customly resized windows and never touched
40 * windows correctly, meaning that the aspect ratio will be maintained when opening new windows.
43 int get_unoccupied_x(Workspace *workspace, int row) {
44 int unoccupied = workspace->rect.width;
45 float default_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width;
47 printf("get_unoccupied_x(), starting with %d, default_factor = %f\n", unoccupied, default_factor);
49 for (int cols = 0; cols < workspace->cols;) {
50 Container *con = workspace->table[cols][row];
51 printf("width_factor[%d][%d] = %f, colspan = %d\n", cols, row, con->width_factor, con->colspan);
52 if (con->width_factor == 0)
53 unoccupied -= workspace->rect.width * default_factor * con->colspan;
57 assert(unoccupied != 0);
59 printf("unoccupied space: %d\n", unoccupied);
63 /* See get_unoccupied_x() */
64 int get_unoccupied_y(Workspace *workspace, int col) {
65 int unoccupied = workspace->rect.height;
66 float default_factor = ((float)workspace->rect.height / workspace->rows) / workspace->rect.height;
68 printf("get_unoccupied_y(), starting with %d, default_factor = %f\n", unoccupied, default_factor);
70 for (int rows = 0; rows < workspace->rows;) {
71 Container *con = workspace->table[col][rows];
72 printf("height_factor[%d][%d] = %f, rowspan %d\n", col, rows, con->height_factor, con->rowspan);
73 if (con->height_factor == 0)
74 unoccupied -= workspace->rect.height * default_factor * con->rowspan;
78 assert(unoccupied != 0);
80 printf("unoccupied space: %d\n", unoccupied);
85 * Redecorates the given client correctly by checking if it’s in a stacking container and
86 * re-rendering the stack window or just calling decorate_window if it’s not in a stacking
90 void redecorate_window(xcb_connection_t *conn, Client *client) {
91 if (client->container->mode == MODE_STACK) {
92 render_container(conn, client->container);
94 } else decorate_window(conn, client, client->frame, client->titlegc, 0);
98 * (Re-)draws window decorations for a given Client onto the given drawable/graphic context.
99 * When in stacking mode, the window decorations are drawn onto an own window.
102 void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable, xcb_gcontext_t gc, int offset) {
103 i3Font *font = load_font(conn, config.font);
104 uint32_t background_color,
108 /* Clients without a container (docks) won’t get decorated */
109 if (client->container == NULL)
112 if (client->container->currently_focused == client) {
113 /* Distinguish if the window is currently focused… */
114 if (CUR_CELL->currently_focused == client)
115 background_color = get_colorpixel(conn, client, client->frame, "#285577");
116 /* …or if it is the focused window in a not focused container */
117 else background_color = get_colorpixel(conn, client, client->frame, "#555555");
119 text_color = get_colorpixel(conn, client, client->frame, "#ffffff");
120 border_color = get_colorpixel(conn, client, client->frame, "#4c7899");
122 background_color = get_colorpixel(conn, client, client->frame, "#222222");
123 text_color = get_colorpixel(conn, client, client->frame, "#888888");
124 border_color = get_colorpixel(conn, client, client->frame, "#333333");
127 /* Our plan is the following:
128 - Draw a rect around the whole client in background_color
129 - Draw two lines in a lighter color
130 - Draw the window’s title
133 /* Draw a rectangle in background color around the window */
134 xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, background_color);
136 xcb_rectangle_t rect = {0, offset, client->rect.width, offset + client->rect.height};
137 xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
140 xcb_draw_line(conn, drawable, gc, border_color, 2, offset, client->rect.width, offset);
141 xcb_draw_line(conn, drawable, gc, border_color, 2, offset + font->height + 3,
142 2 + client->rect.width, offset + font->height + 3);
145 uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
146 uint32_t values[] = { text_color, background_color, font->id };
147 xcb_change_gc(conn, gc, mask, values);
151 asprintf(&label, "%.*s", client->name_len, client->name);
152 xcb_void_cookie_t text_cookie = xcb_image_text_8_checked(conn, strlen(label), drawable,
153 gc, 3 /* X */, offset + font->height /* Y = baseline of font */, label);
154 check_error(conn, text_cookie, "Could not draw client's title");
159 * Pushes the client’s x and y coordinates to X11
162 static void reposition_client(xcb_connection_t *connection, Client *client) {
163 printf("frame needs to be pushed to %dx%d\n", client->rect.x, client->rect.y);
164 /* Note: We can use a pointer to client->x like an array of uint32_ts
165 because it is followed by client->y by definition */
166 xcb_configure_window(connection, client->frame,
167 XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
171 * Pushes the client’s width/height to X11 and resizes the child window
174 static void resize_client(xcb_connection_t *connection, Client *client) {
175 i3Font *font = load_font(connection, config.font);
177 printf("resizing client to %d x %d\n", client->rect.width, client->rect.height);
178 xcb_configure_window(connection, client->frame,
179 XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
180 &(client->rect.width));
182 /* Adjust the position of the child inside its frame.
183 * The coordinates of the child are relative to its frame, we
184 * add a border of 2 pixel to each value */
185 uint32_t mask = XCB_CONFIG_WINDOW_X |
186 XCB_CONFIG_WINDOW_Y |
187 XCB_CONFIG_WINDOW_WIDTH |
188 XCB_CONFIG_WINDOW_HEIGHT;
189 Rect *rect = &(client->child_rect);
190 switch (client->container->mode) {
194 rect->width = client->rect.width - (2 + 2);
195 rect->height = client->rect.height - 2;
198 if (client->titlebar_position == TITLEBAR_OFF) {
201 rect->width = client->rect.width;
202 rect->height = client->rect.height;
205 rect->y = font->height + 2 + 2;
206 rect->width = client->rect.width - (2 + 2);
207 rect->height = client->rect.height - ((font->height + 2 + 2) + 2);
212 printf("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
214 xcb_configure_window(connection, client->child, mask, &(rect->x));
216 /* After configuring a child window we need to fake a configure_notify_event according
217 to ICCCM 4.2.3. This seems rather broken, especially since X sends exactly the same
218 configure_notify_event automatically according to xtrace. Anyone knows details? */
219 xcb_configure_notify_event_t event;
221 event.event = client->child;
222 event.window = client->child;
223 event.response_type = XCB_CONFIGURE_NOTIFY;
227 event.width = rect->width;
228 event.height = rect->height;
230 event.border_width = 0;
231 event.above_sibling = XCB_NONE;
232 event.override_redirect = false;
234 xcb_send_event(connection, false, client->child, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&event);
238 * Renders the given container. Is called by render_layout() or individually (for example
239 * when focus changes in a stacking container)
242 void render_container(xcb_connection_t *connection, Container *container) {
244 int num_clients = 0, current_client = 0;
246 if (container->currently_focused == NULL)
249 CIRCLEQ_FOREACH(client, &(container->clients), clients)
252 if (container->mode == MODE_DEFAULT) {
253 printf("got %d clients in this default container.\n", num_clients);
254 CIRCLEQ_FOREACH(client, &(container->clients), clients) {
255 /* Check if we changed client->x or client->y by updating it.
256 * Note the bitwise OR instead of logical OR to force evaluation of both statements */
257 if (client->force_reconfigure |
258 HAS_CHANGED(old_value_1, client->rect.x, container->x) |
259 HAS_CHANGED(old_value_2, client->rect.y, container->y +
260 (container->height / num_clients) * current_client))
261 reposition_client(connection, client);
263 /* TODO: vertical default layout */
264 if (client->force_reconfigure |
265 HAS_CHANGED(old_value_1, client->rect.width, container->width) |
266 HAS_CHANGED(old_value_2, client->rect.height, container->height / num_clients))
267 resize_client(connection, client);
269 client->force_reconfigure = false;
274 i3Font *font = load_font(connection, config.font);
275 int decoration_height = (font->height + 2 + 2);
276 struct Stack_Window *stack_win = &(container->stack_win);
278 /* Check if we need to remap our stack title window, it gets unmapped when the container
279 is empty in src/handlers.c:unmap_notify() */
280 if (stack_win->height == 0)
281 xcb_map_window(connection, stack_win->window);
283 /* Check if we need to reconfigure our stack title window */
284 if (HAS_CHANGED(old_value_1, stack_win->width, container->width) |
285 HAS_CHANGED(old_value_2, stack_win->height, decoration_height * num_clients)) {
286 xcb_configure_window(connection, stack_win->window,
287 XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, &(stack_win->width));
289 uint32_t values[] = { XCB_STACK_MODE_ABOVE };
290 xcb_configure_window(connection, stack_win->window, XCB_CONFIG_WINDOW_STACK_MODE, values);
293 /* Reconfigure the currently focused client, if necessary. It is the only visible one */
294 client = container->currently_focused;
296 /* Check if we changed client->x or client->y by updating it.
297 * Note the bitwise OR instead of logical OR to force evaluation of both statements */
298 if (client->force_reconfigure |
299 HAS_CHANGED(old_value_1, client->rect.x, container->x) |
300 HAS_CHANGED(old_value_2, client->rect.y, container->y + (decoration_height * num_clients)))
301 reposition_client(connection, client);
303 if (client->force_reconfigure |
304 HAS_CHANGED(old_value_1, client->rect.width, container->width) |
305 HAS_CHANGED(old_value_2, client->rect.height, container->height - (decoration_height * num_clients)))
306 resize_client(connection, client);
308 client->force_reconfigure = false;
310 if (container->workspace->fullscreen_client == NULL) {
311 uint32_t values[] = { XCB_STACK_MODE_ABOVE };
312 xcb_configure_window(connection, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
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);
322 static void render_bars(xcb_connection_t *connection, Workspace *r_ws, int width, int *height) {
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);
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);
335 client->force_reconfigure = false;
336 *height += client->desired_height;
340 static void render_internal_bar(xcb_connection_t *connection, Workspace *r_ws, int width, int height) {
341 printf("Rendering internal bar\n");
342 i3Font *font = load_font(connection, config.font);
343 i3Screen *screen = r_ws->screen;
344 enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
345 uint32_t background_color[2],
351 black = get_colorpixel(connection, NULL, screen->bar, "#000000");
353 background_color[SET_NORMAL] = get_colorpixel(connection, NULL, screen->bar, "#222222");
354 text_color[SET_NORMAL] = get_colorpixel(connection, NULL, screen->bar, "#888888");
355 border_color[SET_NORMAL] = get_colorpixel(connection, NULL, screen->bar, "#333333");
357 background_color[SET_FOCUSED] = get_colorpixel(connection, NULL, screen->bar, "#285577");
358 text_color[SET_FOCUSED] = get_colorpixel(connection, NULL, screen->bar, "#ffffff");
359 border_color[SET_FOCUSED] = get_colorpixel(connection, NULL, screen->bar, "#4c7899");
361 /* Fill the whole bar in black */
362 xcb_change_gc_single(connection, screen->bargc, XCB_GC_FOREGROUND, black);
363 xcb_rectangle_t rect = {0, 0, width, height};
364 xcb_poly_fill_rectangle(connection, screen->bar, screen->bargc, 1, &rect);
367 xcb_change_gc_single(connection, screen->bargc, XCB_GC_FONT, font->id);
370 for (int c = 0; c < 10; c++) {
371 if (workspaces[c].screen == screen) {
372 int set = (screen->current_workspace == c ? SET_FOCUSED : SET_NORMAL);
374 xcb_draw_rect(connection, screen->bar, screen->bargc, border_color[set],
375 drawn * height, 1, height - 2, height - 2);
376 xcb_draw_rect(connection, screen->bar, screen->bargc, background_color[set],
377 drawn * height + 1, 2, height - 4, height - 4);
379 snprintf(label, sizeof(label), "%d", c+1);
380 xcb_change_gc_single(connection, screen->bargc, XCB_GC_FOREGROUND, text_color[set]);
381 xcb_change_gc_single(connection, screen->bargc, XCB_GC_BACKGROUND, background_color[set]);
382 xcb_void_cookie_t text_cookie = xcb_image_text_8_checked(connection, strlen(label), screen->bar,
383 screen->bargc, drawn * height + 5 /* X */,
384 font->height + 1 /* Y = baseline of font */, label);
385 check_error(connection, text_cookie, "Could not draw workspace title");
390 printf("done rendering internal\n");
393 void render_layout(xcb_connection_t *connection) {
395 i3Font *font = load_font(connection, config.font);
397 TAILQ_FOREACH(screen, virtual_screens, screens) {
398 /* r_ws (rendering workspace) is just a shortcut to the Workspace being currently rendered */
399 Workspace *r_ws = &(workspaces[screen->current_workspace]);
401 printf("Rendering screen %d\n", screen->num);
402 if (r_ws->fullscreen_client != NULL)
403 /* This is easy: A client has entered fullscreen mode, so we don’t render at all */
406 int width = r_ws->rect.width;
407 int height = r_ws->rect.height;
409 /* Reserve space for dock clients */
411 SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients)
412 height -= client->desired_height;
414 /* Space for the internal bar */
415 height -= (font->height + 6);
417 printf("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
419 int xoffset[r_ws->rows];
420 int yoffset[r_ws->cols];
421 /* Initialize offsets */
422 for (int cols = 0; cols < r_ws->cols; cols++)
423 yoffset[cols] = r_ws->rect.y;
424 for (int rows = 0; rows < r_ws->rows; rows++)
425 xoffset[rows] = r_ws->rect.x;
427 /* Go through the whole table and render what’s necessary */
428 for (int cols = 0; cols < r_ws->cols; cols++)
429 for (int rows = 0; rows < r_ws->rows; rows++) {
430 Container *container = r_ws->table[cols][rows];
431 printf("\n========\ncontainer has %d colspan, %d rowspan\n",
432 container->colspan, container->rowspan);
433 printf("container at %d, %d\n", xoffset[rows], yoffset[cols]);
434 /* Update position of the container */
435 container->row = rows;
436 container->col = cols;
437 container->x = xoffset[rows];
438 container->y = yoffset[cols];
440 if (container->width_factor == 0)
441 container->width = (width / r_ws->cols);
442 else container->width = get_unoccupied_x(r_ws, rows) * container->width_factor;
443 container->width *= container->colspan;
445 if (container->height_factor == 0)
446 container->height = (height / r_ws->rows);
447 else container->height = get_unoccupied_y(r_ws, cols) * container->height_factor;
448 container->height *= container->rowspan;
450 /* Render the container if it is not empty */
451 render_container(connection, container);
453 xoffset[rows] += container->width;
454 yoffset[cols] += container->height;
455 printf("==========\n");
458 render_bars(connection, r_ws, width, &height);
459 render_internal_bar(connection, r_ws, width, 18);
462 xcb_flush(connection);