i3
|
00001 /* 00002 * vim:ts=8:expandtab 00003 * 00004 * i3 - an improved dynamic tiling window manager 00005 * 00006 * © 2009-2010 Michael Stapelberg and contributors 00007 * 00008 * See file LICENSE for license information. 00009 * 00010 * layout.c: Functions handling layout/drawing of window decorations 00011 * 00012 */ 00013 #include <stdio.h> 00014 #include <string.h> 00015 #include <stdlib.h> 00016 #include <xcb/xcb.h> 00017 #include <assert.h> 00018 #include <math.h> 00019 00020 #include "config.h" 00021 #include "i3.h" 00022 #include "xcb.h" 00023 #include "table.h" 00024 #include "util.h" 00025 #include "randr.h" 00026 #include "layout.h" 00027 #include "client.h" 00028 #include "floating.h" 00029 #include "handlers.h" 00030 #include "workspace.h" 00031 #include "log.h" 00032 #include "container.h" 00033 00034 /* 00035 * Gets the unoccupied space (= space which is available for windows which were resized by the user) 00036 * for the given row. This is necessary to render both, customly resized windows and never touched 00037 * windows correctly, meaning that the aspect ratio will be maintained when opening new windows. 00038 * 00039 */ 00040 int get_unoccupied_x(Workspace *workspace) { 00041 double unoccupied = workspace->rect.width; 00042 double default_factor = 1.0 / workspace->cols; 00043 00044 DLOG("get_unoccupied_x(), starting with %f, default_factor = %f\n", unoccupied, default_factor); 00045 00046 for (int cols = 0; cols < workspace->cols; cols++) { 00047 DLOG("width_factor[%d] = %f, unoccupied = %f\n", cols, workspace->width_factor[cols], unoccupied); 00048 if (workspace->width_factor[cols] == 0) 00049 unoccupied -= workspace->rect.width * default_factor; 00050 } 00051 00052 DLOG("unoccupied space: %f\n", unoccupied); 00053 return unoccupied; 00054 } 00055 00056 /* See get_unoccupied_x() */ 00057 int get_unoccupied_y(Workspace *workspace) { 00058 int height = workspace_height(workspace); 00059 double unoccupied = height; 00060 double default_factor = 1.0 / workspace->rows; 00061 00062 DLOG("get_unoccupied_y(), starting with %f, default_factor = %f\n", unoccupied, default_factor); 00063 00064 for (int rows = 0; rows < workspace->rows; rows++) { 00065 DLOG("height_factor[%d] = %f, unoccupied = %f\n", rows, workspace->height_factor[rows], unoccupied); 00066 if (workspace->height_factor[rows] == 0) 00067 unoccupied -= height * default_factor; 00068 } 00069 00070 DLOG("unoccupied space: %f\n", unoccupied); 00071 return unoccupied; 00072 } 00073 00074 /* 00075 * Redecorates the given client correctly by checking if it’s in a stacking container and 00076 * re-rendering the stack window or just calling decorate_window if it’s not in a stacking 00077 * container. 00078 * 00079 */ 00080 void redecorate_window(xcb_connection_t *conn, Client *client) { 00081 if (client->container != NULL && 00082 (client->container->mode == MODE_STACK || 00083 client->container->mode == MODE_TABBED)) { 00084 render_container(conn, client->container); 00085 /* We clear the frame to generate exposure events, because the color used 00086 in drawing may be different */ 00087 xcb_clear_area(conn, true, client->frame, 0, 0, client->rect.width, client->rect.height); 00088 } else decorate_window(conn, client, client->frame, client->titlegc, 0, 0); 00089 xcb_flush(conn); 00090 } 00091 00092 /* 00093 * (Re-)draws window decorations for a given Client onto the given drawable/graphic context. 00094 * When in stacking mode, the window decorations are drawn onto an own window. 00095 * 00096 */ 00097 void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable, 00098 xcb_gcontext_t gc, int offset_x, int offset_y) { 00099 i3Font *font = load_font(conn, config.font); 00100 int decoration_height = font->height + 2 + 2; 00101 struct Colortriple *color; 00102 Client *last_focused; 00103 00104 /* Clients without a container (docks) won’t get decorated */ 00105 if (client->dock) 00106 return; 00107 00108 last_focused = SLIST_FIRST(&(client->workspace->focus_stack)); 00109 /* Is the window urgent? */ 00110 if (client->urgent) 00111 color = &(config.client.urgent); 00112 else { 00113 if (client_is_floating(client)) { 00114 if (last_focused == client) 00115 color = &(config.client.focused); 00116 else color = &(config.client.unfocused); 00117 } else { 00118 if (client->container->currently_focused == client) { 00119 /* Distinguish if the window is currently focused… */ 00120 if (last_focused == client && c_ws == client->workspace) 00121 color = &(config.client.focused); 00122 /* …or if it is the focused window in a not focused container */ 00123 else color = &(config.client.focused_inactive); 00124 } else color = &(config.client.unfocused); 00125 } 00126 } 00127 00128 /* Our plan is the following: 00129 - Draw a rect around the whole client in color->background 00130 - Draw two lines in a lighter color 00131 - Draw the window’s title 00132 */ 00133 int mode = container_mode(client->container, true); 00134 00135 /* Draw a rectangle in background color around the window */ 00136 if (client->borderless && mode == MODE_DEFAULT) 00137 xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, config.client.background); 00138 else xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, color->background); 00139 00140 /* In stacking mode, we only render the rect for this specific decoration */ 00141 if (mode == MODE_STACK || mode == MODE_TABBED) { 00142 /* We need to use the container’s width because it is the more recent value - when 00143 in stacking mode, clients get reconfigured only on demand (the not active client 00144 is not reconfigured), so the client’s rect.width would be wrong */ 00145 xcb_rectangle_t rect = {offset_x, offset_y, 00146 offset_x + client->container->width, 00147 offset_y + decoration_height }; 00148 xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); 00149 } else { 00150 xcb_rectangle_t rect = {0, 0, client->rect.width, client->rect.height}; 00151 xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect); 00152 00153 /* Draw the inner background to a frame around clients (such as mplayer) 00154 which cannot be resized exactly in our frames and therefore are centered */ 00155 xcb_change_gc_single(conn, client->titlegc, XCB_GC_FOREGROUND, config.client.background); 00156 if (client->titlebar_position == TITLEBAR_OFF && client->borderless) { 00157 xcb_rectangle_t crect = {0, 0, client->rect.width, client->rect.height}; 00158 xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); 00159 } else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) { 00160 xcb_rectangle_t crect = {1, 1, client->rect.width - (1 + 1), client->rect.height - (1 + 1)}; 00161 xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); 00162 } else { 00163 xcb_rectangle_t crect = {2, decoration_height, 00164 client->rect.width - (2 + 2), client->rect.height - 2 - decoration_height}; 00165 xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &crect); 00166 } 00167 } 00168 00169 mode = container_mode(client->container, false); 00170 00171 if (client->titlebar_position != TITLEBAR_OFF) { 00172 /* Draw the lines */ 00173 xcb_draw_line(conn, drawable, gc, color->border, offset_x, offset_y, offset_x + client->rect.width, offset_y); 00174 xcb_draw_line(conn, drawable, gc, color->border, 00175 offset_x + 2, /* x */ 00176 offset_y + font->height + 3, /* y */ 00177 offset_x + client->rect.width - 3, /* to_x */ 00178 offset_y + font->height + 3 /* to_y */); 00179 } 00180 00181 /* If the client has a title, we draw it */ 00182 if (client->name != NULL && 00183 (mode != MODE_DEFAULT || client->titlebar_position != TITLEBAR_OFF)) { 00184 /* Draw the font */ 00185 uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT; 00186 uint32_t values[] = { color->text, color->background, font->id }; 00187 xcb_change_gc(conn, gc, mask, values); 00188 00189 /* name_len == -1 means this is a legacy application which does not specify _NET_WM_NAME, 00190 and we don’t handle the old window name (COMPOUND_TEXT) but only _NET_WM_NAME, which 00191 is UTF-8 */ 00192 if (client->name_len == -1) 00193 xcb_image_text_8(conn, strlen(client->name), drawable, gc, offset_x + 3 /* X */, 00194 offset_y + font->height /* Y = baseline of font */, client->name); 00195 else 00196 xcb_image_text_16(conn, client->name_len, drawable, gc, offset_x + 3 /* X */, 00197 offset_y + font->height /* Y = baseline of font */, (xcb_char2b_t*)client->name); 00198 } 00199 } 00200 00201 /* 00202 * Pushes the client’s x and y coordinates to X11 00203 * 00204 */ 00205 void reposition_client(xcb_connection_t *conn, Client *client) { 00206 Output *output; 00207 00208 DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); 00209 /* Note: We can use a pointer to client->x like an array of uint32_ts 00210 because it is followed by client->y by definition */ 00211 xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x)); 00212 00213 if (!client_is_floating(client)) 00214 return; 00215 00216 /* If the client is floating, we need to check if we moved it to a different workspace */ 00217 output = get_output_containing(client->rect.x + (client->rect.width / 2), 00218 client->rect.y + (client->rect.height / 2)); 00219 if (client->workspace->output == output) 00220 return; 00221 00222 if (output == NULL) { 00223 DLOG("Boundary checking disabled, no output found for (%d, %d)\n", client->rect.x, client->rect.y); 00224 return; 00225 } 00226 00227 if (output->current_workspace == NULL) { 00228 DLOG("Boundary checking deferred, no current workspace on output\n"); 00229 client->force_reconfigure = true; 00230 return; 00231 } 00232 00233 DLOG("Client is on workspace %p with output %p\n", client->workspace, client->workspace->output); 00234 DLOG("but output at %d, %d is %p\n", client->rect.x, client->rect.y, output); 00235 floating_assign_to_workspace(client, output->current_workspace); 00236 00237 set_focus(conn, client, true); 00238 } 00239 00240 /* 00241 * Pushes the client’s width/height to X11 and resizes the child window. This 00242 * function also updates the client’s position, so if you work on tiling clients 00243 * only, you can use this function instead of separate calls to reposition_client 00244 * and resize_client to reduce flickering. 00245 * 00246 */ 00247 void resize_client(xcb_connection_t *conn, Client *client) { 00248 i3Font *font = load_font(conn, config.font); 00249 00250 DLOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y); 00251 DLOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height); 00252 xcb_set_window_rect(conn, client->frame, client->rect); 00253 00254 /* Adjust the position of the child inside its frame. 00255 * The coordinates of the child are relative to its frame, we 00256 * add a border of 2 pixel to each value */ 00257 Rect *rect = &(client->child_rect); 00258 switch (container_mode(client->container, true)) { 00259 case MODE_STACK: 00260 case MODE_TABBED: 00261 rect->x = 2; 00262 rect->y = 0; 00263 rect->width = client->rect.width - (2 + 2); 00264 rect->height = client->rect.height - 2; 00265 break; 00266 default: 00267 if (client->titlebar_position == TITLEBAR_OFF && client->borderless) { 00268 rect->x = 0; 00269 rect->y = 0; 00270 rect->width = client->rect.width; 00271 rect->height = client->rect.height; 00272 } else if (client->titlebar_position == TITLEBAR_OFF && !client->borderless) { 00273 rect->x = 1; 00274 rect->y = 1; 00275 rect->width = client->rect.width - 1 - 1; 00276 rect->height = client->rect.height - 1 - 1; 00277 } else { 00278 rect->x = 2; 00279 rect->y = font->height + 2 + 2; 00280 rect->width = client->rect.width - (2 + 2); 00281 rect->height = client->rect.height - ((font->height + 2 + 2) + 2); 00282 } 00283 break; 00284 } 00285 00286 rect->width -= (2 * client->border_width); 00287 rect->height -= (2 * client->border_width); 00288 00289 /* Obey the ratio, if any */ 00290 if (client->proportional_height != 0 && 00291 client->proportional_width != 0) { 00292 DLOG("proportional height = %d, width = %d\n", client->proportional_height, client->proportional_width); 00293 double new_height = rect->height + 1; 00294 int new_width = rect->width; 00295 00296 while (new_height > rect->height) { 00297 new_height = ((double)client->proportional_height / client->proportional_width) * new_width; 00298 00299 if (new_height > rect->height) 00300 new_width--; 00301 } 00302 /* Center the window */ 00303 rect->y += ceil(rect->height / 2) - floor(new_height / 2); 00304 rect->x += ceil(rect->width / 2) - floor(new_width / 2); 00305 00306 rect->height = new_height; 00307 rect->width = new_width; 00308 DLOG("new_height = %f, new_width = %d\n", new_height, new_width); 00309 } 00310 00311 if (client->height_increment > 1) { 00312 int old_height = rect->height; 00313 rect->height -= (rect->height - client->base_height) % client->height_increment; 00314 DLOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n", 00315 old_height - rect->height, client->height_increment, client->base_height); 00316 } 00317 00318 if (client->width_increment > 1) { 00319 int old_width = rect->width; 00320 rect->width -= (rect->width - client->base_width) % client->width_increment; 00321 DLOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n", 00322 old_width - rect->width, client->width_increment, client->base_width); 00323 } 00324 00325 DLOG("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height); 00326 00327 xcb_set_window_rect(conn, client->child, *rect); 00328 00329 /* After configuring a child window we need to fake a configure_notify_event (see ICCCM 4.2.3). 00330 * This is necessary to inform the client of its position relative to the root window, 00331 * not relative to its frame (as done in the configure_notify_event by the x server). */ 00332 fake_absolute_configure_notify(conn, client); 00333 00334 /* Force redrawing after resizing the window because any now lost 00335 * pixels could contain old garbage. */ 00336 xcb_expose_event_t generated; 00337 generated.window = client->frame; 00338 generated.count = 0; 00339 handle_expose_event(NULL, conn, &generated); 00340 } 00341 00342 /* 00343 * Renders the given container. Is called by render_layout() or individually (for example 00344 * when focus changes in a stacking container) 00345 * 00346 */ 00347 void render_container(xcb_connection_t *conn, Container *container) { 00348 Client *client; 00349 int num_clients = 0, current_client = 0; 00350 00351 CIRCLEQ_FOREACH(client, &(container->clients), clients) 00352 num_clients++; 00353 00354 if (container->mode == MODE_DEFAULT) { 00355 int height = (container->height / max(1, num_clients)); 00356 int rest_pixels = (container->height % max(1, num_clients)); 00357 DLOG("height per client = %d, rest = %d\n", height, rest_pixels); 00358 00359 CIRCLEQ_FOREACH(client, &(container->clients), clients) { 00360 /* If the client is in fullscreen mode, it does not get reconfigured */ 00361 if (container->workspace->fullscreen_client == client) { 00362 current_client++; 00363 continue; 00364 } 00365 00366 /* If we have some pixels left to distribute, add one 00367 * pixel to each client as long as possible. */ 00368 int this_height = height; 00369 if (rest_pixels > 0) { 00370 height++; 00371 rest_pixels--; 00372 } 00373 /* Check if we changed client->x or client->y by updating it. 00374 * Note the bitwise OR instead of logical OR to force evaluation of both statements */ 00375 if (client->force_reconfigure | 00376 update_if_necessary(&(client->rect.x), container->x) | 00377 update_if_necessary(&(client->rect.y), container->y + 00378 (container->height / num_clients) * current_client) | 00379 update_if_necessary(&(client->rect.width), container->width) | 00380 update_if_necessary(&(client->rect.height), this_height)) 00381 resize_client(conn, client); 00382 00383 /* TODO: vertical default layout */ 00384 00385 client->force_reconfigure = false; 00386 00387 current_client++; 00388 } 00389 } else { 00390 i3Font *font = load_font(conn, config.font); 00391 int decoration_height = (font->height + 2 + 2); 00392 struct Stack_Window *stack_win = &(container->stack_win); 00393 /* The size for each tab (width), necessary as a separate variable 00394 * because num_clients gets fixed to 1 in tabbed mode. */ 00395 int size_each = (num_clients == 0 ? container->width : container->width / num_clients); 00396 int stack_lines = num_clients; 00397 00398 /* Check if we need to remap our stack title window, it gets unmapped when the container 00399 is empty in src/handlers.c:unmap_notify() */ 00400 if (stack_win->rect.height == 0 && num_clients > 1) { 00401 DLOG("remapping stack win\n"); 00402 xcb_map_window(conn, stack_win->window); 00403 } else DLOG("not remapping stackwin, height = %d, num_clients = %d\n", 00404 stack_win->rect.height, num_clients); 00405 00406 if (container->mode == MODE_TABBED) { 00407 /* By setting num_clients to 1 we force that the stack window will be only one line 00408 * high. The rest of the code is useful in both cases. */ 00409 DLOG("tabbed mode, setting num_clients = 1\n"); 00410 if (stack_lines > 1) 00411 stack_lines = 1; 00412 } 00413 00414 if (container->stack_limit == STACK_LIMIT_COLS) { 00415 stack_lines = ceil((float)num_clients / container->stack_limit_value); 00416 } else if (container->stack_limit == STACK_LIMIT_ROWS) { 00417 stack_lines = min(num_clients, container->stack_limit_value); 00418 } 00419 00420 int height = decoration_height * stack_lines; 00421 if (num_clients == 1) { 00422 height = 0; 00423 stack_win->rect.height = 0; 00424 xcb_unmap_window(conn, stack_win->window); 00425 00426 DLOG("Just one client, setting height to %d\n", height); 00427 } 00428 00429 /* Check if we need to reconfigure our stack title window */ 00430 if (height > 0 && ( 00431 update_if_necessary(&(stack_win->rect.x), container->x) | 00432 update_if_necessary(&(stack_win->rect.y), container->y) | 00433 update_if_necessary(&(stack_win->rect.width), container->width) | 00434 update_if_necessary(&(stack_win->rect.height), height))) { 00435 00436 /* Configuration can happen in two slightly different ways: 00437 00438 If there is no client in fullscreen mode, 5 parameters are passed 00439 (x, y, width, height, stack mode is set to above which means top-most position). 00440 00441 If there is a fullscreen client, the fourth parameter is set to to the 00442 fullscreen window as sibling and the stack mode is set to below, which means 00443 that the stack_window will be placed just below the sibling, that is, under 00444 the fullscreen window. 00445 */ 00446 uint32_t values[] = { stack_win->rect.x, stack_win->rect.y, 00447 stack_win->rect.width, stack_win->rect.height, 00448 XCB_STACK_MODE_ABOVE, XCB_STACK_MODE_BELOW }; 00449 uint32_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | 00450 XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | 00451 XCB_CONFIG_WINDOW_STACK_MODE; 00452 00453 /* Raise the stack window, but keep it below the first floating client 00454 * and below the fullscreen client (if any) */ 00455 Client *first_floating = TAILQ_FIRST(&(container->workspace->floating_clients)); 00456 if (container->workspace->fullscreen_client != NULL) { 00457 mask |= XCB_CONFIG_WINDOW_SIBLING; 00458 values[4] = container->workspace->fullscreen_client->frame; 00459 } else if (first_floating != TAILQ_END(&(container->workspace->floating_clients))) { 00460 mask |= XCB_CONFIG_WINDOW_SIBLING; 00461 values[4] = first_floating->frame; 00462 } 00463 00464 xcb_configure_window(conn, stack_win->window, mask, values); 00465 } 00466 00467 /* Prepare the pixmap for usage */ 00468 if (num_clients > 1) 00469 cached_pixmap_prepare(conn, &(stack_win->pixmap)); 00470 00471 int current_row = 0, current_col = 0; 00472 int wrap = 0; 00473 00474 if (container->stack_limit == STACK_LIMIT_COLS) { 00475 /* wrap stores the number of rows after which we will 00476 * wrap to a new column. */ 00477 wrap = ceil((float)num_clients / container->stack_limit_value); 00478 } else if (container->stack_limit == STACK_LIMIT_ROWS) { 00479 /* When limiting rows, the wrap variable serves a 00480 * slightly different purpose: it holds the number of 00481 * pixels which each client will get. This is constant 00482 * during the following loop, so it saves us some 00483 * divisions and ceil()ing. */ 00484 wrap = (stack_win->rect.width / ceil((float)num_clients / container->stack_limit_value)); 00485 } 00486 00487 /* Render the decorations of all clients */ 00488 CIRCLEQ_FOREACH(client, &(container->clients), clients) { 00489 /* If the client is in fullscreen mode, it does not get reconfigured */ 00490 if (container->workspace->fullscreen_client == client) { 00491 current_client++; 00492 continue; 00493 } 00494 00495 /* Check if we changed client->x or client->y by updating it. 00496 * Note the bitwise OR instead of logical OR to force evaluation of all statements */ 00497 if (client->force_reconfigure | 00498 update_if_necessary(&(client->rect.x), container->x) | 00499 update_if_necessary(&(client->rect.y), container->y + height) | 00500 update_if_necessary(&(client->rect.width), container->width) | 00501 update_if_necessary(&(client->rect.height), container->height - height)) 00502 resize_client(conn, client); 00503 00504 client->force_reconfigure = false; 00505 00506 int offset_x = 0; 00507 int offset_y = 0; 00508 if (container->mode == MODE_STACK || 00509 (container->mode == MODE_TABBED && 00510 container->stack_limit == STACK_LIMIT_COLS)) { 00511 if (container->stack_limit == STACK_LIMIT_COLS) { 00512 offset_x = current_col * (stack_win->rect.width / container->stack_limit_value); 00513 offset_y = current_row * decoration_height; 00514 current_row++; 00515 if ((current_row % wrap) == 0) { 00516 current_col++; 00517 current_row = 0; 00518 } 00519 } else if (container->stack_limit == STACK_LIMIT_ROWS) { 00520 offset_x = current_col * wrap; 00521 offset_y = current_row * decoration_height; 00522 current_row++; 00523 if ((current_row % container->stack_limit_value) == 0) { 00524 current_col++; 00525 current_row = 0; 00526 } 00527 } else { 00528 offset_y = current_client * decoration_height; 00529 } 00530 current_client++; 00531 } else if (container->mode == MODE_TABBED) { 00532 if (container->stack_limit == STACK_LIMIT_ROWS) { 00533 LOG("You limited a tabbed container in its rows. " 00534 "This makes no sense in tabbing mode.\n"); 00535 } 00536 offset_x = current_client++ * size_each; 00537 } 00538 if (stack_win->pixmap.id != XCB_NONE) 00539 decorate_window(conn, client, stack_win->pixmap.id, 00540 stack_win->pixmap.gc, offset_x, offset_y); 00541 else 00542 decorate_window(conn, client, client->frame, client->titlegc, 0, 0); 00543 } 00544 00545 /* Check if we need to fill one column because of an uneven 00546 * amount of windows */ 00547 if (container->mode == MODE_STACK) { 00548 if (container->stack_limit == STACK_LIMIT_COLS && (current_col % 2) != 0) { 00549 xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, config.client.background); 00550 00551 int offset_x = current_col * (stack_win->rect.width / container->stack_limit_value); 00552 int offset_y = current_row * decoration_height; 00553 xcb_rectangle_t rect = {offset_x, offset_y, 00554 offset_x + container->width, 00555 offset_y + decoration_height }; 00556 xcb_poly_fill_rectangle(conn, stack_win->pixmap.id, stack_win->pixmap.gc, 1, &rect); 00557 } else if (container->stack_limit == STACK_LIMIT_ROWS && (current_row % 2) != 0) { 00558 xcb_change_gc_single(conn, stack_win->pixmap.gc, XCB_GC_FOREGROUND, config.client.background); 00559 00560 int offset_x = current_col * wrap; 00561 int offset_y = current_row * decoration_height; 00562 xcb_rectangle_t rect = {offset_x, offset_y, 00563 offset_x + container->width, 00564 offset_y + decoration_height }; 00565 xcb_poly_fill_rectangle(conn, stack_win->pixmap.id, stack_win->pixmap.gc, 1, &rect); 00566 } 00567 } 00568 00569 if (stack_win->pixmap.id == XCB_NONE) 00570 return; 00571 xcb_copy_area(conn, stack_win->pixmap.id, stack_win->window, stack_win->pixmap.gc, 00572 0, 0, 0, 0, stack_win->rect.width, stack_win->rect.height); 00573 } 00574 } 00575 00576 static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) { 00577 Client *client; 00578 SLIST_FOREACH(client, &(r_ws->output->dock_clients), dock_clients) { 00579 DLOG("client is at %d, should be at %d\n", client->rect.y, *height); 00580 if (client->force_reconfigure | 00581 update_if_necessary(&(client->rect.x), r_ws->rect.x) | 00582 update_if_necessary(&(client->rect.y), *height)) 00583 reposition_client(conn, client); 00584 00585 if (client->force_reconfigure | 00586 update_if_necessary(&(client->rect.width), width) | 00587 update_if_necessary(&(client->rect.height), client->desired_height)) 00588 resize_client(conn, client); 00589 00590 client->force_reconfigure = false; 00591 DLOG("desired_height = %d\n", client->desired_height); 00592 *height += client->desired_height; 00593 } 00594 } 00595 00596 static void render_internal_bar(xcb_connection_t *conn, Workspace *r_ws, int width, int height) { 00597 i3Font *font = load_font(conn, config.font); 00598 Output *output = r_ws->output; 00599 enum { SET_NORMAL = 0, SET_FOCUSED = 1 }; 00600 00601 /* Fill the whole bar in black */ 00602 xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000")); 00603 xcb_rectangle_t rect = {0, 0, width, height}; 00604 xcb_poly_fill_rectangle(conn, output->bar, output->bargc, 1, &rect); 00605 00606 /* Set font */ 00607 xcb_change_gc_single(conn, output->bargc, XCB_GC_FONT, font->id); 00608 00609 int drawn = 0; 00610 Workspace *ws; 00611 TAILQ_FOREACH(ws, workspaces, workspaces) { 00612 if (ws->output != output) 00613 continue; 00614 00615 struct Colortriple *color; 00616 00617 if (output->current_workspace == ws) 00618 color = &(config.bar.focused); 00619 else if (ws->urgent) 00620 color = &(config.bar.urgent); 00621 else color = &(config.bar.unfocused); 00622 00623 /* Draw the outer rect */ 00624 xcb_draw_rect(conn, output->bar, output->bargc, color->border, 00625 drawn, /* x */ 00626 1, /* y */ 00627 ws->text_width + 5 + 5, /* width = text width + 5 px left + 5px right */ 00628 height - 2 /* height = max. height - 1 px upper and 1 px bottom border */); 00629 00630 /* Draw the background of this rect */ 00631 xcb_draw_rect(conn, output->bar, output->bargc, color->background, 00632 drawn + 1, 00633 2, 00634 ws->text_width + 4 + 4, 00635 height - 4); 00636 00637 xcb_change_gc_single(conn, output->bargc, XCB_GC_FOREGROUND, color->text); 00638 xcb_change_gc_single(conn, output->bargc, XCB_GC_BACKGROUND, color->background); 00639 xcb_image_text_16(conn, ws->name_len, output->bar, output->bargc, drawn + 5 /* X */, 00640 font->height + 1 /* Y = baseline of font */, 00641 (xcb_char2b_t*)ws->name); 00642 drawn += ws->text_width + 12; 00643 } 00644 } 00645 00646 /* 00647 * Modifies the event mask of all clients on the given workspace to either ignore or to handle 00648 * enter notifies. It is handy to ignore notifies because they will be sent when a window is mapped 00649 * under the cursor, thus when the user didn’t enter the window actively at all. 00650 * 00651 */ 00652 void ignore_enter_notify_forall(xcb_connection_t *conn, Workspace *workspace, bool ignore_enter_notify) { 00653 Client *client; 00654 uint32_t values[1]; 00655 00656 FOR_TABLE(workspace) { 00657 if (workspace->table[cols][rows] == NULL) 00658 continue; 00659 00660 CIRCLEQ_FOREACH(client, &(workspace->table[cols][rows]->clients), clients) { 00661 /* Change event mask for the decorations */ 00662 values[0] = FRAME_EVENT_MASK; 00663 if (ignore_enter_notify) 00664 values[0] &= ~(XCB_EVENT_MASK_ENTER_WINDOW); 00665 xcb_change_window_attributes(conn, client->frame, XCB_CW_EVENT_MASK, values); 00666 00667 /* Change event mask for the child itself */ 00668 values[0] = CHILD_EVENT_MASK; 00669 if (ignore_enter_notify) 00670 values[0] &= ~(XCB_EVENT_MASK_ENTER_WINDOW); 00671 xcb_change_window_attributes(conn, client->child, XCB_CW_EVENT_MASK, values); 00672 } 00673 } 00674 } 00675 00676 /* 00677 * Renders the given workspace on the given screen 00678 * 00679 */ 00680 void render_workspace(xcb_connection_t *conn, Output *output, Workspace *r_ws) { 00681 i3Font *font = load_font(conn, config.font); 00682 int width = r_ws->rect.width; 00683 int height = r_ws->rect.height; 00684 00685 /* Reserve space for dock clients */ 00686 Client *client; 00687 SLIST_FOREACH(client, &(output->dock_clients), dock_clients) 00688 height -= client->desired_height; 00689 00690 /* Space for the internal bar */ 00691 if (!config.disable_workspace_bar) 00692 height -= (font->height + 6); 00693 00694 int xoffset[r_ws->rows]; 00695 int yoffset[r_ws->cols]; 00696 /* Initialize offsets */ 00697 for (int cols = 0; cols < r_ws->cols; cols++) 00698 yoffset[cols] = r_ws->rect.y; 00699 for (int rows = 0; rows < r_ws->rows; rows++) 00700 xoffset[rows] = r_ws->rect.x; 00701 00702 ignore_enter_notify_forall(conn, r_ws, true); 00703 00704 /* Get the width of the cols */ 00705 int col_width[r_ws->cols]; 00706 int unoccupied_x = get_unoccupied_x(r_ws); 00707 int default_col_width = unoccupied_x / r_ws->cols; 00708 int total_col_width = 0; 00709 for (int i = 0; i < r_ws->cols; ++i) { 00710 col_width[i] = r_ws->width_factor[i] == 0 ? default_col_width : unoccupied_x * r_ws->width_factor[i]; 00711 total_col_width += col_width[i]; 00712 } 00713 00714 /* Correct rounding errors */ 00715 int error = r_ws->rect.width - total_col_width, error_index = r_ws->cols - 1; 00716 while (error) { 00717 ++col_width[error_index]; 00718 --error; 00719 error_index = error_index == 0 ? r_ws->cols - 1 : error_index - 1; 00720 } 00721 00722 /* Get the height of the rows */ 00723 int row_height[r_ws->rows]; 00724 int unoccupied_y = get_unoccupied_y(r_ws); 00725 int default_row_height = unoccupied_y / r_ws->rows; 00726 int total_row_height = 0; 00727 for (int i = 0; i < r_ws->rows; ++i) { 00728 row_height[i] = r_ws->height_factor[i] == 0 ? default_row_height : unoccupied_y * r_ws->height_factor[i]; 00729 total_row_height += row_height[i]; 00730 } 00731 00732 /* Correct rounding errors */ 00733 error = workspace_height(r_ws) - total_row_height; 00734 error_index = r_ws->rows - 1; 00735 while (error) { 00736 ++row_height[error_index]; 00737 --error; 00738 error_index = error_index == 0 ? r_ws->rows - 1 : error_index - 1; 00739 } 00740 00741 /* Go through the whole table and render what’s necessary */ 00742 FOR_TABLE(r_ws) { 00743 Container *container = r_ws->table[cols][rows]; 00744 if (container == NULL) 00745 continue; 00746 int single_width = -1, single_height = -1; 00747 /* Update position of the container */ 00748 container->row = rows; 00749 container->col = cols; 00750 container->x = xoffset[rows]; 00751 container->y = yoffset[cols]; 00752 container->width = 0; 00753 00754 for (int c = 0; c < container->colspan; c++) { 00755 container->width += col_width[cols + c]; 00756 if (single_width == -1) 00757 single_width = container->width; 00758 } 00759 00760 DLOG("height is %d\n", height); 00761 00762 container->height = 0; 00763 00764 for (int c = 0; c < container->rowspan; c++) { 00765 container->height += row_height[rows + c]; 00766 if (single_height == -1) 00767 single_height = container->height; 00768 } 00769 00770 /* Render the container if it is not empty */ 00771 render_container(conn, container); 00772 00773 xoffset[rows] += single_width; 00774 yoffset[cols] += single_height; 00775 } 00776 00777 /* Reposition all floating clients with force_reconfigure == true */ 00778 TAILQ_FOREACH(client, &(r_ws->floating_clients), floating_clients) { 00779 if (!client->force_reconfigure) 00780 continue; 00781 00782 client->force_reconfigure = false; 00783 reposition_client(conn, client); 00784 resize_client(conn, client); 00785 } 00786 00787 ignore_enter_notify_forall(conn, r_ws, false); 00788 00789 render_bars(conn, r_ws, width, &height); 00790 if (!config.disable_workspace_bar) 00791 render_internal_bar(conn, r_ws, width, font->height + 6); 00792 } 00793 00794 /* 00795 * Renders the whole layout, that is: Go through each screen, each workspace, each container 00796 * and render each client. This also renders the bars. 00797 * 00798 * If you don’t need to render *everything*, you should call render_container on the container 00799 * you want to refresh. 00800 * 00801 */ 00802 void render_layout(xcb_connection_t *conn) { 00803 Output *output; 00804 00805 TAILQ_FOREACH(output, &outputs, outputs) 00806 if (output->current_workspace != NULL) 00807 render_workspace(conn, output, output->current_workspace); 00808 00809 xcb_flush(conn); 00810 }