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