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 * src/manage.c: Contains all functions for initially managing new windows 00011 * (or existing ones on restart). 00012 * 00013 */ 00014 #include <stdlib.h> 00015 #include <string.h> 00016 #include <assert.h> 00017 00018 #include <xcb/xcb.h> 00019 #include <xcb/xcb_icccm.h> 00020 00021 #include "xcb.h" 00022 #include "data.h" 00023 #include "util.h" 00024 #include "i3.h" 00025 #include "table.h" 00026 #include "config.h" 00027 #include "handlers.h" 00028 #include "layout.h" 00029 #include "manage.h" 00030 #include "floating.h" 00031 #include "client.h" 00032 #include "workspace.h" 00033 #include "log.h" 00034 #include "ewmh.h" 00035 00036 /* 00037 * Go through all existing windows (if the window manager is restarted) and manage them 00038 * 00039 */ 00040 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) { 00041 xcb_query_tree_reply_t *reply; 00042 int i, len; 00043 xcb_window_t *children; 00044 xcb_get_window_attributes_cookie_t *cookies; 00045 00046 /* Get the tree of windows whose parent is the root window (= all) */ 00047 if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL) 00048 return; 00049 00050 len = xcb_query_tree_children_length(reply); 00051 cookies = smalloc(len * sizeof(*cookies)); 00052 00053 /* Request the window attributes for every window */ 00054 children = xcb_query_tree_children(reply); 00055 for (i = 0; i < len; ++i) 00056 cookies[i] = xcb_get_window_attributes(conn, children[i]); 00057 00058 /* Call manage_window with the attributes for every window */ 00059 for (i = 0; i < len; ++i) 00060 manage_window(prophs, conn, children[i], cookies[i], true); 00061 00062 free(reply); 00063 free(cookies); 00064 } 00065 00066 /* 00067 * Restores the geometry of each window by reparenting it to the root window 00068 * at the position of its frame. 00069 * 00070 * This is to be called *only* before exiting/restarting i3 because of evil 00071 * side-effects which are to be expected when continuing to run i3. 00072 * 00073 */ 00074 void restore_geometry(xcb_connection_t *conn) { 00075 Workspace *ws; 00076 Client *client; 00077 DLOG("Restoring geometry\n"); 00078 00079 TAILQ_FOREACH(ws, workspaces, workspaces) 00080 SLIST_FOREACH(client, &(ws->focus_stack), focus_clients) 00081 xcb_reparent_window(conn, client->child, root, 00082 client->rect.x, client->rect.y); 00083 00084 /* Make sure our changes reach the X server, we restart/exit now */ 00085 xcb_flush(conn); 00086 } 00087 00088 /* 00089 * Do some sanity checks and then reparent the window. 00090 * 00091 */ 00092 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, 00093 xcb_window_t window, xcb_get_window_attributes_cookie_t cookie, 00094 bool needs_to_be_mapped) { 00095 xcb_drawable_t d = { window }; 00096 xcb_get_geometry_cookie_t geomc; 00097 xcb_get_geometry_reply_t *geom; 00098 xcb_get_window_attributes_reply_t *attr = 0; 00099 00100 geomc = xcb_get_geometry(conn, d); 00101 00102 /* Check if the window is mapped (it could be not mapped when intializing and 00103 calling manage_window() for every window) */ 00104 if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) { 00105 ELOG("Could not get attributes\n"); 00106 return; 00107 } 00108 00109 if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) 00110 goto out; 00111 00112 /* Don’t manage clients with the override_redirect flag */ 00113 if (attr->override_redirect) 00114 goto out; 00115 00116 /* Check if the window is already managed */ 00117 if (table_get(&by_child, window)) 00118 goto out; 00119 00120 /* Get the initial geometry (position, size, …) */ 00121 if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) 00122 goto out; 00123 00124 /* Reparent the window and add it to our list of managed windows */ 00125 reparent_window(conn, window, attr->visual, geom->root, geom->depth, 00126 geom->x, geom->y, geom->width, geom->height, 00127 geom->border_width); 00128 00129 /* Generate callback events for every property we watch */ 00130 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS); 00131 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME); 00132 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS); 00133 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_HINTS); 00134 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR); 00135 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[WM_CLIENT_LEADER]); 00136 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]); 00137 00138 free(geom); 00139 out: 00140 free(attr); 00141 return; 00142 } 00143 00144 /* 00145 * reparent_window() gets called when a new window was opened and becomes a child of the root 00146 * window, or it gets called by us when we manage the already existing windows at startup. 00147 * 00148 * Essentially, this is the point where we take over control. 00149 * 00150 */ 00151 void reparent_window(xcb_connection_t *conn, xcb_window_t child, 00152 xcb_visualid_t visual, xcb_window_t root, uint8_t depth, 00153 int16_t x, int16_t y, uint16_t width, uint16_t height, 00154 uint32_t border_width) { 00155 00156 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie, 00157 utf8_title_cookie, title_cookie, 00158 class_cookie, leader_cookie; 00159 uint32_t mask = 0; 00160 uint32_t values[3]; 00161 uint16_t original_height = height; 00162 bool map_frame = true; 00163 00164 /* We are interested in property changes */ 00165 mask = XCB_CW_EVENT_MASK; 00166 values[0] = CHILD_EVENT_MASK; 00167 xcb_change_window_attributes(conn, child, mask, values); 00168 00169 /* Place requests for properties ASAP */ 00170 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); 00171 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX); 00172 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX); 00173 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128); 00174 leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX); 00175 title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128); 00176 class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128); 00177 00178 Client *new = table_get(&by_child, child); 00179 00180 /* Events for already managed windows should already be filtered in manage_window() */ 00181 assert(new == NULL); 00182 00183 LOG("Managing window 0x%08x\n", child); 00184 DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height); 00185 new = scalloc(sizeof(Client)); 00186 new->force_reconfigure = true; 00187 00188 /* Update the data structures */ 00189 Client *old_focused = CUR_CELL->currently_focused; 00190 00191 new->container = CUR_CELL; 00192 new->workspace = new->container->workspace; 00193 00194 /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */ 00195 width = max(width, 75); 00196 height = max(height, 50); 00197 00198 new->frame = xcb_generate_id(conn); 00199 new->child = child; 00200 new->rect.width = width; 00201 new->rect.height = height; 00202 new->width_increment = 1; 00203 new->height_increment = 1; 00204 new->border_width = border_width; 00205 /* Pre-initialize the values for floating */ 00206 new->floating_rect.x = -1; 00207 new->floating_rect.width = width; 00208 new->floating_rect.height = height; 00209 00210 if (config.default_border != NULL) 00211 client_init_border(conn, new, config.default_border[1]); 00212 00213 mask = 0; 00214 00215 /* Don’t generate events for our new window, it should *not* be managed */ 00216 mask |= XCB_CW_OVERRIDE_REDIRECT; 00217 values[0] = 1; 00218 00219 /* We want to know when… */ 00220 mask |= XCB_CW_EVENT_MASK; 00221 values[1] = FRAME_EVENT_MASK; 00222 00223 i3Font *font = load_font(conn, config.font); 00224 width = min(width, c_ws->rect.x + c_ws->rect.width); 00225 height = min(height, c_ws->rect.y + c_ws->rect.height); 00226 00227 Rect framerect = {x, y, 00228 width + 2 + 2, /* 2 px border at each side */ 00229 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */ 00230 00231 /* Yo dawg, I heard you like windows, so I create a window around your window… */ 00232 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values); 00233 00234 /* Put the client inside the save set. Upon termination (whether killed or normal exit 00235 does not matter) of the window manager, these clients will be correctly reparented 00236 to their most closest living ancestor (= cleanup) */ 00237 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child); 00238 00239 /* Generate a graphics context for the titlebar */ 00240 new->titlegc = xcb_generate_id(conn); 00241 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0); 00242 00243 /* Moves the original window into the new frame we've created for it */ 00244 new->awaiting_useless_unmap = true; 00245 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height); 00246 if (xcb_request_check(conn, cookie) != NULL) { 00247 DLOG("Could not reparent the window, aborting\n"); 00248 xcb_destroy_window(conn, new->frame); 00249 free(new); 00250 return; 00251 } 00252 00253 /* Put our data structure (Client) into the table */ 00254 table_put(&by_parent, new->frame, new); 00255 table_put(&by_child, child, new); 00256 00257 /* We need to grab the mouse buttons for click to focus */ 00258 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS, 00259 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, 00260 1 /* left mouse button */, 00261 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); 00262 00263 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS, 00264 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, 00265 3 /* right mouse button */, 00266 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); 00267 00268 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */ 00269 xcb_atom_t *atom; 00270 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL); 00271 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) { 00272 for (int i = 0; i < xcb_get_property_value_length(preply); i++) 00273 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) { 00274 DLOG("Window is a dock.\n"); 00275 Output *t_out = get_output_containing(x, y); 00276 if (t_out == NULL) 00277 t_out = c_ws->output; 00278 if (t_out != c_ws->output) { 00279 DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n", 00280 t_out->name, x, y); 00281 new->workspace = t_out->current_workspace; 00282 } 00283 new->dock = true; 00284 new->borderless = true; 00285 new->titlebar_position = TITLEBAR_OFF; 00286 new->force_reconfigure = true; 00287 new->container = NULL; 00288 SLIST_INSERT_HEAD(&(t_out->dock_clients), new, dock_clients); 00289 /* If it’s a dock we can’t make it float, so we break */ 00290 new->floating = FLOATING_AUTO_OFF; 00291 break; 00292 } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] || 00293 atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] || 00294 atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] || 00295 atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) { 00296 /* Set the dialog window to automatically floating, will be used below */ 00297 new->floating = FLOATING_AUTO_ON; 00298 DLOG("dialog/utility/toolbar/splash window, automatically floating\n"); 00299 } 00300 } 00301 00302 /* All clients which have a leader should be floating */ 00303 if (!new->dock && !client_is_floating(new) && new->leader != 0) { 00304 DLOG("Client has WM_CLIENT_LEADER hint set, setting floating\n"); 00305 new->floating = FLOATING_AUTO_ON; 00306 } 00307 00308 if (new->workspace->auto_float) { 00309 new->floating = FLOATING_AUTO_ON; 00310 DLOG("workspace is in autofloat mode, setting floating\n"); 00311 } 00312 00313 if (new->dock) { 00314 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */ 00315 uint32_t *strut; 00316 preply = xcb_get_property_reply(conn, strut_cookie, NULL); 00317 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) { 00318 /* We only use a subset of the provided values, namely the reserved space at the top/bottom 00319 of the screen. This is because the only possibility for bars is at to be at the top/bottom 00320 with maximum horizontal size. 00321 TODO: bars at the top */ 00322 new->desired_height = strut[3]; 00323 if (new->desired_height == 0) { 00324 DLOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height); 00325 new->desired_height = original_height; 00326 } 00327 DLOG("the client wants to be %d pixels high\n", new->desired_height); 00328 } else { 00329 DLOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height); 00330 new->desired_height = original_height; 00331 } 00332 } else { 00333 /* If it’s not a dock, we can check on which workspace we should put it. */ 00334 00335 /* Firstly, we need to get the window’s class / title. We asked for the properties at the 00336 * top of this function, get them now and pass them to our callback function for window class / title 00337 * changes. It is important that the client was already inserted into the by_child table, 00338 * because the callbacks won’t work otherwise. */ 00339 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL); 00340 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply); 00341 00342 preply = xcb_get_property_reply(conn, title_cookie, NULL); 00343 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply); 00344 00345 preply = xcb_get_property_reply(conn, class_cookie, NULL); 00346 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply); 00347 00348 preply = xcb_get_property_reply(conn, leader_cookie, NULL); 00349 handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply); 00350 00351 /* if WM_CLIENT_LEADER is set, we put the new window on the 00352 * same window as its leader. This might be overwritten by 00353 * assignments afterwards. */ 00354 if (new->leader != XCB_NONE) { 00355 DLOG("client->leader is set (to 0x%08x)\n", new->leader); 00356 Client *parent = table_get(&by_child, new->leader); 00357 if (parent != NULL && parent->container != NULL) { 00358 Workspace *t_ws = parent->workspace; 00359 new->container = t_ws->table[parent->container->col][parent->container->row]; 00360 new->workspace = t_ws; 00361 old_focused = new->container->currently_focused; 00362 map_frame = workspace_is_visible(t_ws); 00363 new->urgent = true; 00364 /* This is a little tricky: we cannot use 00365 * workspace_update_urgent_flag() because the 00366 * new window was not yet inserted into the 00367 * focus stack on t_ws. */ 00368 t_ws->urgent = true; 00369 } else { 00370 DLOG("parent is not usable\n"); 00371 } 00372 } 00373 00374 struct Assignment *assign; 00375 TAILQ_FOREACH(assign, &assignments, assignments) { 00376 if (get_matching_client(conn, assign->windowclass_title, new) == NULL) 00377 continue; 00378 00379 if (assign->floating == ASSIGN_FLOATING_ONLY || 00380 assign->floating == ASSIGN_FLOATING) { 00381 new->floating = FLOATING_AUTO_ON; 00382 LOG("Assignment matches, putting client into floating mode\n"); 00383 if (assign->floating == ASSIGN_FLOATING_ONLY) 00384 break; 00385 } 00386 00387 LOG("Assignment \"%s\" matches, so putting it on workspace %d\n", 00388 assign->windowclass_title, assign->workspace); 00389 00390 if (c_ws->output->current_workspace->num == (assign->workspace-1)) { 00391 DLOG("We are already there, no need to do anything\n"); 00392 break; 00393 } 00394 00395 DLOG("Changing container/workspace and unmapping the client\n"); 00396 Workspace *t_ws = workspace_get(assign->workspace-1); 00397 workspace_initialize(t_ws, c_ws->output, false); 00398 00399 new->container = t_ws->table[t_ws->current_col][t_ws->current_row]; 00400 new->workspace = t_ws; 00401 old_focused = new->container->currently_focused; 00402 00403 map_frame = workspace_is_visible(t_ws); 00404 break; 00405 } 00406 } 00407 00408 if (new->workspace->fullscreen_client != NULL) { 00409 DLOG("Setting below fullscreen window\n"); 00410 00411 /* If we are in fullscreen, we should place the window below 00412 * the fullscreen window to not be annoying */ 00413 uint32_t values[] = { 00414 new->workspace->fullscreen_client->frame, 00415 XCB_STACK_MODE_BELOW 00416 }; 00417 xcb_configure_window(conn, new->frame, 00418 XCB_CONFIG_WINDOW_SIBLING | 00419 XCB_CONFIG_WINDOW_STACK_MODE, values); 00420 } 00421 00422 /* Insert into the currently active container, if it’s not a dock window */ 00423 if (!new->dock && !client_is_floating(new)) { 00424 /* Insert after the old active client, if existing. If it does not exist, the 00425 container is empty and it does not matter, where we insert it */ 00426 if (old_focused != NULL && !old_focused->dock) 00427 CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients); 00428 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients); 00429 00430 if (new->container->workspace->fullscreen_client != NULL) 00431 SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients); 00432 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients); 00433 00434 client_set_below_floating(conn, new); 00435 } 00436 00437 if (client_is_floating(new)) { 00438 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients); 00439 00440 /* Add the client to the list of floating clients for its workspace */ 00441 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients); 00442 00443 new->container = NULL; 00444 00445 new->rect.width = new->floating_rect.width + 2 + 2; 00446 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2; 00447 00448 /* Some clients (like GIMP’s color picker window) get mapped 00449 * to (0, 0), so we push them to a reasonable position 00450 * (centered over their leader) */ 00451 if (new->leader != 0 && x == 0 && y == 0) { 00452 DLOG("Floating client wants to (0x0), moving it over its leader instead\n"); 00453 Client *leader = table_get(&by_child, new->leader); 00454 if (leader == NULL) { 00455 DLOG("leader is NULL, centering it over current workspace\n"); 00456 00457 x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2); 00458 y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2); 00459 } else { 00460 x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2); 00461 y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2); 00462 } 00463 } 00464 new->floating_rect.x = new->rect.x = x; 00465 new->floating_rect.y = new->rect.y = y; 00466 DLOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n", 00467 new->floating_rect.x, new->floating_rect.y, 00468 new->floating_rect.width, new->floating_rect.height); 00469 DLOG("outer rect (%d, %d) size (%d, %d)\n", 00470 new->rect.x, new->rect.y, new->rect.width, new->rect.height); 00471 00472 /* Make sure it is on top of the other windows */ 00473 xcb_raise_window(conn, new->frame); 00474 reposition_client(conn, new); 00475 resize_client(conn, new); 00476 /* redecorate_window flushes */ 00477 redecorate_window(conn, new); 00478 } 00479 00480 new->initialized = true; 00481 00482 /* Check if the window already got the fullscreen hint set */ 00483 xcb_atom_t *state; 00484 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL && 00485 (state = xcb_get_property_value(preply)) != NULL) 00486 /* Check all set _NET_WM_STATEs */ 00487 for (int i = 0; i < xcb_get_property_value_length(preply); i++) { 00488 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN]) 00489 continue; 00490 /* If the window got the fullscreen state, we just toggle fullscreen 00491 and don’t event bother to redraw the layout – that would not change 00492 anything anyways */ 00493 client_toggle_fullscreen(conn, new); 00494 goto map; 00495 } 00496 00497 render_layout(conn); 00498 00499 map: 00500 /* Map the window first to avoid flickering */ 00501 xcb_map_window(conn, child); 00502 if (map_frame) 00503 client_map(conn, new); 00504 00505 if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !new->dock) { 00506 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */ 00507 if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) { 00508 if (!client_is_floating(new)) { 00509 new->container->currently_focused = new; 00510 if (map_frame) 00511 render_container(conn, new->container); 00512 } 00513 if (new->container == CUR_CELL || client_is_floating(new)) { 00514 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); 00515 ewmh_update_active_window(new->child); 00516 } 00517 } 00518 } 00519 00520 xcb_flush(conn); 00521 }