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