wayland: implement appicon dock/clip clamping

Enable the full appicon drag-to-dock/clip workflow for native Wayland
clients:

- window.c: Set main_window = client_win for Wayland clients that lack
  client_leader and window_group, so wApplicationCreate runs and
  produces a WAppIcon.

- wl_xdg.c: Call wApplicationDestroy after wUnmanageWindow on toplevel
  unmap, mirroring the X11 handleDestroyNotify path. Fixes appicons
  persisting after app close.

- wl_event.c: Commit the wlroots scene in wl_event_flush so
  slide_windows animation renders intermediate frames.

- backend.h, x11_backend.c, wl_icon.c, superfluous.c: Add
  create_rimage_from_drawable vtable method so MakeGhostIcon can read
  frame_buf pixels on Wayland for the dock shadow overlay.

- wl_icon.c, wl_stubs.c: Implement wl_icon_update_pixmap with
  shadowed (dimmed) overlay for attracted icons.

- wl_dock.c: Disable the placeholder shm_surface dock panel now that
  the real wmaker dock framework operates via frame_bufs.
This commit is contained in:
lkn 2026-06-21 16:44:37 +02:00
parent 50d0cc2c04
commit 29d183fe8f
9 changed files with 138 additions and 4 deletions

View File

@ -468,6 +468,13 @@ typedef struct WMBackend {
*/
void *(*window_get_icon_image)(WNativeWindow win);
/*
* Create an RImage from a drawable (window or pixmap ID).
* Used by MakeGhostIcon to read pixel data from an icon's frame buffer.
* Returns a new RImage* or NULL. Caller must RReleaseImage.
*/
void *(*create_rimage_from_drawable)(void *rcontext, WMDrawable drawable);
/*
* Read the _WINDOWMAKER_MENU text property from win.
* On success stores an XFreeStringList-allocated string array in *list_out,

View File

@ -102,6 +102,10 @@ wm_dock_render(struct wm_shm_surface *s, int icon_sz, int n_slots)
void
wm_dock_create(WScreen *scr)
{
/* Disabled: the real wmaker dock framework handles icons via frame_bufs.
* This shm_surface panel was a placeholder. */
(void)scr;
return;
if (!wl_state.scene || !wl_state.output)
return;
@ -273,6 +277,8 @@ wm_dock_refresh(void)
void
wm_dock_app_add(struct wl_toplevel_view *v)
{
(void)v;
return; /* Disabled: real dock handles icons via frame_bufs */
if (!wl_state.dock_surface) return;
if (wl_state.dock_app_count >= WM_DOCK_MAX_APPS) return;
@ -298,6 +304,8 @@ wm_dock_app_add(struct wl_toplevel_view *v)
void
wm_dock_app_remove(WNativeWindow id)
{
(void)id;
return; /* Disabled: real dock handles icons via frame_bufs */
if (!wl_state.dock_surface) return;
for (int i = 0; i < wl_state.dock_app_count; i++) {

View File

@ -1,6 +1,8 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <wayland-server-core.h>
#include <wlr/types/wlr_scene.h>
#include "../../screen.h"
#include "wl_types.h"
@ -8,6 +10,9 @@
void wl_event_flush(void)
{
wl_xdisplay_flush();
/* Commit the scene so intermediate animation frames are visible */
if (wl_state.scene_output && wl_state.allocator)
wlr_scene_output_commit(wl_state.scene_output, NULL);
}
void wl_event_flush_expose(void) {}
void wl_event_sync(int discard)

View File

@ -280,3 +280,80 @@ 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 */
void *
wl_create_rimage_from_drawable(void *rcontext, WMDrawable drawable)
{
(void)rcontext;
if (!drawable)
return NULL;
struct wl_frame_buf *fb = frame_buf_find((WNativeWindow)drawable);
if (!fb || !fb->image)
return NULL;
int w = pixman_image_get_width(fb->image);
int h = pixman_image_get_height(fb->image);
if (w <= 0 || h <= 0)
return NULL;
RImage *img = RCreateImage(w, h, 1);
if (!img)
return NULL;
uint32_t *src = (uint32_t *)pixman_image_get_data(fb->image);
int stride = pixman_image_get_stride(fb->image) / 4;
unsigned char *dst = img->data;
for (int row = 0; row < h; row++) {
for (int col = 0; col < w; col++) {
uint32_t px = src[row * stride + col];
*dst++ = (px >> 16) & 0xFF; /* R */
*dst++ = (px >> 8) & 0xFF; /* G */
*dst++ = px & 0xFF; /* B */
*dst++ = (px >> 24) & 0xFF; /* A */
}
}
return img;
}
/* Re-render icon pixmap; applies shadowed (dimmed) overlay for attracted icons */
void
wl_icon_update_pixmap(WIcon *icon)
{
if (!icon || !icon->core)
return;
/* Find the owning appicon and repaint it */
WAppIcon *aicon = (WAppIcon *)icon->core->descriptor.parent;
if (aicon && icon->core->descriptor.parent_type == WCLASS_APPICON)
wl_appicon_paint(aicon);
else if (aicon && icon->core->descriptor.parent_type == WCLASS_DOCK_ICON)
wl_appicon_paint(aicon);
else
wl_icon_paint(icon);
/* Apply shadowed overlay (semi-transparent dark wash) for attracted icons */
if (icon->shadowed) {
struct wl_frame_buf *fb = frame_buf_find((WNativeWindow)icon->core->window);
if (fb && fb->image) {
int w = pixman_image_get_width(fb->image);
int h = pixman_image_get_height(fb->image);
uint32_t *px = (uint32_t *)pixman_image_get_data(fb->image);
int stride = pixman_image_get_stride(fb->image) / 4;
for (int row = 0; row < h; row++) {
for (int col = 0; col < w; col++) {
uint32_t p = px[row * stride + col];
uint32_t a = (p >> 24) & 0xFF;
uint32_t r = ((p >> 16) & 0xFF) / 2;
uint32_t g = ((p >> 8) & 0xFF) / 2;
uint32_t b = (p & 0xFF) / 2;
px[row * stride + col] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
wlr_scene_buffer_set_buffer_with_damage(fb->scene_buf, &fb->base, NULL);
}
}
}

View File

@ -44,6 +44,8 @@ void wl_netwm_update_opacity(WWindow *wwin);
void wl_icon_paint(WIcon *icon);
void wl_appicon_show(WApplication *wapp);
void wl_appicon_paint(WAppIcon *aicon);
void *wl_create_rimage_from_drawable(void *rcontext, WMDrawable drawable);
void wl_icon_update_pixmap(WIcon *icon);
void wl_client_set_state(WWindow *wwin, int state, WNativeWindow icon_win);
void wl_client_set_maximized(WNativeWindow win, int maximized);
void wl_client_send_protocol(WWindow *wwin, unsigned long protocol, unsigned long timestamp);
@ -325,6 +327,7 @@ const WMBackend wl_backend = {
.window_get_icon_name = wl_window_get_icon_name,
.screen_get_head_usable_area = wl_noop_screen_get_head_usable_area,
.window_get_icon_image = wl_window_get_icon_image,
.create_rimage_from_drawable = wl_create_rimage_from_drawable,
.prop_get_wmaker_menu = wl_prop_get_wmaker_menu,
.prop_get_transient_for = wl_prop_get_transient_for,
.prop_has_wmaker_menu = wl_prop_has_wmaker_menu,
@ -449,7 +452,7 @@ const WMBackend wl_backend = {
.get_text_selection = wl_noop_get_text_selection,
.get_shortcut_string = wl_noop_get_shortcut_string,
.get_command_for_window = wl_get_command_for_window,
.icon_update_pixmap = wl_noop_icon_update_pixmap,
.icon_update_pixmap = wl_icon_update_pixmap,
.icon_paint = wl_icon_paint,
.appicon_show = wl_appicon_show,
.appicon_paint = wl_appicon_paint,

View File

@ -18,6 +18,7 @@
#include "../../actions.h"
#include "../../xinerama.h"
#include "../../framewin.h"
#include "../../application.h"
#include <WINGs/WINGsP.h>
/* Forward declarations for functions defined in other wl_*.c files */
@ -393,7 +394,16 @@ handle_toplevel_unmap(struct wl_listener *listener, void *data)
WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN);
if (desc && desc->parent_type == WCLASS_WINDOW) {
WWindow *wwin = (WWindow *)desc->parent;
WNativeWindow main_win = wwin->main_window;
wUnmanageWindow(wwin, False, True);
/* Destroy the WApplication (and its appicon) if this was the
* main window. Mirrors handleDestroyNotify in event.c. */
WApplication *app = wApplicationOf(main_win);
if (app) {
if (main_win == app->main_window)
app->refcount = 0;
wApplicationDestroy(app);
}
}
}
}

View File

@ -1356,6 +1356,12 @@ x11_window_get_icon_image(WNativeWindow win)
return get_window_image_from_x11(dpy, WNW_TO_XID(win));
}
static void *
x11_create_rimage_from_drawable(void *rcontext, WMDrawable drawable)
{
return RCreateImageFromDrawable((RContext *)rcontext, (Drawable)drawable, None);
}
static int
x11_prop_get_wmaker_menu(WNativeWindow win, char ***list_out, int *count_out)
{
@ -4795,6 +4801,7 @@ const WMBackend x11_backend = {
.window_get_icon_name = x11_window_get_icon_name,
.screen_get_head_usable_area = x11_screen_get_head_usable_area,
.window_get_icon_image = x11_window_get_icon_image,
.create_rimage_from_drawable = x11_create_rimage_from_drawable,
.prop_get_wmaker_menu = x11_prop_get_wmaker_menu,
.monitors_query = x11_monitors_query,
.monitors_select_events = x11_monitors_select_events,

View File

@ -61,9 +61,12 @@ WMNativePixmap MakeGhostIcon(WScreen * scr, WMDrawable drawable)
if (!drawable)
return WM_NATIVE_NONE;
back = RCreateImageFromDrawable(scr->rcontext, drawable, WM_NATIVE_NONE);
if (!back)
back = (RImage *)wm_backend->create_rimage_from_drawable(scr->rcontext, drawable);
if (!back) {
wwarning("MakeGhostIcon: create_rimage_from_drawable failed for drawable=0x%lx", (unsigned long)drawable);
return WM_NATIVE_NONE;
}
wwarning("MakeGhostIcon: got RImage %dx%d from drawable=0x%lx", back->width, back->height, (unsigned long)drawable);
color.red = 0xff;
color.green = 0xff;
@ -71,7 +74,12 @@ WMNativePixmap MakeGhostIcon(WScreen * scr, WMDrawable drawable)
color.alpha = 200;
RClearImage(back, &color);
RConvertImage(scr->rcontext, back, &pixmap);
if (!RConvertImage(scr->rcontext, back, &pixmap)) {
wwarning("MakeGhostIcon: RConvertImage failed");
RReleaseImage(back);
return WM_NATIVE_NONE;
}
wwarning("MakeGhostIcon: converted to pixmap=0x%lx", (unsigned long)pixmap);
RReleaseImage(back);

View File

@ -749,6 +749,15 @@ WWindow *wManageWindow(WScreen *scr, WNativeWindow window)
if (WFLAGP(wwin, emulate_appicon))
wwin->main_window = wwin->client_win;
/* Wayland native clients have no client_leader or window_group.
* Treat each non-transient window as its own app leader so that
* wApplicationCreate runs and an appicon is produced. */
if (wwin->main_window == WM_NATIVE_NONE
&& wwin->transient_for == WM_NATIVE_NONE
&& wwin->client_leader == WM_NATIVE_NONE
&& wwin->group_id == WM_NATIVE_NONE)
wwin->main_window = wwin->client_win;
fixLeaderProperties(wwin);
wwin->orig_main_window = wwin->main_window;