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)
116 /* Get the initial geometry (position, size, …) */
117 if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
120 LOG("reparenting!\n");
123 mask = XCB_CW_EVENT_MASK;
124 values[0] = CHILD_EVENT_MASK;
125 xcb_change_window_attributes(conn, window, mask, values);
127 i3Window *cwindow = scalloc(sizeof(i3Window));
128 cwindow->id = window;
130 /* update as much information as possible so far (some replies may be NULL) */
131 window_update_class(cwindow, xcb_get_property_reply(conn, class_cookie, NULL));
132 window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL));
133 window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL));
138 /* TODO: assignments */
139 /* TODO: two matches for one container */
140 /* See if any container swallows this new window */
141 nc = con_for_window(cwindow, &match);
143 if (focused->type == CT_CON && con_accepts_window(focused)) {
144 LOG("using current container, focused = %p, focused->name = %s\n",
145 focused, focused->name);
147 } else nc = tree_open_con(NULL);
149 if (match != NULL && match->insert_where == M_ACTIVE) {
150 /* We need to go down the focus stack starting from nc */
151 while (TAILQ_FIRST(&(nc->focus_head)) != TAILQ_END(&(nc->focus_head))) {
152 printf("walking down one step...\n");
153 nc = TAILQ_FIRST(&(nc->focus_head));
155 /* We need to open a new con */
156 /* TODO: make a difference between match-once containers (directly assign
157 * cwindow) and match-multiple (tree_open_con first) */
158 nc = tree_open_con(nc->parent);
161 nc->window = cwindow;
163 xcb_void_cookie_t rcookie = xcb_reparent_window_checked(conn, window, nc->frame, 0, 0);
164 if (xcb_request_check(conn, rcookie) != NULL) {
165 LOG("Could not reparent the window, aborting\n");
167 //xcb_destroy_window(conn, nc->frame);
170 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, window);
175 /* Reparent the window and add it to our list of managed windows */
176 reparent_window(conn, window, attr->visual, geom->root, geom->depth,
177 geom->x, geom->y, geom->width, geom->height,
189 * reparent_window() gets called when a new window was opened and becomes a child of the root
190 * window, or it gets called by us when we manage the already existing windows at startup.
192 * Essentially, this is the point where we take over control.
195 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
196 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
197 int16_t x, int16_t y, uint16_t width, uint16_t height,
198 uint32_t border_width) {
200 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
201 utf8_title_cookie, title_cookie,
202 class_cookie, leader_cookie;
205 uint16_t original_height = height;
206 bool map_frame = true;
208 /* We are interested in property changes */
209 mask = XCB_CW_EVENT_MASK;
210 values[0] = CHILD_EVENT_MASK;
211 xcb_change_window_attributes(conn, child, mask, values);
213 /* Place requests for properties ASAP */
214 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
215 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
216 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
217 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
218 leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX);
219 title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
220 class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
222 Client *new = table_get(&by_child, child);
224 /* Events for already managed windows should already be filtered in manage_window() */
227 LOG("Managing window 0x%08x\n", child);
228 DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
229 new = scalloc(sizeof(Client));
230 new->force_reconfigure = true;
232 /* Update the data structures */
233 Client *old_focused = CUR_CELL->currently_focused;
235 new->container = CUR_CELL;
236 new->workspace = new->container->workspace;
238 /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
239 width = max(width, 75);
240 height = max(height, 50);
242 new->frame = xcb_generate_id(conn);
244 new->rect.width = width;
245 new->rect.height = height;
246 new->width_increment = 1;
247 new->height_increment = 1;
248 new->border_width = border_width;
249 /* Pre-initialize the values for floating */
250 new->floating_rect.x = -1;
251 new->floating_rect.width = width;
252 new->floating_rect.height = height;
254 if (config.default_border != NULL)
255 client_init_border(conn, new, config.default_border[1]);
259 /* Don’t generate events for our new window, it should *not* be managed */
260 mask |= XCB_CW_OVERRIDE_REDIRECT;
263 /* We want to know when… */
264 mask |= XCB_CW_EVENT_MASK;
265 values[1] = FRAME_EVENT_MASK;
267 i3Font *font = load_font(conn, config.font);
268 width = min(width, c_ws->rect.x + c_ws->rect.width);
269 height = min(height, c_ws->rect.y + c_ws->rect.height);
271 Rect framerect = {x, y,
272 width + 2 + 2, /* 2 px border at each side */
273 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
275 /* Yo dawg, I heard you like windows, so I create a window around your window… */
276 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
278 /* Put the client inside the save set. Upon termination (whether killed or normal exit
279 does not matter) of the window manager, these clients will be correctly reparented
280 to their most closest living ancestor (= cleanup) */
281 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
283 /* Generate a graphics context for the titlebar */
284 new->titlegc = xcb_generate_id(conn);
285 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
287 /* Moves the original window into the new frame we've created for it */
288 new->awaiting_useless_unmap = true;
289 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
290 if (xcb_request_check(conn, cookie) != NULL) {
291 DLOG("Could not reparent the window, aborting\n");
292 xcb_destroy_window(conn, new->frame);
297 /* Put our data structure (Client) into the table */
298 table_put(&by_parent, new->frame, new);
299 table_put(&by_child, child, new);
301 /* We need to grab the mouse buttons for click to focus */
302 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
303 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
304 1 /* left mouse button */,
305 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
307 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
308 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
309 3 /* right mouse button */,
310 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
312 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
314 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
315 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
316 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
317 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
318 DLOG("Window is a dock.\n");
319 Output *t_out = get_output_containing(x, y);
320 if (t_out != c_ws->output) {
321 DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n",
323 new->workspace = t_out->current_workspace;
326 new->borderless = true;
327 new->titlebar_position = TITLEBAR_OFF;
328 new->force_reconfigure = true;
329 new->container = NULL;
330 SLIST_INSERT_HEAD(&(t_out->dock_clients), new, dock_clients);
331 /* If it’s a dock we can’t make it float, so we break */
332 new->floating = FLOATING_AUTO_OFF;
334 } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
335 atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
336 atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
337 atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
338 /* Set the dialog window to automatically floating, will be used below */
339 new->floating = FLOATING_AUTO_ON;
340 DLOG("dialog/utility/toolbar/splash window, automatically floating\n");
344 /* All clients which have a leader should be floating */
345 if (!new->dock && !client_is_floating(new) && new->leader != 0) {
346 DLOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
347 new->floating = FLOATING_AUTO_ON;
350 if (new->workspace->auto_float) {
351 new->floating = FLOATING_AUTO_ON;
352 DLOG("workspace is in autofloat mode, setting floating\n");
356 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
358 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
359 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
360 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
361 of the screen. This is because the only possibility for bars is at to be at the top/bottom
362 with maximum horizontal size.
363 TODO: bars at the top */
364 new->desired_height = strut[3];
365 if (new->desired_height == 0) {
366 DLOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
367 new->desired_height = original_height;
369 DLOG("the client wants to be %d pixels high\n", new->desired_height);
371 DLOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
372 new->desired_height = original_height;
375 /* If it’s not a dock, we can check on which workspace we should put it. */
377 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
378 * top of this function, get them now and pass them to our callback function for window class / title
379 * changes. It is important that the client was already inserted into the by_child table,
380 * because the callbacks won’t work otherwise. */
381 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
382 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
384 preply = xcb_get_property_reply(conn, title_cookie, NULL);
385 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
387 preply = xcb_get_property_reply(conn, class_cookie, NULL);
388 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
390 preply = xcb_get_property_reply(conn, leader_cookie, NULL);
391 handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
393 /* if WM_CLIENT_LEADER is set, we put the new window on the
394 * same window as its leader. This might be overwritten by
395 * assignments afterwards. */
396 if (new->leader != XCB_NONE) {
397 DLOG("client->leader is set (to 0x%08x)\n", new->leader);
398 Client *parent = table_get(&by_child, new->leader);
399 if (parent != NULL && parent->container != NULL) {
400 Workspace *t_ws = parent->workspace;
401 new->container = t_ws->table[parent->container->col][parent->container->row];
402 new->workspace = t_ws;
403 old_focused = new->container->currently_focused;
404 map_frame = workspace_is_visible(t_ws);
406 /* This is a little tricky: we cannot use
407 * workspace_update_urgent_flag() because the
408 * new window was not yet inserted into the
409 * focus stack on t_ws. */
412 DLOG("parent is not usable\n");
416 struct Assignment *assign;
417 TAILQ_FOREACH(assign, &assignments, assignments) {
418 if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
421 if (assign->floating == ASSIGN_FLOATING_ONLY ||
422 assign->floating == ASSIGN_FLOATING) {
423 new->floating = FLOATING_AUTO_ON;
424 LOG("Assignment matches, putting client into floating mode\n");
425 if (assign->floating == ASSIGN_FLOATING_ONLY)
429 LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
430 assign->windowclass_title, assign->workspace);
432 if (c_ws->output->current_workspace->num == (assign->workspace-1)) {
433 DLOG("We are already there, no need to do anything\n");
437 DLOG("Changing container/workspace and unmapping the client\n");
438 Workspace *t_ws = workspace_get(assign->workspace-1);
439 workspace_initialize(t_ws, c_ws->output, false);
441 new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
442 new->workspace = t_ws;
443 old_focused = new->container->currently_focused;
445 map_frame = workspace_is_visible(t_ws);
450 if (new->workspace->fullscreen_client != NULL) {
451 DLOG("Setting below fullscreen window\n");
453 /* If we are in fullscreen, we should place the window below
454 * the fullscreen window to not be annoying */
455 uint32_t values[] = {
456 new->workspace->fullscreen_client->frame,
459 xcb_configure_window(conn, new->frame,
460 XCB_CONFIG_WINDOW_SIBLING |
461 XCB_CONFIG_WINDOW_STACK_MODE, values);
464 /* Insert into the currently active container, if it’s not a dock window */
465 if (!new->dock && !client_is_floating(new)) {
466 /* Insert after the old active client, if existing. If it does not exist, the
467 container is empty and it does not matter, where we insert it */
468 if (old_focused != NULL && !old_focused->dock)
469 CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
470 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
472 if (new->container->workspace->fullscreen_client != NULL)
473 SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
474 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
476 client_set_below_floating(conn, new);
479 if (client_is_floating(new)) {
480 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
482 /* Add the client to the list of floating clients for its workspace */
483 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
485 new->container = NULL;
487 new->rect.width = new->floating_rect.width + 2 + 2;
488 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
490 /* Some clients (like GIMP’s color picker window) get mapped
491 * to (0, 0), so we push them to a reasonable position
492 * (centered over their leader) */
493 if (new->leader != 0 && x == 0 && y == 0) {
494 DLOG("Floating client wants to (0x0), moving it over its leader instead\n");
495 Client *leader = table_get(&by_child, new->leader);
496 if (leader == NULL) {
497 DLOG("leader is NULL, centering it over current workspace\n");
499 x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
500 y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
502 x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
503 y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
506 new->floating_rect.x = new->rect.x = x;
507 new->floating_rect.y = new->rect.y = y;
508 DLOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
509 new->floating_rect.x, new->floating_rect.y,
510 new->floating_rect.width, new->floating_rect.height);
511 DLOG("outer rect (%d, %d) size (%d, %d)\n",
512 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
514 /* Make sure it is on top of the other windows */
515 xcb_raise_window(conn, new->frame);
516 reposition_client(conn, new);
517 resize_client(conn, new);
518 /* redecorate_window flushes */
519 redecorate_window(conn, new);
522 new->initialized = true;
524 /* Check if the window already got the fullscreen hint set */
526 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
527 (state = xcb_get_property_value(preply)) != NULL)
528 /* Check all set _NET_WM_STATEs */
529 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
530 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
532 /* If the window got the fullscreen state, we just toggle fullscreen
533 and don’t event bother to redraw the layout – that would not change
535 client_toggle_fullscreen(conn, new);
542 /* Map the window first to avoid flickering */
543 xcb_map_window(conn, child);
545 client_map(conn, new);
547 if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !new->dock) {
548 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
549 if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) {
550 if (!client_is_floating(new)) {
551 new->container->currently_focused = new;
553 render_container(conn, new->container);
555 if (new->container == CUR_CELL || client_is_floating(new)) {
556 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
557 ewmh_update_active_window(new->child);