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