]> git.sur5r.net Git - i3/i3/blob - src/manage.c
5e6e6eed58506bfe60bdfe8d927f9bced9070dbe
[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, atoms[_NET_WM_NAME]);
117
118         free(geom);
119 out:
120         free(attr);
121         return;
122 }
123
124 /*
125  * reparent_window() gets called when a new window was opened and becomes a child of the root
126  * window, or it gets called by us when we manage the already existing windows at startup.
127  *
128  * Essentially, this is the point where we take over control.
129  *
130  */
131 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
132                      xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
133                      int16_t x, int16_t y, uint16_t width, uint16_t height) {
134
135         xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
136                                   utf8_title_cookie, title_cookie, class_cookie;
137         uint32_t mask = 0;
138         uint32_t values[3];
139
140         /* We are interested in property changes */
141         mask = XCB_CW_EVENT_MASK;
142         values[0] = CHILD_EVENT_MASK;
143         xcb_change_window_attributes(conn, child, mask, values);
144
145         /* Map the window first to avoid flickering */
146         xcb_map_window(conn, child);
147
148         /* Place requests for properties ASAP */
149         wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
150         strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
151         state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
152         utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
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         new = calloc(sizeof(Client), 1);
163         new->force_reconfigure = true;
164
165         /* Update the data structures */
166         Client *old_focused = CUR_CELL->currently_focused;
167
168         new->container = CUR_CELL;
169         new->workspace = new->container->workspace;
170
171         new->frame = xcb_generate_id(conn);
172         new->child = child;
173         new->rect.width = width;
174         new->rect.height = height;
175
176         mask = 0;
177
178         /* Don’t generate events for our new window, it should *not* be managed */
179         mask |= XCB_CW_OVERRIDE_REDIRECT;
180         values[0] = 1;
181
182         /* We want to know when… */
183         mask |= XCB_CW_EVENT_MASK;
184         values[1] = FRAME_EVENT_MASK;
185
186         LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
187
188         i3Font *font = load_font(conn, config.font);
189         width = min(width, c_ws->rect.x + c_ws->rect.width);
190         height = min(height, c_ws->rect.y + c_ws->rect.height);
191
192         Rect framerect = {x, y,
193                           width + 2 + 2,                  /* 2 px border at each side */
194                           height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
195
196         /* Yo dawg, I heard you like windows, so I create a window around your window… */
197         new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, mask, values);
198
199         /* Set WM_STATE_NORMAL because GTK applications don’t want to drag & drop if we don’t.
200          * Also, xprop(1) needs that to work. */
201         long data[] = { XCB_WM_STATE_NORMAL, XCB_NONE };
202         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, new->child, atoms[WM_STATE], atoms[WM_STATE], 32, 2, data);
203
204         /* Put the client inside the save set. Upon termination (whether killed or normal exit
205            does not matter) of the window manager, these clients will be correctly reparented
206            to their most closest living ancestor (= cleanup) */
207         xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
208
209         /* Generate a graphics context for the titlebar */
210         new->titlegc = xcb_generate_id(conn);
211         xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
212
213         /* Moves the original window into the new frame we've created for it */
214         new->awaiting_useless_unmap = true;
215         xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
216         if (xcb_request_check(conn, cookie) != NULL) {
217                 LOG("Could not reparent the window, aborting\n");
218                 xcb_destroy_window(conn, new->frame);
219                 free(new);
220                 return;
221         }
222
223         /* Put our data structure (Client) into the table */
224         table_put(&by_parent, new->frame, new);
225         table_put(&by_child, child, new);
226
227         /* We need to grab the mouse buttons for click to focus */
228         xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
229                         XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
230                         1 /* left mouse button */,
231                         XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
232
233         /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
234         xcb_atom_t *atom;
235         xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
236         if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
237                 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
238                         if (atom[i] != atoms[_NET_WM_WINDOW_TYPE_DOCK])
239                                 continue;
240                         LOG("Window is a dock.\n");
241                         new->dock = true;
242                         new->titlebar_position = TITLEBAR_OFF;
243                         new->force_reconfigure = true;
244                         new->container = NULL;
245                         SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
246                 }
247         }
248
249         if (new->dock) {
250                 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
251                 uint32_t *strut;
252                 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
253                 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
254                         /* We only use a subset of the provided values, namely the reserved space at the top/bottom
255                            of the screen. This is because the only possibility for bars is at to be at the top/bottom
256                            with maximum horizontal size.
257                            TODO: bars at the top */
258                         new->desired_height = strut[3];
259                         if (new->desired_height == 0) {
260                                 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height);
261                                 new->desired_height = height;
262                         }
263                         LOG("the client wants to be %d pixels high\n", new->desired_height);
264                 } else {
265                         LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
266                         new->desired_height = height;
267                 }
268         } else {
269                 /* If it’s not a dock, we can check on which workspace we should put it. */
270
271                 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
272                  * top of this function, get them now and pass them to our callback function for window class / title
273                  * changes. It is important that the client was already inserted into the by_child table,
274                  * because the callbacks won’t work otherwise. */
275                 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
276                 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
277
278                 preply = xcb_get_property_reply(conn, title_cookie, NULL);
279                 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
280
281                 preply = xcb_get_property_reply(conn, class_cookie, NULL);
282                 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
283
284                 LOG("DEBUG: should have all infos now\n");
285                 struct Assignment *assign;
286                 TAILQ_FOREACH(assign, &assignments, assignments) {
287                         if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
288                                 continue;
289
290                         LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
291                             assign->windowclass_title, assign->workspace);
292
293                         if (c_ws->screen->current_workspace == (assign->workspace-1)) {
294                                 LOG("We are already there, no need to do anything\n");
295                                 break;
296                         }
297
298                         LOG("Changin container/workspace and unmapping the client\n");
299                         Workspace *t_ws = &(workspaces[assign->workspace-1]);
300                         if (t_ws->screen == NULL) {
301                                 LOG("initializing new workspace, setting num to %d\n", assign->workspace);
302                                 t_ws->screen = c_ws->screen;
303                                 /* Copy the dimensions from the virtual screen */
304                                 memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
305                         }
306
307                         new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
308                         new->workspace = t_ws;
309                         xcb_unmap_window(conn, new->frame);
310                         break;
311                 }
312         }
313
314         if (CUR_CELL->workspace->fullscreen_client != NULL) {
315                 if (new->container == CUR_CELL) {
316                         /* If we are in fullscreen, we should lower the window to not be annoying */
317                         uint32_t values[] = { XCB_STACK_MODE_BELOW };
318                         xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
319                 }
320         } else if (!new->dock) {
321                 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
322                 if (new->container->workspace->fullscreen_client == NULL) {
323                         new->container->currently_focused = new;
324                         if (new->container == CUR_CELL)
325                                 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
326                 }
327         }
328
329         /* Insert into the currently active container, if it’s not a dock window */
330         if (!new->dock) {
331                 /* Insert after the old active client, if existing. If it does not exist, the
332                    container is empty and it does not matter, where we insert it */
333                 if (old_focused != NULL && !old_focused->dock)
334                         CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
335                 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
336
337                 SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
338
339                 /* Ensure that it is below all floating clients */
340                 Client *first_floating;
341                 SLIST_FOREACH(first_floating, &(new->container->workspace->focus_stack), focus_clients)
342                         if (first_floating->floating)
343                                 break;
344
345                 if (first_floating != SLIST_END(&(new->container->workspace->focus_stack))) {
346                         LOG("Setting below floating\n");
347                         uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
348                         xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
349                 }
350         }
351
352         /* Check if the window already got the fullscreen hint set */
353         xcb_atom_t *state;
354         if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
355             (state = xcb_get_property_value(preply)) != NULL)
356                 /* Check all set _NET_WM_STATEs */
357                 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
358                         if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
359                                 continue;
360                         /* If the window got the fullscreen state, we just toggle fullscreen
361                            and don’t event bother to redraw the layout – that would not change
362                            anything anyways */
363                         client_toggle_fullscreen(conn, new);
364                         return;
365                 }
366
367         render_layout(conn);
368 }