]> git.sur5r.net Git - i3/i3/blob - src/manage.c
Bugfix: Correctly grab the right mouse button for resizing windows (Thanks xeen)
[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         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         geomc = xcb_get_geometry(conn, d);
77
78         /* Check if the window is mapped (it could be not mapped when intializing and
79            calling manage_window() for every window) */
80         if ((attr = xcb_get_window_attributes_reply(conn, cookie, 0)) == NULL) {
81                 LOG("Could not get attributes\n");
82                 return;
83         }
84
85         if (needs_to_be_mapped && attr->map_state != XCB_MAP_STATE_VIEWABLE)
86                 goto out;
87
88         /* Don’t manage clients with the override_redirect flag */
89         if (attr->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         if ((geom = xcb_get_geometry_reply(conn, geomc, 0)) == NULL)
98                 goto out;
99
100         /* Reparent the window and add it to our list of managed windows */
101         reparent_window(conn, window, attr->visual, geom->root, geom->depth,
102                         geom->x, geom->y, geom->width, geom->height);
103
104         /* Generate callback events for every property we watch */
105         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_CLASS);
106         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME);
107         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NORMAL_HINTS);
108         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_TRANSIENT_FOR);
109         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[WM_CLIENT_LEADER]);
110         xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, atoms[_NET_WM_NAME]);
111
112         free(geom);
113 out:
114         free(attr);
115         return;
116 }
117
118 /*
119  * reparent_window() gets called when a new window was opened and becomes a child of the root
120  * window, or it gets called by us when we manage the already existing windows at startup.
121  *
122  * Essentially, this is the point where we take over control.
123  *
124  */
125 void reparent_window(xcb_connection_t *conn, xcb_window_t child,
126                      xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
127                      int16_t x, int16_t y, uint16_t width, uint16_t height) {
128
129         xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
130                                   utf8_title_cookie, title_cookie,
131                                   class_cookie, leader_cookie;
132         uint32_t mask = 0;
133         uint32_t values[3];
134         uint16_t original_height = height;
135         bool map_frame = true;
136
137         /* We are interested in property changes */
138         mask = XCB_CW_EVENT_MASK;
139         values[0] = CHILD_EVENT_MASK;
140         xcb_change_window_attributes(conn, child, mask, values);
141
142         /* Place requests for properties ASAP */
143         wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
144         strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
145         state_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STATE], UINT32_MAX);
146         utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_NAME], 128);
147         leader_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[WM_CLIENT_LEADER], UINT32_MAX);
148         title_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_NAME, 128);
149         class_cookie = xcb_get_any_property_unchecked(conn, false, child, WM_CLASS, 128);
150
151         Client *new = table_get(&by_child, child);
152
153         /* Events for already managed windows should already be filtered in manage_window() */
154         assert(new == NULL);
155
156         LOG("Reparenting window 0x%08x\n", child);
157         LOG("x = %d, y = %d, width = %d, height = %d\n", x, y, width, height);
158         new = calloc(sizeof(Client), 1);
159         new->force_reconfigure = true;
160
161         /* Update the data structures */
162         Client *old_focused = CUR_CELL->currently_focused;
163
164         new->container = CUR_CELL;
165         new->workspace = new->container->workspace;
166
167         /* Minimum useful size for managed windows is 75x50 (primarily affects floating) */
168         width = max(width, 75);
169         height = max(height, 50);
170
171         new->frame = xcb_generate_id(conn);
172         new->child = child;
173         new->rect.width = width;
174         new->rect.height = height;
175         new->width_increment = 1;
176         new->height_increment = 1;
177         /* Pre-initialize the values for floating */
178         new->floating_rect.x = -1;
179         new->floating_rect.width = width;
180         new->floating_rect.height = height;
181
182         mask = 0;
183
184         /* Don’t generate events for our new window, it should *not* be managed */
185         mask |= XCB_CW_OVERRIDE_REDIRECT;
186         values[0] = 1;
187
188         /* We want to know when… */
189         mask |= XCB_CW_EVENT_MASK;
190         values[1] = FRAME_EVENT_MASK;
191
192         i3Font *font = load_font(conn, config.font);
193         width = min(width, c_ws->rect.x + c_ws->rect.width);
194         height = min(height, c_ws->rect.y + c_ws->rect.height);
195
196         Rect framerect = {x, y,
197                           width + 2 + 2,                  /* 2 px border at each side */
198                           height + 2 + 2 + font->height}; /* 2 px border plus font’s height */
199
200         /* Yo dawg, I heard you like windows, so I create a window around your window… */
201         new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_CURSOR_LEFT_PTR, false, mask, values);
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         xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS,
233                         XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE,
234                         3 /* right mouse button */,
235                         XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */);
236
237         /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
238         xcb_atom_t *atom;
239         xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
240         if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
241                 for (int i = 0; i < xcb_get_property_value_length(preply); i++)
242                         if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
243                                 LOG("Window is a dock.\n");
244                                 new->dock = true;
245                                 new->borderless = true;
246                                 new->titlebar_position = TITLEBAR_OFF;
247                                 new->force_reconfigure = true;
248                                 new->container = NULL;
249                                 SLIST_INSERT_HEAD(&(c_ws->screen->dock_clients), new, dock_clients);
250                                 /* If it’s a dock we can’t make it float, so we break */
251                                 new->floating = FLOATING_AUTO_OFF;
252                                 break;
253                         } else if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DIALOG] ||
254                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_UTILITY] ||
255                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_TOOLBAR] ||
256                                    atom[i] == atoms[_NET_WM_WINDOW_TYPE_SPLASH]) {
257                                 /* Set the dialog window to automatically floating, will be used below */
258                                 new->floating = FLOATING_AUTO_ON;
259                                 LOG("dialog/utility/toolbar/splash window, automatically floating\n");
260                         }
261         }
262
263         /* All clients which have a leader should be floating */
264         if (!new->dock && !client_is_floating(new) && new->leader != 0) {
265                 LOG("Client has WM_CLIENT_LEADER hint set, setting floating\n");
266                 new->floating = FLOATING_AUTO_ON;
267         }
268
269         if (new->workspace->auto_float) {
270                 new->floating = FLOATING_AUTO_ON;
271                 LOG("workspace is in autofloat mode, setting floating\n");
272         }
273
274         if (new->dock) {
275                 /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
276                 uint32_t *strut;
277                 preply = xcb_get_property_reply(conn, strut_cookie, NULL);
278                 if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
279                         /* We only use a subset of the provided values, namely the reserved space at the top/bottom
280                            of the screen. This is because the only possibility for bars is at to be at the top/bottom
281                            with maximum horizontal size.
282                            TODO: bars at the top */
283                         new->desired_height = strut[3];
284                         if (new->desired_height == 0) {
285                                 LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", original_height);
286                                 new->desired_height = original_height;
287                         }
288                         LOG("the client wants to be %d pixels high\n", new->desired_height);
289                 } else {
290                         LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", original_height);
291                         new->desired_height = original_height;
292                 }
293         } else {
294                 /* If it’s not a dock, we can check on which workspace we should put it. */
295
296                 /* Firstly, we need to get the window’s class / title. We asked for the properties at the
297                  * top of this function, get them now and pass them to our callback function for window class / title
298                  * changes. It is important that the client was already inserted into the by_child table,
299                  * because the callbacks won’t work otherwise. */
300                 preply = xcb_get_property_reply(conn, utf8_title_cookie, NULL);
301                 handle_windowname_change(NULL, conn, 0, new->child, atoms[_NET_WM_NAME], preply);
302
303                 preply = xcb_get_property_reply(conn, title_cookie, NULL);
304                 handle_windowname_change_legacy(NULL, conn, 0, new->child, WM_NAME, preply);
305
306                 preply = xcb_get_property_reply(conn, class_cookie, NULL);
307                 handle_windowclass_change(NULL, conn, 0, new->child, WM_CLASS, preply);
308
309                 preply = xcb_get_property_reply(conn, leader_cookie, NULL);
310                 handle_clientleader_change(NULL, conn, 0, new->child, atoms[WM_CLIENT_LEADER], preply);
311
312                 struct Assignment *assign;
313                 TAILQ_FOREACH(assign, &assignments, assignments) {
314                         if (get_matching_client(conn, assign->windowclass_title, new) == NULL)
315                                 continue;
316
317                         if (assign->floating == ASSIGN_FLOATING_ONLY ||
318                             assign->floating == ASSIGN_FLOATING) {
319                                 new->floating = FLOATING_AUTO_ON;
320                                 LOG("Assignment matches, putting client into floating mode\n");
321                                 if (assign->floating == ASSIGN_FLOATING_ONLY)
322                                         break;
323                         }
324
325                         LOG("Assignment \"%s\" matches, so putting it on workspace %d\n",
326                             assign->windowclass_title, assign->workspace);
327
328                         if (c_ws->screen->current_workspace == (assign->workspace-1)) {
329                                 LOG("We are already there, no need to do anything\n");
330                                 break;
331                         }
332
333                         LOG("Changing container/workspace and unmapping the client\n");
334                         Workspace *t_ws = &(workspaces[assign->workspace-1]);
335                         workspace_initialize(t_ws, c_ws->screen);
336
337                         new->container = t_ws->table[t_ws->current_col][t_ws->current_row];
338                         new->workspace = t_ws;
339                         old_focused = new->container->currently_focused;
340
341                         map_frame = workspace_is_visible(t_ws);
342                         break;
343                 }
344         }
345
346         if (CUR_CELL->workspace->fullscreen_client != NULL) {
347                 if (new->container == CUR_CELL) {
348                         /* If we are in fullscreen, we should lower the window to not be annoying */
349                         uint32_t values[] = { XCB_STACK_MODE_BELOW };
350                         xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
351                 }
352         }
353
354         /* Insert into the currently active container, if it’s not a dock window */
355         if (!new->dock && !client_is_floating(new)) {
356                 /* Insert after the old active client, if existing. If it does not exist, the
357                    container is empty and it does not matter, where we insert it */
358                 if (old_focused != NULL && !old_focused->dock)
359                         CIRCLEQ_INSERT_AFTER(&(new->container->clients), old_focused, new, clients);
360                 else CIRCLEQ_INSERT_TAIL(&(new->container->clients), new, clients);
361
362                 if (new->container->workspace->fullscreen_client != NULL)
363                         SLIST_INSERT_AFTER(new->container->workspace->fullscreen_client, new, focus_clients);
364                 else SLIST_INSERT_HEAD(&(new->container->workspace->focus_stack), new, focus_clients);
365
366                 client_set_below_floating(conn, new);
367         }
368
369         if (client_is_floating(new)) {
370                 SLIST_INSERT_HEAD(&(new->workspace->focus_stack), new, focus_clients);
371
372                 /* Add the client to the list of floating clients for its workspace */
373                 TAILQ_INSERT_TAIL(&(new->workspace->floating_clients), new, floating_clients);
374
375                 new->container = NULL;
376
377                 new->rect.width = new->floating_rect.width + 2 + 2;
378                 new->rect.height = new->floating_rect.height + (font->height + 2 + 2) + 2;
379
380                 /* Some clients (like GIMP’s color picker window) get mapped
381                  * to (0, 0), so we push them to a reasonable position
382                  * (centered over their leader) */
383                 if (new->leader != 0 && x == 0 && y == 0) {
384                         LOG("Floating client wants to (0x0), moving it over its leader instead\n");
385                         Client *leader = table_get(&by_child, new->leader);
386                         if (leader == NULL) {
387                                 LOG("leader is NULL, centering it over current workspace\n");
388
389                                 x = c_ws->rect.x + (c_ws->rect.width / 2) - (new->rect.width / 2);
390                                 y = c_ws->rect.y + (c_ws->rect.height / 2) - (new->rect.height / 2);
391                         } else {
392                                 x = leader->rect.x + (leader->rect.width / 2) - (new->rect.width / 2);
393                                 y = leader->rect.y + (leader->rect.height / 2) - (new->rect.height / 2);
394                         }
395                 }
396                 new->floating_rect.x = new->rect.x = x;
397                 new->floating_rect.y = new->rect.y = y;
398                 LOG("copying floating_rect from tiling (%d, %d) size (%d, %d)\n",
399                                 new->floating_rect.x, new->floating_rect.y,
400                                 new->floating_rect.width, new->floating_rect.height);
401                 LOG("outer rect (%d, %d) size (%d, %d)\n",
402                                 new->rect.x, new->rect.y, new->rect.width, new->rect.height);
403
404                 /* Make sure it is on top of the other windows */
405                 xcb_raise_window(conn, new->frame);
406                 reposition_client(conn, new);
407                 resize_client(conn, new);
408                 /* redecorate_window flushes */
409                 redecorate_window(conn, new);
410         }
411
412         new->initialized = true;
413
414         /* Check if the window already got the fullscreen hint set */
415         xcb_atom_t *state;
416         if ((preply = xcb_get_property_reply(conn, state_cookie, NULL)) != NULL &&
417             (state = xcb_get_property_value(preply)) != NULL)
418                 /* Check all set _NET_WM_STATEs */
419                 for (int i = 0; i < xcb_get_property_value_length(preply); i++) {
420                         if (state[i] != atoms[_NET_WM_STATE_FULLSCREEN])
421                                 continue;
422                         /* If the window got the fullscreen state, we just toggle fullscreen
423                            and don’t event bother to redraw the layout – that would not change
424                            anything anyways */
425                         client_toggle_fullscreen(conn, new);
426                         goto map;
427                 }
428
429         render_layout(conn);
430
431 map:
432         /* Map the window first to avoid flickering */
433         xcb_map_window(conn, child);
434         if (map_frame)
435                 client_map(conn, new);
436
437         if ((CUR_CELL->workspace->fullscreen_client == NULL || new->fullscreen) && !new->dock) {
438                 /* Focus the new window if we’re not in fullscreen mode and if it is not a dock window */
439                 if ((new->workspace->fullscreen_client == NULL) || new->fullscreen) {
440                         if (!client_is_floating(new))
441                                 new->container->currently_focused = new;
442                         if (new->container == CUR_CELL || client_is_floating(new))
443                                 xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
444                 }
445         }
446
447         xcb_flush(conn);
448 }