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