]> git.mdlowis.com Git - proto/labwc.git/commitdiff
xwayland: support _NET_WM_ICON
authorConsolatis <35009135+Consolatis@users.noreply.github.com>
Thu, 29 May 2025 17:17:24 +0000 (19:17 +0200)
committerHiroaki Yamamoto <hrak1529@gmail.com>
Wed, 18 Jun 2025 05:31:05 +0000 (14:31 +0900)
meson.build
src/xdg.c
src/xwayland.c

index 41b500b667b8e93752c48ba8d39918620681e8bb..67e378d21d05d18cd03810b98e58382246e8d557 100644 (file)
@@ -63,6 +63,7 @@ wayland_server = dependency('wayland-server', version: '>=1.19.0')
 wayland_protos = dependency('wayland-protocols', version: '>=1.39')
 xkbcommon = dependency('xkbcommon')
 xcb = dependency('xcb', required: get_option('xwayland'))
+xcb_ewmh = dependency('xcb-ewmh', required: get_option('xwayland'))
 xcb_icccm = dependency('xcb-icccm', required: get_option('xwayland'))
 drm_full = dependency('libdrm')
 drm = drm_full.partial_dependency(compile_args: true, includes: true)
@@ -136,6 +137,7 @@ labwc_deps = [
   wayland_server,
   wlroots,
   xkbcommon,
+       xcb_ewmh,
   xcb_icccm,
   xml2,
   glib,
index c2da35b7e8c854db9c3c9d3f14ccca8591cc3b5b..5ff5ccb0142a2256770258427f72b228f52a7ab9 100644 (file)
--- a/src/xdg.c
+++ b/src/xdg.c
@@ -1043,6 +1043,7 @@ handle_xdg_toplevel_icon_set_icon(struct wl_listener *listener, void *data)
                }
        }
 
+       /* view takes ownership of the buffers */
        view_set_icon(view, icon_name, &buffers);
        wl_array_release(&buffers);
 }
index 0185e61ddfc11e06c5017eac4b5fd08e04764da3..a648877ef5aac0f79bbff7782a69f585eab2ad8f 100644 (file)
@@ -3,6 +3,8 @@
 #include <assert.h>
 #include <stdlib.h>
 #include <wlr/xwayland.h>
+#include "buffer.h"
+#include "common/array.h"
 #include "common/macros.h"
 #include "common/mem.h"
 #include "config/rcxml.h"
 #include "workspaces.h"
 #include "xwayland.h"
 
+enum atoms {
+       ATOM_NET_WM_ICON = 0,
+
+       ATOM_COUNT,
+};
+
+static const char * const atom_names[] = {
+       [ATOM_NET_WM_ICON] = "_NET_WM_ICON",
+};
+
+static_assert(ARRAY_SIZE(atom_names) == ATOM_COUNT, "atom names out of sync");
+
+static xcb_atom_t atoms[ATOM_COUNT] = {0};
+
 static void xwayland_view_unmap(struct view *view, bool client_request);
 
 static bool
@@ -580,6 +596,63 @@ handle_set_strut_partial(struct wl_listener *listener, void *data)
        }
 }
 
+static void
+update_icon(struct xwayland_view *xwayland_view)
+{
+       if (!xwayland_view->xwayland_surface) {
+               return;
+       }
+
+       xcb_window_t window_id = xwayland_view->xwayland_surface->window_id;
+
+       xcb_connection_t *xcb_conn = wlr_xwayland_get_xwm_connection(
+               xwayland_view->base.server->xwayland);
+       xcb_get_property_cookie_t cookie = xcb_get_property(xcb_conn, 0,
+               window_id, atoms[ATOM_NET_WM_ICON], XCB_ATOM_CARDINAL, 0, 0x10000);
+       xcb_get_property_reply_t *reply = xcb_get_property_reply(xcb_conn, cookie, NULL);
+       if (!reply) {
+               return;
+       }
+       xcb_ewmh_get_wm_icon_reply_t icon;
+       if (!xcb_ewmh_get_wm_icon_from_reply(&icon, reply)) {
+               wlr_log(WLR_INFO, "Invalid x11 icon");
+               view_set_icon(&xwayland_view->base, NULL, NULL);
+               goto out;
+       }
+
+       xcb_ewmh_wm_icon_iterator_t iter = xcb_ewmh_get_wm_icon_iterator(&icon);
+       struct wl_array buffers;
+       wl_array_init(&buffers);
+       for (; iter.rem; xcb_ewmh_get_wm_icon_next(&iter)) {
+               size_t stride = iter.height * 4;
+               uint32_t *buf = xzalloc(iter.width * stride);
+
+               /* Pre-multiply alpha */
+               for (uint32_t y = 0; y < iter.height; y++) {
+                       for (uint32_t x = 0; x < iter.width; x++) {
+                               uint32_t i = x + y * iter.width;
+                               uint8_t *src_pixel = (uint8_t *)&iter.data[i];
+                               uint8_t *dst_pixel = (uint8_t *)&buf[i];
+                               dst_pixel[0] = src_pixel[0] * src_pixel[3] / 255;
+                               dst_pixel[1] = src_pixel[1] * src_pixel[3] / 255;
+                               dst_pixel[2] = src_pixel[2] * src_pixel[3] / 255;
+                               dst_pixel[3] = src_pixel[3];
+                       }
+               }
+
+               struct lab_data_buffer *buffer = buffer_create_from_data(
+                       buf, iter.width, iter.height, stride);
+               array_add(&buffers, buffer);
+       }
+
+       /* view takes ownership of the buffers */
+       view_set_icon(&xwayland_view->base, NULL, &buffers);
+       wl_array_release(&buffers);
+
+out:
+       free(reply);
+}
+
 static void
 handle_focus_in(struct wl_listener *listener, void *data)
 {
@@ -1069,11 +1142,94 @@ handle_new_surface(struct wl_listener *listener, void *data)
        }
 }
 
