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);
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 xcb_rectangle_t rect = {0, offset, client->rect.width, offset + client->rect.height};
139 xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
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);
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);
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");
161 * Pushes the client’s x and y coordinates to X11
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));
173 * Pushes the client’s width/height to X11 and resizes the child window
176 static void resize_client(xcb_connection_t *connection, Client *client) {
177 i3Font *font = load_font(connection, config.font);
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));
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(connection, 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(connection, 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 *connection, 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(connection, 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(connection, client);
271 client->force_reconfigure = false;
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);
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);
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));
291 uint32_t values[] = { XCB_STACK_MODE_ABOVE };
292 xcb_configure_window(connection, stack_win->window, XCB_CONFIG_WINDOW_STACK_MODE, values);
295 /* Reconfigure the currently focused client, if necessary. It is the only visible one */
296 client = container->currently_focused;
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);
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);
310 client->force_reconfigure = false;
312 uint32_t values[] = { XCB_STACK_MODE_ABOVE };
313 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 void render_layout(xcb_connection_t *connection) {
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]);
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 */
352 int width = r_ws->rect.width;
353 int height = r_ws->rect.height;
355 /* Reserve space for dock clients */
357 SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients)
358 height -= client->desired_height;
360 printf("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
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;
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];
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;
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;
393 /* Render the container if it is not empty */
394 render_container(connection, container);
396 xoffset[rows] += container->width;
397 yoffset[cols] += container->height;
398 printf("==========\n");
401 render_bars(connection, r_ws, width, height);
404 xcb_flush(connection);