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 * Go through all existing windows (if the window manager is restarted) and manage them
35 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
36 xcb_query_tree_reply_t *reply;
38 xcb_window_t *children;
39 xcb_get_window_attributes_cookie_t *cookies;
41 /* Get the tree of windows whose parent is the root window (= all) */
42 if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
45 len = xcb_query_tree_children_length(reply);
46 cookies = smalloc(len * sizeof(*cookies));
48 /* Request the window attributes for every window */
49 children = xcb_query_tree_children(reply);
50 for(i = 0; i < len; ++i)
51 cookies[i] = xcb_get_window_attributes(conn, children[i]);
53 /* Call manage_window with the attributes for every window */
54 for(i = 0; i < len; ++i) {
55 window_attributes_t wa = { TAG_COOKIE, { cookies[i] } };
56 manage_window(prophs, conn, children[i], wa);
64 * Do some sanity checks and then reparent the window.
67 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn, xcb_window_t window, window_attributes_t wa) {
68 LOG("managing window.\n");
69 xcb_drawable_t d = { window };
70 xcb_get_geometry_cookie_t geomc;
71 xcb_get_geometry_reply_t *geom;
72 xcb_get_window_attributes_reply_t *attr = 0;
74 if (wa.tag == TAG_COOKIE) {
75 /* Check if the window is mapped (it could be not mapped when intializing and
76 calling manage_window() for every window) */
77 if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
80 if (attr->map_state != XCB_MAP_STATE_VIEWABLE)
84 wa.u.override_redirect = attr->override_redirect;
87 /* Don’t manage clients with the override_redirect flag */
88 if (wa.u.override_redirect)
91 /* Check if the window is already managed */
92 if (table_get(&by_child, window))
95 /* Get the initial geometry (position, size, …) */
96 geomc = xcb_get_geometry(conn, d);
99 wa.u.cookie = xcb_get_window_attributes(conn, window);
100 if ((attr = xcb_get_window_attributes_reply(conn, wa.u.cookie, 0)) == NULL)
103 if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
106 /* Reparent the window and add it to our list of managed windows */
107 reparent_window(conn, window, attr->visual, geom->root, geom->depth,
108 geom->x, geom->y, geom->width, geom->height);
110 /* Generate callback events for every property we watch */
111 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
112 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
113 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
114 xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
123 * reparent_window() gets called when a new window was opened and becomes a child of the root
124 * window, or it gets called by us when we manage the already existing windows at startup.
126 * Essentially, this is the point where we take over control.
129 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
130 xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
131 int16_t x, int16_t y, uint16_t width, uint16_t height) {
133 xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
134 utf8_title_cookie, title_cookie, class_cookie;
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 new = calloc(sizeof(Client), 1);
161 new->force_reconfigure = true;
163 /* Update the data structures */
164 Client *old_focused = CUR_CELL->currently_focused;
166 new->container = CUR_CELL;
167 new->workspace = new->container->workspace;
169 new->frame = xcb_generate_id(conn);
171 new->rect.width = width;
172 new->rect.height = height;
176 /* Don’t generate events for our new window, it should *not* be managed */
177 mask |= XCB_CW_OVERRIDE_REDIRECT;
180 /* We want to know when… */
181 mask |= XCB_CW_EVENT_MASK;
182 values[1] = FRAME_EVENT_MASK;
184 LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
186 i3Font *font = load_font(conn, config.font);
187 width = min(width, c_ws->rect.x + c_ws->rect.width);
188 height = min(height, c_ws->rect.y + c_ws->rect.height);
190 Rect framerect = {x, y,
191 width + 2 + 2, /* 2 px border at each side */
192 height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
194 /* Yo dawg, I heard you like windows, so I create a window around your window… */
195 new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
197 /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t.
198 * Also, xprop(1) needs that to work. */
199 long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
200 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
202 /* Put the client inside the save set. Upon termination (whether killed or normal exit
203 does not matter) of the window manager, these clients will be correctly reparented
204 to their most closest living ancestor (= cleanup) */
205 xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
207 /* Generate a graphics context for the titlebar */
208 new->titlegc = xcb_generate_id(conn);
209 xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
211 /* Moves the original window into the new frame we've created for it */
212 new->awaiting_useless_unmap = true;
213 xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
214 if (xcb_request_check(conn, cookie) != NULL) {
215 LOG("Could not reparent the window, aborting\n");
216 xcb_destroy_window(conn, new->frame);
221 /* Put our data structure (Client) into the table */
222 table_put(&by_parent, new->frame, new);
223 table_put(&by_child, child, new);
225 /* We need to grab the mouse buttons for click to focus */
226 xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
227 XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
228 1 /* left mouse button */,
229 XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
231 /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
233 xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
234 if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
235 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
236 if (atom[i] != atoms[_NET_WM_WINDOW_TYPE_DOCK])
238 LOG("Window is a dock.\n");
240 new->titlebar_position = TITLEBAR_OFF;
241 new->force_reconfigure = true;
242 new->container = NULL;
243 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
248 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
250 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
251 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
252 /* We only use a subset of the provided values, namely the reserved space at the top/bottom
253 of the screen. This is because the only possibility for bars is at to be at the top/bottom
254 with maximum horizontal size.
255 TODO: bars at the top */
256 new->desired_height = strut[3];
257 if (new->desired_height == 0) {
258 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height);
259 new->desired_height = height;
261 LOG("the client wants to be %d pixels high\n", new->desired_height);
263 LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
264 new->desired_height = height;
267 /* If it’s not a dock, we can check on which workspace we should put it. */
269 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
270 * top of this function, get them now and pass them to our callback function for window class / title
271 * changes. It is important that the client was already inserted into the by_child table,
272 * because the callbacks won’t work otherwise. */
273 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
274 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
276 preply = xcb_get_property_reply(conn, title_cookie, NULL);
277 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
279 preply = xcb_get_property_reply(conn, class_cookie, NULL);
280 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
282 LOG("DEBUG: should have all infos now\n");
283 struct Assignment *assign;
284 TAILQ_FOREACH(assign, &assignments, assignments) {
285 if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
288 LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
289 assign->windowclass_title, assign->workspace);
291 if (c_ws->screen->current_workspace == (assign->workspace-1)) {
292 LOG("We are already there, no need to do anything\n");
296 LOG("Changin container/workspace and unmapping the client\n");
297 Workspace *t_ws = &(workspaces[assign->workspace-1]);
298 if (t_ws->screen == NULL) {
299 LOG("initializing new workspace, setting num to %d\n", assign->workspace);
300 t_ws->screen = c_ws->screen;
301 /* Copy the dimensions from the virtual screen */
302 memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
305 new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
306 new->workspace = t_ws;
307 xcb_unmap_window(conn, new->frame);
312 if (CUR_CELL->workspace->fullscreen_client != NULL) {
313 if (new->container == CUR_CELL) {
314 /* If we are in fullscreen, we should lower the window to not be annoying */
315 uint32_t values[] = { XCB_STACK_MODE_BELOW };
316 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
318 } else if (!new->dock) {
319 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
320 if (new->container->workspace->fullscreen_client == NULL) {
321 new->container->currently_focused = new;
322 if (new->container == CUR_CELL)
323 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
327 /* Insert into the currently active container, if it’s not a dock window */
329 /* Insert after the old active client, if existing. If it does not exist, the
330 container is empty and it does not matter, where we insert it */
331 if (old_focused != NULL && !old_focused->dock)
332 CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
333 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
335 SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
338 /* Check if the window already got the fullscreen hint set */
340 if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
341 (state = xcb_get_property_value(preply)) != NULL)
342 /* Check all set _NET_WM_STATEs */
343 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
344 if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
346 /* If the window got the fullscreen state, we just toggle fullscreen
347 and don’t event bother to redraw the layout – that would not change
349 toggle_fullscreen(conn, new);