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