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, atoms[_NET_WM_NAME]);
125 * reparent_window() gets called when a new window was opened and becomes a child of the root
126 * window, or it gets called by us when we manage the already existing windows at startup.
128 * Essentially, this is the point where we take over control.
131 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
132 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
133 int16_t x, int16_t y, uint16_t width, uint16_t height) {
135 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
136 utf8_title_cookie, title_cookie, class_cookie;
140 /* We are interested in property changes */
141 mask = XCB_CW_EVENT_MASK;
142 values[0] = CHILD_EVENT_MASK;
143 xcb_change_window_attributes(conn, child, mask, values);
145 /* Map the window first to avoid flickering */
146 xcb_map_window(conn, child);
148 /* Place requests for properties ASAP */
149 wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
150 strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
151 state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
152 utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
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 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, 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 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
240 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
241 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
242 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
243 if (atom[i] != atoms[_NET_WM_WINDOW_TYPE_DOCK])
245 LOG("Window is a dock.\n");
247 new->titlebar_position = TITLEBAR_OFF;
248 new->force_reconfigure = true;
249 new->container = NULL;
250 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
255 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
257 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
258 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
259 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
260 of the screen. This is because the only possibility for bars is at to be at the top/bottom
261 with maximum horizontal size.
262 TODO: bars at the top */
263 new->desired_height = strut[3];
264 if (new->desired_height == 0) {
265 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height);
266 new->desired_height = height;
268 LOG("the client wants to be %d pixels high\n", new->desired_height);
270 LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
271 new->desired_height = height;
274 /* If it’s not a dock, we can check on which workspace we should put it. */
276 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
277 * top of this function, get them now and pass them to our callback function for window class / title
278 * changes. It is important that the client was already inserted into the by_child table,
279 * because the callbacks won’t work otherwise. */
280 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
281 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
283 preply = xcb_get_property_reply(conn, title_cookie, NULL);
284 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
286 preply = xcb_get_property_reply(conn, class_cookie, NULL);
287 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
289 LOG("DEBUG: should have all infos now\n");
290 struct Assignment *assign;
291 TAILQ_FOREACH(assign, &assignments, assignments) {
292 if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
295 LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
296 assign->windowclass_title, assign->workspace);
298 if (c_ws->screen->current_workspace == (assign->workspace-1)) {
299 LOG("We are already there, no need to do anything\n");
303 LOG("Changin container/workspace and unmapping the client\n");
304 Workspace *t_ws = &(workspaces[assign->workspace-1]);
305 if (t_ws->screen == NULL) {
306 LOG("initializing new workspace, setting num to %d\n", assign->workspace);
307 t_ws->screen = c_ws->screen;
308 /* Copy the dimensions from the virtual screen */
309 memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
312 new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
313 new->workspace = t_ws;
314 xcb_unmap_window(conn, new->frame);
319 if (CUR_CELL->workspace->fullscreen_client != NULL) {
320 if (new->container == CUR_CELL) {
321 /* If we are in fullscreen, we should lower the window to not be annoying */
322 uint32_t values[] = { XCB_STACK_MODE_BELOW };
323 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
325 } else if (!new->dock) {
326 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
327 if (new->container->workspace->fullscreen_client == NULL) {
328 new->container->currently_focused = new;
329 if (new->container == CUR_CELL)
330 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
334 /* Insert into the currently active container, if it’s not a dock window */
336 /* Insert after the old active client, if existing. If it does not exist, the
337 container is empty and it does not matter, where we insert it */
338 if (old_focused != NULL && !old_focused->dock)
339 CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
340 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
342 SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
344 /* Ensure that it is below all floating clients */
345 Client *first_floating;
346 SLIST_FOREACH(first_floating, &(new->container->workspace->focus_stack), focus_clients)
347 if (first_floating->floating >= FLOATING_AUTO_ON)
350 if (first_floating != SLIST_END(&(new->container->workspace->focus_stack))) {
351 LOG("Setting below floating\n");
352 uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
353 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
357 new->initialized = true;
359 /* Check if the window already got the fullscreen hint set */
361 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
362 (state = xcb_get_property_value(preply)) != NULL)
363 /* Check all set _NET_WM_STATEs */
364 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
365 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
367 /* If the window got the fullscreen state, we just toggle fullscreen
368 and don’t event bother to redraw the layout – that would not change
370 client_toggle_fullscreen(conn, new);