]> git.sur5r.net Git - i3/i3/blob - src/ewmh.c
a26eef39c491931033fb932a6bfbe4bf54d41593
[i3/i3] / src / ewmh.c
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
6  *
7  * ewmh.c: Get/set certain EWMH properties easily.
8  *
9  */
10 #include "all.h"
11
12 xcb_window_t ewmh_window;
13
14 #define FOREACH_NONINTERNAL                                             \
15     TAILQ_FOREACH(output, &(croot->nodes_head), nodes)                  \
16     TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) \
17     if (!con_is_internal(ws))
18
19 /*
20  * Updates _NET_CURRENT_DESKTOP with the current desktop number.
21  *
22  * EWMH: The index of the current desktop. This is always an integer between 0
23  * and _NET_NUMBER_OF_DESKTOPS - 1.
24  *
25  */
26 void ewmh_update_current_desktop(void) {
27     static uint32_t old_idx = NET_WM_DESKTOP_NONE;
28     const uint32_t idx = ewmh_get_workspace_index(focused);
29
30     if (idx == old_idx || idx == NET_WM_DESKTOP_NONE) {
31         return;
32     }
33     old_idx = idx;
34
35     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_CURRENT_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &idx);
36 }
37
38 /*
39  * Updates _NET_NUMBER_OF_DESKTOPS which we interpret as the number of
40  * noninternal workspaces.
41  */
42 void ewmh_update_number_of_desktops(void) {
43     Con *output, *ws;
44     static uint32_t old_idx = 0;
45     uint32_t idx = 0;
46
47     FOREACH_NONINTERNAL {
48         idx++;
49     };
50
51     if (idx == old_idx) {
52         return;
53     }
54     old_idx = idx;
55
56     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
57                         A__NET_NUMBER_OF_DESKTOPS, XCB_ATOM_CARDINAL, 32, 1, &idx);
58 }
59
60 /*
61  * Updates _NET_DESKTOP_NAMES: "The names of all virtual desktops. This is a
62  * list of NULL-terminated strings in UTF-8 encoding"
63  */
64 void ewmh_update_desktop_names(void) {
65     Con *output, *ws;
66     int msg_length = 0;
67
68     /* count the size of the property message to set */
69     FOREACH_NONINTERNAL {
70         msg_length += strlen(ws->name) + 1;
71     };
72
73     char desktop_names[msg_length];
74     int current_position = 0;
75
76     /* fill the buffer with the names of the i3 workspaces */
77     FOREACH_NONINTERNAL {
78         for (size_t i = 0; i < strlen(ws->name) + 1; i++) {
79             desktop_names[current_position++] = ws->name[i];
80         }
81     }
82
83     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
84                         A__NET_DESKTOP_NAMES, A_UTF8_STRING, 8, msg_length, desktop_names);
85 }
86
87 /*
88  * Updates _NET_DESKTOP_VIEWPORT, which is an array of pairs of cardinals that
89  * define the top left corner of each desktop's viewport.
90  */
91 void ewmh_update_desktop_viewport(void) {
92     Con *output, *ws;
93     int num_desktops = 0;
94     /* count number of desktops */
95     FOREACH_NONINTERNAL {
96         num_desktops++;
97     }
98
99     uint32_t viewports[num_desktops * 2];
100
101     int current_position = 0;
102     /* fill the viewport buffer */
103     FOREACH_NONINTERNAL {
104         viewports[current_position++] = output->rect.x;
105         viewports[current_position++] = output->rect.y;
106     }
107
108     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
109                         A__NET_DESKTOP_VIEWPORT, XCB_ATOM_CARDINAL, 32, current_position, &viewports);
110 }
111
112 static void ewmh_update_wm_desktop_recursively(Con *con, const uint32_t desktop) {
113     Con *child;
114
115     /* Recursively call this to descend through the entire subtree. */
116     TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
117         ewmh_update_wm_desktop_recursively(child, desktop);
118     }
119
120     /* If con is a workspace, we also need to go through the floating windows on it. */
121     if (con->type == CT_WORKSPACE) {
122         TAILQ_FOREACH(child, &(con->floating_head), floating_windows) {
123             ewmh_update_wm_desktop_recursively(child, desktop);
124         }
125     }
126
127     if (!con_has_managed_window(con))
128         return;
129
130     uint32_t wm_desktop = desktop;
131     /* Sticky windows are only actually sticky when they are floating or inside
132      * a floating container. This is technically still slightly wrong, since
133      * sticky windows will only be on all workspaces on this output, but we
134      * ignore multi-monitor situations for this since the spec isn't too
135      * precise on this anyway. */
136     if (con_is_sticky(con) && con_is_floating(con)) {
137         wm_desktop = NET_WM_DESKTOP_ALL;
138     }
139
140     /* If the window is on the scratchpad we assign the sticky value to it
141      * since showing it works on any workspace. We cannot remove the property
142      * as per specification. */
143     Con *ws = con_get_workspace(con);
144     if (ws != NULL && con_is_internal(ws)) {
145         wm_desktop = NET_WM_DESKTOP_ALL;
146     }
147
148     /* If this is the cached value, we don't need to do anything. */
149     if (con->window->wm_desktop == wm_desktop)
150         return;
151     con->window->wm_desktop = wm_desktop;
152
153     const xcb_window_t window = con->window->id;
154     if (wm_desktop != NET_WM_DESKTOP_NONE) {
155         DLOG("Setting _NET_WM_DESKTOP = %d for window 0x%08x.\n", wm_desktop, window);
156         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, A__NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &wm_desktop);
157     } else {
158         /* If we can't determine the workspace index, delete the property. We'd
159          * rather not set it than lie. */
160         ELOG("Failed to determine the proper EWMH desktop index for window 0x%08x, deleting _NET_WM_DESKTOP.\n", window);
161         xcb_delete_property(conn, window, A__NET_WM_DESKTOP);
162     }
163 }
164
165 /*
166  * Updates _NET_WM_DESKTOP for all windows.
167  * A request will only be made if the cached value differs from the calculated value.
168  *
169  */
170 void ewmh_update_wm_desktop(void) {
171     uint32_t desktop = 0;
172
173     Con *output;
174     TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
175         Con *workspace;
176         TAILQ_FOREACH(workspace, &(output_get_content(output)->nodes_head), nodes) {
177             ewmh_update_wm_desktop_recursively(workspace, desktop);
178
179             if (!con_is_internal(workspace)) {
180                 ++desktop;
181             }
182         }
183     }
184 }
185
186 /*
187  * Updates _NET_ACTIVE_WINDOW with the currently focused window.
188  *
189  * EWMH: The window ID of the currently active window or None if no window has
190  * the focus.
191  *
192  */
193 void ewmh_update_active_window(xcb_window_t window) {
194     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
195                         A__NET_ACTIVE_WINDOW, XCB_ATOM_WINDOW, 32, 1, &window);
196 }
197
198 /*
199  * Updates _NET_WM_VISIBLE_NAME.
200  *
201  */
202 void ewmh_update_visible_name(xcb_window_t window, const char *name) {
203     if (name != NULL) {
204         xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, A__NET_WM_VISIBLE_NAME, A_UTF8_STRING, 8, strlen(name), name);
205     } else {
206         xcb_delete_property(conn, window, A__NET_WM_VISIBLE_NAME);
207     }
208 }
209
210 /*
211  * i3 currently does not support _NET_WORKAREA, because it does not correspond
212  * to i3’s concept of workspaces. See also:
213  * https://bugs.i3wm.org/539
214  * https://bugs.i3wm.org/301
215  * https://bugs.i3wm.org/1038
216  *
217  * We need to actively delete this property because some display managers (e.g.
218  * LightDM) set it.
219  *
220  * EWMH: Contains a geometry for each desktop. These geometries specify an area
221  * that is completely contained within the viewport. Work area SHOULD be used by
222  * desktop applications to place desktop icons appropriately.
223  *
224  */
225 void ewmh_update_workarea(void) {
226     xcb_delete_property(conn, root, A__NET_WORKAREA);
227 }
228
229 /*
230  * Updates the _NET_CLIENT_LIST hint.
231  *
232  */
233 void ewmh_update_client_list(xcb_window_t *list, int num_windows) {
234     xcb_change_property(
235         conn,
236         XCB_PROP_MODE_REPLACE,
237         root,
238         A__NET_CLIENT_LIST,
239         XCB_ATOM_WINDOW,
240         32,
241         num_windows,
242         list);
243 }
244
245 /*
246  * Updates the _NET_CLIENT_LIST_STACKING hint.
247  *
248  */
249 void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows) {
250     xcb_change_property(
251         conn,
252         XCB_PROP_MODE_REPLACE,
253         root,
254         A__NET_CLIENT_LIST_STACKING,
255         XCB_ATOM_WINDOW,
256         32,
257         num_windows,
258         stack);
259 }
260
261 /*
262  * Set or remove _NET_WM_STATE_STICKY on the window.
263  *
264  */
265 void ewmh_update_sticky(xcb_window_t window, bool sticky) {
266     if (sticky) {
267         DLOG("Setting _NET_WM_STATE_STICKY for window = %d.\n", window);
268         xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY);
269     } else {
270         DLOG("Removing _NET_WM_STATE_STICKY for window = %d.\n", window);
271         xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY);
272     }
273 }
274
275 /*
276  * Set or remove _NEW_WM_STATE_FOCUSED on the window.
277  *
278  */
279 void ewmh_update_focused(xcb_window_t window, bool is_focused) {
280     if (is_focused) {
281         DLOG("Setting _NET_WM_STATE_FOCUSED for window = %d.\n", window);
282         xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
283     } else {
284         DLOG("Removing _NET_WM_STATE_FOCUSED for window = %d.\n", window);
285         xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
286     }
287 }
288
289 /*
290  * Set up the EWMH hints on the root window.
291  *
292  */
293 void ewmh_setup_hints(void) {
294     xcb_atom_t supported_atoms[] = {
295 #define xmacro(atom) A_##atom,
296 #include "atoms_NET_SUPPORTED.xmacro"
297 #undef xmacro
298     };
299
300     /* Set up the window manager’s name. According to EWMH, section "Root Window
301      * Properties", to indicate that an EWMH-compliant window manager is
302      * present, a child window has to be created (and kept alive as long as the
303      * window manager is running) which has the _NET_SUPPORTING_WM_CHECK and
304      * _NET_WM_ATOMS. */
305     ewmh_window = xcb_generate_id(conn);
306     /* We create the window and put it at (-1, -1) so that it is off-screen. */
307     xcb_create_window(
308         conn,
309         XCB_COPY_FROM_PARENT,        /* depth */
310         ewmh_window,                 /* window id */
311         root,                        /* parent */
312         -1, -1, 1, 1,                /* dimensions (x, y, w, h) */
313         0,                           /* border */
314         XCB_WINDOW_CLASS_INPUT_ONLY, /* window class */
315         XCB_COPY_FROM_PARENT,        /* visual */
316         XCB_CW_OVERRIDE_REDIRECT,
317         (uint32_t[]){1});
318     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, ewmh_window, A__NET_SUPPORTING_WM_CHECK, XCB_ATOM_WINDOW, 32, 1, &ewmh_window);
319     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, ewmh_window, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3");
320     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTING_WM_CHECK, XCB_ATOM_WINDOW, 32, 1, &ewmh_window);
321
322     /* I’m not entirely sure if we need to keep _NET_WM_NAME on root. */
323     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3");
324
325     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, /* number of atoms */ sizeof(supported_atoms) / sizeof(xcb_atom_t), supported_atoms);
326
327     /* We need to map this window to be able to set the input focus to it if no other window is available to be focused. */
328     xcb_map_window(conn, ewmh_window);
329     xcb_configure_window(conn, ewmh_window, XCB_CONFIG_WINDOW_STACK_MODE, (uint32_t[]){XCB_STACK_MODE_BELOW});
330 }
331
332 /*
333  * Returns the workspace container as enumerated by the EWMH desktop model.
334  * Returns NULL if no workspace could be found for the index.
335  *
336  * This is the reverse of ewmh_get_workspace_index.
337  *
338  */
339 Con *ewmh_get_workspace_by_index(uint32_t idx) {
340     if (idx == NET_WM_DESKTOP_NONE)
341         return NULL;
342
343     uint32_t current_index = 0;
344
345     Con *output, *ws;
346     FOREACH_NONINTERNAL {
347         if (current_index == idx) {
348             return ws;
349         }
350         current_index++;
351     }
352
353     return NULL;
354 }
355
356 /*
357  * Returns the EWMH desktop index for the workspace the given container is on.
358  * Returns NET_WM_DESKTOP_NONE if the desktop index cannot be determined.
359  *
360  * This is the reverse of ewmh_get_workspace_by_index.
361  *
362  */
363 uint32_t ewmh_get_workspace_index(Con *con) {
364     uint32_t index = 0;
365
366     Con *target_workspace = con_get_workspace(con);
367     Con *output, *ws;
368     FOREACH_NONINTERNAL {
369         if (ws == target_workspace) {
370             return index;
371         }
372
373         index++;
374     }
375
376     return NET_WM_DESKTOP_NONE;
377 }