+
+ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
+ A__NET_NUMBER_OF_DESKTOPS, XCB_ATOM_CARDINAL, 32, 1, &idx);
+}
+
+/*
+ * Updates _NET_DESKTOP_NAMES: "The names of all virtual desktops. This is a
+ * list of NULL-terminated strings in UTF-8 encoding"
+ */
+void ewmh_update_desktop_names(void) {
+ Con *output;
+ int msg_length = 0;
+
+ /* count the size of the property message to set */
+ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+ Con *ws;
+ TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
+ if (STARTS_WITH(ws->name, "__"))
+ continue;
+ msg_length += strlen(ws->name) + 1;
+ }
+ }
+
+ char desktop_names[msg_length];
+ int current_position = 0;
+
+ /* fill the buffer with the names of the i3 workspaces */
+ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+ Con *ws;
+ TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
+ if (STARTS_WITH(ws->name, "__"))
+ continue;
+
+ for (size_t i = 0; i < strlen(ws->name) + 1; i++) {
+ desktop_names[current_position++] = ws->name[i];
+ }
+ }
+ }
+
+ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
+ A__NET_DESKTOP_NAMES, A_UTF8_STRING, 8, msg_length, desktop_names);
+}
+
+/*
+ * Updates _NET_DESKTOP_VIEWPORT, which is an array of pairs of cardinals that
+ * define the top left corner of each desktop's viewport.
+ */
+void ewmh_update_desktop_viewport(void) {
+ Con *output;
+ int num_desktops = 0;
+ /* count number of desktops */
+ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+ Con *ws;
+ TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
+ if (STARTS_WITH(ws->name, "__"))
+ continue;
+
+ num_desktops++;
+ }
+ }
+
+ uint32_t viewports[num_desktops * 2];
+
+ int current_position = 0;
+ /* fill the viewport buffer */
+ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+ Con *ws;
+ TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) {
+ if (STARTS_WITH(ws->name, "__"))
+ continue;
+
+ viewports[current_position++] = output->rect.x;
+ viewports[current_position++] = output->rect.y;
+ }
+ }
+
+ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root,
+ A__NET_DESKTOP_VIEWPORT, XCB_ATOM_CARDINAL, 32, current_position, &viewports);
+}
+
+static void ewmh_update_wm_desktop_recursively(Con *con, const uint32_t desktop) {
+ /* Recursively call this to descend through the entire subtree. */
+ Con *child;
+ TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
+ ewmh_update_wm_desktop_recursively(child, desktop);
+ }
+ /* If con is a workspace, we also need to go through the floating windows on it. */
+ if (con->type == CT_WORKSPACE) {
+ TAILQ_FOREACH(child, &(con->floating_head), floating_windows) {
+ ewmh_update_wm_desktop_recursively(child, desktop);
+ }
+ }
+
+ if (!con_has_managed_window(con))
+ return;
+
+ const xcb_window_t window = con->window->id;
+
+ uint32_t wm_desktop = desktop;
+ /* Sticky windows are only actually sticky when they are floating or inside
+ * a floating container. This is technically still slightly wrong, since
+ * sticky windows will only be on all workspaces on this output, but we
+ * ignore multi-monitor situations for this since the spec isn't too
+ * precise on this anyway. */
+ if (con_is_sticky(con) && con_is_floating(con)) {
+ wm_desktop = NET_WM_DESKTOP_ALL;
+ }
+
+ /* If this is the cached value, we don't need to do anything. */
+ if (con->window->wm_desktop == wm_desktop)
+ return;
+ con->window->wm_desktop = wm_desktop;
+
+ if (wm_desktop != NET_WM_DESKTOP_NONE) {
+ DLOG("Setting _NET_WM_DESKTOP = %d for window 0x%08x.\n", wm_desktop, window);
+ xcb_change_property(conn, XCB_PROP_MODE_REPLACE, window, A__NET_WM_DESKTOP, XCB_ATOM_CARDINAL, 32, 1, &wm_desktop);
+ } else {
+ /* If we can't determine the workspace index, delete the property. We'd
+ * rather not set it than lie. */
+ ELOG("Failed to determine the proper EWMH desktop index for window 0x%08x, deleting _NET_WM_DESKTOP.\n", window);
+ xcb_delete_property(conn, window, A__NET_WM_DESKTOP);
+ }
+}
+
+/*
+ * Updates _NET_WM_DESKTOP for all windows.
+ * A request will only be made if the cached value differs from the calculated value.
+ *
+ */
+void ewmh_update_wm_desktop(void) {
+ uint32_t desktop = 0;
+
+ Con *output;
+ TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
+ Con *workspace;
+ TAILQ_FOREACH(workspace, &(output_get_content(output)->nodes_head), nodes) {
+ if (con_is_internal(workspace))
+ continue;
+
+ ewmh_update_wm_desktop_recursively(workspace, desktop);
+ ++desktop;
+ }
+ }