]> git.sur5r.net Git - i3/i3/blob - src/manage.c
c4118641f4bdc914450637738e4960ce0a5658c1
[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 #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                                 new->dock = true;
276                                 new->borderless = true;
277                                 new->titlebar_position = TITLEBAR_OFF;
278                                 new->force_reconfigure = true;
279                                 new->container = NULL;
280                                 SLIST_INSERT_HEAD(&(c_ws->output->dock_clients), new, dock_clients);
281                                 /* If it’s a dock we can’t make it float, so we break */
282                                 new->floating = FLOATING_AUTO_OFF;
283                                 break;
284                         } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
285                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
286                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
287                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
288                                 /* Set the dialog window to automatically floating, will be used below */
289                                 new->floating = FLOATING_AUTO_ON;
290                                 DLOG("dialog/utility/toolbar/splash window, automatically floating\n");
291                         }
292         }
293
294         /* All clients which have a leader should be floating */
295         if (!new->dock && !client_is_floating(new) && new->leader != 0) {
296                 DLOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
297                 new->floating = FLOATING_AUTO_ON;
298         }
299
300         if (new->workspace->auto_float) {
301                 new->floating = FLOATING_AUTO_ON;
302                 DLOG("workspace is in autofloat mode, setting floating\n");
303         }
304
305         if (new->dock) {
306                 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
307                 uint32_t *strut;
308                 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
309                 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
310                         /* We only use a subset of the provided values, namely the reserved space at the top/bottom
311                            of the screen. This is because the only possibility for bars is at to be at the top/bottom
312                            with maximum horizontal size.
313                            TODO: bars at the top */
314                         new->desired_height = strut[3];
315                         if (new->desired_height == 0) {
316                                 DLOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
317                                 new->desired_height = original_height;
318                         }
319                         DLOG("the client wants to be %d pixels high\n", new->desired_height);
320                 } else {
321                         DLOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
322                         new->desired_height = original_height;
323                 }
324         } else {
325                 /* If it’s not a dock, we can check on which workspace we should put it. */
326
327                 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
328                  * top of this function, get them now and pass them to our callback function for window class / title
329                  * changes. It is important that the client was already inserted into the by_child table,
330                  * because the callbacks won’t work otherwise. */
331                 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
332                 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
333
334                 preply = xcb_get_property_reply(conn, title_cookie, NULL);
335                 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
336
337                 preply = xcb_get_property_reply(conn, class_cookie, NULL);
338                 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
339
340                 preply = xcb_get_property_reply(conn, leader_cookie, NULL);
341                 handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
342
343                 struct Assignment *assign;
344                 TAILQ_FOREACH(assign, &assignments, assignments) {
345                         if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
346                                 continue;
347
348                         if (assign->floating == ASSIGN_FLOATING_ONLY ||
349                             assign->floating == ASSIGN_FLOATING) {
350                                 new->floating = FLOATING_AUTO_ON;
351                                 LOG("Assignment matches, putting client into floating mode\n");
352                                 if (assign->floating == ASSIGN_FLOATING_ONLY)
353                                         break;
354                         }
355
356                         LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
357                             assign->windowclass_title, assign->workspace);
358
359                         if (c_ws->output->current_workspace->num == (assign->workspace-1)) {
360                                 DLOG("We are already there, no need to do anything\n");
361                                 break;
362                         }
363
364                         DLOG("Changing container/workspace and unmapping the client\n");
365                         Workspace *t_ws = workspace_get(assign->workspace-1);
366                         workspace_initialize(t_ws, c_ws->output, false);
367
368                         new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
369                         new->workspace = t_ws;
370                         old_focused = new->container->currently_focused;
371
372                         map_frame = workspace_is_visible(t_ws);
373                         break;
374                 }
375         }
376
377         if (new->workspace->fullscreen_client != NULL) {
378                 DLOG("Setting below fullscreen window\n");
379
380                 /* If we are in fullscreen, we should lower the window to not be annoying */
381                 uint32_t values[] = { XCB_STACK_MODE_BELOW };
382                 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
383         }
384
385         /* Insert into the currently active container, if it’s not a dock window */
386         if (!new->dock && !client_is_floating(new)) {
387                 /* Insert after the old active client, if existing. If it does not exist, the
388                    container is empty and it does not matter, where we insert it */
389                 if (old_focused != NULL && !old_focused->dock)
390                         CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
391                 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
392
393                 if (new->container->workspace->fullscreen_client != NULL)
394                         SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
395                 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
396
397                 client_set_below_floating(conn, new);
398         }
399
400         if (client_is_floating(new)) {
401                 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
402
403                 /* Add the client to the list of floating clients for its workspace */
404                 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
405
406                 new->container = NULL;
407
408                 new->rect.width = new->floating_rect.width + 2 + 2;
409                 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
410
411                 /* Some clients (like GIMP’s color picker window) get mapped
412                  * to (0, 0), so we push them to a reasonable position
413                  * (centered over their leader) */
414                 if (new->leader != 0 && x == 0 && y == 0) {
415                         DLOG("Floating client wants to (0x0), moving it over its leader instead\n");
416                         Client *leader = table_get(&by_child, new->leader);
417                         if (leader == NULL) {
418                                 DLOG("leader is NULL, centering it over current workspace\n");
419
420                                 x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
421                                 y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
422                         } else {
423                                 x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
424                                 y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
425                         }
426                 }
427                 new->floating_rect.x = new->rect.x = x;
428                 new->floating_rect.y = new->rect.y = y;
429                 DLOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
430                                 new->floating_rect.x, new->floating_rect.y,
431                                 new->floating_rect.width, new->floating_rect.height);
432                 DLOG("outer rect (%d, %d) size (%d, %d)\n",
433                                 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
434
435                 /* Make sure it is on top of the other windows */
436                 xcb_raise_window(conn, new->frame);
437                 reposition_client(conn, new);
438                 resize_client(conn, new);
439                 /* redecorate_window flushes */
440                 redecorate_window(conn, new);
441         }
442
443         new->initialized = true;
444
445         /* Check if the window already got the fullscreen hint set */
446         xcb_atom_t *state;
447         if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
448             (state = xcb_get_property_value(preply)) != NULL)
449                 /* Check all set _NET_WM_STATEs */
450                 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
451                         if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
452                                 continue;
453                         /* If the window got the fullscreen state, we just toggle fullscreen
454                            and don’t event bother to redraw the layout – that would not change
455                            anything anyways */
456                         client_toggle_fullscreen(conn, new);
457                         goto map;
458                 }
459
460         render_layout(conn);
461
462 map:
463         /* Map the window first to avoid flickering */
464         xcb_map_window(conn, child);
465         if (map_frame)
466                 client_map(conn, new);
467
468         if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !new->dock) {
469                 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
470                 if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) {
471                         if (!client_is_floating(new)) {
472                                 new->container->currently_focused = new;
473                                 if (map_frame)
474                                         render_container(conn, new->container);
475                         }
476                         if (new->container == CUR_CELL || client_is_floating(new)) {
477                                 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
478                                 ewmh_update_active_window(new->child);
479                         }
480                 }
481         }
482
483         xcb_flush(conn);
484 }