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