4 * i3 - an improved dynamic tiling window manager
6 * © 2009 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"
35 * Go through all existing windows (if the window manager is restarted) and manage them
38 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
39 xcb_query_tree_reply_t *reply;
41 xcb_window_t *children;
42 xcb_get_window_attributes_cookie_t *cookies;
44 /* Get the tree of windows whose parent is the root window (= all) */
45 if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
48 len = xcb_query_tree_children_length(reply);
49 cookies = smalloc(len * sizeof(*cookies));
51 /* Request the window attributes for every window */
52 children = xcb_query_tree_children(reply);
53 for(i = 0; i < len; ++i)
54 cookies[i] = xcb_get_window_attributes(conn, children[i]);
56 /* Call manage_window with the attributes for every window */
57 for(i = 0; i < len; ++i)
58 manage_window(prophs, conn, children[i], cookies[i], true);
65 * Do some sanity checks and then reparent the window.
68 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
69 xcb_window_t window, xcb_get_window_attributes_cookie_t cookie,
70 bool needs_to_be_mapped) {
71 xcb_drawable_t d = { window };
72 xcb_get_geometry_cookie_t geomc;
73 xcb_get_geometry_reply_t *geom;
74 xcb_get_window_attributes_reply_t *attr = 0;
76 geomc = xcb_get_geometry(conn, d);
78 /* Check if the window is mapped (it could be not mapped when intializing and
79 calling manage_window() for every window) */
80 if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
81 LOG("Could not get attributes\n");
85 if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE)
88 /* Don’t manage clients with the override_redirect flag */
89 if (attr->override_redirect)
92 /* Check if the window is already managed */
93 if (table_get(&by_child, window))
96 /* Get the initial geometry (position, size, …) */
97 if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
100 /* Reparent the window and add it to our list of managed windows */
101 reparent_window(conn, window, attr->visual, geom->root, geom->depth,
102 geom->x, geom->y, geom->width, geom->height);
104 /* Generate callback events for every property we watch */
105 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
106 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
107 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
108 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR);
109 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[WM_CLIENT_LEADER]);
110 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
119 * reparent_window() gets called when a new window was opened and becomes a child of the root
120 * window, or it gets called by us when we manage the already existing windows at startup.
122 * Essentially, this is the point where we take over control.
125 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
126 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
127 int16_t x, int16_t y, uint16_t width, uint16_t height) {
129 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
130 utf8_title_cookie, title_cookie,
131 class_cookie, leader_cookie;
134 uint16_t original_height = height;
135 bool map_frame = true;
137 /* We are interested in property changes */
138 mask = XCB_CW_EVENT_MASK;
139 values[0] = CHILD_EVENT_MASK;
140 xcb_change_window_attributes(conn, child, mask, values);
142 /* Place requests for properties ASAP */
143 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
144 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
145 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
146 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
147 leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX);
148 title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
149 class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
151 Client *new = table_get(&by_child, child);
153 /* Events for already managed windows should already be filtered in manage_window() */
156 LOG("Reparenting window 0x%08x\n", child);
157 LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
158 new = calloc(sizeof(Client), 1);
159 new->force_reconfigure = true;
161 /* Update the data structures */
162 Client *old_focused = CUR_CELL->currently_focused;
164 new->container = CUR_CELL;
165 new->workspace = new->container->workspace;
167 /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
168 width = max(width, 75);
169 height = max(height, 50);
171 new->frame = xcb_generate_id(conn);
173 new->rect.width = width;
174 new->rect.height = height;
175 new->width_increment = 1;
176 new->height_increment = 1;
177 /* Pre-initialize the values for floating */
178 new->floating_rect.x = -1;
179 new->floating_rect.width = width;
180 new->floating_rect.height = height;
184 /* Don’t generate events for our new window, it should *not* be managed */
185 mask |= XCB_CW_OVERRIDE_REDIRECT;
188 /* We want to know when… */
189 mask |= XCB_CW_EVENT_MASK;
190 values[1] = FRAME_EVENT_MASK;
192 i3Font *font = load_font(conn, config.font);
193 width = min(width, c_ws->rect.x + c_ws->rect.width);
194 height = min(height, c_ws->rect.y + c_ws->rect.height);
196 Rect framerect = {x, y,
197 width + 2 + 2, /* 2 px border at each side */
198 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
200 /* Yo dawg, I heard you like windows, so I create a window around your window… */
201 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
203 /* Put the client inside the save set. Upon termination (whether killed or normal exit
204 does not matter) of the window manager, these clients will be correctly reparented
205 to their most closest living ancestor (= cleanup) */
206 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
208 /* Generate a graphics context for the titlebar */
209 new->titlegc = xcb_generate_id(conn);
210 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
212 /* Moves the original window into the new frame we've created for it */
213 new->awaiting_useless_unmap = true;
214 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
215 if (xcb_request_check(conn, cookie) != NULL) {
216 LOG("Could not reparent the window, aborting\n");
217 xcb_destroy_window(conn, new->frame);
222 /* Put our data structure (Client) into the table */
223 table_put(&by_parent, new->frame, new);
224 table_put(&by_child, child, new);
226 /* We need to grab the mouse buttons for click to focus */
227 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
228 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
229 1 /* left mouse button */,
230 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
232 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
233 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
234 3 /* right mouse button */,
235 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
237 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
239 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
240 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
241 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
242 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
243 LOG("Window is a dock.\n");
245 new->borderless = true;
246 new->titlebar_position = TITLEBAR_OFF;
247 new->force_reconfigure = true;
248 new->container = NULL;
249 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
250 /* If it’s a dock we can’t make it float, so we break */
251 new->floating = FLOATING_AUTO_OFF;
253 } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
254 atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
255 atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
256 atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
257 /* Set the dialog window to automatically floating, will be used below */
258 new->floating = FLOATING_AUTO_ON;
259 LOG("dialog/utility/toolbar/splash window, automatically floating\n");
263 /* All clients which have a leader should be floating */
264 if (!new->dock && !client_is_floating(new) && new->leader != 0) {
265 LOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
266 new->floating = FLOATING_AUTO_ON;
269 if (new->workspace->auto_float) {
270 new->floating = FLOATING_AUTO_ON;
271 LOG("workspace is in autofloat mode, setting floating\n");
275 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
277 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
278 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
279 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
280 of the screen. This is because the only possibility for bars is at to be at the top/bottom
281 with maximum horizontal size.
282 TODO: bars at the top */
283 new->desired_height = strut[3];
284 if (new->desired_height == 0) {
285 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
286 new->desired_height = original_height;
288 LOG("the client wants to be %d pixels high\n", new->desired_height);
290 LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
291 new->desired_height = original_height;
294 /* If it’s not a dock, we can check on which workspace we should put it. */
296 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
297 * top of this function, get them now and pass them to our callback function for window class / title
298 * changes. It is important that the client was already inserted into the by_child table,
299 * because the callbacks won’t work otherwise. */
300 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
301 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
303 preply = xcb_get_property_reply(conn, title_cookie, NULL);
304 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
306 preply = xcb_get_property_reply(conn, class_cookie, NULL);
307 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
309 preply = xcb_get_property_reply(conn, leader_cookie, NULL);
310 handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
312 struct Assignment *assign;
313 TAILQ_FOREACH(assign, &assignments, assignments) {
314 if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
317 if (assign->floating == ASSIGN_FLOATING_ONLY ||
318 assign->floating == ASSIGN_FLOATING) {
319 new->floating = FLOATING_AUTO_ON;
320 LOG("Assignment matches, putting client into floating mode\n");
321 if (assign->floating == ASSIGN_FLOATING_ONLY)
325 LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
326 assign->windowclass_title, assign->workspace);
328 if (c_ws->screen->current_workspace == (assign->workspace-1)) {
329 LOG("We are already there, no need to do anything\n");
333 LOG("Changing container/workspace and unmapping the client\n");
334 Workspace *t_ws = &(workspaces[assign->workspace-1]);
335 workspace_initialize(t_ws, c_ws->screen);
337 new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
338 new->workspace = t_ws;
339 old_focused = new->container->currently_focused;
341 map_frame = workspace_is_visible(t_ws);
346 if (CUR_CELL->workspace->fullscreen_client != NULL) {
347 if (new->container == CUR_CELL) {
348 /* If we are in fullscreen, we should lower the window to not be annoying */
349 uint32_t values[] = { XCB_STACK_MODE_BELOW };
350 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
354 /* Insert into the currently active container, if it’s not a dock window */
355 if (!new->dock && !client_is_floating(new)) {
356 /* Insert after the old active client, if existing. If it does not exist, the
357 container is empty and it does not matter, where we insert it */
358 if (old_focused != NULL && !old_focused->dock)
359 CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
360 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
362 if (new->container->workspace->fullscreen_client != NULL)
363 SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
364 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
366 client_set_below_floating(conn, new);
369 if (client_is_floating(new)) {
370 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
372 /* Add the client to the list of floating clients for its workspace */
373 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
375 new->container = NULL;
377 new->rect.width = new->floating_rect.width + 2 + 2;
378 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
380 /* Some clients (like GIMP’s color picker window) get mapped
381 * to (0, 0), so we push them to a reasonable position
382 * (centered over their leader) */
383 if (new->leader != 0 && x == 0 && y == 0) {
384 LOG("Floating client wants to (0x0), moving it over its leader instead\n");
385 Client *leader = table_get(&by_child, new->leader);
386 if (leader == NULL) {
387 LOG("leader is NULL, centering it over current workspace\n");
389 x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
390 y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
392 x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
393 y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
396 new->floating_rect.x = new->rect.x = x;
397 new->floating_rect.y = new->rect.y = y;
398 LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
399 new->floating_rect.x, new->floating_rect.y,
400 new->floating_rect.width, new->floating_rect.height);
401 LOG("outer rect (%d, %d) size (%d, %d)\n",
402 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
404 /* Make sure it is on top of the other windows */
405 xcb_raise_window(conn, new->frame);
406 reposition_client(conn, new);
407 resize_client(conn, new);
408 /* redecorate_window flushes */
409 redecorate_window(conn, new);
412 new->initialized = true;
414 /* Check if the window already got the fullscreen hint set */
416 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
417 (state = xcb_get_property_value(preply)) != NULL)
418 /* Check all set _NET_WM_STATEs */
419 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
420 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
422 /* If the window got the fullscreen state, we just toggle fullscreen
423 and don’t event bother to redraw the layout – that would not change
425 client_toggle_fullscreen(conn, new);
432 /* Map the window first to avoid flickering */
433 xcb_map_window(conn, child);
435 client_map(conn, new);
437 if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !new->dock) {
438 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
439 if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) {
440 if (!client_is_floating(new))
441 new->container->currently_focused = new;
442 if (new->container == CUR_CELL || client_is_floating(new))
443 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);