]> git.sur5r.net Git - i3/i3/blob - src/manage.c
Use the size when mapping the window as size for floating (correct size for tool...
[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         LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
163         new = calloc(sizeof(Client), 1);
164         new->force_reconfigure = true;
165
166         /* Update the data structures */
167         Client *old_focused = CUR_CELL->currently_focused;
168
169         new->container = CUR_CELL;
170         new->workspace = new->container->workspace;
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, 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         /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
239         xcb_atom_t *atom;
240         xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
241         if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
242                 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
243                         if (atom[i] != atoms[_NET_WM_WINDOW_TYPE_DOCK])
244                                 continue;
245                         LOG("Window is a dock.\n");
246                         new->dock = true;
247                         new->titlebar_position = TITLEBAR_OFF;
248                         new->force_reconfigure = true;
249                         new->container = NULL;
250                         SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
251                 }
252         }
253
254         if (new->dock) {
255                 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
256                 uint32_t *strut;
257                 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
258                 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
259                         /* We only use a subset of the provided values, namely the reserved space at the top/bottom
260                            of the screen. This is because the only possibility for bars is at to be at the top/bottom
261                            with maximum horizontal size.
262                            TODO: bars at the top */
263                         new->desired_height = strut[3];
264                         if (new->desired_height == 0) {
265                                 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height);
266                                 new->desired_height = height;
267                         }
268                         LOG("the client wants to be %d pixels high\n", new->desired_height);
269                 } else {
270                         LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
271                         new->desired_height = height;
272                 }
273         } else {
274                 /* If it’s not a dock, we can check on which workspace we should put it. */
275
276                 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
277                  * top of this function, get them now and pass them to our callback function for window class / title
278                  * changes. It is important that the client was already inserted into the by_child table,
279                  * because the callbacks won’t work otherwise. */
280                 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
281                 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
282
283                 preply = xcb_get_property_reply(conn, title_cookie, NULL);
284                 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
285
286                 preply = xcb_get_property_reply(conn, class_cookie, NULL);
287                 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
288
289                 LOG("DEBUG: should have all infos now\n");
290                 struct Assignment *assign;
291                 TAILQ_FOREACH(assign, &assignments, assignments) {
292                         if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
293                                 continue;
294
295                         LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
296                             assign->windowclass_title, assign->workspace);
297
298                         if (c_ws->screen->current_workspace == (assign->workspace-1)) {
299                                 LOG("We are already there, no need to do anything\n");
300                                 break;
301                         }
302
303                         LOG("Changin container/workspace and unmapping the client\n");
304                         Workspace *t_ws = &(workspaces[assign->workspace-1]);
305                         if (t_ws->screen == NULL) {
306                                 LOG("initializing new workspace, setting num to %d\n", assign->workspace);
307                                 t_ws->screen = c_ws->screen;
308                                 /* Copy the dimensions from the virtual screen */
309                                 memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
310                         }
311
312                         new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
313                         new->workspace = t_ws;
314                         xcb_unmap_window(conn, new->frame);
315                         break;
316                 }
317         }
318
319         if (CUR_CELL->workspace->fullscreen_client != NULL) {
320                 if (new->container == CUR_CELL) {
321                         /* If we are in fullscreen, we should lower the window to not be annoying */
322                         uint32_t values[] = { XCB_STACK_MODE_BELOW };
323                         xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
324                 }
325         } else if (!new->dock) {
326                 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
327                 if (new->container->workspace->fullscreen_client == NULL) {
328                         new->container->currently_focused = new;
329                         if (new->container == CUR_CELL)
330                                 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
331                 }
332         }
333
334         /* Insert into the currently active container, if it’s not a dock window */
335         if (!new->dock) {
336                 /* Insert after the old active client, if existing. If it does not exist, the
337                    container is empty and it does not matter, where we insert it */
338                 if (old_focused != NULL && !old_focused->dock)
339                         CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
340                 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
341
342                 SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
343
344                 /* Ensure that it is below all floating clients */
345                 Client *first_floating;
346                 SLIST_FOREACH(first_floating, &(new->container->workspace->focus_stack), focus_clients)
347                         if (first_floating->floating >= FLOATING_AUTO_ON)
348                                 break;
349
350                 if (first_floating != SLIST_END(&(new->container->workspace->focus_stack))) {
351                         LOG("Setting below floating\n");
352                         uint32_t values[] = { first_floating->frame, XCB_STACK_MODE_BELOW };
353                         xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE, values);
354                 }
355         }
356
357         new->initialized = true;
358
359         /* Check if the window already got the fullscreen hint set */
360         xcb_atom_t *state;
361         if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
362             (state = xcb_get_property_value(preply)) != NULL)
363                 /* Check all set _NET_WM_STATEs */
364                 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
365                         if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
366                                 continue;
367                         /* If the window got the fullscreen state, we just toggle fullscreen
368                            and don’t event bother to redraw the layout – that would not change
369                            anything anyways */
370                         client_toggle_fullscreen(conn, new);
371                         return;
372                 }
373
374         render_layout(conn);
375 }