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