]> git.sur5r.net Git - i3/i3/blob - src/manage.c
Map window/its decoration *after* calling render_layout()
[i3/i3] / src / manage.c
1 /*
2  * vim:ts=8:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  *
6  * © 2009 Michael Stapelberg and contributors
7  *
8  * See file LICENSE for license information.
9  *
10  * src/manage.c: Contains all functions for initially managing new windows
11  *               (or existing ones on restart).
12  *
13  */
14 #include <stdlib.h>
15 #include <string.h>
16 #include <assert.h>
17
18 #include <xcb/xcb.h>
19 #include <xcb/xcb_icccm.h>
20
21 #include "xcb.h"
22 #include "data.h"
23 #include "util.h"
24 #include "i3.h"
25 #include "table.h"
26 #include "config.h"
27 #include "handlers.h"
28 #include "layout.h"
29 #include "manage.h"
30 #include "floating.h"
31 #include "client.h"
32
33 /*
34  * Go through all existing windows (if the window manager is restarted) and manage them
35  *
36  */
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;
39         int i, len;
40         xcb_window_t *children;
41         xcb_get_window_attributes_cookie_t *cookies;
42
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)
45                 return;
46
47         len = xcb_query_tree_children_length(reply);
48         cookies = smalloc(len * sizeof(*cookies));
49
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]);
54
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);
58
59         free(reply);
60         free(cookies);
61 }
62
63 /*
64  * Do some sanity checks and then reparent the window.
65  *
66  */
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;
75
76         geomc = xcb_get_geometry(conn, d);
77
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");
82                 return;
83         }
84
85         if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) {
86                 LOG("Window not mapped, not managing\n");
87                 goto out;
88         }
89
90         /* Don’t manage clients with the override_redirect flag */
91         if (attr->override_redirect) {
92                 LOG("override_redirect set, not managing\n");
93                 goto out;
94         }
95
96         /* Check if the window is already managed */
97         if (table_get(&by_child, window))
98                 goto out;
99
100         /* Get the initial geometry (position, size, …) */
101         if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
102                 goto out;
103
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);
107
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]);
114
115         free(geom);
116 out:
117         free(attr);
118         return;
119 }
120
121 /*
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.
124  *
125  * Essentially, this is the point where we take over control.
126  *
127  */
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) {
131
132         xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
133                                   utf8_title_cookie, title_cookie, class_cookie;
134         uint32_t mask = 0;
135         uint32_t values[3];
136         uint16_t original_height = height;
137
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);
142
143         /* Place requests for properties ASAP */
144         wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
145         strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
146         state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
147         utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
148         title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
149         class_cookie  = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
150
151         Client *new = table_get(&by_child, child);
152
153         /* Events for already managed windows should already be filtered in manage_window() */
154         assert(new == NULL);
155
156         LOG("reparenting new client\n");
157         LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
158         new = calloc(sizeof(Client), 1);
159         new->force_reconfigure = true;
160
161         /* Update the data structures */
162         Client *old_focused = CUR_CELL->currently_focused;
163
164         new->container = CUR_CELL;
165         new->workspace = new->container->workspace;
166
167         /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
168         width = max(width, 75);
169         height = max(height, 50);
170
171         new->frame = xcb_generate_id(conn);
172         new->child = child;
173         new->rect.width = width;
174         new->rect.height = height;
175         /* Pre-initialize the values for floating */
176         new->floating_rect.x = -1;
177         new->floating_rect.width = width;
178         new->floating_rect.height = height;
179
180         mask = 0;
181
182         /* Don’t generate events for our new window, it should *not* be managed */
183         mask |= XCB_CW_OVERRIDE_REDIRECT;
184         values[0] = 1;
185
186         /* We want to know when… */
187         mask |= XCB_CW_EVENT_MASK;
188         values[1] = FRAME_EVENT_MASK;
189
190         LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
191
192         i3Font *font = load_font(conn, config.font);
193         width = min(width, c_ws->rect.x + c_ws->rect.width);
194         height = min(height, c_ws->rect.y + c_ws->rect.height);
195
196         Rect framerect = {x, y,
197                           width + 2 + 2,                  /* 2 px border at each side */
198                           height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
199
200         /* Yo dawg, I heard you like windows, so I create a window around your window… */
201         new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
202
203         /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t.
204          * Also, xprop(1) needs that to work. */
205         long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
206         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
207
208         /* Put the client inside the save set. Upon termination (whether killed or normal exit
209            does not matter) of the window manager, these clients will be correctly reparented
210            to their most closest living ancestor (= cleanup) */
211         xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
212
213         /* Generate a graphics context for the titlebar */
214         new->titlegc = xcb_generate_id(conn);
215         xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
216
217         /* Moves the original window into the new frame we've created for it */
218         new->awaiting_useless_unmap = true;
219         xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
220         if (xcb_request_check(conn, cookie) != NULL) {
221                 LOG("Could not reparent the window, aborting\n");
222                 xcb_destroy_window(conn, new->frame);
223                 free(new);
224                 return;
225         }
226
227         /* Put our data structure (Client) into the table */
228         table_put(&by_parent, new->frame, new);
229         table_put(&by_child, child, new);
230
231         /* We need to grab the mouse buttons for click to focus */
232         xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
233                         XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
234                         1 /* left mouse button */,
235                         XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
236
237         xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
238                         XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
239                         1 /* left mouse button */, XCB_MOD_MASK_1);
240
241         /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
242         xcb_atom_t *atom;
243         xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
244         if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
245                 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
246                         if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
247                                 LOG("Window is a dock.\n");
248                                 new->dock = true;
249                                 new->titlebar_position = TITLEBAR_OFF;
250                                 new->force_reconfigure = true;
251                                 new->container = NULL;
252                                 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
253                                 /* If it’s a dock we can’t make it float, so we break */
254                                 break;
255                         } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
256                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
257                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
258                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
259                                 /* Set the dialog window to automatically floating, will be used below */
260                                 new->floating = FLOATING_AUTO_ON;
261                                 LOG("dialog/utility/toolbar/splash window, automatically floating\n");
262                         }
263         }
264
265         if (new->workspace->auto_float) {
266                 new->floating = FLOATING_AUTO_ON;
267                 LOG("workspace is in autofloat mode, setting floating\n");
268         }
269
270         if (new->dock) {
271                 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
272                 uint32_t *strut;
273                 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
274                 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
275                         /* We only use a subset of the provided values, namely the reserved space at the top/bottom
276                            of the screen. This is because the only possibility for bars is at to be at the top/bottom
277                            with maximum horizontal size.
278                            TODO: bars at the top */
279                         new->desired_height = strut[3];
280                         if (new->desired_height == 0) {
281                                 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
282                                 new->desired_height = original_height;
283                         }
284                         LOG("the client wants to be %d pixels high\n", new->desired_height);
285                 } else {
286                         LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
287                         new->desired_height = original_height;
288                 }
289         } else {
290                 /* If it’s not a dock, we can check on which workspace we should put it. */
291
292                 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
293                  * top of this function, get them now and pass them to our callback function for window class / title
294                  * changes. It is important that the client was already inserted into the by_child table,
295                  * because the callbacks won’t work otherwise. */
296                 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
297                 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
298
299                 preply = xcb_get_property_reply(conn, title_cookie, NULL);
300                 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
301
302                 preply = xcb_get_property_reply(conn, class_cookie, NULL);
303                 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
304
305                 LOG("DEBUG: should have all infos now\n");
306                 struct Assignment *assign;
307                 TAILQ_FOREACH(assign, &assignments, assignments) {
308                         if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
309                                 continue;
310
311                         if (assign->floating == ASSIGN_FLOATING_ONLY ||
312                             assign->floating == ASSIGN_FLOATING) {
313                                 new->floating = FLOATING_AUTO_ON;
314                                 LOG("Assignment matches, putting client into floating mode\n");
315                                 if (assign->floating == ASSIGN_FLOATING_ONLY)
316                                         break;
317                         }
318
319                         LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
320                             assign->windowclass_title, assign->workspace);
321
322                         if (c_ws->screen->current_workspace == (assign->workspace-1)) {
323                                 LOG("We are already there, no need to do anything\n");
324                                 break;
325                         }
326
327                         LOG("Changin container/workspace and unmapping the client\n");
328                         Workspace *t_ws = &(workspaces[assign->workspace-1]);
329                         if (t_ws->screen == NULL) {
330                                 LOG("initializing new workspace, setting num to %d\n", assign->workspace);
331                                 t_ws->screen = c_ws->screen;
332                                 /* Copy the dimensions from the virtual screen */
333                                 memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
334                         }
335
336                         new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
337                         new->workspace = t_ws;
338                         old_focused = new->container->currently_focused;
339
340                         xcb_unmap_window(conn, new->frame);
341                         break;
342                 }
343         }
344
345         if (CUR_CELL->workspace->fullscreen_client != NULL) {
346                 if (new->container == CUR_CELL) {
347                         /* If we are in fullscreen, we should lower the window to not be annoying */
348                         uint32_t values[] = { XCB_STACK_MODE_BELOW };
349                         xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
350                 }
351         }
352
353         /* Insert into the currently active container, if it’s not a dock window */
354         if (!new->dock && !client_is_floating(new)) {
355                 /* Insert after the old active client, if existing. If it does not exist, the
356                    container is empty and it does not matter, where we insert it */
357                 if (old_focused != NULL && !old_focused->dock)
358                         CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
359                 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
360
361                 if (new->container->workspace->fullscreen_client != NULL)
362                         SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
363                 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
364
365                 client_set_below_floating(conn, new);
366         }
367
368         if (client_is_floating(new)) {
369                 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
370
371                 /* Add the client to the list of floating clients for its workspace */
372                 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
373
374                 new->container = NULL;
375
376                 new->floating_rect.x = new->rect.x = x;
377                 new->floating_rect.y = new->rect.y = y;
378                 new->rect.width = new->floating_rect.width + 2 + 2;
379                 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
380                 LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
381                                 new->floating_rect.x, new->floating_rect.y,
382                                 new->floating_rect.width, new->floating_rect.height);
383                 LOG("outer rect (%d, %d) size (%d, %d)\n",
384                                 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
385
386                 /* Make sure it is on top of the other windows */
387                 xcb_raise_window(conn, new->frame);
388                 reposition_client(conn, new);
389                 resize_client(conn, new);
390                 /* redecorate_window flushes */
391                 redecorate_window(conn, new);
392         }
393
394         new->initialized = true;
395
396         /* Check if the window already got the fullscreen hint set */
397         xcb_atom_t *state;
398         if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
399             (state = xcb_get_property_value(preply)) != NULL)
400                 /* Check all set _NET_WM_STATEs */
401                 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
402                         if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
403                                 continue;
404                         /* If the window got the fullscreen state, we just toggle fullscreen
405                            and don’t event bother to redraw the layout – that would not change
406                            anything anyways */
407                         client_toggle_fullscreen(conn, new);
408                         return;
409                 }
410
411         render_layout(conn);
412
413         /* Map the window first to avoid flickering */
414         xcb_map_window(conn, new->frame);
415         xcb_map_window(conn, child);
416         if (CUR_CELL->workspace->fullscreen_client == NULL && !new->dock) {
417                 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
418                 if (new->container->workspace->fullscreen_client == NULL) {
419                         if (!client_is_floating(new))
420                                 new->container->currently_focused = new;
421                         if (new->container == CUR_CELL)
422                                 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
423                 }
424         }
425
426         xcb_flush(conn);
427 }