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,
105 /* Generate callback events for every property we watch */
106 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
107 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
108 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
109 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_HINTS);
110 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR);
111 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[WM_CLIENT_LEADER]);
112 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
121 * reparent_window() gets called when a new window was opened and becomes a child of the root
122 * window, or it gets called by us when we manage the already existing windows at startup.
124 * Essentially, this is the point where we take over control.
127 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
128 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
129 int16_t x, int16_t y, uint16_t width, uint16_t height,
130 uint32_t border_width) {
132 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
133 utf8_title_cookie, title_cookie,
134 class_cookie, leader_cookie;
137 uint16_t original_height = height;
138 bool map_frame = true;
140 /* We are interested in property changes */
141 mask = XCB_CW_EVENT_MASK;
142 values[0] = CHILD_EVENT_MASK;
143 xcb_change_window_attributes(conn, child, mask, values);
145 /* Place requests for properties ASAP */
146 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
147 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
148 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
149 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
150 leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX);
151 title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
152 class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
154 Client *new = table_get(&by_child, child);
156 /* Events for already managed windows should already be filtered in manage_window() */
159 LOG("Reparenting window 0x%08x\n", child);
160 LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
161 new = calloc(sizeof(Client), 1);
162 new->force_reconfigure = true;
164 /* Update the data structures */
165 Client *old_focused = CUR_CELL->currently_focused;
167 new->container = CUR_CELL;
168 new->workspace = new->container->workspace;
170 /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
171 width = max(width, 75);
172 height = max(height, 50);
174 new->frame = xcb_generate_id(conn);
176 new->rect.width = width;
177 new->rect.height = height;
178 new->width_increment = 1;
179 new->height_increment = 1;
180 new->border_width = border_width;
181 /* Pre-initialize the values for floating */
182 new->floating_rect.x = -1;
183 new->floating_rect.width = width;
184 new->floating_rect.height = height;
188 /* Don’t generate events for our new window, it should *not* be managed */
189 mask |= XCB_CW_OVERRIDE_REDIRECT;
192 /* We want to know when… */
193 mask |= XCB_CW_EVENT_MASK;
194 values[1] = FRAME_EVENT_MASK;
196 i3Font *font = load_font(conn, config.font);
197 width = min(width, c_ws->rect.x + c_ws->rect.width);
198 height = min(height, c_ws->rect.y + c_ws->rect.height);
200 Rect framerect = {x, y,
201 width + 2 + 2, /* 2 px border at each side */
202 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
204 /* Yo dawg, I heard you like windows, so I create a window around your window… */
205 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
207 /* Put the client inside the save set. Upon termination (whether killed or normal exit
208 does not matter) of the window manager, these clients will be correctly reparented
209 to their most closest living ancestor (= cleanup) */
210 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
212 /* Generate a graphics context for the titlebar */
213 new->titlegc = xcb_generate_id(conn);
214 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
216 /* Moves the original window into the new frame we've created for it */
217 new->awaiting_useless_unmap = true;
218 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
219 if (xcb_request_check(conn, cookie) != NULL) {
220 LOG("Could not reparent the window, aborting\n");
221 xcb_destroy_window(conn, new->frame);
226 /* Put our data structure (Client) into the table */
227 table_put(&by_parent, new->frame, new);
228 table_put(&by_child, child, new);
230 /* We need to grab the mouse buttons for click to focus */
231 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
232 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
233 1 /* left mouse button */,
234 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
236 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
237 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
238 3 /* right mouse button */,
239 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
241 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
243 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
244 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
245 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
246 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
247 LOG("Window is a dock.\n");
249 new->borderless = true;
250 new->titlebar_position = TITLEBAR_OFF;
251 new->force_reconfigure = true;
252 new->container = NULL;
253 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
254 /* If it’s a dock we can’t make it float, so we break */
255 new->floating = FLOATING_AUTO_OFF;
257 } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
258 atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
259 atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
260 atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
261 /* Set the dialog window to automatically floating, will be used below */
262 new->floating = FLOATING_AUTO_ON;
263 LOG("dialog/utility/toolbar/splash window, automatically floating\n");
267 /* All clients which have a leader should be floating */
268 if (!new->dock && !client_is_floating(new) && new->leader != 0) {
269 LOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
270 new->floating = FLOATING_AUTO_ON;
273 if (new->workspace->auto_float) {
274 new->floating = FLOATING_AUTO_ON;
275 LOG("workspace is in autofloat mode, setting floating\n");
279 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
281 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
282 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
283 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
284 of the screen. This is because the only possibility for bars is at to be at the top/bottom
285 with maximum horizontal size.
286 TODO: bars at the top */
287 new->desired_height = strut[3];
288 if (new->desired_height == 0) {
289 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
290 new->desired_height = original_height;
292 LOG("the client wants to be %d pixels high\n", new->desired_height);
294 LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
295 new->desired_height = original_height;
298 /* If it’s not a dock, we can check on which workspace we should put it. */
300 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
301 * top of this function, get them now and pass them to our callback function for window class / title
302 * changes. It is important that the client was already inserted into the by_child table,
303 * because the callbacks won’t work otherwise. */
304 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
305 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
307 preply = xcb_get_property_reply(conn, title_cookie, NULL);
308 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
310 preply = xcb_get_property_reply(conn, class_cookie, NULL);
311 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
313 preply = xcb_get_property_reply(conn, leader_cookie, NULL);
314 handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
316 struct Assignment *assign;
317 TAILQ_FOREACH(assign, &assignments, assignments) {
318 if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
321 if (assign->floating == ASSIGN_FLOATING_ONLY ||
322 assign->floating == ASSIGN_FLOATING) {
323 new->floating = FLOATING_AUTO_ON;
324 LOG("Assignment matches, putting client into floating mode\n");
325 if (assign->floating == ASSIGN_FLOATING_ONLY)
329 LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
330 assign->windowclass_title, assign->workspace);
332 if (c_ws->screen->current_workspace == (assign->workspace-1)) {
333 LOG("We are already there, no need to do anything\n");
337 LOG("Changing container/workspace and unmapping the client\n");
338 Workspace *t_ws = workspace_get(assign->workspace-1);
339 workspace_initialize(t_ws, c_ws->screen);
341 new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
342 new->workspace = t_ws;
343 old_focused = new->container->currently_focused;
345 map_frame = workspace_is_visible(t_ws);
350 if (CUR_CELL->workspace->fullscreen_client != NULL) {
351 if (new->container == CUR_CELL) {
352 /* If we are in fullscreen, we should lower the window to not be annoying */
353 uint32_t values[] = { XCB_STACK_MODE_BELOW };
354 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
358 /* Insert into the currently active container, if it’s not a dock window */
359 if (!new->dock && !client_is_floating(new)) {
360 /* Insert after the old active client, if existing. If it does not exist, the
361 container is empty and it does not matter, where we insert it */
362 if (old_focused != NULL && !old_focused->dock)
363 CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
364 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
366 if (new->container->workspace->fullscreen_client != NULL)
367 SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
368 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
370 client_set_below_floating(conn, new);
373 if (client_is_floating(new)) {
374 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
376 /* Add the client to the list of floating clients for its workspace */
377 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
379 new->container = NULL;
381 new->rect.width = new->floating_rect.width + 2 + 2;
382 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
384 /* Some clients (like GIMP’s color picker window) get mapped
385 * to (0, 0), so we push them to a reasonable position
386 * (centered over their leader) */
387 if (new->leader != 0 && x == 0 && y == 0) {
388 LOG("Floating client wants to (0x0), moving it over its leader instead\n");
389 Client *leader = table_get(&by_child, new->leader);
390 if (leader == NULL) {
391 LOG("leader is NULL, centering it over current workspace\n");
393 x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
394 y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
396 x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
397 y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
400 new->floating_rect.x = new->rect.x = x;
401 new->floating_rect.y = new->rect.y = y;
402 LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
403 new->floating_rect.x, new->floating_rect.y,
404 new->floating_rect.width, new->floating_rect.height);
405 LOG("outer rect (%d, %d) size (%d, %d)\n",
406 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
408 /* Make sure it is on top of the other windows */
409 xcb_raise_window(conn, new->frame);
410 reposition_client(conn, new);
411 resize_client(conn, new);
412 /* redecorate_window flushes */
413 redecorate_window(conn, new);
416 new->initialized = true;
418 /* Check if the window already got the fullscreen hint set */
420 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
421 (state = xcb_get_property_value(preply)) != NULL)
422 /* Check all set _NET_WM_STATEs */
423 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
424 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
426 /* If the window got the fullscreen state, we just toggle fullscreen
427 and don’t event bother to redraw the layout – that would not change
429 client_toggle_fullscreen(conn, new);
436 /* Map the window first to avoid flickering */
437 xcb_map_window(conn, child);
439 client_map(conn, new);
441 if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !new->dock) {
442 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
443 if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) {
444 if (!client_is_floating(new))
445 new->container->currently_focused = new;
446 if (new->container == CUR_CELL || client_is_floating(new))
447 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);