+static struct xwayland_view *
+xwayland_view_from_window_id(struct server *server, xcb_window_t id)
+{
+       struct view *view;
+       wl_list_for_each(view, &server->views, link) {
+               if (view->type != LAB_XWAYLAND_VIEW) {
+                       continue;
+               }
+               struct xwayland_view *xwayland_view = xwayland_view_from_view(view);
+               if (xwayland_view->xwayland_surface
+                               && xwayland_view->xwayland_surface->window_id == id) {
+                       return xwayland_view;
+               }
+       }
+       return NULL;
+}
+
+#define XCB_EVENT_RESPONSE_TYPE_MASK 0x7f
+static bool
+handle_x11_event(struct wlr_xwayland *wlr_xwayland, xcb_generic_event_t *event)
+{
+       switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) {
+       case XCB_PROPERTY_NOTIFY: {
+               xcb_property_notify_event_t *ev = (void *)event;
+               if (ev->atom == atoms[ATOM_NET_WM_ICON]) {
+                       struct server *server = wlr_xwayland->data;
+                       struct xwayland_view *xwayland_view =
+                               xwayland_view_from_window_id(server, ev->window);
+                       if (xwayland_view) {
+                               update_icon(xwayland_view);
+                       } else {
+                               wlr_log(WLR_DEBUG, "icon property changed for unknown window");
+                       }
+                       return true;
+               }
+               break;
+       }
+       default:
+               break;
+       }
+
+       return false;
+}
+
+static void
+sync_atoms(struct server *server)
+{
+       xcb_connection_t *xcb_conn =
+               wlr_xwayland_get_xwm_connection(server->xwayland);
+       assert(xcb_conn);
+
+       wlr_log(WLR_DEBUG, "Syncing X11 atoms");
+       xcb_intern_atom_cookie_t cookies[ATOM_COUNT];
+
+       /* First request everything and then loop over the results to reduce latency */
+       for (size_t i = 0; i < ATOM_COUNT; i++) {
+               cookies[i] = xcb_intern_atom(xcb_conn, 0,
+                       strlen(atom_names[i]), atom_names[i]);
+       }
+
+       for (size_t i = 0; i < ATOM_COUNT; i++) {
+               xcb_generic_error_t *err = NULL;
+               xcb_intern_atom_reply_t *reply =
+                       xcb_intern_atom_reply(xcb_conn, cookies[i], &err);
+               if (reply) {
+                       atoms[i] = reply->atom;
+                       wlr_log(WLR_DEBUG, "Got X11 atom for %s: %u",
+                               atom_names[i], reply->atom);
+               }
+               if (err) {
+                       atoms[i] = XCB_ATOM_NONE;
+                       wlr_log(WLR_INFO, "Failed to get X11 atom for %s",
+                               atom_names[i]);
+               }
+               free(reply);
+               free(err);
+       }
+}
+
 static void
 handle_server_ready(struct wl_listener *listener, void *data)
 {
        /* Fire an Xwayland startup script if one (or many) can be found */
        session_run_script("xinitrc");
+
+       struct server *server =
+               wl_container_of(listener, server, xwayland_server_ready);
+       sync_atoms(server);
 }
 
 static void
@@ -1107,6 +1263,9 @@ xwayland_server_init(struct server *server, struct wlr_compositor *compositor)
        wl_signal_add(&server->xwayland->events.ready,
                &server->xwayland_xwm_ready);
 
+       server->xwayland->data = server;
+       server->xwayland->user_event_handler = handle_x11_event;
+
        if (setenv("DISPLAY", server->xwayland->display_name, true) < 0) {
                wlr_log_errno(WLR_ERROR, "unable to set DISPLAY for xwayland");
        } else {