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);
45 * Restores the geometry of each window by reparenting it to the root window
46 * at the position of its frame.
48 * This is to be called *only* before exiting/restarting i3 because of evil
49 * side-effects which are to be expected when continuing to run i3.
52 void restore_geometry() {
53 LOG("Restoring geometry\n");
56 TAILQ_FOREACH(con, &all_cons, all_cons)
58 printf("placing window at %d %d\n", con->rect.x, con->rect.y);
59 xcb_reparent_window(conn, con->window->id, root,
60 con->rect.x, con->rect.y);
63 /* Make sure our changes reach the X server, we restart/exit now */
68 * Do some sanity checks and then reparent the window.
71 void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cookie,
72 bool needs_to_be_mapped) {
73 xcb_drawable_t d = { window };
74 xcb_get_geometry_cookie_t geomc;
75 xcb_get_geometry_reply_t *geom;
76 xcb_get_window_attributes_reply_t *attr = 0;
78 printf("---> looking at window 0x%08x\n", window);
80 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
81 utf8_title_cookie, title_cookie,
82 class_cookie, leader_cookie;
84 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
85 strut_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
86 state_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STATE], UINT32_MAX);
87 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_NAME], 128);
88 leader_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[WM_CLIENT_LEADER], UINT32_MAX);
89 title_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_NAME, 128);
90 class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128);
93 geomc = xcb_get_geometry(conn, d);
95 /* Check if the window is mapped (it could be not mapped when intializing and
96 calling manage_window() for every window) */
97 if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
98 LOG("Could not get attributes\n");
102 if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) {
103 LOG("map_state unviewable\n");
107 /* Don’t manage clients with the override_redirect flag */
108 LOG("override_redirect is %d\n", attr->override_redirect);
109 if (attr->override_redirect)
112 /* Check if the window is already managed */
113 if (con_by_window_id(window) != NULL) {
114 LOG("already managed\n");
118 /* Get the initial geometry (position, size, …) */
119 if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL) {
120 LOG("could not get geometry\n");
124 LOG("reparenting!\n");
127 mask = XCB_CW_EVENT_MASK;
128 values[0] = CHILD_EVENT_MASK;
129 xcb_change_window_attributes(conn, window, mask, values);
131 i3Window *cwindow = scalloc(sizeof(i3Window));
132 cwindow->id = window;
134 /* update as much information as possible so far (some replies may be NULL) */
135 window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL));
136 window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL));
137 window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL));
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);
165 nc->window = cwindow;
168 xcb_void_cookie_t rcookie = xcb_reparent_window_checked(conn, window, nc->frame, 0, 0);
169 if (xcb_request_check(conn, rcookie) != NULL) {
170 LOG("Could not reparent the window, aborting\n");
172 //xcb_destroy_window(conn, nc->frame);
176 xcb_get_property_reply_t *preply;
177 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
178 (state = xcb_get_property_value(preply)) != NULL) {
179 /* Check all set _NET_WM_STATEs */
180 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
181 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
183 con_toggle_fullscreen(nc);
188 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window);
193 /* Reparent the window and add it to our list of managed windows */
194 reparent_window(conn, window, attr->visual, geom->root, geom->depth,
195 geom->x, geom->y, geom->width, geom->height,
207 * reparent_window() gets called when a new window was opened and becomes a child of the root
208 * window, or it gets called by us when we manage the already existing windows at startup.
210 * Essentially, this is the point where we take over control.
213 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
214 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
215 int16_t x, int16_t y, uint16_t width, uint16_t height,
216 uint32_t border_width) {
218 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
219 utf8_title_cookie, title_cookie,
220 class_cookie, leader_cookie;
223 uint16_t original_height = height;
224 bool map_frame = true;
226 /* We are interested in property changes */
227 mask = XCB_CW_EVENT_MASK;
228 values[0] = CHILD_EVENT_MASK;
229 xcb_change_window_attributes(conn, child, mask, values);
231 /* Place requests for properties ASAP */
232 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
233 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
234 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
235 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
236 leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX);
237 title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
238 class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
240 Client *new = table_get(&by_child, child);
242 /* Events for already managed windows should already be filtered in manage_window() */
245 LOG("Managing window 0x%08x\n", child);
246 DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
247 new = scalloc(sizeof(Client));
248 new->force_reconfigure = true;
250 /* Update the data structures */
251 Client *old_focused = CUR_CELL->currently_focused;
253 new->container = CUR_CELL;
254 new->workspace = new->container->workspace;
256 /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
257 width = max(width, 75);
258 height = max(height, 50);
260 new->frame = xcb_generate_id(conn);
262 new->rect.width = width;
263 new->rect.height = height;
264 new->width_increment = 1;
265 new->height_increment = 1;
266 new->border_width = border_width;
267 /* Pre-initialize the values for floating */
268 new->floating_rect.x = -1;
269 new->floating_rect.width = width;
270 new->floating_rect.height = height;
272 if (config.default_border != NULL)
273 client_init_border(conn, new, config.default_border[1]);
277 /* Don’t generate events for our new window, it should *not* be managed */
278 mask |= XCB_CW_OVERRIDE_REDIRECT;
281 /* We want to know when… */
282 mask |= XCB_CW_EVENT_MASK;
283 values[1] = FRAME_EVENT_MASK;
285 i3Font *font = load_font(conn, config.font);
286 width = min(width, c_ws->rect.x + c_ws->rect.width);
287 height = min(height, c_ws->rect.y + c_ws->rect.height);
289 Rect framerect = {x, y,
290 width + 2 + 2, /* 2 px border at each side */
291 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
293 /* Yo dawg, I heard you like windows, so I create a window around your window… */
294 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
296 /* Put the client inside the save set. Upon termination (whether killed or normal exit
297 does not matter) of the window manager, these clients will be correctly reparented
298 to their most closest living ancestor (= cleanup) */
299 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
301 /* Generate a graphics context for the titlebar */
302 new->titlegc = xcb_generate_id(conn);
303 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
305 /* Moves the original window into the new frame we've created for it */
306 new->awaiting_useless_unmap = true;
307 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
308 if (xcb_request_check(conn, cookie) != NULL) {
309 DLOG("Could not reparent the window, aborting\n");
310 xcb_destroy_window(conn, new->frame);
315 /* Put our data structure (Client) into the table */
316 table_put(&by_parent, new->frame, new);
317 table_put(&by_child, child, new);
319 /* We need to grab the mouse buttons for click to focus */
320 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
321 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
322 1 /* left mouse button */,
323 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
325 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
326 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
327 3 /* right mouse button */,
328 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
330 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
332 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
333 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
334 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
335 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
336 DLOG("Window is a dock.\n");
337 Output *t_out = get_output_containing(x, y);
338 if (t_out != c_ws->output) {
339 DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n",
341 new->workspace = t_out->current_workspace;
344 new->borderless = true;
345 new->titlebar_position = TITLEBAR_OFF;
346 new->force_reconfigure = true;
347 new->container = NULL;
348 SLIST_INSERT_HEAD(&(t_out->dock_clients), new, dock_clients);
349 /* If it’s a dock we can’t make it float, so we break */
350 new->floating = FLOATING_AUTO_OFF;
352 } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
353 atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
354 atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
355 atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
356 /* Set the dialog window to automatically floating, will be used below */
357 new->floating = FLOATING_AUTO_ON;
358 DLOG("dialog/utility/toolbar/splash window, automatically floating\n");
362 /* All clients which have a leader should be floating */
363 if (!new->dock && !client_is_floating(new) && new->leader != 0) {
364 DLOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
365 new->floating = FLOATING_AUTO_ON;
368 if (new->workspace->auto_float) {
369 new->floating = FLOATING_AUTO_ON;
370 DLOG("workspace is in autofloat mode, setting floating\n");
374 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
376 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
377 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
378 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
379 of the screen. This is because the only possibility for bars is at to be at the top/bottom
380 with maximum horizontal size.
381 TODO: bars at the top */
382 new->desired_height = strut[3];
383 if (new->desired_height == 0) {
384 DLOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
385 new->desired_height = original_height;
387 DLOG("the client wants to be %d pixels high\n", new->desired_height);
389 DLOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
390 new->desired_height = original_height;
393 /* If it’s not a dock, we can check on which workspace we should put it. */
395 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
396 * top of this function, get them now and pass them to our callback function for window class / title
397 * changes. It is important that the client was already inserted into the by_child table,
398 * because the callbacks won’t work otherwise. */
399 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
400 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
402 preply = xcb_get_property_reply(conn, title_cookie, NULL);
403 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
405 preply = xcb_get_property_reply(conn, class_cookie, NULL);
406 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
408 preply = xcb_get_property_reply(conn, leader_cookie, NULL);
409 handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
411 /* if WM_CLIENT_LEADER is set, we put the new window on the
412 * same window as its leader. This might be overwritten by
413 * assignments afterwards. */
414 if (new->leader != XCB_NONE) {
415 DLOG("client->leader is set (to 0x%08x)\n", new->leader);
416 Client *parent = table_get(&by_child, new->leader);
417 if (parent != NULL && parent->container != NULL) {
418 Workspace *t_ws = parent->workspace;
419 new->container = t_ws->table[parent->container->col][parent->container->row];
420 new->workspace = t_ws;
421 old_focused = new->container->currently_focused;
422 map_frame = workspace_is_visible(t_ws);
424 /* This is a little tricky: we cannot use
425 * workspace_update_urgent_flag() because the
426 * new window was not yet inserted into the
427 * focus stack on t_ws. */
430 DLOG("parent is not usable\n");
434 struct Assignment *assign;
435 TAILQ_FOREACH(assign, &assignments, assignments) {
436 if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
439 if (assign->floating == ASSIGN_FLOATING_ONLY ||
440 assign->floating == ASSIGN_FLOATING) {
441 new->floating = FLOATING_AUTO_ON;
442 LOG("Assignment matches, putting client into floating mode\n");
443 if (assign->floating == ASSIGN_FLOATING_ONLY)
447 LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
448 assign->windowclass_title, assign->workspace);
450 if (c_ws->output->current_workspace->num == (assign->workspace-1)) {
451 DLOG("We are already there, no need to do anything\n");
455 DLOG("Changing container/workspace and unmapping the client\n");
456 Workspace *t_ws = workspace_get(assign->workspace-1);
457 workspace_initialize(t_ws, c_ws->output, false);
459 new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
460 new->workspace = t_ws;
461 old_focused = new->container->currently_focused;
463 map_frame = workspace_is_visible(t_ws);
468 if (new->workspace->fullscreen_client != NULL) {
469 DLOG("Setting below fullscreen window\n");
471 /* If we are in fullscreen, we should place the window below
472 * the fullscreen window to not be annoying */
473 uint32_t values[] = {
474 new->workspace->fullscreen_client->frame,
477 xcb_configure_window(conn, new->frame,
478 XCB_CONFIG_WINDOW_SIBLING |
479 XCB_CONFIG_WINDOW_STACK_MODE, values);
482 /* Insert into the currently active container, if it’s not a dock window */
483 if (!new->dock && !client_is_floating(new)) {
484 /* Insert after the old active client, if existing. If it does not exist, the
485 container is empty and it does not matter, where we insert it */
486 if (old_focused != NULL && !old_focused->dock)
487 CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
488 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
490 if (new->container->workspace->fullscreen_client != NULL)
491 SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
492 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
494 client_set_below_floating(conn, new);
497 if (client_is_floating(new)) {
498 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
500 /* Add the client to the list of floating clients for its workspace */
501 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
503 new->container = NULL;
505 new->rect.width = new->floating_rect.width + 2 + 2;
506 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
508 /* Some clients (like GIMP’s color picker window) get mapped
509 * to (0, 0), so we push them to a reasonable position
510 * (centered over their leader) */
511 if (new->leader != 0 && x == 0 && y == 0) {
512 DLOG("Floating client wants to (0x0), moving it over its leader instead\n");
513 Client *leader = table_get(&by_child, new->leader);
514 if (leader == NULL) {
515 DLOG("leader is NULL, centering it over current workspace\n");
517 x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
518 y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
520 x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
521 y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
524 new->floating_rect.x = new->rect.x = x;
525 new->floating_rect.y = new->rect.y = y;
526 DLOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
527 new->floating_rect.x, new->floating_rect.y,
528 new->floating_rect.width, new->floating_rect.height);
529 DLOG("outer rect (%d, %d) size (%d, %d)\n",
530 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
532 /* Make sure it is on top of the other windows */
533 xcb_raise_window(conn, new->frame);
534 reposition_client(conn, new);
535 resize_client(conn, new);
536 /* redecorate_window flushes */
537 redecorate_window(conn, new);
540 new->initialized = true;
542 /* Check if the window already got the fullscreen hint set */
544 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
545 (state = xcb_get_property_value(preply)) != NULL)
546 /* Check all set _NET_WM_STATEs */
547 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
548 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
550 /* If the window got the fullscreen state, we just toggle fullscreen
551 and don’t event bother to redraw the layout – that would not change
553 client_toggle_fullscreen(conn, new);
560 /* Map the window first to avoid flickering */
561 xcb_map_window(conn, child);
563 client_map(conn, new);
565 if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !new->dock) {
566 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
567 if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) {
568 if (!client_is_floating(new)) {
569 new->container->currently_focused = new;
571 render_container(conn, new->container);
573 if (new->container == CUR_CELL || client_is_floating(new)) {
574 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
575 ewmh_update_active_window(new->child);