]> git.sur5r.net Git - i3/i3/blob - src/manage.c
Implement support for width_inc and height_inc of size hints
[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 #include "workspace.h"
33
34 /*
35  * Go through all existing windows (if the window manager is restarted) and manage them
36  *
37  */
38 void manage_existing_windows(xcb_connection_t *conn, xcb_property_handlers_t *prophs, xcb_window_t root) {
39         xcb_query_tree_reply_t *reply;
40         int i, len;
41         xcb_window_t *children;
42         xcb_get_window_attributes_cookie_t *cookies;
43
44         /* Get the tree of windows whose parent is the root window (= all) */
45         if ((reply = xcb_query_tree_reply(conn, xcb_query_tree(conn, root), 0)) == NULL)
46                 return;
47
48         len = xcb_query_tree_children_length(reply);
49         cookies = smalloc(len * sizeof(*cookies));
50
51         /* Request the window attributes for every window */
52         children = xcb_query_tree_children(reply);
53         for(i = 0; i < len; ++i)
54                 cookies[i] = xcb_get_window_attributes(conn, children[i]);
55
56         /* Call manage_window with the attributes for every window */
57         for(i = 0; i < len; ++i)
58                 manage_window(prophs, conn, children[i], cookies[i], true);
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,
69                    xcb_window_t window, xcb_get_window_attributes_cookie_t cookie,
70                    bool needs_to_be_mapped) {
71         LOG("managing window.\n");
72         xcb_drawable_t d = { window };
73         xcb_get_geometry_cookie_t geomc;
74         xcb_get_geometry_reply_t *geom;
75         xcb_get_window_attributes_reply_t *attr = 0;
76
77         geomc = xcb_get_geometry(conn, d);
78
79         /* Check if the window is mapped (it could be not mapped when intializing and
80            calling manage_window() for every window) */
81         if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
82                 LOG("Could not get attributes\n");
83                 return;
84         }
85
86         if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE) {
87                 LOG("Window not mapped, not managing\n");
88                 goto out;
89         }
90
91         /* Don’t manage clients with the override_redirect flag */
92         if (attr->override_redirect) {
93                 LOG("override_redirect set, not managing\n");
94                 goto out;
95         }
96
97         /* Check if the window is already managed */
98         if (table_get(&by_child, window))
99                 goto out;
100
101         /* Get the initial geometry (position, size, …) */
102         if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
103                 goto out;
104
105         /* Reparent the window and add it to our list of managed windows */
106         reparent_window(conn, window, attr->visual, geom->root, geom->depth,
107                         geom->x, geom->y, geom->width, geom->height);
108
109         /* Generate callback events for every property we watch */
110         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
111         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
112         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
113         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR);
114         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[WM_CLIENT_LEADER]);
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,
136                                   class_cookie, leader_cookie;
137         uint32_t mask = 0;
138         uint32_t values[3];
139         uint16_t original_height = height;
140         bool map_frame = true;
141
142         /* We are interested in property changes */
143         mask = XCB_CW_EVENT_MASK;
144         values[0] = CHILD_EVENT_MASK;
145         xcb_change_window_attributes(conn, child, mask, values);
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         leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX);
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         /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
173         width = max(width, 75);
174         height = max(height, 50);
175
176         new->frame = xcb_generate_id(conn);
177         new->child = child;
178         new->rect.width = width;
179         new->rect.height = height;
180         new->width_increment = 1;
181         new->height_increment = 1;
182         /* Pre-initialize the values for floating */
183         new->floating_rect.x = -1;
184         new->floating_rect.width = width;
185         new->floating_rect.height = height;
186
187         mask = 0;
188
189         /* Don’t generate events for our new window, it should *not* be managed */
190         mask |= XCB_CW_OVERRIDE_REDIRECT;
191         values[0] = 1;
192
193         /* We want to know when… */
194         mask |= XCB_CW_EVENT_MASK;
195         values[1] = FRAME_EVENT_MASK;
196
197         LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame);
198
199         i3Font *font = load_font(conn, config.font);
200         width = min(width, c_ws->rect.x + c_ws->rect.width);
201         height = min(height, c_ws->rect.y + c_ws->rect.height);
202
203         Rect framerect = {x, y,
204                           width + 2 + 2,                  /* 2 px border at each side */
205                           height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
206
207         /* Yo dawg, I heard you like windows, so I create a window around your window… */
208         new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
209
210         /* Put the client inside the save set. Upon termination (whether killed or normal exit
211            does not matter) of the window manager, these clients will be correctly reparented
212            to their most closest living ancestor (= cleanup) */
213         xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child);
214
215         /* Generate a graphics context for the titlebar */
216         new->titlegc = xcb_generate_id(conn);
217         xcb_create_gc(conn, new->titlegc, new->frame, 0, 0);
218
219         /* Moves the original window into the new frame we've created for it */
220         new->awaiting_useless_unmap = true;
221         xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height);
222         if (xcb_request_check(conn, cookie) != NULL) {
223                 LOG("Could not reparent the window, aborting\n");
224                 xcb_destroy_window(conn, new->frame);
225                 free(new);
226                 return;
227         }
228
229         /* Put our data structure (Client) into the table */
230         table_put(&by_parent, new->frame, new);
231         table_put(&by_child, child, new);
232
233         /* We need to grab the mouse buttons for click to focus */
234         xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
235                         XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
236                         1 /* left mouse button */,
237                         XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
238
239         xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
240                         XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
241                         1 /* left mouse button */, XCB_MOD_MASK_1);
242
243         /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
244         xcb_atom_t *atom;
245         xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
246         if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
247                 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
248                         if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
249                                 LOG("Window is a dock.\n");
250                                 new->dock = true;
251                                 new->borderless = true;
252                                 new->titlebar_position = TITLEBAR_OFF;
253                                 new->force_reconfigure = true;
254                                 new->container = NULL;
255                                 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
256                                 /* If it’s a dock we can’t make it float, so we break */
257                                 new->floating = FLOATING_AUTO_OFF;
258                                 break;
259                         } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
260                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
261                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
262                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
263                                 /* Set the dialog window to automatically floating, will be used below */
264                                 new->floating = FLOATING_AUTO_ON;
265                                 LOG("dialog/utility/toolbar/splash window, automatically floating\n");
266                         }
267         }
268
269         /* All clients which have a leader should be floating */
270         if (!new->dock && !client_is_floating(new) && new->leader != 0) {
271                 LOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
272                 new->floating = FLOATING_AUTO_ON;
273         }
274
275         if (new->workspace->auto_float) {
276                 new->floating = FLOATING_AUTO_ON;
277                 LOG("workspace is in autofloat mode, setting floating\n");
278         }
279
280         if (new->dock) {
281                 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
282                 uint32_t *strut;
283                 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
284                 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
285                         /* We only use a subset of the provided values, namely the reserved space at the top/bottom
286                            of the screen. This is because the only possibility for bars is at to be at the top/bottom
287                            with maximum horizontal size.
288                            TODO: bars at the top */
289                         new->desired_height = strut[3];
290                         if (new->desired_height == 0) {
291                                 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
292                                 new->desired_height = original_height;
293                         }
294                         LOG("the client wants to be %d pixels high\n", new->desired_height);
295                 } else {
296                         LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
297                         new->desired_height = original_height;
298                 }
299         } else {
300                 /* If it’s not a dock, we can check on which workspace we should put it. */
301
302                 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
303                  * top of this function, get them now and pass them to our callback function for window class / title
304                  * changes. It is important that the client was already inserted into the by_child table,
305                  * because the callbacks won’t work otherwise. */
306                 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
307                 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
308
309                 preply = xcb_get_property_reply(conn, title_cookie, NULL);
310                 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
311
312                 preply = xcb_get_property_reply(conn, class_cookie, NULL);
313                 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
314
315                 preply = xcb_get_property_reply(conn, leader_cookie, NULL);
316                 handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
317
318                 LOG("DEBUG: should have all infos now\n");
319                 struct Assignment *assign;
320                 TAILQ_FOREACH(assign, &assignments, assignments) {
321                         if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
322                                 continue;
323
324                         if (assign->floating == ASSIGN_FLOATING_ONLY ||
325                             assign->floating == ASSIGN_FLOATING) {
326                                 new->floating = FLOATING_AUTO_ON;
327                                 LOG("Assignment matches, putting client into floating mode\n");
328                                 if (assign->floating == ASSIGN_FLOATING_ONLY)
329                                         break;
330                         }
331
332                         LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
333                             assign->windowclass_title, assign->workspace);
334
335                         if (c_ws->screen->current_workspace == (assign->workspace-1)) {
336                                 LOG("We are already there, no need to do anything\n");
337                                 break;
338                         }
339
340                         LOG("Changing container/workspace and unmapping the client\n");
341                         Workspace *t_ws = &(workspaces[assign->workspace-1]);
342                         workspace_initialize(t_ws, c_ws->screen);
343
344                         new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
345                         new->workspace = t_ws;
346                         old_focused = new->container->currently_focused;
347
348                         map_frame = workspace_is_visible(t_ws);
349                         break;
350                 }
351         }
352
353         if (CUR_CELL->workspace->fullscreen_client != NULL) {
354                 if (new->container == CUR_CELL) {
355                         /* If we are in fullscreen, we should lower the window to not be annoying */
356                         uint32_t values[] = { XCB_STACK_MODE_BELOW };
357                         xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
358                 }
359         }
360
361         /* Insert into the currently active container, if it’s not a dock window */
362         if (!new->dock && !client_is_floating(new)) {
363                 /* Insert after the old active client, if existing. If it does not exist, the
364                    container is empty and it does not matter, where we insert it */
365                 if (old_focused != NULL && !old_focused->dock)
366                         CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
367                 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
368
369                 if (new->container->workspace->fullscreen_client != NULL)
370                         SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
371                 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
372
373                 client_set_below_floating(conn, new);
374         }
375
376         if (client_is_floating(new)) {
377                 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
378
379                 /* Add the client to the list of floating clients for its workspace */
380                 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
381
382                 new->container = NULL;
383
384                 new->rect.width = new->floating_rect.width + 2 + 2;
385                 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
386
387                 /* Some clients (like GIMP’s color picker window) get mapped
388                  * to (0, 0), so we push them to a reasonable position
389                  * (centered over their leader) */
390                 if (new->leader != 0 && x == 0 && y == 0) {
391                         LOG("Floating client wants to (0x0), moving it over its leader instead\n");
392                         Client *leader = table_get(&by_child, new->leader);
393                         if (leader == NULL) {
394                                 LOG("leader is NULL, centering it over current workspace\n");
395
396                                 x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
397                                 y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
398                         } else {
399                                 x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
400                                 y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
401                         }
402                 }
403                 new->floating_rect.x = new->rect.x = x;
404                 new->floating_rect.y = new->rect.y = y;
405                 LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
406                                 new->floating_rect.x, new->floating_rect.y,
407                                 new->floating_rect.width, new->floating_rect.height);
408                 LOG("outer rect (%d, %d) size (%d, %d)\n",
409                                 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
410
411                 /* Make sure it is on top of the other windows */
412                 xcb_raise_window(conn, new->frame);
413                 reposition_client(conn, new);
414                 resize_client(conn, new);
415                 /* redecorate_window flushes */
416                 redecorate_window(conn, new);
417         }
418
419         new->initialized = true;
420
421         /* Check if the window already got the fullscreen hint set */
422         xcb_atom_t *state;
423         if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
424             (state = xcb_get_property_value(preply)) != NULL)
425                 /* Check all set _NET_WM_STATEs */
426                 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
427                         if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
428                                 continue;
429                         /* If the window got the fullscreen state, we just toggle fullscreen
430                            and don’t event bother to redraw the layout – that would not change
431                            anything anyways */
432                         client_toggle_fullscreen(conn, new);
433                         return;
434                 }
435
436         render_layout(conn);
437
438         /* Map the window first to avoid flickering */
439         xcb_map_window(conn, child);
440         if (map_frame) {
441                 LOG("Mapping client\n");
442                 client_map(conn, new);
443         }
444         if (CUR_CELL->workspace->fullscreen_client == NULL && !new->dock) {
445                 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
446                 if (new->workspace->fullscreen_client == NULL) {
447                         if (!client_is_floating(new))
448                                 new->container->currently_focused = new;
449                         if (new->container == CUR_CELL || client_is_floating(new))
450                                 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
451                 }
452         }
453
454         xcb_flush(conn);
455 }