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 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
58 manage_window(prophs, conn, children[i], wa);
66 * Do some sanity checks and then reparent the window.
69 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
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 if (wa.tag == TAG_COOKIE) {
77 /* Check if the window is mapped (it could be not mapped when intializing and
78 calling manage_window() for every window) */
79 if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
82 if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
86 wa.u.override_redirect = attr->override_redirect;
89 /* Don’t manage clients with the override_redirect flag */
90 if (wa.u.override_redirect)
93 /* Check if the window is already managed */
94 if (table_get(&by_child, window))
97 /* Get the initial geometry (position, size, …) */
98 geomc = xcb_get_geometry(conn, d);
101 wa.u.cookie = xcb_get_window_attributes(conn, window);
102 if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
105 if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
108 /* Reparent the window and add it to our list of managed windows */
109 reparent_window(conn, window, attr->visual, geom->root, geom->depth,
110 geom->x, geom->y, geom->width, geom->height);
112 /* Generate callback events for every property we watch */
113 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
114 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
115 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
116 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR);
117 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
126 * reparent_window() gets called when a new window was opened and becomes a child of the root
127 * window, or it gets called by us when we manage the already existing windows at startup.
129 * Essentially, this is the point where we take over control.
132 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
133 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
134 int16_t x, int16_t y, uint16_t width, uint16_t height) {
136 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
137 utf8_title_cookie, title_cookie, class_cookie;
140 uint16_t original_height = height;
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 /* Map the window first to avoid flickering */
148 xcb_map_window(conn, child);
150 /* Place requests for properties ASAP */
151 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
152 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
153 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
154 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
155 title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
156 class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
158 Client *new = table_get(&by_child, child);
160 /* Events for already managed windows should already be filtered in manage_window() */
163 LOG("reparenting new client\n");
164 LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
165 new = calloc(sizeof(Client), 1);
166 new->force_reconfigure = true;
168 /* Update the data structures */
169 Client *old_focused = CUR_CELL->currently_focused;
171 new->container = CUR_CELL;
172 new->workspace = new->container->workspace;
174 /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
175 width = max(width, 75);
176 height = max(height, 50);
178 new->frame = xcb_generate_id(conn);
180 new->rect.width = width;
181 new->rect.height = height;
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, mask, values);
210 /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t.
211 * Also, xprop(1) needs that to work. */
212 long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
213 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
215 /* Put the client inside the save set. Upon termination (whether killed or normal exit
216 does not matter) of the window manager, these clients will be correctly reparented
217 to their most closest living ancestor (= cleanup) */
218 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
220 /* Generate a graphics context for the titlebar */
221 new->titlegc = xcb_generate_id(conn);
222 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
224 /* Moves the original window into the new frame we've created for it */
225 new->awaiting_useless_unmap = true;
226 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
227 if (xcb_request_check(conn, cookie) != NULL) {
228 LOG("Could not reparent the window, aborting\n");
229 xcb_destroy_window(conn, new->frame);
234 /* Put our data structure (Client) into the table */
235 table_put(&by_parent, new->frame, new);
236 table_put(&by_child, child, new);
238 /* We need to grab the mouse buttons for click to focus */
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 */,
242 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
244 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
245 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
246 1 /* left mouse button */, XCB_MOD_MASK_1);
248 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
250 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
251 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
252 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
253 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
254 LOG("Window is a dock.\n");
256 new->titlebar_position = TITLEBAR_OFF;
257 new->force_reconfigure = true;
258 new->container = NULL;
259 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
260 /* If it’s a dock we can’t make it float, so we break */
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 LOG("dialog window, automatically floating\n");
273 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
275 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
276 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
277 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
278 of the screen. This is because the only possibility for bars is at to be at the top/bottom
279 with maximum horizontal size.
280 TODO: bars at the top */
281 new->desired_height = strut[3];
282 if (new->desired_height == 0) {
283 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
284 new->desired_height = original_height;
286 LOG("the client wants to be %d pixels high\n", new->desired_height);
288 LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
289 new->desired_height = original_height;
292 /* If it’s not a dock, we can check on which workspace we should put it. */
294 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
295 * top of this function, get them now and pass them to our callback function for window class / title
296 * changes. It is important that the client was already inserted into the by_child table,
297 * because the callbacks won’t work otherwise. */
298 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
299 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
301 preply = xcb_get_property_reply(conn, title_cookie, NULL);
302 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
304 preply = xcb_get_property_reply(conn, class_cookie, NULL);
305 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
307 LOG("DEBUG: should have all infos now\n");
308 struct Assignment *assign;
309 TAILQ_FOREACH(assign, &assignments, assignments) {
310 if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
313 LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
314 assign->windowclass_title, assign->workspace);
316 if (c_ws->screen->current_workspace == (assign->workspace-1)) {
317 LOG("We are already there, no need to do anything\n");
321 LOG("Changin container/workspace and unmapping the client\n");
322 Workspace *t_ws = &(workspaces[assign->workspace-1]);
323 if (t_ws->screen == NULL) {
324 LOG("initializing new workspace, setting num to %d\n", assign->workspace);
325 t_ws->screen = c_ws->screen;
326 /* Copy the dimensions from the virtual screen */
327 memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
330 new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
331 new->workspace = t_ws;
332 old_focused = new->container->currently_focused;
334 xcb_unmap_window(conn, new->frame);
339 if (CUR_CELL->workspace->fullscreen_client != NULL) {
340 if (new->container == CUR_CELL) {
341 /* If we are in fullscreen, we should lower the window to not be annoying */
342 uint32_t values[] = { XCB_STACK_MODE_BELOW };
343 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
345 } else if (!new->dock) {
346 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
347 if (new->container->workspace->fullscreen_client == NULL) {
348 if (new->floating <= FLOATING_USER_OFF)
349 new->container->currently_focused = new;
350 if (new->container == CUR_CELL)
351 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
355 /* Insert into the currently active container, if it’s not a dock window */
356 if (!new->dock && new->floating <= FLOATING_USER_OFF) {
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 SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
365 /* Ensure that it is below all floating clients */
366 Client *first_floating;
367 SLIST_FOREACH(first_floating, &(new->container->workspace->focus_stack), focus_clients)
368 if (first_floating->floating >= FLOATING_AUTO_ON)
371 if (first_floating != SLIST_END(&(new->container->workspace->focus_stack))) {
372 LOG("Setting below floating\n");
373 uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
374 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
378 if (new->floating >= FLOATING_AUTO_ON) {
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->floating_rect.x = new->rect.x;
387 new->floating_rect.y = new->rect.y;
388 LOG("copying size from tiling (%d, %d) size (%d, %d)\n",
389 new->floating_rect.x, new->floating_rect.y,
390 new->floating_rect.width, new->floating_rect.height);
392 /* Make sure it is on top of the other windows */
393 xcb_raise_window(conn, new->frame);
396 new->initialized = true;
398 /* Check if the window already got the fullscreen hint set */
400 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
401 (state = xcb_get_property_value(preply)) != NULL)
402 /* Check all set _NET_WM_STATEs */
403 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
404 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
406 /* If the window got the fullscreen state, we just toggle fullscreen
407 and don’t event bother to redraw the layout – that would not change
409 client_toggle_fullscreen(conn, new);