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 LOG("managing window.\n");
72 xcb_drawable_t d = { window };
73 xcb_get_geometry_cookie_t geomc;
74 xcb_get_geometry_reply_t *geom;
75 xcb_get_window_attributes_reply_t *attr = 0;
77 geomc = xcb_get_geometry(conn, d);
79 /* Check if the window is mapped (it could be not mapped when intializing and
80 calling manage_window() for every window) */
81 if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
82 LOG("Could not get attributes\n");
86 if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) {
87 LOG("Window not mapped, not managing\n");
91 /* Don’t manage clients with the override_redirect flag */
92 if (attr->override_redirect) {
93 LOG("override_redirect set, not managing\n");
97 /* Check if the window is already managed */
98 if (table_get(&by_child, window))
101 /* Get the initial geometry (position, size, …) */
102 if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
105 /* Reparent the window and add it to our list of managed windows */
106 reparent_window(conn, window, attr->visual, geom->root, geom->depth,
107 geom->x, geom->y, geom->width, geom->height);
109 /* Generate callback events for every property we watch */
110 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
111 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
112 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
113 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR);
114 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[WM_CLIENT_LEADER]);
115 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
124 * reparent_window() gets called when a new window was opened and becomes a child of the root
125 * window, or it gets called by us when we manage the already existing windows at startup.
127 * Essentially, this is the point where we take over control.
130 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
131 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
132 int16_t x, int16_t y, uint16_t width, uint16_t height) {
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("reparenting new client\n");
162 LOG("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 /* Pre-initialize the values for floating */
183 new->floating_rect.x = -1;
184 new->floating_rect.width = width;
185 new->floating_rect.height = height;
189 /* Don’t generate events for our new window, it should *not* be managed */
190 mask |= XCB_CW_OVERRIDE_REDIRECT;
193 /* We want to know when… */
194 mask |= XCB_CW_EVENT_MASK;
195 values[1] = FRAME_EVENT_MASK;
197 LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
199 i3Font *font = load_font(conn, config.font);
200 width = min(width, c_ws->rect.x + c_ws->rect.width);
201 height = min(height, c_ws->rect.y + c_ws->rect.height);
203 Rect framerect = {x, y,
204 width + 2 + 2, /* 2 px border at each side */
205 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
207 /* Yo dawg, I heard you like windows, so I create a window around your window… */
208 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
210 /* Put the client inside the save set. Upon termination (whether killed or normal exit
211 does not matter) of the window manager, these clients will be correctly reparented
212 to their most closest living ancestor (= cleanup) */
213 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
215 /* Generate a graphics context for the titlebar */
216 new->titlegc = xcb_generate_id(conn);
217 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
219 /* Moves the original window into the new frame we've created for it */
220 new->awaiting_useless_unmap = true;
221 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
222 if (xcb_request_check(conn, cookie) != NULL) {
223 LOG("Could not reparent the window, aborting\n");
224 xcb_destroy_window(conn, new->frame);
229 /* Put our data structure (Client) into the table */
230 table_put(&by_parent, new->frame, new);
231 table_put(&by_child, child, new);
233 /* We need to grab the mouse buttons for click to focus */
234 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
235 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
236 1 /* left mouse button */,
237 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
239 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
240 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
241 1 /* left mouse button */, XCB_MOD_MASK_1);
243 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
245 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
246 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
247 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
248 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
249 LOG("Window is a dock.\n");
251 new->borderless = true;
252 new->titlebar_position = TITLEBAR_OFF;
253 new->force_reconfigure = true;
254 new->container = NULL;
255 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
256 /* If it’s a dock we can’t make it float, so we break */
257 new->floating = FLOATING_AUTO_OFF;
259 } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
260 atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
261 atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
262 atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
263 /* Set the dialog window to automatically floating, will be used below */
264 new->floating = FLOATING_AUTO_ON;
265 LOG("dialog/utility/toolbar/splash window, automatically floating\n");
269 /* All clients which have a leader should be floating */
270 if (!new->dock && !client_is_floating(new) && new->leader != 0) {
271 LOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
272 new->floating = FLOATING_AUTO_ON;
275 if (new->workspace->auto_float) {
276 new->floating = FLOATING_AUTO_ON;
277 LOG("workspace is in autofloat mode, setting floating\n");
281 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
283 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
284 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
285 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
286 of the screen. This is because the only possibility for bars is at to be at the top/bottom
287 with maximum horizontal size.
288 TODO: bars at the top */
289 new->desired_height = strut[3];
290 if (new->desired_height == 0) {
291 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
292 new->desired_height = original_height;
294 LOG("the client wants to be %d pixels high\n", new->desired_height);
296 LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
297 new->desired_height = original_height;
300 /* If it’s not a dock, we can check on which workspace we should put it. */
302 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
303 * top of this function, get them now and pass them to our callback function for window class / title
304 * changes. It is important that the client was already inserted into the by_child table,
305 * because the callbacks won’t work otherwise. */
306 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
307 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
309 preply = xcb_get_property_reply(conn, title_cookie, NULL);
310 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
312 preply = xcb_get_property_reply(conn, class_cookie, NULL);
313 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
315 preply = xcb_get_property_reply(conn, leader_cookie, NULL);
316 handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
318 LOG("DEBUG: should have all infos now\n");
319 struct Assignment *assign;
320 TAILQ_FOREACH(assign, &assignments, assignments) {
321 if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
324 if (assign->floating == ASSIGN_FLOATING_ONLY ||
325 assign->floating == ASSIGN_FLOATING) {
326 new->floating = FLOATING_AUTO_ON;
327 LOG("Assignment matches, putting client into floating mode\n");
328 if (assign->floating == ASSIGN_FLOATING_ONLY)
332 LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
333 assign->windowclass_title, assign->workspace);
335 if (c_ws->screen->current_workspace == (assign->workspace-1)) {
336 LOG("We are already there, no need to do anything\n");
340 LOG("Changing container/workspace and unmapping the client\n");
341 Workspace *t_ws = &(workspaces[assign->workspace-1]);
342 workspace_initialize(t_ws, c_ws->screen);
344 new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
345 new->workspace = t_ws;
346 old_focused = new->container->currently_focused;
348 map_frame = workspace_is_visible(t_ws);
353 if (CUR_CELL->workspace->fullscreen_client != NULL) {
354 if (new->container == CUR_CELL) {
355 /* If we are in fullscreen, we should lower the window to not be annoying */
356 uint32_t values[] = { XCB_STACK_MODE_BELOW };
357 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
361 /* Insert into the currently active container, if it’s not a dock window */
362 if (!new->dock && !client_is_floating(new)) {
363 /* Insert after the old active client, if existing. If it does not exist, the
364 container is empty and it does not matter, where we insert it */
365 if (old_focused != NULL && !old_focused->dock)
366 CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
367 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
369 if (new->container->workspace->fullscreen_client != NULL)
370 SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
371 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
373 client_set_below_floating(conn, new);
376 if (client_is_floating(new)) {
377 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
379 /* Add the client to the list of floating clients for its workspace */
380 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
382 new->container = NULL;
384 new->rect.width = new->floating_rect.width + 2 + 2;
385 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
387 /* Some clients (like GIMP’s color picker window) get mapped
388 * to (0, 0), so we push them to a reasonable position
389 * (centered over their leader) */
390 if (new->leader != 0 && x == 0 && y == 0) {
391 LOG("Floating client wants to (0x0), moving it over its leader instead\n");
392 Client *leader = table_get(&by_child, new->leader);
393 if (leader == NULL) {
394 LOG("leader is NULL, centering it over current workspace\n");
396 x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
397 y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
399 x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
400 y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
403 new->floating_rect.x = new->rect.x = x;
404 new->floating_rect.y = new->rect.y = y;
405 LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
406 new->floating_rect.x, new->floating_rect.y,
407 new->floating_rect.width, new->floating_rect.height);
408 LOG("outer rect (%d, %d) size (%d, %d)\n",
409 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
411 /* Make sure it is on top of the other windows */
412 xcb_raise_window(conn, new->frame);
413 reposition_client(conn, new);
414 resize_client(conn, new);
415 /* redecorate_window flushes */
416 redecorate_window(conn, new);
419 new->initialized = true;
421 /* Check if the window already got the fullscreen hint set */
423 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
424 (state = xcb_get_property_value(preply)) != NULL)
425 /* Check all set _NET_WM_STATEs */
426 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
427 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
429 /* If the window got the fullscreen state, we just toggle fullscreen
430 and don’t event bother to redraw the layout – that would not change
432 client_toggle_fullscreen(conn, new);
438 /* Map the window first to avoid flickering */
439 xcb_map_window(conn, child);
441 LOG("Mapping client\n");
442 client_map(conn, new);
444 if (CUR_CELL->workspace->fullscreen_client == NULL && !new->dock) {
445 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
446 if (new->workspace->fullscreen_client == NULL) {
447 if (!client_is_floating(new))
448 new->container->currently_focused = new;
449 if (new->container == CUR_CELL || client_is_floating(new))
450 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);