i3
ewmh.c
Go to the documentation of this file.
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
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 uint32_t wm_desktop = desktop;
145 /* Sticky windows are only actually sticky when they are floating or inside
146 * a floating container. This is technically still slightly wrong, since
147 * sticky windows will only be on all workspaces on this output, but we
148 * ignore multi-monitor situations for this since the spec isn't too
149 * precise on this anyway. */
150 if (con_is_sticky(con) && con_is_floating(con)) {
151 wm_desktop = NET_WM_DESKTOP_ALL;
152 }
153
154 /* If the window is on the scratchpad we assign the sticky value to it
155 * since showing it works on any workspace. We cannot remove the property
156 * as per specification. */
157 Con *ws = con_get_workspace(con);
158 if (ws != NULL && con_is_internal(ws)) {
159 wm_desktop = NET_WM_DESKTOP_ALL;
160 }
161
162 /* If this is the cached value, we don't need to do anything. */
163 if (con->window->wm_desktop == wm_desktop)
164 return;
165 con->window->wm_desktop = wm_desktop;
166
167 const xcb_window_t window = con->window->id;
168 if (wm_desktop != NET_WM_DESKTOP_NONE) {
169 DLOG("Setting _NET_WM_DESKTOP = %d for window 0x%08x.\n", wm_desktop, window);
170 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, A__NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &wm_desktop);
171 } else {
172 /* If we can't determine the workspace index, delete the property. We'd
173 * rather not set it than lie. */
174 ELOG("Failed to determine the proper EWMH desktop index for window 0x%08x, deleting _NET_WM_DESKTOP.\n", window);
175 xcb_delete_property(conn, window, A__NET_WM_DESKTOP);
176 }
177}
178
179/*
180 * Updates _NET_WM_DESKTOP for all windows.
181 * A request will only be made if the cached value differs from the calculated value.
182 *
183 */
185 uint32_t desktop = 0;
186
187 Con *output;
188 TAILQ_FOREACH (output, &(croot->nodes_head), nodes) {
189 Con *workspace;
190 TAILQ_FOREACH (workspace, &(output_get_content(output)->nodes_head), nodes) {
191 ewmh_update_wm_desktop_recursively(workspace, desktop);
192
193 if (!con_is_internal(workspace)) {
194 ++desktop;
195 }
196 }
197 }
198}
199
200/*
201 * Updates _NET_ACTIVE_WINDOW with the currently focused window.
202 *
203 * EWMH: The window ID of the currently active window or None if no window has
204 * the focus.
205 *
206 */
207void ewmh_update_active_window(xcb_window_t window) {
208 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
209 A__NET_ACTIVE_WINDOW, XCB_ATOM_WINDOW, 32, 1, &window);
210}
211
212/*
213 * Updates _NET_WM_VISIBLE_NAME.
214 *
215 */
216void ewmh_update_visible_name(xcb_window_t window, const char *name) {
217 if (name != NULL) {
218 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, A__NET_WM_VISIBLE_NAME, A_UTF8_STRING, 8, strlen(name), name);
219 } else {
220 xcb_delete_property(conn, window, A__NET_WM_VISIBLE_NAME);
221 }
222}
223
224/*
225 * i3 currently does not support _NET_WORKAREA, because it does not correspond
226 * to i3’s concept of workspaces. See also:
227 * https://bugs.i3wm.org/539
228 * https://bugs.i3wm.org/301
229 * https://bugs.i3wm.org/1038
230 *
231 * We need to actively delete this property because some display managers (e.g.
232 * LightDM) set it.
233 *
234 * EWMH: Contains a geometry for each desktop. These geometries specify an area
235 * that is completely contained within the viewport. Work area SHOULD be used by
236 * desktop applications to place desktop icons appropriately.
237 *
238 */
240 xcb_delete_property(conn, root, A__NET_WORKAREA);
241}
242
243/*
244 * Updates the _NET_CLIENT_LIST hint.
245 *
246 */
247void ewmh_update_client_list(xcb_window_t *list, int num_windows) {
248 xcb_change_property(
249 conn,
250 XCB_PROP_MODE_REPLACE,
251 root,
252 A__NET_CLIENT_LIST,
253 XCB_ATOM_WINDOW,
254 32,
255 num_windows,
256 list);
257}
258
259/*
260 * Updates the _NET_CLIENT_LIST_STACKING hint.
261 *
262 */
263void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows) {
264 xcb_change_property(
265 conn,
266 XCB_PROP_MODE_REPLACE,
267 root,
268 A__NET_CLIENT_LIST_STACKING,
269 XCB_ATOM_WINDOW,
270 32,
271 num_windows,
272 stack);
273}
274
275/*
276 * Set or remove _NET_WM_STATE_STICKY on the window.
277 *
278 */
279void ewmh_update_sticky(xcb_window_t window, bool sticky) {
280 if (sticky) {
281 DLOG("Setting _NET_WM_STATE_STICKY for window = %08x.\n", window);
282 xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY);
283 } else {
284 DLOG("Removing _NET_WM_STATE_STICKY for window = %08x.\n", window);
285 xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_STICKY);
286 }
287}
288
289/*
290 * Set or remove _NEW_WM_STATE_FOCUSED on the window.
291 *
292 */
293void ewmh_update_focused(xcb_window_t window, bool is_focused) {
294 if (is_focused) {
295 DLOG("Setting _NET_WM_STATE_FOCUSED for window = %08x.\n", window);
296 xcb_add_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
297 } else {
298 DLOG("Removing _NET_WM_STATE_FOCUSED for window = %08x.\n", window);
299 xcb_remove_property_atom(conn, window, A__NET_WM_STATE, A__NET_WM_STATE_FOCUSED);
300 }
301}
302
303/*
304 * Set up the EWMH hints on the root window.
305 *
306 */
308 xcb_atom_t supported_atoms[] = {
309#define xmacro(atom) A_##atom,
311#undef xmacro
312 };
313
314 /* Set up the window manager’s name. According to EWMH, section "Root Window
315 * Properties", to indicate that an EWMH-compliant window manager is
316 * present, a child window has to be created (and kept alive as long as the
317 * window manager is running) which has the _NET_SUPPORTING_WM_CHECK and
318 * _NET_WM_ATOMS. */
319 ewmh_window = xcb_generate_id(conn);
320 /* We create the window and put it at (-1, -1) so that it is off-screen. */
321 xcb_create_window(
322 conn,
323 XCB_COPY_FROM_PARENT, /* depth */
324 ewmh_window, /* window id */
325 root, /* parent */
326 -1, -1, 1, 1, /* dimensions (x, y, w, h) */
327 0, /* border */
328 XCB_WINDOW_CLASS_INPUT_ONLY, /* window class */
329 XCB_COPY_FROM_PARENT, /* visual */
330 XCB_CW_OVERRIDE_REDIRECT,
331 (uint32_t[]){1});
332 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, ewmh_window, A__NET_SUPPORTING_WM_CHECK, XCB_ATOM_WINDOW, 32, 1, &ewmh_window);
333 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, ewmh_window, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3");
334 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTING_WM_CHECK, XCB_ATOM_WINDOW, 32, 1, &ewmh_window);
335
336 /* I’m not entirely sure if we need to keep _NET_WM_NAME on root. */
337 xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3");
338
339 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);
340
341 /* 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. */
342 xcb_map_window(conn, ewmh_window);
343 xcb_configure_window(conn, ewmh_window, XCB_CONFIG_WINDOW_STACK_MODE, (uint32_t[]){XCB_STACK_MODE_BELOW});
344}
345
346/*
347 * Returns the workspace container as enumerated by the EWMH desktop model.
348 * Returns NULL if no workspace could be found for the index.
349 *
350 * This is the reverse of ewmh_get_workspace_index.
351 *
352 */
354 if (idx == NET_WM_DESKTOP_NONE)
355 return NULL;
356
357 uint32_t current_index = 0;
358
359 Con *output, *ws;
361 if (current_index == idx) {
362 return ws;
363 }
364 current_index++;
365 }
366
367 return NULL;
368}
369
370/*
371 * Returns the EWMH desktop index for the workspace the given container is on.
372 * Returns NET_WM_DESKTOP_NONE if the desktop index cannot be determined.
373 *
374 * This is the reverse of ewmh_get_workspace_by_index.
375 *
376 */
378 uint32_t index = 0;
379
380 Con *target_workspace = con_get_workspace(con);
381 Con *output, *ws;
383 if (ws == target_workspace) {
384 return index;
385 }
386
387 index++;
388 }
389
390 return NET_WM_DESKTOP_NONE;
391}
bool con_is_floating(Con *con)
Returns true if the node is floating.
Definition con.c:596
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:369
Con * con_get_workspace(Con *con)
Gets the workspace container this node is on.
Definition con.c:477
bool con_is_internal(Con *con)
Returns true if the container is internal, such as __i3_scratch.
Definition con.c:588
bool con_is_sticky(Con *con)
Returns whether the container or any of its children is sticky.
Definition con.c:426
void ewmh_update_active_window(xcb_window_t window)
Updates _NET_ACTIVE_WINDOW with the currently focused window.
Definition ewmh.c:207
void ewmh_update_client_list(xcb_window_t *list, int num_windows)
Updates the _NET_CLIENT_LIST hint.
Definition ewmh.c:247
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:307
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:377
void ewmh_update_visible_name(xcb_window_t window, const char *name)
Updates _NET_WM_VISIBLE_NAME.
Definition ewmh.c:216
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:293
Con * ewmh_get_workspace_by_index(uint32_t idx)
Returns the workspace container as enumerated by the EWMH desktop model.
Definition ewmh.c:353
void ewmh_update_wm_desktop(void)
Updates _NET_WM_DESKTOP for all windows.
Definition ewmh.c:184
#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:279
void ewmh_update_client_list_stacking(xcb_window_t *stack, int num_windows)
Updates the _NET_CLIENT_LIST_STACKING hint.
Definition ewmh.c:263
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:239
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:214
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:224
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:190
uint32_t y
Definition data.h:191
xcb_window_t id
Definition data.h:429
uint32_t wm_desktop
The _NET_WM_DESKTOP for this window.
Definition data.h:472
A 'Con' represents everything from the X11 root window down to a single X11 window.
Definition data.h:647
enum Con::@18 type
struct Rect rect
Definition data.h:686
struct Window * window
Definition data.h:722
char * name
Definition data.h:696