2 * vim:ts=4:sw=4:expandtab
4 * i3 - an improved dynamic tiling window manager
5 * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
7 * manage.c: Contains all functions for initially managing new windows
8 * (or existing ones on restart).
15 * Go through all existing windows (if the window manager is restarted) and manage them
18 void manage_existing_windows(xcb_window_t root) {
19 xcb_query_tree_reply_t *reply;
21 xcb_window_t *children;
22 xcb_get_window_attributes_cookie_t *cookies;
24 /* Get the tree of windows whose parent is the root window (= all) */
25 if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
28 len = xcb_query_tree_children_length(reply);
29 cookies = smalloc(len * sizeof(*cookies));
31 /* Request the window attributes for every window */
32 children = xcb_query_tree_children(reply);
33 for (i = 0; i < len; ++i)
34 cookies[i] = xcb_get_window_attributes(conn, children[i]);
36 /* Call manage_window with the attributes for every window */
37 for (i = 0; i < len; ++i)
38 manage_window(children[i], cookies[i], true);
46 * Restores the geometry of each window by reparenting it to the root window
47 * at the position of its frame.
49 * This is to be called *only* before exiting/restarting i3 because of evil
50 * side-effects which are to be expected when continuing to run i3.
53 void restore_geometry() {
54 LOG("Restoring geometry\n");
57 TAILQ_FOREACH(con, &all_cons, all_cons)
59 printf("placing window at %d %d\n", con->rect.x, con->rect.y);
60 xcb_reparent_window(conn, con->window->id, root,
61 con->rect.x, con->rect.y);
64 /* Make sure our changes reach the X server, we restart/exit now */
69 * Do some sanity checks and then reparent the window.
72 void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cookie,
73 bool needs_to_be_mapped) {
74 xcb_drawable_t d = { window };
75 xcb_get_geometry_cookie_t geomc;
76 xcb_get_geometry_reply_t *geom;
77 xcb_get_window_attributes_reply_t *attr = 0;
79 printf("---> looking at window 0x%08x\n", window);
81 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
82 utf8_title_cookie, title_cookie,
83 class_cookie, leader_cookie;
85 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
86 strut_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
87 state_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STATE], UINT32_MAX);
88 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_NAME], 128);
89 leader_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[WM_CLIENT_LEADER], UINT32_MAX);
90 title_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_NAME, 128);
91 class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128);
94 geomc = xcb_get_geometry(conn, d);
96 /* Check if the window is mapped (it could be not mapped when intializing and
97 calling manage_window() for every window) */
98 if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
99 LOG("Could not get attributes\n");
103 if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) {
104 LOG("map_state unviewable\n");
108 /* Don’t manage clients with the override_redirect flag */
109 LOG("override_redirect is %d\n", attr->override_redirect);
110 if (attr->override_redirect)
113 /* Check if the window is already managed */
114 if (con_by_window_id(window) != NULL)
117 /* Get the initial geometry (position, size, …) */
118 if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
121 LOG("reparenting!\n");
123 i3Window *cwindow = scalloc(sizeof(i3Window));
124 cwindow->id = window;
126 class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128);
127 xcb_get_property_reply_t *preply;
128 preply = xcb_get_property_reply(conn, class_cookie, NULL);
129 if (preply == NULL || xcb_get_property_value_length(preply) == 0) {
130 LOG("cannot get wm_class\n");
131 } else cwindow->class = strdup(xcb_get_property_value(preply));
136 /* TODO: assignments */
137 /* TODO: two matches for one container */
138 /* See if any container swallows this new window */
139 nc = con_for_window(cwindow, &match);
141 if (focused->type == CT_CON && con_accepts_window(focused)) {
142 LOG("using current container, focused = %p, focused->name = %s\n",
143 focused, focused->name);
145 } else nc = tree_open_con(NULL);
147 if (match != NULL && match->insert_where == M_ACTIVE) {
148 /* We need to go down the focus stack starting from nc */
149 while (TAILQ_FIRST(&(nc->focus_head)) != TAILQ_END(&(nc->focus_head))) {
150 printf("walking down one step...\n");
151 nc = TAILQ_FIRST(&(nc->focus_head));
153 /* We need to open a new con */
154 /* TODO: make a difference between match-once containers (directly assign
155 * cwindow) and match-multiple (tree_open_con first) */
156 nc = tree_open_con(nc->parent);
159 nc->window = cwindow;
161 xcb_void_cookie_t rcookie = xcb_reparent_window_checked(conn, window, nc->frame, 0, 0);
162 if (xcb_request_check(conn, rcookie) != NULL) {
163 LOG("Could not reparent the window, aborting\n");
165 //xcb_destroy_window(conn, nc->frame);
168 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window);
173 /* Reparent the window and add it to our list of managed windows */
174 reparent_window(conn, window, attr->visual, geom->root, geom->depth,
175 geom->x, geom->y, geom->width, geom->height,
179 /* Generate callback events for every property we watch */
188 * reparent_window() gets called when a new window was opened and becomes a child of the root
189 * window, or it gets called by us when we manage the already existing windows at startup.
191 * Essentially, this is the point where we take over control.
194 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
195 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
196 int16_t x, int16_t y, uint16_t width, uint16_t height,
197 uint32_t border_width) {
199 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
200 utf8_title_cookie, title_cookie,
201 class_cookie, leader_cookie;
204 uint16_t original_height = height;
205 bool map_frame = true;
207 /* We are interested in property changes */
208 mask = XCB_CW_EVENT_MASK;
209 values[0] = CHILD_EVENT_MASK;
210 xcb_change_window_attributes(conn, child, mask, values);
212 /* Place requests for properties ASAP */
213 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
214 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
215 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
216 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
217 leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX);
218 title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
219 class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
221 Client *new = table_get(&by_child, child);
223 /* Events for already managed windows should already be filtered in manage_window() */
226 LOG("Managing window 0x%08x\n", child);
227 DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
228 new = scalloc(sizeof(Client));
229 new->force_reconfigure = true;
231 /* Update the data structures */
232 Client *old_focused = CUR_CELL->currently_focused;
234 new->container = CUR_CELL;
235 new->workspace = new->container->workspace;
237 /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
238 width = max(width, 75);
239 height = max(height, 50);
241 new->frame = xcb_generate_id(conn);
243 new->rect.width = width;
244 new->rect.height = height;
245 new->width_increment = 1;
246 new->height_increment = 1;
247 new->border_width = border_width;
248 /* Pre-initialize the values for floating */
249 new->floating_rect.x = -1;
250 new->floating_rect.width = width;
251 new->floating_rect.height = height;
253 if (config.default_border != NULL)
254 client_init_border(conn, new, config.default_border[1]);
258 /* Don’t generate events for our new window, it should *not* be managed */
259 mask |= XCB_CW_OVERRIDE_REDIRECT;
262 /* We want to know when… */
263 mask |= XCB_CW_EVENT_MASK;
264 values[1] = FRAME_EVENT_MASK;
266 i3Font *font = load_font(conn, config.font);
267 width = min(width, c_ws->rect.x + c_ws->rect.width);
268 height = min(height, c_ws->rect.y + c_ws->rect.height);
270 Rect framerect = {x, y,
271 width + 2 + 2, /* 2 px border at each side */
272 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
274 /* Yo dawg, I heard you like windows, so I create a window around your window… */
275 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
277 /* Put the client inside the save set. Upon termination (whether killed or normal exit
278 does not matter) of the window manager, these clients will be correctly reparented
279 to their most closest living ancestor (= cleanup) */
280 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
282 /* Generate a graphics context for the titlebar */
283 new->titlegc = xcb_generate_id(conn);
284 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
286 /* Moves the original window into the new frame we've created for it */
287 new->awaiting_useless_unmap = true;
288 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
289 if (xcb_request_check(conn, cookie) != NULL) {
290 DLOG("Could not reparent the window, aborting\n");
291 xcb_destroy_window(conn, new->frame);
296 /* Put our data structure (Client) into the table */
297 table_put(&by_parent, new->frame, new);
298 table_put(&by_child, child, new);
300 /* We need to grab the mouse buttons for click to focus */
301 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
302 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
303 1 /* left mouse button */,
304 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
306 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
307 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
308 3 /* right mouse button */,
309 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
311 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
313 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
314 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
315 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
316 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
317 DLOG("Window is a dock.\n");
318 Output *t_out = get_output_containing(x, y);
319 if (t_out != c_ws->output) {
320 DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n",
322 new->workspace = t_out->current_workspace;
325 new->borderless = true;
326 new->titlebar_position = TITLEBAR_OFF;
327 new->force_reconfigure = true;
328 new->container = NULL;
329 SLIST_INSERT_HEAD(&(t_out->dock_clients), new, dock_clients);
330 /* If it’s a dock we can’t make it float, so we break */
331 new->floating = FLOATING_AUTO_OFF;
333 } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
334 atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
335 atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
336 atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
337 /* Set the dialog window to automatically floating, will be used below */
338 new->floating = FLOATING_AUTO_ON;
339 DLOG("dialog/utility/toolbar/splash window, automatically floating\n");
343 /* All clients which have a leader should be floating */
344 if (!new->dock && !client_is_floating(new) && new->leader != 0) {
345 DLOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
346 new->floating = FLOATING_AUTO_ON;
349 if (new->workspace->auto_float) {
350 new->floating = FLOATING_AUTO_ON;
351 DLOG("workspace is in autofloat mode, setting floating\n");
355 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
357 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
358 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
359 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
360 of the screen. This is because the only possibility for bars is at to be at the top/bottom
361 with maximum horizontal size.
362 TODO: bars at the top */
363 new->desired_height = strut[3];
364 if (new->desired_height == 0) {
365 DLOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
366 new->desired_height = original_height;
368 DLOG("the client wants to be %d pixels high\n", new->desired_height);
370 DLOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
371 new->desired_height = original_height;
374 /* If it’s not a dock, we can check on which workspace we should put it. */
376 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
377 * top of this function, get them now and pass them to our callback function for window class / title
378 * changes. It is important that the client was already inserted into the by_child table,
379 * because the callbacks won’t work otherwise. */
380 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
381 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
383 preply = xcb_get_property_reply(conn, title_cookie, NULL);
384 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
386 preply = xcb_get_property_reply(conn, class_cookie, NULL);
387 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
389 preply = xcb_get_property_reply(conn, leader_cookie, NULL);
390 handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
392 /* if WM_CLIENT_LEADER is set, we put the new window on the
393 * same window as its leader. This might be overwritten by
394 * assignments afterwards. */
395 if (new->leader != XCB_NONE) {
396 DLOG("client->leader is set (to 0x%08x)\n", new->leader);
397 Client *parent = table_get(&by_child, new->leader);
398 if (parent != NULL && parent->container != NULL) {
399 Workspace *t_ws = parent->workspace;
400 new->container = t_ws->table[parent->container->col][parent->container->row];
401 new->workspace = t_ws;
402 old_focused = new->container->currently_focused;
403 map_frame = workspace_is_visible(t_ws);
405 /* This is a little tricky: we cannot use
406 * workspace_update_urgent_flag() because the
407 * new window was not yet inserted into the
408 * focus stack on t_ws. */
411 DLOG("parent is not usable\n");
415 struct Assignment *assign;
416 TAILQ_FOREACH(assign, &assignments, assignments) {
417 if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
420 if (assign->floating == ASSIGN_FLOATING_ONLY ||
421 assign->floating == ASSIGN_FLOATING) {
422 new->floating = FLOATING_AUTO_ON;
423 LOG("Assignment matches, putting client into floating mode\n");
424 if (assign->floating == ASSIGN_FLOATING_ONLY)
428 LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
429 assign->windowclass_title, assign->workspace);
431 if (c_ws->output->current_workspace->num == (assign->workspace-1)) {
432 DLOG("We are already there, no need to do anything\n");
436 DLOG("Changing container/workspace and unmapping the client\n");
437 Workspace *t_ws = workspace_get(assign->workspace-1);
438 workspace_initialize(t_ws, c_ws->output, false);
440 new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
441 new->workspace = t_ws;
442 old_focused = new->container->currently_focused;
444 map_frame = workspace_is_visible(t_ws);
449 if (new->workspace->fullscreen_client != NULL) {
450 DLOG("Setting below fullscreen window\n");
452 /* If we are in fullscreen, we should place the window below
453 * the fullscreen window to not be annoying */
454 uint32_t values[] = {
455 new->workspace->fullscreen_client->frame,
458 xcb_configure_window(conn, new->frame,
459 XCB_CONFIG_WINDOW_SIBLING |
460 XCB_CONFIG_WINDOW_STACK_MODE, values);
463 /* Insert into the currently active container, if it’s not a dock window */
464 if (!new->dock && !client_is_floating(new)) {
465 /* Insert after the old active client, if existing. If it does not exist, the
466 container is empty and it does not matter, where we insert it */
467 if (old_focused != NULL && !old_focused->dock)
468 CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
469 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
471 if (new->container->workspace->fullscreen_client != NULL)
472 SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
473 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
475 client_set_below_floating(conn, new);
478 if (client_is_floating(new)) {
479 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
481 /* Add the client to the list of floating clients for its workspace */
482 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
484 new->container = NULL;
486 new->rect.width = new->floating_rect.width + 2 + 2;
487 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
489 /* Some clients (like GIMP’s color picker window) get mapped
490 * to (0, 0), so we push them to a reasonable position
491 * (centered over their leader) */
492 if (new->leader != 0 && x == 0 && y == 0) {
493 DLOG("Floating client wants to (0x0), moving it over its leader instead\n");
494 Client *leader = table_get(&by_child, new->leader);
495 if (leader == NULL) {
496 DLOG("leader is NULL, centering it over current workspace\n");
498 x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
499 y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
501 x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
502 y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
505 new->floating_rect.x = new->rect.x = x;
506 new->floating_rect.y = new->rect.y = y;
507 DLOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
508 new->floating_rect.x, new->floating_rect.y,
509 new->floating_rect.width, new->floating_rect.height);
510 DLOG("outer rect (%d, %d) size (%d, %d)\n",
511 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
513 /* Make sure it is on top of the other windows */
514 xcb_raise_window(conn, new->frame);
515 reposition_client(conn, new);
516 resize_client(conn, new);
517 /* redecorate_window flushes */
518 redecorate_window(conn, new);
521 new->initialized = true;
523 /* Check if the window already got the fullscreen hint set */
525 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
526 (state = xcb_get_property_value(preply)) != NULL)
527 /* Check all set _NET_WM_STATEs */
528 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
529 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
531 /* If the window got the fullscreen state, we just toggle fullscreen
532 and don’t event bother to redraw the layout – that would not change
534 client_toggle_fullscreen(conn, new);
541 /* Map the window first to avoid flickering */
542 xcb_map_window(conn, child);
544 client_map(conn, new);
546 if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !new->dock) {
547 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
548 if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) {
549 if (!client_is_floating(new)) {
550 new->container->currently_focused = new;
552 render_container(conn, new->container);
554 if (new->container == CUR_CELL || client_is_floating(new)) {
555 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
556 ewmh_update_active_window(new->child);