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