]> git.sur5r.net Git - i3/i3/blob - src/manage.c
Bugfix: Correctly check for fullscreen windows when mapping new clients
[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 #include "workspace.h"
33
34 /*
35  * Go through all existing windows (if the window manager is restarted) and manage them
36  *
37  */
38 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
39         xcb_query_tree_reply_t *reply;
40         int i, len;
41         xcb_window_t *children;
42         xcb_get_window_attributes_cookie_t *cookies;
43
44         /* Get the tree of windows whose parent is the root window (= all) */
45         if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
46                 return;
47
48         len = xcb_query_tree_children_length(reply);
49         cookies = smalloc(len * sizeof(*cookies));
50
51         /* Request the window attributes for every window */
52         children = xcb_query_tree_children(reply);
53         for (i = 0; i < len; ++i)
54                 cookies[i] = xcb_get_window_attributes(conn, children[i]);
55
56         /* Call manage_window with the attributes for every window */
57         for (i = 0; i < len; ++i)
58                 manage_window(prophs, conn, children[i], cookies[i], true);
59
60         free(reply);
61         free(cookies);
62 }
63
64 /*
65  * Do some sanity checks and then reparent the window.
66  *
67  */
68 void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *conn,
69                    xcb_window_t window, xcb_get_window_attributes_cookie_t cookie,
70                    bool needs_to_be_mapped) {
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                 goto out;
87
88         /* Don’t manage clients with the override_redirect flag */
89         if (attr->override_redirect)
90                 goto out;
91
92         /* Check if the window is already managed */
93         if (table_get(&by_child, window))
94                 goto out;
95
96         /* Get the initial geometry (position, size, …) */
97         if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
98                 goto out;
99
100         /* Reparent the window and add it to our list of managed windows */
101         reparent_window(conn, window, attr->visual, geom->root, geom->depth,
102                         geom->x, geom->y, geom->width, geom->height,
103                         geom->border_width);
104
105         /* Generate callback events for every property we watch */
106         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
107         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
108         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
109         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_HINTS);
110         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR);
111         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[WM_CLIENT_LEADER]);
112         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
113
114         free(geom);
115 out:
116         free(attr);
117         return;
118 }
119
120 /*
121  * reparent_window() gets called when a new window was opened and becomes a child of the root
122  * window, or it gets called by us when we manage the already existing windows at startup.
123  *
124  * Essentially, this is the point where we take over control.
125  *
126  */
127 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
128                      xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
129                      int16_t x, int16_t y, uint16_t width, uint16_t height,
130                      uint32_t border_width) {
131
132         xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
133                                   utf8_title_cookie, title_cookie,
134                                   class_cookie, leader_cookie;
135         uint32_t mask = 0;
136         uint32_t values[3];
137         uint16_t original_height = height;
138         bool map_frame = true;
139
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);
144
145         /* Place requests for properties ASAP */
146         wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
147         strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
148         state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
149         utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
150         leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX);
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);
153
154         Client *new = table_get(&by_child, child);
155
156         /* Events for already managed windows should already be filtered in manage_window() */
157         assert(new == NULL);
158
159         LOG("Reparenting window 0x%08x\n", child);
160         LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
161         new = calloc(sizeof(Client), 1);
162         new->force_reconfigure = true;
163
164         /* Update the data structures */
165         Client *old_focused = CUR_CELL->currently_focused;
166
167         new->container = CUR_CELL;
168         new->workspace = new->container->workspace;
169
170         /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
171         width = max(width, 75);
172         height = max(height, 50);
173
174         new->frame = xcb_generate_id(conn);
175         new->child = child;
176         new->rect.width = width;
177         new->rect.height = height;
178         new->width_increment = 1;
179         new->height_increment = 1;
180         new->border_width = border_width;
181         /* Pre-initialize the values for floating */
182         new->floating_rect.x = -1;
183         new->floating_rect.width = width;
184         new->floating_rect.height = height;
185
186         if (config.default_border != NULL)
187                 client_init_border(conn, new, config.default_border[1]);
188
189         mask = 0;
190
191         /* Don’t generate events for our new window, it should *not* be managed */
192         mask |= XCB_CW_OVERRIDE_REDIRECT;
193         values[0] = 1;
194
195         /* We want to know when… */
196         mask |= XCB_CW_EVENT_MASK;
197         values[1] = FRAME_EVENT_MASK;
198
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);
202
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 */
206
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, false, mask, values);
209
210         /* Put the client inside the save set. Upon termination (whether killed or normal exit
211            does not matter) of the window manager, these clients will be correctly reparented
212            to their most closest living ancestor (= cleanup) */
213         xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
214
215         /* Generate a graphics context for the titlebar */
216         new->titlegc = xcb_generate_id(conn);
217         xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
218
219         /* Moves the original window into the new frame we've created for it */
220         new->awaiting_useless_unmap = true;
221         xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
222         if (xcb_request_check(conn, cookie) != NULL) {
223                 LOG("Could not reparent the window, aborting\n");
224                 xcb_destroy_window(conn, new->frame);
225                 free(new);
226                 return;
227         }
228
229         /* Put our data structure (Client) into the table */
230         table_put(&by_parent, new->frame, new);
231         table_put(&by_child, child, new);
232
233         /* We need to grab the mouse buttons for click to focus */
234         xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
235                         XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
236                         1 /* left mouse button */,
237                         XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
238
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                         3 /* right mouse button */,
242                         XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
243
244         /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
245         xcb_atom_t *atom;
246         xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
247         if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
248                 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
249                         if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
250                                 LOG("Window is a dock.\n");
251                                 new->dock = true;
252                                 new->borderless = true;
253                                 new->titlebar_position = TITLEBAR_OFF;
254                                 new->force_reconfigure = true;
255                                 new->container = NULL;
256                                 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
257                                 /* If it’s a dock we can’t make it float, so we break */
258                                 new->floating = FLOATING_AUTO_OFF;
259                                 break;
260                         } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
261                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
262                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
263                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
264                                 /* Set the dialog window to automatically floating, will be used below */
265                                 new->floating = FLOATING_AUTO_ON;
266                                 LOG("dialog/utility/toolbar/splash window, automatically floating\n");
267                         }
268         }
269
270         /* All clients which have a leader should be floating */
271         if (!new->dock && !client_is_floating(new) && new->leader != 0) {
272                 LOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
273                 new->floating = FLOATING_AUTO_ON;
274         }
275
276         if (new->workspace->auto_float) {
277                 new->floating = FLOATING_AUTO_ON;
278                 LOG("workspace is in autofloat mode, setting floating\n");
279         }
280
281         if (new->dock) {
282                 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
283                 uint32_t *strut;
284                 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
285                 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
286                         /* We only use a subset of the provided values, namely the reserved space at the top/bottom
287                            of the screen. This is because the only possibility for bars is at to be at the top/bottom
288                            with maximum horizontal size.
289                            TODO: bars at the top */
290                         new->desired_height = strut[3];
291                         if (new->desired_height == 0) {
292                                 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
293                                 new->desired_height = original_height;
294                         }
295                         LOG("the client wants to be %d pixels high\n", new->desired_height);
296                 } else {
297                         LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
298                         new->desired_height = original_height;
299                 }
300         } else {
301                 /* If it’s not a dock, we can check on which workspace we should put it. */
302
303                 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
304                  * top of this function, get them now and pass them to our callback function for window class / title
305                  * changes. It is important that the client was already inserted into the by_child table,
306                  * because the callbacks won’t work otherwise. */
307                 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
308                 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
309
310                 preply = xcb_get_property_reply(conn, title_cookie, NULL);
311                 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
312
313                 preply = xcb_get_property_reply(conn, class_cookie, NULL);
314                 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
315
316                 preply = xcb_get_property_reply(conn, leader_cookie, NULL);
317                 handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
318
319                 struct Assignment *assign;
320                 TAILQ_FOREACH(assign, &assignments, assignments) {
321                         if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
322                                 continue;
323
324                         if (assign->floating == ASSIGN_FLOATING_ONLY ||
325                             assign->floating == ASSIGN_FLOATING) {
326                                 new->floating = FLOATING_AUTO_ON;
327                                 LOG("Assignment matches, putting client into floating mode\n");
328                                 if (assign->floating == ASSIGN_FLOATING_ONLY)
329                                         break;
330                         }
331
332                         LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
333                             assign->windowclass_title, assign->workspace);
334
335                         if (c_ws->screen->current_workspace->num == (assign->workspace-1)) {
336                                 LOG("We are already there, no need to do anything\n");
337                                 break;
338                         }
339
340                         LOG("Changing container/workspace and unmapping the client\n");
341                         Workspace *t_ws = workspace_get(assign->workspace-1);
342                         workspace_initialize(t_ws, c_ws->screen);
343
344                         new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
345                         new->workspace = t_ws;
346                         old_focused = new->container->currently_focused;
347
348                         map_frame = workspace_is_visible(t_ws);
349                         break;
350                 }
351         }
352
353         if (new->workspace->fullscreen_client != NULL) {
354                 LOG("Setting below fullscreen window\n");
355
356                 /* If we are in fullscreen, we should lower the window to not be annoying */
357                 uint32_t values[] = { XCB_STACK_MODE_BELOW };
358                 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
359         }
360
361         /* Insert into the currently active container, if it’s not a dock window */
362         if (!new->dock && !client_is_floating(new)) {
363                 /* Insert after the old active client, if existing. If it does not exist, the
364                    container is empty and it does not matter, where we insert it */
365                 if (old_focused != NULL && !old_focused->dock)
366                         CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
367                 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
368
369                 if (new->container->workspace->fullscreen_client != NULL)
370                         SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
371                 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
372
373                 client_set_below_floating(conn, new);
374         }
375
376         if (client_is_floating(new)) {
377                 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
378
379                 /* Add the client to the list of floating clients for its workspace */
380                 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
381
382                 new->container = NULL;
383
384                 new->rect.width = new->floating_rect.width + 2 + 2;
385                 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
386
387                 /* Some clients (like GIMP’s color picker window) get mapped
388                  * to (0, 0), so we push them to a reasonable position
389                  * (centered over their leader) */
390                 if (new->leader != 0 && x == 0 && y == 0) {
391                         LOG("Floating client wants to (0x0), moving it over its leader instead\n");
392                         Client *leader = table_get(&by_child, new->leader);
393                         if (leader == NULL) {
394                                 LOG("leader is NULL, centering it over current workspace\n");
395
396                                 x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
397                                 y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
398                         } else {
399                                 x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
400                                 y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
401                         }
402                 }
403                 new->floating_rect.x = new->rect.x = x;
404                 new->floating_rect.y = new->rect.y = y;
405                 LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
406                                 new->floating_rect.x, new->floating_rect.y,
407                                 new->floating_rect.width, new->floating_rect.height);
408                 LOG("outer rect (%d, %d) size (%d, %d)\n",
409                                 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
410
411                 /* Make sure it is on top of the other windows */
412                 xcb_raise_window(conn, new->frame);
413                 reposition_client(conn, new);
414                 resize_client(conn, new);
415                 /* redecorate_window flushes */
416                 redecorate_window(conn, new);
417         }
418
419         new->initialized = true;
420
421         /* Check if the window already got the fullscreen hint set */
422         xcb_atom_t *state;
423         if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
424             (state = xcb_get_property_value(preply)) != NULL)
425                 /* Check all set _NET_WM_STATEs */
426                 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
427                         if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
428                                 continue;
429                         /* If the window got the fullscreen state, we just toggle fullscreen
430                            and don’t event bother to redraw the layout – that would not change
431                            anything anyways */
432                         client_toggle_fullscreen(conn, new);
433                         goto map;
434                 }
435
436         render_layout(conn);
437
438 map:
439         /* Map the window first to avoid flickering */
440         xcb_map_window(conn, child);
441         if (map_frame)
442                 client_map(conn, new);
443
444         if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !new->dock) {
445                 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
446                 if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) {
447                         if (!client_is_floating(new)) {
448                                 new->container->currently_focused = new;
449                                 if (map_frame)
450                                         render_container(conn, new->container);
451                         }
452                         if (new->container == CUR_CELL || client_is_floating(new))
453                                 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
454                 }
455         }
456
457         xcb_flush(conn);
458 }