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>
34 * Go through all existing windows (if the window manager is restarted) and manage them
37 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
38 xcb_query_tree_reply_t *reply;
40 xcb_window_t *children;
41 xcb_get_window_attributes_cookie_t *cookies;
43 /* Get the tree of windows whose parent is the root window (= all) */
44 if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
47 len = xcb_query_tree_children_length(reply);
48 cookies = smalloc(len * sizeof(*cookies));
50 /* Request the window attributes for every window */
51 children = xcb_query_tree_children(reply);
52 for(i = 0; i < len; ++i)
53 cookies[i] = xcb_get_window_attributes(conn, children[i]);
55 /* Call manage_window with the attributes for every window */
56 for(i = 0; i < len; ++i)
57 manage_window(prophs, conn, children[i], cookies[i], true);
64 * Do some sanity checks and then reparent the window.
67 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
68 xcb_window_t window, xcb_get_window_attributes_cookie_t cookie,
69 bool needs_to_be_mapped) {
70 LOG("managing window.\n");
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) {
86 LOG("Window not mapped, not managing\n");
90 /* Don’t manage clients with the override_redirect flag */
91 if (attr->override_redirect) {
92 LOG("override_redirect set, not managing\n");
96 /* Check if the window is already managed */
97 if (table_get(&by_child, window))
100 /* Get the initial geometry (position, size, …) */
101 if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
104 /* Reparent the window and add it to our list of managed windows */
105 reparent_window(conn, window, attr->visual, geom->root, geom->depth,
106 geom->x, geom->y, geom->width, geom->height);
108 /* Generate callback events for every property we watch */
109 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
110 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
111 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_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[_NET_WM_NAME]);
122 * reparent_window() gets called when a new window was opened and becomes a child of the root
123 * window, or it gets called by us when we manage the already existing windows at startup.
125 * Essentially, this is the point where we take over control.
128 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
129 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
130 int16_t x, int16_t y, uint16_t width, uint16_t height) {
132 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
133 utf8_title_cookie, title_cookie, class_cookie;
136 uint16_t original_height = height;
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 /* Map the window first to avoid flickering */
144 xcb_map_window(conn, child);
146 /* Place requests for properties ASAP */
147 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
148 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
149 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
150 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
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 new client\n");
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 /* 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 LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
195 i3Font *font = load_font(conn, config.font);
196 width = min(width, c_ws->rect.x + c_ws->rect.width);
197 height = min(height, c_ws->rect.y + c_ws->rect.height);
199 Rect framerect = {x, y,
200 width + 2 + 2, /* 2 px border at each side */
201 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
203 /* Yo dawg, I heard you like windows, so I create a window around your window… */
204 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
206 /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t.
207 * Also, xprop(1) needs that to work. */
208 long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
209 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
211 /* Put the client inside the save set. Upon termination (whether killed or normal exit
212 does not matter) of the window manager, these clients will be correctly reparented
213 to their most closest living ancestor (= cleanup) */
214 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
216 /* Generate a graphics context for the titlebar */
217 new->titlegc = xcb_generate_id(conn);
218 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
220 /* Moves the original window into the new frame we've created for it */
221 new->awaiting_useless_unmap = true;
222 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
223 if (xcb_request_check(conn, cookie) != NULL) {
224 LOG("Could not reparent the window, aborting\n");
225 xcb_destroy_window(conn, new->frame);
230 /* Put our data structure (Client) into the table */
231 table_put(&by_parent, new->frame, new);
232 table_put(&by_child, child, new);
234 /* We need to grab the mouse buttons for click to focus */
235 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
236 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
237 1 /* left mouse button */,
238 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
240 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
241 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
242 1 /* left mouse button */, XCB_MOD_MASK_1);
244 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
246 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
247 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
248 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
249 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
250 LOG("Window is a dock.\n");
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 */
258 } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
259 atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
260 atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
261 atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
262 /* Set the dialog window to automatically floating, will be used below */
263 new->floating = FLOATING_AUTO_ON;
264 LOG("dialog/utility/toolbar/splash window, automatically floating\n");
268 if (new->workspace->auto_float) {
269 new->floating = FLOATING_AUTO_ON;
270 LOG("workspace is in autofloat mode, setting floating\n");
274 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
276 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
277 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
278 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
279 of the screen. This is because the only possibility for bars is at to be at the top/bottom
280 with maximum horizontal size.
281 TODO: bars at the top */
282 new->desired_height = strut[3];
283 if (new->desired_height == 0) {
284 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
285 new->desired_height = original_height;
287 LOG("the client wants to be %d pixels high\n", new->desired_height);
289 LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
290 new->desired_height = original_height;
293 /* If it’s not a dock, we can check on which workspace we should put it. */
295 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
296 * top of this function, get them now and pass them to our callback function for window class / title
297 * changes. It is important that the client was already inserted into the by_child table,
298 * because the callbacks won’t work otherwise. */
299 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
300 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
302 preply = xcb_get_property_reply(conn, title_cookie, NULL);
303 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
305 preply = xcb_get_property_reply(conn, class_cookie, NULL);
306 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
308 LOG("DEBUG: should have all infos now\n");
309 struct Assignment *assign;
310 TAILQ_FOREACH(assign, &assignments, assignments) {
311 if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
314 if (assign->floating == ASSIGN_FLOATING_ONLY ||
315 assign->floating == ASSIGN_FLOATING) {
316 new->floating = FLOATING_AUTO_ON;
317 LOG("Assignment matches, putting client into floating mode\n");
318 if (assign->floating == ASSIGN_FLOATING_ONLY)
322 LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
323 assign->windowclass_title, assign->workspace);
325 if (c_ws->screen->current_workspace == (assign->workspace-1)) {
326 LOG("We are already there, no need to do anything\n");
330 LOG("Changin container/workspace and unmapping the client\n");
331 Workspace *t_ws = &(workspaces[assign->workspace-1]);
332 if (t_ws->screen == NULL) {
333 LOG("initializing new workspace, setting num to %d\n", assign->workspace);
334 t_ws->screen = c_ws->screen;
335 /* Copy the dimensions from the virtual screen */
336 memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
339 new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
340 new->workspace = t_ws;
341 old_focused = new->container->currently_focused;
343 xcb_unmap_window(conn, new->frame);
348 if (CUR_CELL->workspace->fullscreen_client != NULL) {
349 if (new->container == CUR_CELL) {
350 /* If we are in fullscreen, we should lower the window to not be annoying */
351 uint32_t values[] = { XCB_STACK_MODE_BELOW };
352 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
354 } else if (!new->dock) {
355 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
356 if (new->container->workspace->fullscreen_client == NULL) {
357 if (!client_is_floating(new))
358 new->container->currently_focused = new;
359 if (new->container == CUR_CELL)
360 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
364 /* Insert into the currently active container, if it’s not a dock window */
365 if (!new->dock && !client_is_floating(new)) {
366 /* Insert after the old active client, if existing. If it does not exist, the
367 container is empty and it does not matter, where we insert it */
368 if (old_focused != NULL && !old_focused->dock)
369 CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
370 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
372 if (new->container->workspace->fullscreen_client != NULL)
373 SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
374 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
376 client_set_below_floating(conn, new);
379 if (client_is_floating(new)) {
380 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
382 /* Add the client to the list of floating clients for its workspace */
383 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
385 new->container = NULL;
387 new->floating_rect.x = new->rect.x = x;
388 new->floating_rect.y = new->rect.y = y;
389 new->rect.width = new->floating_rect.width + 2 + 2;
390 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
391 LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
392 new->floating_rect.x, new->floating_rect.y,
393 new->floating_rect.width, new->floating_rect.height);
394 LOG("outer rect (%d, %d) size (%d, %d)\n",
395 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
397 /* Make sure it is on top of the other windows */
398 xcb_raise_window(conn, new->frame);
399 reposition_client(conn, new);
400 resize_client(conn, new);
401 /* redecorate_window flushes */
402 redecorate_window(conn, new);
405 new->initialized = true;
407 /* Check if the window already got the fullscreen hint set */
409 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
410 (state = xcb_get_property_value(preply)) != NULL)
411 /* Check all set _NET_WM_STATEs */
412 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
413 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
415 /* If the window got the fullscreen state, we just toggle fullscreen
416 and don’t event bother to redraw the layout – that would not change
418 client_toggle_fullscreen(conn, new);