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).
19 #include <xcb/xcb_icccm.h>
32 #include "workspace.h"
37 * Go through all existing windows (if the window manager is restarted) and manage them
40 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
41 xcb_query_tree_reply_t *reply;
43 xcb_window_t *children;
44 xcb_get_window_attributes_cookie_t *cookies;
46 /* Get the tree of windows whose parent is the root window (= all) */
47 if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
50 len = xcb_query_tree_children_length(reply);
51 cookies = smalloc(len * sizeof(*cookies));
53 /* Request the window attributes for every window */
54 children = xcb_query_tree_children(reply);
55 for (i = 0; i < len; ++i)
56 cookies[i] = xcb_get_window_attributes(conn, children[i]);
58 /* Call manage_window with the attributes for every window */
59 for (i = 0; i < len; ++i)
60 manage_window(prophs, conn, children[i], cookies[i], true);
67 * Restores the geometry of each window by reparenting it to the root window
68 * at the position of its frame.
70 * This is to be called *only* before exiting/restarting i3 because of evil
71 * side-effects which are to be expected when continuing to run i3.
74 void restore_geometry(xcb_connection_t *conn) {
77 DLOG("Restoring geometry\n");
79 TAILQ_FOREACH(ws, workspaces, workspaces)
80 SLIST_FOREACH(client, &(ws->focus_stack), focus_clients)
81 xcb_reparent_window(conn, client->child, root,
82 client->rect.x, client->rect.y);
84 /* Make sure our changes reach the X server, we restart/exit now */
89 * Do some sanity checks and then reparent the window.
92 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
93 xcb_window_t window, xcb_get_window_attributes_cookie_t cookie,
94 bool needs_to_be_mapped) {
95 xcb_drawable_t d = { window };
96 xcb_get_geometry_cookie_t geomc;
97 xcb_get_geometry_reply_t *geom;
98 xcb_get_window_attributes_reply_t *attr = 0;
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 ELOG("Could not get attributes\n");
109 if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE)
112 /* Don’t manage clients with the override_redirect flag */
113 if (attr->override_redirect)
116 /* Check if the window is already managed */
117 if (table_get(&by_child, window))
120 /* Get the initial geometry (position, size, …) */
121 if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
124 /* Reparent the window and add it to our list of managed windows */
125 reparent_window(conn, window, attr->visual, geom->root, geom->depth,
126 geom->x, geom->y, geom->width, geom->height,
129 /* Generate callback events for every property we watch */
130 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
131 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
132 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
133 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_HINTS);
134 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR);
135 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[WM_CLIENT_LEADER]);
136 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
145 * reparent_window() gets called when a new window was opened and becomes a child of the root
146 * window, or it gets called by us when we manage the already existing windows at startup.
148 * Essentially, this is the point where we take over control.
151 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
152 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
153 int16_t x, int16_t y, uint16_t width, uint16_t height,
154 uint32_t border_width) {
156 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
157 utf8_title_cookie, title_cookie,
158 class_cookie, leader_cookie;
161 uint16_t original_height = height;
162 bool map_frame = true;
164 /* We are interested in property changes */
165 mask = XCB_CW_EVENT_MASK;
166 values[0] = CHILD_EVENT_MASK;
167 xcb_change_window_attributes(conn, child, mask, values);
169 /* Place requests for properties ASAP */
170 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
171 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
172 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
173 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
174 leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX);
175 title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
176 class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
178 Client *new = table_get(&by_child, child);
180 /* Events for already managed windows should already be filtered in manage_window() */
183 LOG("Managing window 0x%08x\n", child);
184 DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
185 new = scalloc(sizeof(Client));
186 new->force_reconfigure = true;
188 /* Update the data structures */
189 Client *old_focused = CUR_CELL->currently_focused;
191 new->container = CUR_CELL;
192 new->workspace = new->container->workspace;
194 /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
195 width = max(width, 75);
196 height = max(height, 50);
198 new->frame = xcb_generate_id(conn);
200 new->rect.width = width;
201 new->rect.height = height;
202 new->width_increment = 1;
203 new->height_increment = 1;
204 new->border_width = border_width;
205 /* Pre-initialize the values for floating */
206 new->floating_rect.x = -1;
207 new->floating_rect.width = width;
208 new->floating_rect.height = height;
210 if (config.default_border != NULL)
211 client_init_border(conn, new, config.default_border[1]);
215 /* Don’t generate events for our new window, it should *not* be managed */
216 mask |= XCB_CW_OVERRIDE_REDIRECT;
219 /* We want to know when… */
220 mask |= XCB_CW_EVENT_MASK;
221 values[1] = FRAME_EVENT_MASK;
223 i3Font *font = load_font(conn, config.font);
224 width = min(width, c_ws->rect.x + c_ws->rect.width);
225 height = min(height, c_ws->rect.y + c_ws->rect.height);
227 Rect framerect = {x, y,
228 width + 2 + 2, /* 2 px border at each side */
229 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
231 /* Yo dawg, I heard you like windows, so I create a window around your window… */
232 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
234 /* Put the client inside the save set. Upon termination (whether killed or normal exit
235 does not matter) of the window manager, these clients will be correctly reparented
236 to their most closest living ancestor (= cleanup) */
237 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
239 /* Generate a graphics context for the titlebar */
240 new->titlegc = xcb_generate_id(conn);
241 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
243 /* Moves the original window into the new frame we've created for it */
244 new->awaiting_useless_unmap = true;
245 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
246 if (xcb_request_check(conn, cookie) != NULL) {
247 DLOG("Could not reparent the window, aborting\n");
248 xcb_destroy_window(conn, new->frame);
253 /* Put our data structure (Client) into the table */
254 table_put(&by_parent, new->frame, new);
255 table_put(&by_child, child, new);
257 /* We need to grab the mouse buttons for click to focus */
258 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
259 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
260 1 /* left mouse button */,
261 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
263 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
264 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
265 3 /* right mouse button */,
266 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
268 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
270 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
271 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
272 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
273 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
274 DLOG("Window is a dock.\n");
275 Output *t_out = get_output_containing(x, y);
277 t_out = c_ws->output;
278 if (t_out != c_ws->output) {
279 DLOG("Dock client requested to be on output %s by geometry (%d, %d)\n",
281 new->workspace = t_out->current_workspace;
284 new->borderless = true;
285 new->titlebar_position = TITLEBAR_OFF;
286 new->force_reconfigure = true;
287 new->container = NULL;
288 SLIST_INSERT_HEAD(&(t_out->dock_clients), new, dock_clients);
289 /* If it’s a dock we can’t make it float, so we break */
290 new->floating = FLOATING_AUTO_OFF;
292 } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
293 atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
294 atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
295 atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
296 /* Set the dialog window to automatically floating, will be used below */
297 new->floating = FLOATING_AUTO_ON;
298 DLOG("dialog/utility/toolbar/splash window, automatically floating\n");
302 /* All clients which have a leader should be floating */
303 if (!new->dock && !client_is_floating(new) && new->leader != 0) {
304 DLOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
305 new->floating = FLOATING_AUTO_ON;
308 if (new->workspace->auto_float) {
309 new->floating = FLOATING_AUTO_ON;
310 DLOG("workspace is in autofloat mode, setting floating\n");
314 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
316 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
317 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
318 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
319 of the screen. This is because the only possibility for bars is at to be at the top/bottom
320 with maximum horizontal size.
321 TODO: bars at the top */
322 new->desired_height = strut[3];
323 if (new->desired_height == 0) {
324 DLOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
325 new->desired_height = original_height;
327 DLOG("the client wants to be %d pixels high\n", new->desired_height);
329 DLOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
330 new->desired_height = original_height;
333 /* If it’s not a dock, we can check on which workspace we should put it. */
335 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
336 * top of this function, get them now and pass them to our callback function for window class / title
337 * changes. It is important that the client was already inserted into the by_child table,
338 * because the callbacks won’t work otherwise. */
339 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
340 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
342 preply = xcb_get_property_reply(conn, title_cookie, NULL);
343 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
345 preply = xcb_get_property_reply(conn, class_cookie, NULL);
346 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
348 preply = xcb_get_property_reply(conn, leader_cookie, NULL);
349 handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
351 /* if WM_CLIENT_LEADER is set, we put the new window on the
352 * same window as its leader. This might be overwritten by
353 * assignments afterwards. */
354 if (new->leader != XCB_NONE) {
355 DLOG("client->leader is set (to 0x%08x)\n", new->leader);
356 Client *parent = table_get(&by_child, new->leader);
357 if (parent != NULL && parent->container != NULL) {
358 Workspace *t_ws = parent->workspace;
359 new->container = t_ws->table[parent->container->col][parent->container->row];
360 new->workspace = t_ws;
361 old_focused = new->container->currently_focused;
362 map_frame = workspace_is_visible(t_ws);
364 /* This is a little tricky: we cannot use
365 * workspace_update_urgent_flag() because the
366 * new window was not yet inserted into the
367 * focus stack on t_ws. */
370 DLOG("parent is not usable\n");
374 struct Assignment *assign;
375 TAILQ_FOREACH(assign, &assignments, assignments) {
376 if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
379 if (assign->floating == ASSIGN_FLOATING_ONLY ||
380 assign->floating == ASSIGN_FLOATING) {
381 new->floating = FLOATING_AUTO_ON;
382 LOG("Assignment matches, putting client into floating mode\n");
383 if (assign->floating == ASSIGN_FLOATING_ONLY)
387 LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
388 assign->windowclass_title, assign->workspace);
390 if (c_ws->output->current_workspace->num == (assign->workspace-1)) {
391 DLOG("We are already there, no need to do anything\n");
395 DLOG("Changing container/workspace and unmapping the client\n");
396 Workspace *t_ws = workspace_get(assign->workspace-1);
397 workspace_initialize(t_ws, c_ws->output, false);
399 new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
400 new->workspace = t_ws;
401 old_focused = new->container->currently_focused;
403 map_frame = workspace_is_visible(t_ws);
408 if (new->workspace->fullscreen_client != NULL) {
409 DLOG("Setting below fullscreen window\n");
411 /* If we are in fullscreen, we should place the window below
412 * the fullscreen window to not be annoying */
413 uint32_t values[] = {
414 new->workspace->fullscreen_client->frame,
417 xcb_configure_window(conn, new->frame,
418 XCB_CONFIG_WINDOW_SIBLING |
419 XCB_CONFIG_WINDOW_STACK_MODE, values);
422 /* Insert into the currently active container, if it’s not a dock window */
423 if (!new->dock && !client_is_floating(new)) {
424 /* Insert after the old active client, if existing. If it does not exist, the
425 container is empty and it does not matter, where we insert it */
426 if (old_focused != NULL && !old_focused->dock)
427 CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
428 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
430 if (new->container->workspace->fullscreen_client != NULL)
431 SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
432 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
434 client_set_below_floating(conn, new);
437 if (client_is_floating(new)) {
438 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
440 /* Add the client to the list of floating clients for its workspace */
441 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
443 new->container = NULL;
445 new->rect.width = new->floating_rect.width + 2 + 2;
446 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
448 /* Some clients (like GIMP’s color picker window) get mapped
449 * to (0, 0), so we push them to a reasonable position
450 * (centered over their leader) */
451 if (new->leader != 0 && x == 0 && y == 0) {
452 DLOG("Floating client wants to (0x0), moving it over its leader instead\n");
453 Client *leader = table_get(&by_child, new->leader);
454 if (leader == NULL) {
455 DLOG("leader is NULL, centering it over current workspace\n");
457 x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
458 y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
460 x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
461 y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
464 new->floating_rect.x = new->rect.x = x;
465 new->floating_rect.y = new->rect.y = y;
466 DLOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
467 new->floating_rect.x, new->floating_rect.y,
468 new->floating_rect.width, new->floating_rect.height);
469 DLOG("outer rect (%d, %d) size (%d, %d)\n",
470 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
472 /* Make sure it is on top of the other windows */
473 xcb_raise_window(conn, new->frame);
474 reposition_client(conn, new);
475 resize_client(conn, new);
476 /* redecorate_window flushes */
477 redecorate_window(conn, new);
480 new->initialized = true;
482 /* Check if the window already got the fullscreen hint set */
484 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
485 (state = xcb_get_property_value(preply)) != NULL)
486 /* Check all set _NET_WM_STATEs */
487 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
488 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
490 /* If the window got the fullscreen state, we just toggle fullscreen
491 and don’t event bother to redraw the layout – that would not change
493 client_toggle_fullscreen(conn, new);
500 /* Map the window first to avoid flickering */
501 xcb_map_window(conn, child);
503 client_map(conn, new);
505 if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !new->dock) {
506 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
507 if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) {
508 if (!client_is_floating(new)) {
509 new->container->currently_focused = new;
511 render_container(conn, new->container);
513 if (new->container == CUR_CELL || client_is_floating(new)) {
514 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
515 ewmh_update_active_window(new->child);