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;
137 bool map_frame = true;
139 /* We are interested in property changes */
140 mask = XCB_CW_EVENT_MASK;
141 values[0] = CHILD_EVENT_MASK;
142 xcb_change_window_attributes(conn, child, mask, values);
144 /* Place requests for properties ASAP */
145 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
146 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
147 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
148 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
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 new client\n");
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 /* Pre-initialize the values for floating */
177 new->floating_rect.x = -1;
178 new->floating_rect.width = width;
179 new->floating_rect.height = height;
183 /* Don’t generate events for our new window, it should *not* be managed */
184 mask |= XCB_CW_OVERRIDE_REDIRECT;
187 /* We want to know when… */
188 mask |= XCB_CW_EVENT_MASK;
189 values[1] = FRAME_EVENT_MASK;
191 LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
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 /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t.
205 * Also, xprop(1) needs that to work. */
206 long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
207 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
209 /* Put the client inside the save set. Upon termination (whether killed or normal exit
210 does not matter) of the window manager, these clients will be correctly reparented
211 to their most closest living ancestor (= cleanup) */
212 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
214 /* Generate a graphics context for the titlebar */
215 new->titlegc = xcb_generate_id(conn);
216 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
218 /* Moves the original window into the new frame we've created for it */
219 new->awaiting_useless_unmap = true;
220 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
221 if (xcb_request_check(conn, cookie) != NULL) {
222 LOG("Could not reparent the window, aborting\n");
223 xcb_destroy_window(conn, new->frame);
228 /* Put our data structure (Client) into the table */
229 table_put(&by_parent, new->frame, new);
230 table_put(&by_child, child, new);
232 /* We need to grab the mouse buttons for click to focus */
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 1 /* left mouse button */,
236 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
238 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
239 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
240 1 /* left mouse button */, XCB_MOD_MASK_1);
242 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
244 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
245 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
246 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
247 if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
248 LOG("Window is a dock.\n");
250 new->titlebar_position = TITLEBAR_OFF;
251 new->force_reconfigure = true;
252 new->container = NULL;
253 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
254 /* If it’s a dock we can’t make it float, so we break */
256 } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
257 atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
258 atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
259 atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
260 /* Set the dialog window to automatically floating, will be used below */
261 new->floating = FLOATING_AUTO_ON;
262 LOG("dialog/utility/toolbar/splash window, automatically floating\n");
266 if (new->workspace->auto_float) {
267 new->floating = FLOATING_AUTO_ON;
268 LOG("workspace is in autofloat mode, setting floating\n");
272 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
274 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
275 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
276 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
277 of the screen. This is because the only possibility for bars is at to be at the top/bottom
278 with maximum horizontal size.
279 TODO: bars at the top */
280 new->desired_height = strut[3];
281 if (new->desired_height == 0) {
282 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
283 new->desired_height = original_height;
285 LOG("the client wants to be %d pixels high\n", new->desired_height);
287 LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
288 new->desired_height = original_height;
291 /* If it’s not a dock, we can check on which workspace we should put it. */
293 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
294 * top of this function, get them now and pass them to our callback function for window class / title
295 * changes. It is important that the client was already inserted into the by_child table,
296 * because the callbacks won’t work otherwise. */
297 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
298 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
300 preply = xcb_get_property_reply(conn, title_cookie, NULL);
301 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
303 preply = xcb_get_property_reply(conn, class_cookie, NULL);
304 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
306 LOG("DEBUG: should have all infos now\n");
307 struct Assignment *assign;
308 TAILQ_FOREACH(assign, &assignments, assignments) {
309 if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
312 if (assign->floating == ASSIGN_FLOATING_ONLY ||
313 assign->floating == ASSIGN_FLOATING) {
314 new->floating = FLOATING_AUTO_ON;
315 LOG("Assignment matches, putting client into floating mode\n");
316 if (assign->floating == ASSIGN_FLOATING_ONLY)
320 LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
321 assign->windowclass_title, assign->workspace);
323 if (c_ws->screen->current_workspace == (assign->workspace-1)) {
324 LOG("We are already there, no need to do anything\n");
328 LOG("Changin container/workspace and unmapping the client\n");
329 Workspace *t_ws = &(workspaces[assign->workspace-1]);
330 if (t_ws->screen == NULL) {
331 LOG("initializing new workspace, setting num to %d\n", assign->workspace);
332 t_ws->screen = c_ws->screen;
333 /* Copy the dimensions from the virtual screen */
334 memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
337 new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
338 new->workspace = t_ws;
339 old_focused = new->container->currently_focused;
346 if (CUR_CELL->workspace->fullscreen_client != NULL) {
347 if (new->container == CUR_CELL) {
348 /* If we are in fullscreen, we should lower the window to not be annoying */
349 uint32_t values[] = { XCB_STACK_MODE_BELOW };
350 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
354 /* Insert into the currently active container, if it’s not a dock window */
355 if (!new->dock && !client_is_floating(new)) {
356 /* Insert after the old active client, if existing. If it does not exist, the
357 container is empty and it does not matter, where we insert it */
358 if (old_focused != NULL && !old_focused->dock)
359 CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
360 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
362 if (new->container->workspace->fullscreen_client != NULL)
363 SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
364 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
366 client_set_below_floating(conn, new);
369 if (client_is_floating(new)) {
370 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
372 /* Add the client to the list of floating clients for its workspace */
373 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
375 new->container = NULL;
377 new->floating_rect.x = new->rect.x = x;
378 new->floating_rect.y = new->rect.y = y;
379 new->rect.width = new->floating_rect.width + 2 + 2;
380 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
381 LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
382 new->floating_rect.x, new->floating_rect.y,
383 new->floating_rect.width, new->floating_rect.height);
384 LOG("outer rect (%d, %d) size (%d, %d)\n",
385 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
387 /* Make sure it is on top of the other windows */
388 xcb_raise_window(conn, new->frame);
389 reposition_client(conn, new);
390 resize_client(conn, new);
391 /* redecorate_window flushes */
392 redecorate_window(conn, new);
395 new->initialized = true;
397 /* Check if the window already got the fullscreen hint set */
399 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
400 (state = xcb_get_property_value(preply)) != NULL)
401 /* Check all set _NET_WM_STATEs */
402 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
403 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
405 /* If the window got the fullscreen state, we just toggle fullscreen
406 and don’t event bother to redraw the layout – that would not change
408 client_toggle_fullscreen(conn, new);
414 /* Map the window first to avoid flickering */
415 xcb_map_window(conn, child);
417 xcb_map_window(conn, new->frame);
418 if (CUR_CELL->workspace->fullscreen_client == NULL && !new->dock) {
419 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
420 if (new->workspace->fullscreen_client == NULL) {
421 if (!client_is_floating(new))
422 new->container->currently_focused = new;
423 if (new->container == CUR_CELL || client_is_floating(new))
424 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);