From b6ad1df42eb39984e5d462d5e3c5483845658ef5 Mon Sep 17 00:00:00 2001 From: lkn Date: Tue, 23 Jun 2026 12:55:48 +0200 Subject: [PATCH] wayland: implement dockapp rendering inside dock icons XWayland dockapps (wmclock, etc.) now render their content inside the dock icon tile instead of in a separate window. Key changes: - wl_props.c: implement wl_prop_get_wm_hints for XWayland surfaces, reading xcb_icccm_wm_hints_t to detect WithdrawnState dockapps - appicon.c: for is_dockapp windows, use client_win as icon_win - icon.c: capture icon_window pixels via XGetImage with XShape mask for transparency, skip surface embedding when image is captured - wl_icon.c: create_rimage_from_drawable handles X11 Window IDs via XGetImage, applies XShape bounding rectangles as alpha mask - wl_stubs.c: pixmap_get_size returns actual view dimensions - wl_window.c: map XWayland dockapp windows and reparent icon_window child into main window for compositing --- src/appicon.c | 7 +++- src/backend/wayland/wl_icon.c | 65 +++++++++++++++++++++++++++++++-- src/backend/wayland/wl_props.c | 22 ++++++++++- src/backend/wayland/wl_stubs.c | 21 ++++++++++- src/backend/wayland/wl_window.c | 18 +++++++++ src/icon.c | 25 ++++++++++--- 6 files changed, 146 insertions(+), 12 deletions(-) diff --git a/src/appicon.c b/src/appicon.c index f5116af..827195e 100644 --- a/src/appicon.c +++ b/src/appicon.c @@ -146,6 +146,9 @@ void create_appicon_for_application(WApplication *wapp, WWindow *wwin) if (!wapp->app_icon) { /* Create the icon */ wapp->app_icon = wAppIconCreate(wapp->main_window_desc); + if (wapp->app_icon->icon->icon_win == WM_NATIVE_NONE && + wapp->main_window_desc->flags.is_dockapp) + wapp->app_icon->icon->icon_win = wapp->main_window_desc->client_win; wIconUpdate(wapp->app_icon->icon); /* Now, paint the icon */ @@ -1119,7 +1122,9 @@ static void create_appicon_from_dock(WWindow *wwin, WApplication *wapp, WNativeW wapp->app_icon->running = 1; wapp->app_icon->icon->owner = mainw; - if (mainw->wm_hints && (mainw->wm_hints->flags & WMH_ICON_WINDOW_HINT)) + if (mainw->flags.is_dockapp) + wapp->app_icon->icon->icon_win = mainw->client_win; + else if (mainw->wm_hints && (mainw->wm_hints->flags & WMH_ICON_WINDOW_HINT)) wapp->app_icon->icon->icon_win = mainw->wm_hints->icon_window; /* Update the icon images */ diff --git a/src/backend/wayland/wl_icon.c b/src/backend/wayland/wl_icon.c index 1d7e010..f5ee31c 100644 --- a/src/backend/wayland/wl_icon.c +++ b/src/backend/wayland/wl_icon.c @@ -7,6 +7,10 @@ #include #include #include +#include +#include +#include +#include #include "../../screen.h" #include "../../framewin.h" #include "../../window.h" @@ -64,7 +68,7 @@ wl_icon_paint(WIcon *icon) for (int col = 0; col < size && col < fb->width; col++) { int si = (row * tile->width + col) * (has_alpha ? 4 : 3); unsigned char r = tsrc[si], g = tsrc[si+1], b = tsrc[si+2]; - unsigned char a = has_alpha ? tsrc[si+3] : 0xFF; + unsigned char a = 0xFF; (void)has_alpha; dst[row * stride + col] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b; } @@ -172,7 +176,7 @@ wl_appicon_paint(WAppIcon *aicon) for (int col = 0; col < size && col < fb->width; col++) { int si = (row * tile->width + col) * (has_alpha ? 4 : 3); unsigned char r = src[si], g = src[si+1], b = src[si+2]; - unsigned char a = has_alpha ? src[si+3] : 0xFF; + unsigned char a = 0xFF; (void)has_alpha; dst[row * stride + col] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b; } @@ -397,7 +401,8 @@ void wl_drawer_icon_paint(WAppIcon *aicon) wlr_scene_buffer_set_buffer_with_damage(fb->scene_buf, &fb->base, NULL); } -/* Create an RImage by reading pixels from a frame_buf's pixman image */ +/* Create an RImage by reading pixels from a frame_buf's pixman image, + * or via XGetImage for X11 window IDs (XWayland dockapps). */ void * wl_create_rimage_from_drawable(void *rcontext, WMDrawable drawable) { @@ -405,6 +410,59 @@ wl_create_rimage_from_drawable(void *rcontext, WMDrawable drawable) if (!drawable) return NULL; + /* X11 window ID (XWayland): capture via XGetImage */ + if ((unsigned long)drawable < 0x10000000 && wl_state.x_display) { + Window xwin = (Window)(unsigned long)drawable; + WScreen *scr = wScreenWithNumber(0); + /* Ensure the window is mapped so we can capture its pixels */ + XMapWindow(wl_state.x_display, xwin); + XFlush(wl_state.x_display); + XWindowAttributes attr; + if (XGetWindowAttributes(wl_state.x_display, xwin, &attr) && + attr.width > 0 && attr.height > 0) { + XImage *ximg = XGetImage(wl_state.x_display, xwin, + 0, 0, attr.width, attr.height, AllPlanes, ZPixmap); + if (ximg) { + RImage *res = RCreateImageFromXImage(scr ? scr->rcontext : NULL, ximg, NULL); + XDestroyImage(ximg); + /* Apply XShape mask: only pixels within the shape + * bounding region are visible (dockapp convention). */ + if (res) { + int count = 0, ordering; + XRectangle *rects = XShapeGetRectangles(wl_state.x_display, + xwin, ShapeBounding, &count, &ordering); + if (rects && count > 0) { + int iw = res->width, ih = res->height; + int bpp = (res->format == RRGBAFormat) ? 4 : 3; + RImage *masked = RCreateImage(iw, ih, 1); + if (masked) { + /* Start fully transparent */ + memset(masked->data, 0, iw * ih * 4); + /* Copy pixels inside shape rects */ + for (int r = 0; r < count; r++) { + int rx = rects[r].x, ry = rects[r].y; + int rw = rects[r].width, rh = rects[r].height; + for (int row = ry; row < ry + rh && row < ih; row++) { + if (row < 0) continue; + for (int col = rx; col < rx + rw && col < iw; col++) { + if (col < 0) continue; + unsigned char *s = res->data + (row * iw + col) * bpp; + unsigned char *d = masked->data + (row * iw + col) * 4; + d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; d[3] = 255; + } + } + } + RReleaseImage(res); + res = masked; + } + XFree(rects); + } + } + return res; + } + } + } + pixman_image_t *pimg = NULL; struct wl_frame_buf *fb = frame_buf_find((WNativeWindow)drawable); if (fb && fb->image) { @@ -478,4 +536,3 @@ wl_icon_update_pixmap(WIcon *icon) } } } - diff --git a/src/backend/wayland/wl_props.c b/src/backend/wayland/wl_props.c index b4496b4..59a1b8d 100644 --- a/src/backend/wayland/wl_props.c +++ b/src/backend/wayland/wl_props.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "../../screen.h" #include "wl_types.h" @@ -75,7 +76,26 @@ wl_prop_get_normal_hints(WNativeWindow win, WNormalHints *out, int *pre_icccm) } WWMHints * -wl_prop_get_wm_hints(WNativeWindow win) { (void)win; return NULL; } +wl_prop_get_wm_hints(WNativeWindow win) +{ + struct wl_toplevel_view *v = wl_find_view_by_id(win); + if (!v || !v->xw_surface || !v->xw_surface->hints) + return NULL; + + xcb_icccm_wm_hints_t *xh = v->xw_surface->hints; + WWMHints *out = wmalloc(sizeof(WWMHints)); + memset(out, 0, sizeof(*out)); + + out->flags = xh->flags; + out->input = (xh->flags & XCB_ICCCM_WM_HINT_INPUT) ? xh->input : 1; + out->initial_state = (xh->flags & XCB_ICCCM_WM_HINT_STATE) ? xh->initial_state : WMH_NORMAL_STATE; + out->icon_pixmap = (xh->flags & XCB_ICCCM_WM_HINT_ICON_PIXMAP) ? (WNativeWindow)xh->icon_pixmap : WM_NATIVE_NONE; + out->icon_window = (xh->flags & XCB_ICCCM_WM_HINT_ICON_WINDOW) ? (WNativeWindow)xh->icon_window : WM_NATIVE_NONE; + out->icon_mask = (xh->flags & XCB_ICCCM_WM_HINT_ICON_MASK) ? (WNativeWindow)xh->icon_mask : WM_NATIVE_NONE; + out->window_group = (xh->flags & XCB_ICCCM_WM_HINT_WINDOW_GROUP) ? (WNativeWindow)xh->window_group : WM_NATIVE_NONE; + + return out; +} int diff --git a/src/backend/wayland/wl_stubs.c b/src/backend/wayland/wl_stubs.c index d30dc8a..841d9de 100644 --- a/src/backend/wayland/wl_stubs.c +++ b/src/backend/wayland/wl_stubs.c @@ -223,7 +223,26 @@ void wl_noop_cursor_set(WNativeWindow window, WMNativeCursor cursor) { (void)win void wl_appicon_paint_dock_dots(WAppIcon *aicon, void *wapp); int wl_noop_pixmap_get_size(unsigned long drawable, unsigned int *w, unsigned int *h, unsigned int *depth) -{ (void)drawable; *w = 0; *h = 0; *depth = 0; return 0; } +{ + struct wl_toplevel_view *v = wl_find_view_by_id((WNativeWindow)drawable); + if (v) { + if (v->xw_surface) { + *w = v->xw_surface->width; + *h = v->xw_surface->height; + } else if (v->xdg_surface) { + struct wlr_box geo = v->xdg_surface->geometry; + *w = geo.width > 0 ? geo.width : v->xdg_surface->surface->current.width; + *h = geo.height > 0 ? geo.height : v->xdg_surface->surface->current.height; + } else { + *w = 64; + *h = 64; + } + *depth = 24; + return 1; + } + *w = 0; *h = 0; *depth = 0; + return 0; +} void wl_noop_icon_paint_title_bg(WScreen *scr, unsigned long pixmap, int height) { (void)scr; (void)pixmap; (void)height; } void wl_noop_icon_select_paint(WIcon *icon, int step) diff --git a/src/backend/wayland/wl_window.c b/src/backend/wayland/wl_window.c index a8b7c69..e6c8d9c 100644 --- a/src/backend/wayland/wl_window.c +++ b/src/backend/wayland/wl_window.c @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include "../../menu.h" #include "../../balloon.h" #include "../../framewin.h" @@ -79,6 +81,22 @@ wl_window_map(WNativeWindow win) struct wl_toplevel_view *v = wl_find_view_by_id(win); if (v && v->scene_node) { wlr_scene_node_set_enabled(&v->scene_node->node, true); + /* Ensure XWayland X11 window is mapped so it composites children + * (needed for dockapps whose content is in a child icon_window). */ + /* For XWayland dockapps: map the X11 window and reparent the + * icon_window child into it so XWayland composites the content. */ + if (v->xw_surface && wl_state.x_display && v->xw_surface->hints && + (v->xw_surface->hints->flags & XCB_ICCCM_WM_HINT_STATE) && + v->xw_surface->hints->initial_state == 0 /* WithdrawnState */) { + Window xwin = (Window)v->xw_surface->window_id; + XMapWindow(wl_state.x_display, xwin); + if (v->xw_surface->hints->flags & XCB_ICCCM_WM_HINT_ICON_WINDOW) { + Window icon_xwin = (Window)v->xw_surface->hints->icon_window; + XReparentWindow(wl_state.x_display, icon_xwin, xwin, 0, 0); + XMapWindow(wl_state.x_display, icon_xwin); + } + XFlush(wl_state.x_display); + } return; } if (v) diff --git a/src/icon.c b/src/icon.c index b210cfb..5cb3fa6 100644 --- a/src/icon.c +++ b/src/icon.c @@ -675,7 +675,7 @@ void update_icon_pixmap(WIcon *icon) icon_update_pixmap(icon, icon->file_image); /* If dockapp, put inside the icon */ - if (icon->icon_win != WM_NATIVE_NONE) { + if (icon->icon_win != WM_NATIVE_NONE && !icon->file_image) { /* file_image is NULL, because is docked app */ icon_update_pixmap(icon, NULL); set_dockapp_in_icon(icon); @@ -766,10 +766,19 @@ static void get_rimage_icon_from_icon_win(WIcon *icon) { RImage *image; - /* Create the new RImage via backend (X11: parses _NET_WM_ICON via dpy; - * Wayland: parses _NET_WM_ICON via wl_state.x_display for XWayland). */ + /* Try _NET_WM_ICON first */ image = (RImage *)wm_backend->window_get_icon_image(icon->icon_win); + /* For dockapps: capture pixels from the real icon_window via XGetImage. + * The icon_window must be mapped first. */ + if (!image && icon->owner && icon->owner->wm_hints && + (icon->owner->wm_hints->flags & WMH_ICON_WINDOW_HINT)) { + WNativeWindow real_icon_win = icon->owner->wm_hints->icon_window; + wm_backend->window_map((WNativeWindow)real_icon_win); + image = (RImage *)wm_backend->create_rimage_from_drawable( + icon->core->screen_ptr->rcontext, (WMDrawable)real_icon_win); + } + /* Free the icon info */ unset_icon_image(icon); @@ -785,9 +794,15 @@ static void set_dockapp_in_icon(WIcon *icon) /* Reparent the dock application to the icon */ - /* We need the application size to center it - * and show in the correct position */ + /* Resize the dockapp to fill the icon tile if it's too small. + * XWayland dockapps often have a tiny main window with content + * drawn into a child icon_window. */ getSize(icon->icon_win, &w, &h, &d); + if (w < (unsigned)wPreferences.icon_size || h < (unsigned)wPreferences.icon_size) { + w = wPreferences.icon_size; + h = wPreferences.icon_size; + wm_backend->window_resize((WNativeWindow)icon->icon_win, w, h); + } /* Set the background pixmap */ wm_backend->window_set_background_pixmap((WNativeWindow)icon->core->window, (unsigned long)scr->icon_tile_pixmap);