diff --git a/meson.build b/meson.build index 41b500b6..67e378d2 100644 --- a/meson.build +++ b/meson.build @@ -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, diff --git a/src/xdg.c b/src/xdg.c index c2da35b7..5ff5ccb0 100644 --- 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); } diff --git a/src/xwayland.c b/src/xwayland.c index 0185e61d..a648877e 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -3,6 +3,8 @@ #include #include #include +#include "buffer.h" +#include "common/array.h" #include "common/macros.h" #include "common/mem.h" #include "config/rcxml.h" @@ -17,6 +19,20 @@ #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 {