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+.
This commit is contained in:
Nikita Tsarev
2025-11-20 11:49:50 +01:00
committed by jbrbot
parent c17ffe0e22
commit 5f3db1e190
7 changed files with 158 additions and 3 deletions

View File

@@ -33,6 +33,7 @@ WAYLAND_BASIC_PROTOCOL_FILES := \
$(WAYLAND_PROTOCOLS_ROOT)/stable/viewporter/viewporter.xml \ $(WAYLAND_PROTOCOLS_ROOT)/stable/viewporter/viewporter.xml \
$(WAYLAND_PROTOCOLS_ROOT)/stable/xdg-shell/xdg-shell.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-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/primary-selection/primary-selection-unstable-v1.xml \
$(WAYLAND_PROTOCOLS_ROOT)/unstable/xdg-output/xdg-output-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 \ $(WAYLAND_PROTOCOLS_ROOT)/unstable/relative-pointer/relative-pointer-unstable-v1.xml \

View File

@@ -899,6 +899,10 @@ public class WLComponentPeer implements ComponentPeer, WLSurfaceSizeListener {
performLocked(() -> nativeShowWindowMenu(serial, nativePtr, xNative, yNative)); performLocked(() -> nativeShowWindowMenu(serial, nativePtr, xNative, yNative));
} }
void setIcon(int size, int[] pixels) {
performLocked(() -> nativeSetIcon(nativePtr, size, pixels));
}
@Override @Override
public ColorModel getColorModel() { public ColorModel getColorModel() {
GraphicsConfiguration graphicsConfig = target.getGraphicsConfiguration(); 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 nativeSetMinimumSize(long ptr, int width, int height);
private native void nativeSetMaximumSize(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 nativeShowWindowMenu(long serial, long ptr, int x, int y);
private native void nativeSetIcon(long ptr, int size, int[] pixels);
static long getNativePtrFor(Component component) { static long getNativePtrFor(Component component) {
final ComponentAccessor acc = AWTAccessor.getComponentAccessor(); final ComponentAccessor acc = AWTAccessor.getComponentAccessor();

View File

@@ -84,6 +84,7 @@ import java.awt.peer.WindowPeer;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.util.Arrays; import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -142,6 +143,11 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
private static final boolean isKDE; 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<Integer> preferredIconSizes = new ArrayList<>();
private static native void initIDs(long displayPtr); private static native void initIDs(long displayPtr);
static { static {
@@ -1120,4 +1126,9 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
public static boolean isKDE() { public static boolean isKDE() {
return isKDE; return isKDE;
} }
// called from native
private static void handleToplevelIconSize(int size) {
preferredIconSizes.add(size);
}
} }

View File

@@ -39,7 +39,9 @@ import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Dialog; import java.awt.Dialog;
import java.awt.Font; import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration; import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Insets; import java.awt.Insets;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.RenderingHints; import java.awt.RenderingHints;
@@ -51,6 +53,7 @@ import java.awt.image.BufferedImage;
import java.awt.peer.ComponentPeer; import java.awt.peer.ComponentPeer;
import java.awt.peer.WindowPeer; import java.awt.peer.WindowPeer;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.List;
public class WLWindowPeer extends WLComponentPeer implements WindowPeer, SurfacePixelGrabber { public class WLWindowPeer extends WLComponentPeer implements WindowPeer, SurfacePixelGrabber {
private static Font defaultFont; private static Font defaultFont;
@@ -180,7 +183,38 @@ public class WLWindowPeer extends WLComponentPeer implements WindowPeer, Surface
@Override @Override
public void updateIconImages() { public void updateIconImages() {
// No support for this from Wayland, icon is a desktop integration feature. List<Image> 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 @Override

View File

@@ -36,6 +36,8 @@
#include "WLGraphicsEnvironment.h" #include "WLGraphicsEnvironment.h"
#include "xdg-decoration-unstable-v1.h" #include "xdg-decoration-unstable-v1.h"
#include <stdbool.h>
#ifdef WAKEFIELD_ROBOT #ifdef WAKEFIELD_ROBOT
#include "wakefield.h" #include "wakefield.h"
#endif #endif
@@ -67,6 +69,9 @@ struct WLFrame {
jboolean configuredActive; jboolean configuredActive;
jboolean configuredMaximized; jboolean configuredMaximized;
jboolean configuredFullscreen; jboolean configuredFullscreen;
struct wl_buffer* iconBuffer;
struct wl_shm_pool* iconPool;
}; };
static void 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;
}
}

View File

@@ -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 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 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 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 // This group of definitions corresponds to declarations from awt.h
jclass tkClass = NULL; jclass tkClass = NULL;
@@ -135,6 +137,7 @@ static jmethodID dispatchKeyboardModifiersEventMID;
static jmethodID dispatchKeyboardEnterEventMID; static jmethodID dispatchKeyboardEnterEventMID;
static jmethodID dispatchKeyboardLeaveEventMID; static jmethodID dispatchKeyboardLeaveEventMID;
static jmethodID dispatchRelativePointerEventMID; static jmethodID dispatchRelativePointerEventMID;
static jmethodID handleToplevelIconSizeMID;
JNIEnv *getEnv() { JNIEnv *getEnv() {
JNIEnv *env; JNIEnv *env;
@@ -570,6 +573,36 @@ static const struct wl_seat_listener wl_seat_listener = {
.name = wl_seat_name .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 static void
registry_global(void *data, struct wl_registry *wl_registry, registry_global(void *data, struct wl_registry *wl_registry,
uint32_t name, const char *interface, uint32_t version) 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) { } 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); 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 #ifdef WAKEFIELD_ROBOT
@@ -710,6 +749,11 @@ initJavaRefs(JNIEnv *env, jclass clazz)
"(Lsun/awt/wl/WLPointerEvent;)V"), "(Lsun/awt/wl/WLPointerEvent;)V"),
JNI_FALSE); JNI_FALSE);
CHECK_NULL_RETURN(handleToplevelIconSizeMID = (*env)->GetStaticMethodID(env, tkClass,
"handleToplevelIconSize",
"(I)V"),
JNI_FALSE);
CHECK_NULL_RETURN(pointerEventClass = (*env)->FindClass(env, CHECK_NULL_RETURN(pointerEventClass = (*env)->FindClass(env,
"sun/awt/wl/WLPointerEvent"), "sun/awt/wl/WLPointerEvent"),
JNI_FALSE); JNI_FALSE);
@@ -844,7 +888,7 @@ getCursorTheme(int scale) {
static void static void
finalizeInit(JNIEnv *env) { finalizeInit(JNIEnv *env) {
// NB: we are NOT on EDT here so shouldn't dispatch EDT-sensitive stuff // 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 // There are outstanding events that carry information essential for the toolkit
// to be fully operational, such as, for example, the number of outputs. // to be fully operational, such as, for example, the number of outputs.
// Those events were subscribed to when handling globals in registry_global(). // Those events were subscribed to when handling globals in registry_global().

View File

@@ -33,7 +33,7 @@
#include "relative-pointer-unstable-v1.h" #include "relative-pointer-unstable-v1.h"
#include "text-input-unstable-v3.h" #include "text-input-unstable-v3.h"
#include "xdg-decoration-unstable-v1.h" #include "xdg-decoration-unstable-v1.h"
#include "xdg-toplevel-icon-v1.h"
#include "jvm_md.h" #include "jvm_md.h"
#include "jni_util.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_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 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 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(); JNIEnv *getEnv();