]> git.sur5r.net Git - i3/i3/blob - src/manage.c
ipc: add active flag
[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                                 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                 /* if WM_CLIENT_LEADER is set, we put the new window on the
344                  * same window as its leader. This might be overwritten by
345                  * assignments afterwards. */
346                 if (new->leader != XCB_NONE) {
347                         DLOG("client->leader is set (to 0x%08x)\n", new->leader);
348                         Client *parent = table_get(&by_child, new->leader);
349                         if (parent != NULL && parent->container != NULL) {
350                                 Workspace *t_ws = parent->workspace;
351                                 new->container = t_ws->table[parent->container->col][parent->container->row];
352                                 new->workspace = t_ws;
353                                 old_focused = new->container->currently_focused;
354                                 map_frame = workspace_is_visible(t_ws);
355                                 new->urgent = true;
356                                 /* This is a little tricky: we cannot use
357                                  * workspace_update_urgent_flag() because the
358                                  * new window was not yet inserted into the
359                                  * focus stack on t_ws. */
360                                 t_ws->urgent = true;
361                         } else {
362                                 DLOG("parent is not usable\n");
363                         }
364                 }
365
366                 struct Assignment *assign;
367                 TAILQ_FOREACH(assign, &assignments, assignments) {
368                         if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
369                                 continue;
370
371                         if (assign->floating == ASSIGN_FLOATING_ONLY ||
372                             assign->floating == ASSIGN_FLOATING) {
373                                 new->floating = FLOATING_AUTO_ON;
374                                 LOG("Assignment matches, putting client into floating mode\n");
375                                 if (assign->floating == ASSIGN_FLOATING_ONLY)
376                                         break;
377                         }
378
379                         LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
380                             assign->windowclass_title, assign->workspace);
381
382                         if (c_ws->output->current_workspace->num == (assign->workspace-1)) {
383                                 DLOG("We are already there, no need to do anything\n");
384                                 break;
385                         }
386
387                         DLOG("Changing container/workspace and unmapping the client\n");
388                         Workspace *t_ws = workspace_get(assign->workspace-1);
389                         workspace_initialize(t_ws, c_ws->output, false);
390
391                         new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
392                         new->workspace = t_ws;
393                         old_focused = new->container->currently_focused;
394
395                         map_frame = workspace_is_visible(t_ws);
396                         break;
397                 }
398         }
399
400         if (new->workspace->fullscreen_client != NULL) {
401                 DLOG("Setting below fullscreen window\n");
402
403                 /* If we are in fullscreen, we should lower the window to not be annoying */
404                 uint32_t values[] = { XCB_STACK_MODE_BELOW };
405                 xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
406         }
407
408         /* Insert into the currently active container, if it’s not a dock window */
409         if (!new->dock && !client_is_floating(new)) {
410                 /* Insert after the old active client, if existing. If it does not exist, the
411                    container is empty and it does not matter, where we insert it */
412                 if (old_focused != NULL && !old_focused->dock)
413                         CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
414                 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
415
416                 if (new->container->workspace->fullscreen_client != NULL)
417                         SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
418                 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
419
420                 client_set_below_floating(conn, new);
421         }
422
423         if (client_is_floating(new)) {
424                 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
425
426                 /* Add the client to the list of floating clients for its workspace */
427                 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
428
429                 new->container = NULL;
430
431                 new->rect.width = new->floating_rect.width + 2 + 2;
432                 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
433
434                 /* Some clients (like GIMP’s color picker window) get mapped
435                  * to (0, 0), so we push them to a reasonable position
436                  * (centered over their leader) */
437                 if (new->leader != 0 && x == 0 && y == 0) {
438                         DLOG("Floating client wants to (0x0), moving it over its leader instead\n");
439                         Client *leader = table_get(&by_child, new->leader);
440                         if (leader == NULL) {
441                                 DLOG("leader is NULL, centering it over current workspace\n");
442
443                                 x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
444                                 y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
445                         } else {
446                                 x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
447                                 y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
448                         }
449                 }
450                 new->floating_rect.x = new->rect.x = x;
451                 new->floating_rect.y = new->rect.y = y;
452                 DLOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
453                                 new->floating_rect.x, new->floating_rect.y,
454                                 new->floating_rect.width, new->floating_rect.height);
455                 DLOG("outer rect (%d, %d) size (%d, %d)\n",
456                                 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
457
458                 /* Make sure it is on top of the other windows */
459                 xcb_raise_window(conn, new->frame);
460                 reposition_client(conn, new);
461                 resize_client(conn, new);
462                 /* redecorate_window flushes */
463                 redecorate_window(conn, new);
464         }
465
466         new->initialized = true;
467
468         /* Check if the window already got the fullscreen hint set */
469         xcb_atom_t *state;
470         if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
471             (state = xcb_get_property_value(preply)) != NULL)
472                 /* Check all set _NET_WM_STATEs */
473                 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
474                         if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
475                                 continue;
476                         /* If the window got the fullscreen state, we just toggle fullscreen
477                            and don’t event bother to redraw the layout – that would not change
478                            anything anyways */
479                         client_toggle_fullscreen(conn, new);
480                         goto map;
481                 }
482
483         render_layout(conn);
484
485 map:
486         /* Map the window first to avoid flickering */
487         xcb_map_window(conn, child);
488         if (map_frame)
489                 client_map(conn, new);
490
491         if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !new->dock) {
492                 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
493                 if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) {
494                         if (!client_is_floating(new)) {
495                                 new->container->currently_focused = new;
496                                 if (map_frame)
497                                         render_container(conn, new->container);
498                         }
499                         if (new->container == CUR_CELL || client_is_floating(new)) {
500                                 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
501                                 ewmh_update_active_window(new->child);
502                         }
503                 }
504         }
505
506         xcb_flush(conn);
507 }