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