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"
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 * Do some sanity checks and then reparent the window.
70 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
71 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 geomc = xcb_get_geometry(conn, d);
80 /* Check if the window is mapped (it could be not mapped when intializing and
81 calling manage_window() for every window) */
82 if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
83 ELOG("Could not get attributes\n");
87 if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE)
90 /* Don’t manage clients with the override_redirect flag */
91 if (attr->override_redirect)
94 /* Check if the window is already managed */
95 if (table_get(&by_child, window))
98 /* Get the initial geometry (position, size, …) */
99 if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
102 /* Reparent the window and add it to our list of managed windows */
103 reparent_window(conn, window, attr->visual, geom->root, geom->depth,
104 geom->x, geom->y, geom->width, geom->height,
107 /* Generate callback events for every property we watch */
108 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
109 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
110 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
111 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_HINTS);
112 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR);
113 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[WM_CLIENT_LEADER]);
114 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
123 * reparent_window() gets called when a new window was opened and becomes a child of the root
124 * window, or it gets called by us when we manage the already existing windows at startup.
126 * Essentially, this is the point where we take over control.
129 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
130 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
131 int16_t x, int16_t y, uint16_t width, uint16_t height,
132 uint32_t border_width) {
134 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
135 utf8_title_cookie, title_cookie,
136 class_cookie, leader_cookie;
139 uint16_t original_height = height;
140 bool map_frame = true;
142 /* We are interested in property changes */
143 mask = XCB_CW_EVENT_MASK;
144 values[0] = CHILD_EVENT_MASK;
145 xcb_change_window_attributes(conn, child, mask, values);
147 /* Place requests for properties ASAP */
148 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
149 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
150 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
151 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
152 leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX);
153 title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
154 class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
156 Client *new = table_get(&by_child, child);
158 /* Events for already managed windows should already be filtered in manage_window() */
161 LOG("Managing window 0x%08x\n", child);
162 DLOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
163 new = calloc(sizeof(Client), 1);
164 new->force_reconfigure = true;
166 /* Update the data structures */
167 Client *old_focused = CUR_CELL->currently_focused;
169 new->container = CUR_CELL;
170 new->workspace = new->container->workspace;
172 /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
173 width = max(width, 75);
174 height = max(height, 50);
176 new->frame = xcb_generate_id(conn);
178 new->rect.width = width;
179 new->rect.height = height;
180 new->width_increment = 1;
181 new->height_increment = 1;
182 new->border_width = border_width;
183 /* Pre-initialize the values for floating */
184 new->floating_rect.x = -1;
185 new->floating_rect.width = width;
186 new->floating_rect.height = height;
188 if (config.default_border != NULL)
189 client_init_border(conn, new, config.default_border[1]);
193 /* Don’t generate events for our new window, it should *not* be managed */
194 mask |= XCB_CW_OVERRIDE_REDIRECT;
197 /* We want to know when… */
198 mask |= XCB_CW_EVENT_MASK;
199 values[1] = FRAME_EVENT_MASK;
201 i3Font *font = load_font(conn, config.font);
202 width = min(width, c_ws->rect.x + c_ws->rect.width);
203 height = min(height, c_ws->rect.y + c_ws->rect.height);
205 Rect framerect = {x, y,
206 width + 2 + 2, /* 2 px border at each side */
207 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
209 /* Yo dawg, I heard you like windows, so I create a window around your window… */
210 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
212 /* Put the client inside the save set. Upon termination (whether killed or normal exit
213 does not matter) of the window manager, these clients will be correctly reparented
214 to their most closest living ancestor (= cleanup) */
215 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
217 /* Generate a graphics context for the titlebar */
218 new->titlegc = xcb_generate_id(conn);
219 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
221 /* Moves the original window into the new frame we've created for it */
222 new->awaiting_useless_unmap = true;
223 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
224 if (xcb_request_check(conn, cookie) != NULL) {
225 DLOG("Could not reparent the window, aborting\n");
226 xcb_destroy_window(conn, new->frame);
231 /* Put our data structure (Client) into the table */
232 table_put(&by_parent, new->frame, new);
233 table_put(&by_child, child, new);
235 /* We need to grab the mouse buttons for click to focus */
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 1 /* left mouse button */,
239 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
241 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
242 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
243 3 /* right mouse button */,
244 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
246 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
248 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
249 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
250 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
251 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
252 DLOG("Window is a dock.\n");
254 new->borderless = true;
255 new->titlebar_position = TITLEBAR_OFF;
256 new->force_reconfigure = true;
257 new->container = NULL;
258 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
259 /* If it’s a dock we can’t make it float, so we break */
260 new->floating = FLOATING_AUTO_OFF;
262 } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
263 atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
264 atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
265 atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
266 /* Set the dialog window to automatically floating, will be used below */
267 new->floating = FLOATING_AUTO_ON;
268 DLOG("dialog/utility/toolbar/splash window, automatically floating\n");
272 /* All clients which have a leader should be floating */
273 if (!new->dock && !client_is_floating(new) && new->leader != 0) {
274 DLOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
275 new->floating = FLOATING_AUTO_ON;
278 if (new->workspace->auto_float) {
279 new->floating = FLOATING_AUTO_ON;
280 DLOG("workspace is in autofloat mode, setting floating\n");
284 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
286 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
287 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
288 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
289 of the screen. This is because the only possibility for bars is at to be at the top/bottom
290 with maximum horizontal size.
291 TODO: bars at the top */
292 new->desired_height = strut[3];
293 if (new->desired_height == 0) {
294 DLOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
295 new->desired_height = original_height;
297 DLOG("the client wants to be %d pixels high\n", new->desired_height);
299 DLOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
300 new->desired_height = original_height;
303 /* If it’s not a dock, we can check on which workspace we should put it. */
305 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
306 * top of this function, get them now and pass them to our callback function for window class / title
307 * changes. It is important that the client was already inserted into the by_child table,
308 * because the callbacks won’t work otherwise. */
309 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
310 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
312 preply = xcb_get_property_reply(conn, title_cookie, NULL);
313 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
315 preply = xcb_get_property_reply(conn, class_cookie, NULL);
316 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
318 preply = xcb_get_property_reply(conn, leader_cookie, NULL);
319 handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
321 struct Assignment *assign;
322 TAILQ_FOREACH(assign, &assignments, assignments) {
323 if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
326 if (assign->floating == ASSIGN_FLOATING_ONLY ||
327 assign->floating == ASSIGN_FLOATING) {
328 new->floating = FLOATING_AUTO_ON;
329 LOG("Assignment matches, putting client into floating mode\n");
330 if (assign->floating == ASSIGN_FLOATING_ONLY)
334 LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
335 assign->windowclass_title, assign->workspace);
337 if (c_ws->screen->current_workspace->num == (assign->workspace-1)) {
338 DLOG("We are already there, no need to do anything\n");
342 DLOG("Changing container/workspace and unmapping the client\n");
343 Workspace *t_ws = workspace_get(assign->workspace-1);
344 workspace_initialize(t_ws, c_ws->screen, false);
346 new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
347 new->workspace = t_ws;
348 old_focused = new->container->currently_focused;
350 map_frame = workspace_is_visible(t_ws);
355 if (new->workspace->fullscreen_client != NULL) {
356 DLOG("Setting below fullscreen window\n");
358 /* If we are in fullscreen, we should lower the window to not be annoying */
359 uint32_t values[] = { XCB_STACK_MODE_BELOW };
360 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
363 /* Insert into the currently active container, if it’s not a dock window */
364 if (!new->dock && !client_is_floating(new)) {
365 /* Insert after the old active client, if existing. If it does not exist, the
366 container is empty and it does not matter, where we insert it */
367 if (old_focused != NULL && !old_focused->dock)
368 CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
369 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
371 if (new->container->workspace->fullscreen_client != NULL)
372 SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
373 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
375 client_set_below_floating(conn, new);
378 if (client_is_floating(new)) {
379 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
381 /* Add the client to the list of floating clients for its workspace */
382 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
384 new->container = NULL;
386 new->rect.width = new->floating_rect.width + 2 + 2;
387 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
389 /* Some clients (like GIMP’s color picker window) get mapped
390 * to (0, 0), so we push them to a reasonable position
391 * (centered over their leader) */
392 if (new->leader != 0 && x == 0 && y == 0) {
393 DLOG("Floating client wants to (0x0), moving it over its leader instead\n");
394 Client *leader = table_get(&by_child, new->leader);
395 if (leader == NULL) {
396 DLOG("leader is NULL, centering it over current workspace\n");
398 x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
399 y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
401 x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
402 y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
405 new->floating_rect.x = new->rect.x = x;
406 new->floating_rect.y = new->rect.y = y;
407 DLOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
408 new->floating_rect.x, new->floating_rect.y,
409 new->floating_rect.width, new->floating_rect.height);
410 DLOG("outer rect (%d, %d) size (%d, %d)\n",
411 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
413 /* Make sure it is on top of the other windows */
414 xcb_raise_window(conn, new->frame);
415 reposition_client(conn, new);
416 resize_client(conn, new);
417 /* redecorate_window flushes */
418 redecorate_window(conn, new);
421 new->initialized = true;
423 /* Check if the window already got the fullscreen hint set */
425 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
426 (state = xcb_get_property_value(preply)) != NULL)
427 /* Check all set _NET_WM_STATEs */
428 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
429 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
431 /* If the window got the fullscreen state, we just toggle fullscreen
432 and don’t event bother to redraw the layout – that would not change
434 client_toggle_fullscreen(conn, new);
441 /* Map the window first to avoid flickering */
442 xcb_map_window(conn, child);
444 client_map(conn, new);
446 if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !new->dock) {
447 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
448 if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) {
449 if (!client_is_floating(new)) {
450 new->container->currently_focused = new;
452 render_container(conn, new->container);
454 if (new->container == CUR_CELL || client_is_floating(new)) {
455 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
456 ewmh_update_active_window(new->child);