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