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