]> git.sur5r.net Git - i3/i3/blob - src/manage.c
5d2468334dcf32de9476ee6f7c167422eda0ff72
[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         bool map_frame = true;
138
139         /* We are interested in property changes */
140         mask = XCB_CW_EVENT_MASK;
141         values[0] = CHILD_EVENT_MASK;
142         xcb_change_window_attributes(conn, child, mask, values);
143
144         /* Place requests for properties ASAP */
145         wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
146         strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
147         state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
148         utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
149         title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
150         class_cookie  = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
151
152         Client *new = table_get(&by_child, child);
153
154         /* Events for already managed windows should already be filtered in manage_window() */
155         assert(new == NULL);
156
157         LOG("reparenting new client\n");
158         LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
159         new = calloc(sizeof(Client), 1);
160         new->force_reconfigure = true;
161
162         /* Update the data structures */
163         Client *old_focused = CUR_CELL->currently_focused;
164
165         new->container = CUR_CELL;
166         new->workspace = new->container->workspace;
167
168         /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
169         width = max(width, 75);
170         height = max(height, 50);
171
172         new->frame = xcb_generate_id(conn);
173         new->child = child;
174         new->rect.width = width;
175         new->rect.height = height;
176         /* Pre-initialize the values for floating */
177         new->floating_rect.x = -1;
178         new->floating_rect.width = width;
179         new->floating_rect.height = height;
180
181         mask = 0;
182
183         /* Don’t generate events for our new window, it should *not* be managed */
184         mask |= XCB_CW_OVERRIDE_REDIRECT;
185         values[0] = 1;
186
187         /* We want to know when… */
188         mask |= XCB_CW_EVENT_MASK;
189         values[1] = FRAME_EVENT_MASK;
190
191         LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
192
193         i3Font *font = load_font(conn, config.font);
194         width = min(width, c_ws->rect.x + c_ws->rect.width);
195         height = min(height, c_ws->rect.y + c_ws->rect.height);
196
197         Rect framerect = {x, y,
198                           width + 2 + 2,                  /* 2 px border at each side */
199                           height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
200
201         /* Yo dawg, I heard you like windows, so I create a window around your window… */
202         new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
203
204         /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t.
205          * Also, xprop(1) needs that to work. */
206         long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
207         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
208
209         /* Put the client inside the save set. Upon termination (whether killed or normal exit
210            does not matter) of the window manager, these clients will be correctly reparented
211            to their most closest living ancestor (= cleanup) */
212         xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
213
214         /* Generate a graphics context for the titlebar */
215         new->titlegc = xcb_generate_id(conn);
216         xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
217
218         /* Moves the original window into the new frame we've created for it */
219         new->awaiting_useless_unmap = true;
220         xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
221         if (xcb_request_check(conn, cookie) != NULL) {
222                 LOG("Could not reparent the window, aborting\n");
223                 xcb_destroy_window(conn, new->frame);
224                 free(new);
225                 return;
226         }
227
228         /* Put our data structure (Client) into the table */
229         table_put(&by_parent, new->frame, new);
230         table_put(&by_child, child, new);
231
232         /* We need to grab the mouse buttons for click to focus */
233         xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
234                         XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
235                         1 /* left mouse button */,
236                         XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
237
238         xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
239                         XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
240                         1 /* left mouse button */, XCB_MOD_MASK_1);
241
242         /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
243         xcb_atom_t *atom;
244         xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
245         if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
246                 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
247                         if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
248                                 LOG("Window is a dock.\n");
249                                 new->dock = true;
250                                 new->titlebar_position = TITLEBAR_OFF;
251                                 new->force_reconfigure = true;
252                                 new->container = NULL;
253                                 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
254                                 /* If it’s a dock we can’t make it float, so we break */
255                                 break;
256                         } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
257                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
258                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
259                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
260                                 /* Set the dialog window to automatically floating, will be used below */
261                                 new->floating = FLOATING_AUTO_ON;
262                                 LOG("dialog/utility/toolbar/splash window, automatically floating\n");
263                         }
264         }
265
266         if (new->workspace->auto_float) {
267                 new->floating = FLOATING_AUTO_ON;
268                 LOG("workspace is in autofloat mode, setting floating\n");
269         }
270
271         if (new->dock) {
272                 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
273                 uint32_t *strut;
274                 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
275                 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
276                         /* We only use a subset of the provided values, namely the reserved space at the top/bottom
277                            of the screen. This is because the only possibility for bars is at to be at the top/bottom
278                            with maximum horizontal size.
279                            TODO: bars at the top */
280                         new->desired_height = strut[3];
281                         if (new->desired_height == 0) {
282                                 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
283                                 new->desired_height = original_height;
284                         }
285                         LOG("the client wants to be %d pixels high\n", new->desired_height);
286                 } else {
287                         LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
288                         new->desired_height = original_height;
289                 }
290         } else {
291                 /* If it’s not a dock, we can check on which workspace we should put it. */
292
293                 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
294                  * top of this function, get them now and pass them to our callback function for window class / title
295                  * changes. It is important that the client was already inserted into the by_child table,
296                  * because the callbacks won’t work otherwise. */
297                 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
298                 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
299
300                 preply = xcb_get_property_reply(conn, title_cookie, NULL);
301                 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
302
303                 preply = xcb_get_property_reply(conn, class_cookie, NULL);
304                 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
305
306                 LOG("DEBUG: should have all infos now\n");
307                 struct Assignment *assign;
308                 TAILQ_FOREACH(assign, &assignments, assignments) {
309                         if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
310                                 continue;
311
312                         if (assign->floating == ASSIGN_FLOATING_ONLY ||
313                             assign->floating == ASSIGN_FLOATING) {
314                                 new->floating = FLOATING_AUTO_ON;
315                                 LOG("Assignment matches, putting client into floating mode\n");
316                                 if (assign->floating == ASSIGN_FLOATING_ONLY)
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                         map_frame = false;
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         }
353
354         /* Insert into the currently active container, if it’s not a dock window */
355         if (!new->dock && !client_is_floating(new)) {
356                 /* Insert after the old active client, if existing. If it does not exist, the
357                    container is empty and it does not matter, where we insert it */
358                 if (old_focused != NULL && !old_focused->dock)
359                         CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
360                 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
361
362                 if (new->container->workspace->fullscreen_client != NULL)
363                         SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
364                 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
365
366                 client_set_below_floating(conn, new);
367         }
368
369         if (client_is_floating(new)) {
370                 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
371
372                 /* Add the client to the list of floating clients for its workspace */
373                 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
374
375                 new->container = NULL;
376
377                 new->floating_rect.x = new->rect.x = x;
378                 new->floating_rect.y = new->rect.y = y;
379                 new->rect.width = new->floating_rect.width + 2 + 2;
380                 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
381                 LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
382                                 new->floating_rect.x, new->floating_rect.y,
383                                 new->floating_rect.width, new->floating_rect.height);
384                 LOG("outer rect (%d, %d) size (%d, %d)\n",
385                                 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
386
387                 /* Make sure it is on top of the other windows */
388                 xcb_raise_window(conn, new->frame);
389                 reposition_client(conn, new);
390                 resize_client(conn, new);
391                 /* redecorate_window flushes */
392                 redecorate_window(conn, new);
393         }
394
395         new->initialized = true;
396
397         /* Check if the window already got the fullscreen hint set */
398         xcb_atom_t *state;
399         if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
400             (state = xcb_get_property_value(preply)) != NULL)
401                 /* Check all set _NET_WM_STATEs */
402                 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
403                         if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
404                                 continue;
405                         /* If the window got the fullscreen state, we just toggle fullscreen
406                            and don’t event bother to redraw the layout – that would not change
407                            anything anyways */
408                         client_toggle_fullscreen(conn, new);
409                         return;
410                 }
411
412         render_layout(conn);
413
414         /* Map the window first to avoid flickering */
415         xcb_map_window(conn, child);
416         if (map_frame)
417                 xcb_map_window(conn, new->frame);
418         if (CUR_CELL->workspace->fullscreen_client == NULL && !new->dock) {
419                 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
420                 if (new->workspace->fullscreen_client == NULL) {
421                         if (!client_is_floating(new))
422                                 new->container->currently_focused = new;
423                         if (new->container == CUR_CELL || client_is_floating(new))
424                                 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
425                 }
426         }
427
428         xcb_flush(conn);
429 }