4 * i3 - an improved dynamic tiling window manager
6 * © 2009-2010 Michael Stapelberg and contributors
8 * See file LICENSE for license information.
10 * src/manage.c: Contains all functions for initially managing new windows
11 * (or existing ones on restart).
17 extern struct Con *focused;
21 * Go through all existing windows (if the window manager is restarted) and manage them
24 void manage_existing_windows(xcb_window_t root) {
25 xcb_query_tree_reply_t *reply;
27 xcb_window_t *children;
28 xcb_get_window_attributes_cookie_t *cookies;
30 /* Get the tree of windows whose parent is the root window (= all) */
31 if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
34 len = xcb_query_tree_children_length(reply);
35 cookies = smalloc(len * sizeof(*cookies));
37 /* Request the window attributes for every window */
38 children = xcb_query_tree_children(reply);
39 for (i = 0; i < len; ++i)
40 cookies[i] = xcb_get_window_attributes(conn, children[i]);
42 /* Call manage_window with the attributes for every window */
43 for (i = 0; i < len; ++i)
44 manage_window(children[i], cookies[i], true);
52 * Restores the geometry of each window by reparenting it to the root window
53 * at the position of its frame.
55 * This is to be called *only* before exiting/restarting i3 because of evil
56 * side-effects which are to be expected when continuing to run i3.
59 void restore_geometry() {
60 LOG("Restoring geometry\n");
63 TAILQ_FOREACH(con, &all_cons, all_cons)
65 printf("placing window at %d %d\n", con->rect.x, con->rect.y);
66 xcb_reparent_window(conn, con->window->id, root,
67 con->rect.x, con->rect.y);
70 /* Make sure our changes reach the X server, we restart/exit now */
75 * Do some sanity checks and then reparent the window.
78 void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cookie,
79 bool needs_to_be_mapped) {
80 xcb_drawable_t d = { window };
81 xcb_get_geometry_cookie_t geomc;
82 xcb_get_geometry_reply_t *geom;
83 xcb_get_window_attributes_reply_t *attr = 0;
85 printf("---> looking at window 0x%08x\n", window);
87 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
88 utf8_title_cookie, title_cookie,
89 class_cookie, leader_cookie;
91 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
92 strut_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
93 state_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STATE], UINT32_MAX);
94 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_NAME], 128);
95 leader_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[WM_CLIENT_LEADER], UINT32_MAX);
96 title_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_NAME, 128);
97 class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128);
100 geomc = xcb_get_geometry(conn, d);
102 /* Check if the window is mapped (it could be not mapped when intializing and
103 calling manage_window() for every window) */
104 if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
105 LOG("Could not get attributes\n");
109 if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) {
110 LOG("map_state unviewable\n");
114 /* Don’t manage clients with the override_redirect flag */
115 LOG("override_redirect is %d\n", attr->override_redirect);
116 if (attr->override_redirect)
119 /* Check if the window is already managed */
120 if (con_by_window_id(window) != NULL)
123 /* Get the initial geometry (position, size, …) */
124 if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
127 LOG("reparenting!\n");
129 i3Window *cwindow = scalloc(sizeof(i3Window));
130 cwindow->id = window;
132 class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128);
133 xcb_get_property_reply_t *preply;
134 preply = xcb_get_property_reply(conn, class_cookie, NULL);
135 if (preply == NULL || xcb_get_property_value_length(preply) == 0) {
136 LOG("cannot get wm_class\n");
137 } else cwindow->class = strdup(xcb_get_property_value(preply));
142 /* TODO: assignments */
143 /* TODO: two matches for one container */
144 /* See if any container swallows this new window */
145 nc = con_for_window(cwindow, &match);
147 if (focused->type == CT_CON && con_accepts_window(focused)) {
148 LOG("using current container, focused = %p, focused->name = %s\n",
149 focused, focused->name);
151 } else nc = tree_open_con(NULL);
153 if (match != NULL && match->insert_where == M_ACTIVE) {
154 /* We need to go down the focus stack starting from nc */
155 while (TAILQ_FIRST(&(nc->focus_head)) != TAILQ_END(&(nc->focus_head))) {
156 printf("walking down one step...\n");
157 nc = TAILQ_FIRST(&(nc->focus_head));
159 /* We need to open a new con */
160 /* TODO: make a difference between match-once containers (directly assign
161 * cwindow) and match-multiple (tree_open_con first) */
162 nc = tree_open_con(nc->parent);
167 nc->window = cwindow;
169 xcb_void_cookie_t rcookie = xcb_reparent_window_checked(conn, window, nc->frame, 0, 0);
170 if (xcb_request_check(conn, rcookie) != NULL) {
171 LOG("Could not reparent the window, aborting\n");
173 //xcb_destroy_window(conn, nc->frame);
176 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window);
181 /* Reparent the window and add it to our list of managed windows */
182 reparent_window(conn, window, attr->visual, geom->root, geom->depth,
183 geom->x, geom->y, geom->width, geom->height,
187 /* Generate callback events for every property we watch */
196 * reparent_window() gets called when a new window was opened and becomes a child of the root
197 * window, or it gets called by us when we manage the already existing windows at startup.
199 * Essentially, this is the point where we take over control.
202 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
203 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
204 int16_t x, int16_t y, uint16_t width, uint16_t height,
205 uint32_t border_width) {
207 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
208 utf8_title_cookie, title_cookie,
209 class_cookie, leader_cookie;
212 uint16_t original_height = height;
213 bool map_frame = true;
215 /* We are interested in property changes */
216 mask = XCB_CW_EVENT_MASK;
217 values[0] = CHILD_EVENT_MASK;
218 xcb_change_window_attributes(conn, child, mask, values);
220 /* Place requests for properties ASAP */
221 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
222 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
223 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
224 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
225 leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX);
226 title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
227 class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
229 Client *new = table_get(&by_child, child);
231 /* Events for already managed windows should already be filtered in manage_window() */
234 LOG("Managing window 0x%08x\n", child);
235 DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
236 new = scalloc(sizeof(Client));
237 new->force_reconfigure = true;
239 /* Update the data structures */
240 Client *old_focused = CUR_CELL->currently_focused;
242 new->container = CUR_CELL;
243 new->workspace = new->container->workspace;
245 /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
246 width = max(width, 75);
247 height = max(height, 50);
249 new->frame = xcb_generate_id(conn);
251 new->rect.width = width;
252 new->rect.height = height;
253 new->width_increment = 1;
254 new->height_increment = 1;
255 new->border_width = border_width;
256 /* Pre-initialize the values for floating */
257 new->floating_rect.x = -1;
258 new->floating_rect.width = width;
259 new->floating_rect.height = height;
261 if (config.default_border != NULL)
262 client_init_border(conn, new, config.default_border[1]);
266 /* Don’t generate events for our new window, it should *not* be managed */
267 mask |= XCB_CW_OVERRIDE_REDIRECT;
270 /* We want to know when… */
271 mask |= XCB_CW_EVENT_MASK;
272 values[1] = FRAME_EVENT_MASK;
274 i3Font *font = load_font(conn, config.font);
275 width = min(width, c_ws->rect.x + c_ws->rect.width);
276 height = min(height, c_ws->rect.y + c_ws->rect.height);
278 Rect framerect = {x, y,
279 width + 2 + 2, /* 2 px border at each side */
280 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
282 /* Yo dawg, I heard you like windows, so I create a window around your window… */
283 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
285 /* Put the client inside the save set. Upon termination (whether killed or normal exit
286 does not matter) of the window manager, these clients will be correctly reparented
287 to their most closest living ancestor (= cleanup) */
288 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
290 /* Generate a graphics context for the titlebar */
291 new->titlegc = xcb_generate_id(conn);
292 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
294 /* Moves the original window into the new frame we've created for it */
295 new->awaiting_useless_unmap = true;
296 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
297 if (xcb_request_check(conn, cookie) != NULL) {
298 DLOG("Could not reparent the window, aborting\n");
299 xcb_destroy_window(conn, new->frame);
304 /* Put our data structure (Client) into the table */
305 table_put(&by_parent, new->frame, new);
306 table_put(&by_child, child, new);
308 /* We need to grab the mouse buttons for click to focus */
309 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
310 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
311 1 /* left mouse button */,
312 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
314 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
315 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
316 3 /* right mouse button */,
317 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
319 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
321 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
322 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
323 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
324 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
325 DLOG("Window is a dock.\n");
326 Output *t_out = get_output_containing(x, y);
327 if (t_out != c_ws->output) {
328 DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n",
330 new->workspace = t_out->current_workspace;
333 new->borderless = true;
334 new->titlebar_position = TITLEBAR_OFF;
335 new->force_reconfigure = true;
336 new->container = NULL;
337 SLIST_INSERT_HEAD(&(t_out->dock_clients), new, dock_clients);
338 /* If it’s a dock we can’t make it float, so we break */
339 new->floating = FLOATING_AUTO_OFF;
341 } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
342 atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
343 atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
344 atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
345 /* Set the dialog window to automatically floating, will be used below */
346 new->floating = FLOATING_AUTO_ON;
347 DLOG("dialog/utility/toolbar/splash window, automatically floating\n");
351 /* All clients which have a leader should be floating */
352 if (!new->dock && !client_is_floating(new) && new->leader != 0) {
353 DLOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
354 new->floating = FLOATING_AUTO_ON;
357 if (new->workspace->auto_float) {
358 new->floating = FLOATING_AUTO_ON;
359 DLOG("workspace is in autofloat mode, setting floating\n");
363 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
365 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
366 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
367 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
368 of the screen. This is because the only possibility for bars is at to be at the top/bottom
369 with maximum horizontal size.
370 TODO: bars at the top */
371 new->desired_height = strut[3];
372 if (new->desired_height == 0) {
373 DLOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
374 new->desired_height = original_height;
376 DLOG("the client wants to be %d pixels high\n", new->desired_height);
378 DLOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
379 new->desired_height = original_height;
382 /* If it’s not a dock, we can check on which workspace we should put it. */
384 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
385 * top of this function, get them now and pass them to our callback function for window class / title
386 * changes. It is important that the client was already inserted into the by_child table,
387 * because the callbacks won’t work otherwise. */
388 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
389 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
391 preply = xcb_get_property_reply(conn, title_cookie, NULL);
392 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
394 preply = xcb_get_property_reply(conn, class_cookie, NULL);
395 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
397 preply = xcb_get_property_reply(conn, leader_cookie, NULL);
398 handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
400 /* if WM_CLIENT_LEADER is set, we put the new window on the
401 * same window as its leader. This might be overwritten by
402 * assignments afterwards. */
403 if (new->leader != XCB_NONE) {
404 DLOG("client->leader is set (to 0x%08x)\n", new->leader);
405 Client *parent = table_get(&by_child, new->leader);
406 if (parent != NULL && parent->container != NULL) {
407 Workspace *t_ws = parent->workspace;
408 new->container = t_ws->table[parent->container->col][parent->container->row];
409 new->workspace = t_ws;
410 old_focused = new->container->currently_focused;
411 map_frame = workspace_is_visible(t_ws);
413 /* This is a little tricky: we cannot use
414 * workspace_update_urgent_flag() because the
415 * new window was not yet inserted into the
416 * focus stack on t_ws. */
419 DLOG("parent is not usable\n");
423 struct Assignment *assign;
424 TAILQ_FOREACH(assign, &assignments, assignments) {
425 if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
428 if (assign->floating == ASSIGN_FLOATING_ONLY ||
429 assign->floating == ASSIGN_FLOATING) {
430 new->floating = FLOATING_AUTO_ON;
431 LOG("Assignment matches, putting client into floating mode\n");
432 if (assign->floating == ASSIGN_FLOATING_ONLY)
436 LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
437 assign->windowclass_title, assign->workspace);
439 if (c_ws->output->current_workspace->num == (assign->workspace-1)) {
440 DLOG("We are already there, no need to do anything\n");
444 DLOG("Changing container/workspace and unmapping the client\n");
445 Workspace *t_ws = workspace_get(assign->workspace-1);
446 workspace_initialize(t_ws, c_ws->output, false);
448 new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
449 new->workspace = t_ws;
450 old_focused = new->container->currently_focused;
452 map_frame = workspace_is_visible(t_ws);
457 if (new->workspace->fullscreen_client != NULL) {
458 DLOG("Setting below fullscreen window\n");
460 /* If we are in fullscreen, we should place the window below
461 * the fullscreen window to not be annoying */
462 uint32_t values[] = {
463 new->workspace->fullscreen_client->frame,
466 xcb_configure_window(conn, new->frame,
467 XCB_CONFIG_WINDOW_SIBLING |
468 XCB_CONFIG_WINDOW_STACK_MODE, values);
471 /* Insert into the currently active container, if it’s not a dock window */
472 if (!new->dock && !client_is_floating(new)) {
473 /* Insert after the old active client, if existing. If it does not exist, the
474 container is empty and it does not matter, where we insert it */
475 if (old_focused != NULL && !old_focused->dock)
476 CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
477 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
479 if (new->container->workspace->fullscreen_client != NULL)
480 SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
481 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
483 client_set_below_floating(conn, new);
486 if (client_is_floating(new)) {
487 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
489 /* Add the client to the list of floating clients for its workspace */
490 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
492 new->container = NULL;
494 new->rect.width = new->floating_rect.width + 2 + 2;
495 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
497 /* Some clients (like GIMP’s color picker window) get mapped
498 * to (0, 0), so we push them to a reasonable position
499 * (centered over their leader) */
500 if (new->leader != 0 && x == 0 && y == 0) {
501 DLOG("Floating client wants to (0x0), moving it over its leader instead\n");
502 Client *leader = table_get(&by_child, new->leader);
503 if (leader == NULL) {
504 DLOG("leader is NULL, centering it over current workspace\n");
506 x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
507 y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
509 x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
510 y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
513 new->floating_rect.x = new->rect.x = x;
514 new->floating_rect.y = new->rect.y = y;
515 DLOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
516 new->floating_rect.x, new->floating_rect.y,
517 new->floating_rect.width, new->floating_rect.height);
518 DLOG("outer rect (%d, %d) size (%d, %d)\n",
519 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
521 /* Make sure it is on top of the other windows */
522 xcb_raise_window(conn, new->frame);
523 reposition_client(conn, new);
524 resize_client(conn, new);
525 /* redecorate_window flushes */
526 redecorate_window(conn, new);
529 new->initialized = true;
531 /* Check if the window already got the fullscreen hint set */
533 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
534 (state = xcb_get_property_value(preply)) != NULL)
535 /* Check all set _NET_WM_STATEs */
536 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
537 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
539 /* If the window got the fullscreen state, we just toggle fullscreen
540 and don’t event bother to redraw the layout – that would not change
542 client_toggle_fullscreen(conn, new);
549 /* Map the window first to avoid flickering */
550 xcb_map_window(conn, child);
552 client_map(conn, new);
554 if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !new->dock) {
555 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
556 if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) {
557 if (!client_is_floating(new)) {
558 new->container->currently_focused = new;
560 render_container(conn, new->container);
562 if (new->container == CUR_CELL || client_is_floating(new)) {
563 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
564 ewmh_update_active_window(new->child);