]> git.sur5r.net Git - i3/i3/blob - src/window.c
Merge pull request #2148 from Airblader/feature-2120-memleaks
[i3/i3] / src / window.c
1 #undef I3__FILE__
2 #define I3__FILE__ "window.c"
3 /*
4  * vim:ts=4:sw=4:expandtab
5  *
6  * i3 - an improved dynamic tiling window manager
7  * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
8  *
9  * window.c: Updates window attributes (X11 hints/properties).
10  *
11  */
12 #include "all.h"
13
14 /*
15  * Updates the WM_CLASS (consisting of the class and instance) for the
16  * given window.
17  *
18  */
19 void window_update_class(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
20     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
21         DLOG("WM_CLASS not set.\n");
22         FREE(prop);
23         return;
24     }
25
26     /* We cannot use asprintf here since this property contains two
27      * null-terminated strings (for compatibility reasons). Instead, we
28      * use strdup() on both strings */
29     const size_t prop_length = xcb_get_property_value_length(prop);
30     char *new_class = xcb_get_property_value(prop);
31     const size_t class_class_index = strnlen(new_class, prop_length) + 1;
32
33     FREE(win->class_instance);
34     FREE(win->class_class);
35
36     win->class_instance = sstrndup(new_class, prop_length);
37     if (class_class_index < prop_length)
38         win->class_class = sstrndup(new_class + class_class_index, prop_length - class_class_index);
39     else
40         win->class_class = NULL;
41     LOG("WM_CLASS changed to %s (instance), %s (class)\n",
42         win->class_instance, win->class_class);
43
44     if (before_mgmt) {
45         free(prop);
46         return;
47     }
48
49     run_assignments(win);
50
51     free(prop);
52 }
53
54 /*
55  * Updates the name by using _NET_WM_NAME (encoded in UTF-8) for the given
56  * window. Further updates using window_update_name_legacy will be ignored.
57  *
58  */
59 void window_update_name(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
60     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
61         DLOG("_NET_WM_NAME not specified, not changing\n");
62         FREE(prop);
63         return;
64     }
65
66     i3string_free(win->name);
67     win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop),
68                                                xcb_get_property_value_length(prop));
69
70     if (win->title_format != NULL) {
71         i3String *name = window_parse_title_format(win);
72         ewmh_update_visible_name(win->id, i3string_as_utf8(name));
73         I3STRING_FREE(name);
74     }
75     win->name_x_changed = true;
76     LOG("_NET_WM_NAME changed to \"%s\"\n", i3string_as_utf8(win->name));
77
78     win->uses_net_wm_name = true;
79
80     if (before_mgmt) {
81         free(prop);
82         return;
83     }
84
85     run_assignments(win);
86
87     free(prop);
88 }
89
90 /*
91  * Updates the name by using WM_NAME (encoded in COMPOUND_TEXT). We do not
92  * touch what the client sends us but pass it to xcb_image_text_8. To get
93  * proper unicode rendering, the application has to use _NET_WM_NAME (see
94  * window_update_name()).
95  *
96  */
97 void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
98     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
99         DLOG("WM_NAME not set (_NET_WM_NAME is what you want anyways).\n");
100         FREE(prop);
101         return;
102     }
103
104     /* ignore update when the window is known to already have a UTF-8 name */
105     if (win->uses_net_wm_name) {
106         free(prop);
107         return;
108     }
109
110     i3string_free(win->name);
111     win->name = i3string_from_utf8_with_length(xcb_get_property_value(prop),
112                                                xcb_get_property_value_length(prop));
113     if (win->title_format != NULL) {
114         i3String *name = window_parse_title_format(win);
115         ewmh_update_visible_name(win->id, i3string_as_utf8(name));
116         I3STRING_FREE(name);
117     }
118
119     LOG("WM_NAME changed to \"%s\"\n", i3string_as_utf8(win->name));
120     LOG("Using legacy window title. Note that in order to get Unicode window "
121         "titles in i3, the application has to set _NET_WM_NAME (UTF-8)\n");
122
123     win->name_x_changed = true;
124
125     if (before_mgmt) {
126         free(prop);
127         return;
128     }
129
130     run_assignments(win);
131
132     free(prop);
133 }
134
135 /*
136  * Updates the CLIENT_LEADER (logical parent window).
137  *
138  */
139 void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop) {
140     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
141         DLOG("CLIENT_LEADER not set on window 0x%08x.\n", win->id);
142         win->leader = XCB_NONE;
143         FREE(prop);
144         return;
145     }
146
147     xcb_window_t *leader = xcb_get_property_value(prop);
148     if (leader == NULL) {
149         free(prop);
150         return;
151     }
152
153     DLOG("Client leader changed to %08x\n", *leader);
154
155     win->leader = *leader;
156
157     free(prop);
158 }
159
160 /*
161  * Updates the TRANSIENT_FOR (logical parent window).
162  *
163  */
164 void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop) {
165     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
166         DLOG("TRANSIENT_FOR not set on window 0x%08x.\n", win->id);
167         win->transient_for = XCB_NONE;
168         FREE(prop);
169         return;
170     }
171
172     xcb_window_t transient_for;
173     if (!xcb_icccm_get_wm_transient_for_from_reply(&transient_for, prop)) {
174         free(prop);
175         return;
176     }
177
178     DLOG("Transient for changed to 0x%08x (window 0x%08x)\n", transient_for, win->id);
179
180     win->transient_for = transient_for;
181
182     free(prop);
183 }
184
185 /*
186  * Updates the _NET_WM_STRUT_PARTIAL (reserved pixels at the screen edges)
187  *
188  */
189 void window_update_strut_partial(i3Window *win, xcb_get_property_reply_t *prop) {
190     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
191         DLOG("_NET_WM_STRUT_PARTIAL not set.\n");
192         FREE(prop);
193         return;
194     }
195
196     uint32_t *strut;
197     if (!(strut = xcb_get_property_value(prop))) {
198         free(prop);
199         return;
200     }
201
202     DLOG("Reserved pixels changed to: left = %d, right = %d, top = %d, bottom = %d\n",
203          strut[0], strut[1], strut[2], strut[3]);
204
205     win->reserved = (struct reservedpx){strut[0], strut[1], strut[2], strut[3]};
206
207     free(prop);
208 }
209
210 /*
211  * Updates the WM_WINDOW_ROLE
212  *
213  */
214 void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool before_mgmt) {
215     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
216         DLOG("WM_WINDOW_ROLE not set.\n");
217         FREE(prop);
218         return;
219     }
220
221     char *new_role;
222     sasprintf(&new_role, "%.*s", xcb_get_property_value_length(prop),
223               (char *)xcb_get_property_value(prop));
224     FREE(win->role);
225     win->role = new_role;
226     LOG("WM_WINDOW_ROLE changed to \"%s\"\n", win->role);
227
228     if (before_mgmt) {
229         free(prop);
230         return;
231     }
232
233     run_assignments(win);
234
235     free(prop);
236 }
237
238 /*
239  * Updates the _NET_WM_WINDOW_TYPE property.
240  *
241  */
242 void window_update_type(i3Window *window, xcb_get_property_reply_t *reply) {
243     xcb_atom_t new_type = xcb_get_preferred_window_type(reply);
244     if (new_type == XCB_NONE) {
245         DLOG("cannot read _NET_WM_WINDOW_TYPE from window.\n");
246         return;
247     }
248
249     window->window_type = new_type;
250     LOG("_NET_WM_WINDOW_TYPE changed to %i.\n", window->window_type);
251
252     run_assignments(window);
253 }
254
255 /*
256  * Updates the WM_HINTS (we only care about the input focus handling part).
257  *
258  */
259 void window_update_hints(i3Window *win, xcb_get_property_reply_t *prop, bool *urgency_hint) {
260     if (urgency_hint != NULL)
261         *urgency_hint = false;
262
263     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
264         DLOG("WM_HINTS not set.\n");
265         FREE(prop);
266         return;
267     }
268
269     xcb_icccm_wm_hints_t hints;
270
271     if (!xcb_icccm_get_wm_hints_from_reply(&hints, prop)) {
272         DLOG("Could not get WM_HINTS\n");
273         free(prop);
274         return;
275     }
276
277     if (hints.flags & XCB_ICCCM_WM_HINT_INPUT) {
278         win->doesnt_accept_focus = !hints.input;
279         LOG("WM_HINTS.input changed to \"%d\"\n", hints.input);
280     }
281
282     if (urgency_hint != NULL)
283         *urgency_hint = (xcb_icccm_wm_hints_get_urgency(&hints) != 0);
284
285     free(prop);
286 }
287
288 /*
289  * Updates the MOTIF_WM_HINTS. The container's border style should be set to
290  * `motif_border_style' if border style is not BS_NORMAL.
291  *
292  * i3 only uses this hint when it specifies a window should have no
293  * title bar, or no decorations at all, which is how most window managers
294  * handle it.
295  *
296  * The EWMH spec intended to replace Motif hints with _NET_WM_WINDOW_TYPE, but
297  * it is still in use by popular widget toolkits such as GTK+ and Java AWT.
298  *
299  */
300 void window_update_motif_hints(i3Window *win, xcb_get_property_reply_t *prop, border_style_t *motif_border_style) {
301 /* This implementation simply mirrors Gnome's Metacity. Official
302      * documentation of this hint is nowhere to be found.
303      * For more information see:
304      * https://people.gnome.org/~tthurman/docs/metacity/xprops_8h-source.html
305      * http://stackoverflow.com/questions/13787553/detect-if-a-x11-window-has-decorations
306      */
307 #define MWM_HINTS_DECORATIONS (1 << 1)
308 #define MWM_DECOR_ALL (1 << 0)
309 #define MWM_DECOR_BORDER (1 << 1)
310 #define MWM_DECOR_TITLE (1 << 3)
311
312     if (motif_border_style != NULL)
313         *motif_border_style = BS_NORMAL;
314
315     if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
316         FREE(prop);
317         return;
318     }
319
320     /* The property consists of an array of 5 uint64_t's. The first value is a bit
321      * mask of what properties the hint will specify. We are only interested in
322      * MWM_HINTS_DECORATIONS because it indicates that the second value of the
323      * array tells us which decorations the window should have, each flag being
324      * a particular decoration. */
325     uint64_t *motif_hints = (uint64_t *)xcb_get_property_value(prop);
326
327     if (motif_border_style != NULL && motif_hints[0] & MWM_HINTS_DECORATIONS) {
328         if (motif_hints[1] & MWM_DECOR_ALL || motif_hints[1] & MWM_DECOR_TITLE)
329             *motif_border_style = BS_NORMAL;
330         else if (motif_hints[1] & MWM_DECOR_BORDER)
331             *motif_border_style = BS_PIXEL;
332         else
333             *motif_border_style = BS_NONE;
334     }
335
336     FREE(prop);
337
338 #undef MWM_HINTS_DECORATIONS
339 #undef MWM_DECOR_ALL
340 #undef MWM_DECOR_BORDER
341 #undef MWM_DECOR_TITLE
342 }
343
344 /*
345  * Returns the window title considering the current title format.
346  * If no format is set, this will simply return the window's name.
347  *
348  */
349 i3String *window_parse_title_format(i3Window *win) {
350     char *format = win->title_format;
351     if (format == NULL)
352         return i3string_copy(win->name);
353
354     /* We initialize these lazily so we only escape them if really necessary. */
355     char *escaped_title = NULL;
356     char *escaped_class = NULL;
357     char *escaped_instance = NULL;
358
359     /* We have to first iterate over the string to see how much buffer space
360      * we need to allocate. */
361     int buffer_len = strlen(format) + 1;
362     for (char *walk = format; *walk != '\0'; walk++) {
363         if (STARTS_WITH(walk, "%title")) {
364             if (escaped_title == NULL)
365                 escaped_title = pango_escape_markup(sstrdup((win->name == NULL) ? "" : i3string_as_utf8(win->name)));
366
367             buffer_len = buffer_len - strlen("%title") + strlen(escaped_title);
368             walk += strlen("%title") - 1;
369         } else if (STARTS_WITH(walk, "%class")) {
370             if (escaped_class == NULL)
371                 escaped_class = pango_escape_markup(sstrdup((win->class_class == NULL) ? "" : win->class_class));
372
373             buffer_len = buffer_len - strlen("%class") + strlen(escaped_class);
374             walk += strlen("%class") - 1;
375         } else if (STARTS_WITH(walk, "%instance")) {
376             if (escaped_instance == NULL)
377                 escaped_instance = pango_escape_markup(sstrdup((win->class_instance == NULL) ? "" : win->class_instance));
378
379             buffer_len = buffer_len - strlen("%instance") + strlen(escaped_instance);
380             walk += strlen("%instance") - 1;
381         }
382     }
383
384     /* Now we can parse the format string. */
385     char buffer[buffer_len];
386     char *outwalk = buffer;
387     for (char *walk = format; *walk != '\0'; walk++) {
388         if (*walk != '%') {
389             *(outwalk++) = *walk;
390             continue;
391         }
392
393         if (STARTS_WITH(walk + 1, "title")) {
394             outwalk += sprintf(outwalk, "%s", escaped_title);
395             walk += strlen("title");
396         } else if (STARTS_WITH(walk + 1, "class")) {
397             outwalk += sprintf(outwalk, "%s", escaped_class);
398             walk += strlen("class");
399         } else if (STARTS_WITH(walk + 1, "instance")) {
400             outwalk += sprintf(outwalk, "%s", escaped_instance);
401             walk += strlen("instance");
402         } else {
403             *(outwalk++) = *walk;
404         }
405     }
406     *outwalk = '\0';
407
408     i3String *formatted = i3string_from_utf8(buffer);
409     i3string_set_markup(formatted, font_is_pango());
410
411     FREE(escaped_title);
412     FREE(escaped_class);
413     FREE(escaped_instance);
414
415     return formatted;
416 }