From 5f3db1e190bbdd1d0faf771e45478dc8e2b54037 Mon Sep 17 00:00:00 2001 From: Nikita Tsarev Date: Thu, 20 Nov 2025 11:49:50 +0100 Subject: [PATCH] JBR-9483 Wayland: Support toplevel icons This patch implements support for the xdg_toplevel_icon_v1 protocol. The image choosing logic is just to pick the largest square image for now. The image scale factor is also not set, since it's unclear if it's needed and how it interacts with multi-monitor setups. NOTE: this patch introduces a dependency on wayland-protocols 1.37+. --- .../java.desktop/gensrc/GensrcWayland.gmk | 1 + .../classes/sun/awt/wl/WLComponentPeer.java | 5 ++ .../unix/classes/sun/awt/wl/WLToolkit.java | 11 ++++ .../unix/classes/sun/awt/wl/WLWindowPeer.java | 36 ++++++++++- .../native/libawt_wlawt/WLComponentPeer.c | 59 +++++++++++++++++++ .../unix/native/libawt_wlawt/WLToolkit.c | 46 ++++++++++++++- .../unix/native/libawt_wlawt/WLToolkit.h | 3 +- 7 files changed, 158 insertions(+), 3 deletions(-) diff --git a/make/modules/java.desktop/gensrc/GensrcWayland.gmk b/make/modules/java.desktop/gensrc/GensrcWayland.gmk index 32cbae05e25e..9ed8f210d065 100644 --- a/make/modules/java.desktop/gensrc/GensrcWayland.gmk +++ b/make/modules/java.desktop/gensrc/GensrcWayland.gmk @@ -33,6 +33,7 @@ WAYLAND_BASIC_PROTOCOL_FILES := \ $(WAYLAND_PROTOCOLS_ROOT)/stable/viewporter/viewporter.xml \ $(WAYLAND_PROTOCOLS_ROOT)/stable/xdg-shell/xdg-shell.xml \ $(WAYLAND_PROTOCOLS_ROOT)/staging/xdg-activation/xdg-activation-v1.xml \ + $(WAYLAND_PROTOCOLS_ROOT)/staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml \ $(WAYLAND_PROTOCOLS_ROOT)/unstable/primary-selection/primary-selection-unstable-v1.xml \ $(WAYLAND_PROTOCOLS_ROOT)/unstable/xdg-output/xdg-output-unstable-v1.xml \ $(WAYLAND_PROTOCOLS_ROOT)/unstable/relative-pointer/relative-pointer-unstable-v1.xml \ diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLComponentPeer.java b/src/java.desktop/unix/classes/sun/awt/wl/WLComponentPeer.java index 124a7e9793c7..c6940b003d88 100644 --- a/src/java.desktop/unix/classes/sun/awt/wl/WLComponentPeer.java +++ b/src/java.desktop/unix/classes/sun/awt/wl/WLComponentPeer.java @@ -899,6 +899,10 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener { performLocked(() -> nativeShowWindowMenu(serial, nativePtr, xNative, yNative)); } + void setIcon(int size, int[] pixels) { + performLocked(() -> nativeSetIcon(nativePtr, size, pixels)); + } + @Override public ColorModel getColorModel() { GraphicsConfiguration graphicsConfig = target.getGraphicsConfiguration(); @@ -1167,6 +1171,7 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener { private native void nativeSetMinimumSize(long ptr, int width, int height); private native void nativeSetMaximumSize(long ptr, int width, int height); private native void nativeShowWindowMenu(long serial, long ptr, int x, int y); + private native void nativeSetIcon(long ptr, int size, int[] pixels); static long getNativePtrFor(Component component) { final ComponentAccessor acc = AWTAccessor.getComponentAccessor(); diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLToolkit.java b/src/java.desktop/unix/classes/sun/awt/wl/WLToolkit.java index 0e2bded3fb4c..cbab8a45e392 100644 --- a/src/java.desktop/unix/classes/sun/awt/wl/WLToolkit.java +++ b/src/java.desktop/unix/classes/sun/awt/wl/WLToolkit.java @@ -84,6 +84,7 @@ import java.awt.peer.WindowPeer; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Arrays; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -142,6 +143,11 @@ public class WLToolkit extends UNIXToolkit implements Runnable { private static final boolean isKDE; + // NOTE: xdg_toplevel_icon_v1 is pretty much only supported on KDE, and KWin always sends 96px as the icon size, + // regardless of the display resolution, scale, or anything else. + // TODO: this is currently unused + private static final java.util.List preferredIconSizes = new ArrayList<>(); + private static native void initIDs(long displayPtr); static { @@ -1120,4 +1126,9 @@ public class WLToolkit extends UNIXToolkit implements Runnable { public static boolean isKDE() { return isKDE; } + + // called from native + private static void handleToplevelIconSize(int size) { + preferredIconSizes.add(size); + } } diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLWindowPeer.java b/src/java.desktop/unix/classes/sun/awt/wl/WLWindowPeer.java index c57edc7e3463..67c1542b8aab 100644 --- a/src/java.desktop/unix/classes/sun/awt/wl/WLWindowPeer.java +++ b/src/java.desktop/unix/classes/sun/awt/wl/WLWindowPeer.java @@ -39,7 +39,9 @@ import java.awt.Color; import java.awt.Component; import java.awt.Dialog; import java.awt.Font; +import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; +import java.awt.Image; import java.awt.Insets; import java.awt.Rectangle; import java.awt.RenderingHints; @@ -51,6 +53,7 @@ import java.awt.image.BufferedImage; import java.awt.peer.ComponentPeer; import java.awt.peer.WindowPeer; import java.lang.ref.WeakReference; +import java.util.List; public class WLWindowPeer extends WLComponentPeer implements WindowPeer, SurfacePixelGrabber { private static Font defaultFont; @@ -180,7 +183,38 @@ public class WLWindowPeer extends WLComponentPeer implements WindowPeer, Surface @Override public void updateIconImages() { - // No support for this from Wayland, icon is a desktop integration feature. + List iconImages = getWindow().getIconImages(); + if (iconImages == null || iconImages.isEmpty()) { + setIcon(0, null); + return; + } + + Image image = iconImages.stream() + .filter(x -> x.getWidth(null) > 0 && x.getHeight(null) > 0) + .filter(x -> x.getWidth(null) == x.getHeight(null)) + .max((a, b) -> Integer.compare(a.getWidth(null), b.getWidth(null))) + .orElse(null); + if (image == null) { + return; + } + + int width = image.getWidth(null); + int height = image.getHeight(null); + int size = width; + + BufferedImage bufferedImage; + if (image instanceof BufferedImage && ((BufferedImage) image).getType() == BufferedImage.TYPE_INT_ARGB) { + bufferedImage = (BufferedImage) image; + } else { + bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = bufferedImage.createGraphics(); + g.drawImage(image, 0, 0, null); + g.dispose(); + } + + int[] pixels = new int[width * height]; + bufferedImage.getRGB(0, 0, width, height, pixels, 0, width); + setIcon(size, pixels); } @Override diff --git a/src/java.desktop/unix/native/libawt_wlawt/WLComponentPeer.c b/src/java.desktop/unix/native/libawt_wlawt/WLComponentPeer.c index e5e91b052286..aacf555b3fd7 100644 --- a/src/java.desktop/unix/native/libawt_wlawt/WLComponentPeer.c +++ b/src/java.desktop/unix/native/libawt_wlawt/WLComponentPeer.c @@ -36,6 +36,8 @@ #include "WLGraphicsEnvironment.h" #include "xdg-decoration-unstable-v1.h" +#include + #ifdef WAKEFIELD_ROBOT #include "wakefield.h" #endif @@ -67,6 +69,9 @@ struct WLFrame { jboolean configuredActive; jboolean configuredMaximized; jboolean configuredFullscreen; + + struct wl_buffer* iconBuffer; + struct wl_shm_pool* iconPool; }; static void @@ -571,3 +576,57 @@ JNIEXPORT void JNICALL Java_sun_awt_wl_ServerSideFrameDecoration_disposeImpl } } +JNIEXPORT void JNICALL Java_sun_awt_wl_WLComponentPeer_nativeSetIcon + (JNIEnv *env, jobject obj, jlong ptr, jint size, jobject pixelArray) +{ + struct WLFrame *frame = jlong_to_ptr(ptr); + if (frame == NULL || !frame->toplevel || xdg_toplevel_icon_manager == NULL) { + return; + } + + bool hasIcon = frame->iconBuffer != NULL; + bool willHaveIcon = size > 0 && pixelArray != NULL; + size_t iconByteSize = size * size * 4; + + if (!willHaveIcon) { + xdg_toplevel_icon_manager_v1_set_icon(xdg_toplevel_icon_manager, frame->xdg_toplevel, NULL); + } + + if (hasIcon) { + if (frame->iconBuffer != NULL) { + wl_buffer_destroy(frame->iconBuffer); + frame->iconBuffer = NULL; + } + if (frame->iconPool != NULL) { + wl_shm_pool_destroy(frame->iconPool); + frame->iconPool = NULL; + } + } + + if (willHaveIcon) { + void* poolData = NULL; + struct wl_shm_pool* pool = CreateShmPool(iconByteSize, "toplevel_icon", &poolData, NULL); + if (pool == NULL) { + return; + } + (*env)->GetIntArrayRegion(env, pixelArray, 0, size * size, poolData); + struct wl_buffer* buffer = wl_shm_pool_create_buffer(pool, 0, size, size, size * 4, WL_SHM_FORMAT_ARGB8888); + if (buffer == NULL) { + wl_shm_pool_destroy(pool); + return; + } + + struct xdg_toplevel_icon_v1* icon = xdg_toplevel_icon_manager_v1_create_icon(xdg_toplevel_icon_manager); + if (icon == NULL) { + wl_buffer_destroy(buffer); + wl_shm_pool_destroy(pool); + return; + } + xdg_toplevel_icon_v1_add_buffer(icon, buffer, 1); + xdg_toplevel_icon_manager_v1_set_icon(xdg_toplevel_icon_manager, frame->xdg_toplevel, icon); + xdg_toplevel_icon_v1_destroy(icon); + + frame->iconPool = pool; + frame->iconBuffer = buffer; + } +} diff --git a/src/java.desktop/unix/native/libawt_wlawt/WLToolkit.c b/src/java.desktop/unix/native/libawt_wlawt/WLToolkit.c index d7205cb0a57b..02542720e9b1 100644 --- a/src/java.desktop/unix/native/libawt_wlawt/WLToolkit.c +++ b/src/java.desktop/unix/native/libawt_wlawt/WLToolkit.c @@ -93,8 +93,10 @@ struct zwp_primary_selection_device_manager_v1 *zwp_selection_dm = NULL; // opti struct zxdg_output_manager_v1 *zxdg_output_manager_v1 = NULL; // optional, check for NULL before use struct zwp_text_input_manager_v3 *zwp_text_input_manager = NULL; // optional, check for NULL before use +struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager; // optional, check for NULL before use static uint32_t num_of_outstanding_sync = 0; +static bool waiting_for_xdg_toplevel_icon_manager_done = false; // This group of definitions corresponds to declarations from awt.h jclass tkClass = NULL; @@ -135,6 +137,7 @@ static jmethodID dispatchKeyboardModifiersEventMID; static jmethodID dispatchKeyboardEnterEventMID; static jmethodID dispatchKeyboardLeaveEventMID; static jmethodID dispatchRelativePointerEventMID; +static jmethodID handleToplevelIconSizeMID; JNIEnv *getEnv() { JNIEnv *env; @@ -570,6 +573,36 @@ static const struct wl_seat_listener wl_seat_listener = { .name = wl_seat_name }; +static void +xdg_toplevel_icon_manager_icon_size(void *data, + struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1, + int32_t size) +{ + (void)data; + (void)xdg_toplevel_icon_manager_v1; + JNIEnv* env = getEnv(); + if (env == NULL) { + return; + } + + (*env)->CallStaticVoidMethod(env, tkClass, handleToplevelIconSizeMID, size); + JNU_CHECK_EXCEPTION(env); +} + +static void +xdg_toplevel_icon_manager_icon_size_done(void *data, + struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1) +{ + (void)data; + (void)xdg_toplevel_icon_manager_v1; + waiting_for_xdg_toplevel_icon_manager_done = false; +} + +static const struct xdg_toplevel_icon_manager_v1_listener xdg_toplevel_icon_manager_v1_listener = { + .icon_size = xdg_toplevel_icon_manager_icon_size, + .done = xdg_toplevel_icon_manager_icon_size_done, +}; + static void registry_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version) @@ -636,6 +669,12 @@ registry_global(void *data, struct wl_registry *wl_registry, } } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { xdg_decoration_manager = wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1); + } else if (strcmp(interface, xdg_toplevel_icon_manager_v1_interface.name) == 0) { + xdg_toplevel_icon_manager = wl_registry_bind(wl_registry, name, &xdg_toplevel_icon_manager_v1_interface, 1); + if (xdg_toplevel_icon_manager != NULL) { + waiting_for_xdg_toplevel_icon_manager_done = true; + xdg_toplevel_icon_manager_v1_add_listener(xdg_toplevel_icon_manager, &xdg_toplevel_icon_manager_v1_listener, NULL); + } } #ifdef WAKEFIELD_ROBOT @@ -710,6 +749,11 @@ initJavaRefs(JNIEnv *env, jclass clazz) "(Lsun/awt/wl/WLPointerEvent;)V"), JNI_FALSE); + CHECK_NULL_RETURN(handleToplevelIconSizeMID = (*env)->GetStaticMethodID(env, tkClass, + "handleToplevelIconSize", + "(I)V"), + JNI_FALSE); + CHECK_NULL_RETURN(pointerEventClass = (*env)->FindClass(env, "sun/awt/wl/WLPointerEvent"), JNI_FALSE); @@ -844,7 +888,7 @@ getCursorTheme(int scale) { static void finalizeInit(JNIEnv *env) { // NB: we are NOT on EDT here so shouldn't dispatch EDT-sensitive stuff - while (num_of_outstanding_sync > 0) { + while (num_of_outstanding_sync > 0 || waiting_for_xdg_toplevel_icon_manager_done) { // There are outstanding events that carry information essential for the toolkit // to be fully operational, such as, for example, the number of outputs. // Those events were subscribed to when handling globals in registry_global(). diff --git a/src/java.desktop/unix/native/libawt_wlawt/WLToolkit.h b/src/java.desktop/unix/native/libawt_wlawt/WLToolkit.h index e4d01b2a6e47..a4cd8f5720d5 100644 --- a/src/java.desktop/unix/native/libawt_wlawt/WLToolkit.h +++ b/src/java.desktop/unix/native/libawt_wlawt/WLToolkit.h @@ -33,7 +33,7 @@ #include "relative-pointer-unstable-v1.h" #include "text-input-unstable-v3.h" #include "xdg-decoration-unstable-v1.h" - +#include "xdg-toplevel-icon-v1.h" #include "jvm_md.h" #include "jni_util.h" @@ -73,6 +73,7 @@ extern struct zxdg_output_manager_v1 *zxdg_output_manager_v1; // optional, check extern struct zwp_relative_pointer_manager_v1* relative_pointer_manager; extern struct zwp_text_input_manager_v3 *zwp_text_input_manager; // optional, check for NULL before use extern struct zxdg_decoration_manager_v1* xdg_decoration_manager; // optional, check for NULL before use +extern struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager; // optional, check for NULL before use JNIEnv *getEnv();