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