summaryrefslogtreecommitdiff
path: root/gnu
diff options
context:
space:
mode:
authorDanny Milosavljevic <dannym@friendly-machines.com>2026-04-01 01:19:29 +0200
committerDanny Milosavljevic <dannym@friendly-machines.com>2026-05-03 02:30:27 +0200
commit95d5ecee8121145a51ab6844645b73a8c35114f5 (patch)
tree30991c58800098cb223ccc24be707946e8b6a065 /gnu
parent472c94ff67ba84676be980ca2ec58f8f4b296552 (diff)
gnu: Add jbr@25.
* gnu/packages/patches/jbr-25-wayland-drag-icon.patch: New file. * gnu/packages/patches/jbr-25-wayland-intra-jvm-dnd.patch: New file. * gnu/local.mk (dist_patch_DATA): Add references to them. * gnu/packages/java.scm (jbr25): New variable. Change-Id: I3358c4be096f887f2eca451396ddd44f6bb6bfa6
Diffstat (limited to 'gnu')
-rw-r--r--gnu/local.mk2
-rw-r--r--gnu/packages/java.scm61
-rw-r--r--gnu/packages/patches/jbr-25-wayland-drag-icon.patch508
-rw-r--r--gnu/packages/patches/jbr-25-wayland-intra-jvm-dnd.patch144
4 files changed, 715 insertions, 0 deletions
diff --git a/gnu/local.mk b/gnu/local.mk
index 7f221ddfd53..2e0b35a2210 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -1686,6 +1686,8 @@ dist_patch_DATA = \
1686 %D%/packages/patches/java-xerces-build_dont_unzip.patch \ 1686 %D%/packages/patches/java-xerces-build_dont_unzip.patch \
1687 %D%/packages/patches/java-xerces-xjavac_taskdef.patch \ 1687 %D%/packages/patches/java-xerces-xjavac_taskdef.patch \
1688 %D%/packages/patches/jbr-17-xcursor-no-dynamic.patch \ 1688 %D%/packages/patches/jbr-17-xcursor-no-dynamic.patch \
1689 %D%/packages/patches/jbr-25-wayland-drag-icon.patch \
1690 %D%/packages/patches/jbr-25-wayland-intra-jvm-dnd.patch \
1689 %D%/packages/patches/jdk-currency-time-bomb.patch \ 1691 %D%/packages/patches/jdk-currency-time-bomb.patch \
1690 %D%/packages/patches/jdk-currency-time-bomb2.patch \ 1692 %D%/packages/patches/jdk-currency-time-bomb2.patch \
1691 %D%/packages/patches/jfsutils-add-sysmacros.patch \ 1693 %D%/packages/patches/jfsutils-add-sysmacros.patch \
diff --git a/gnu/packages/java.scm b/gnu/packages/java.scm
index b6cc95a3114..b0c98d51474 100644
--- a/gnu/packages/java.scm
+++ b/gnu/packages/java.scm
@@ -2008,6 +2008,67 @@ OpenJDK.")
2008 (home-page "https://www.jetbrains.com/") 2008 (home-page "https://www.jetbrains.com/")
2009 (license license:gpl2+))) 2009 (license license:gpl2+)))
2010 2010
2011(define-public jbr25
2012 (package
2013 (inherit openjdk25)
2014 (name "jbr")
2015 (version "25.0.2b329.72")
2016 (source (origin
2017 (method git-fetch)
2018 (uri (git-reference
2019 (url "https://github.com/JetBrains/JetBrainsRuntime.git")
2020 (commit (string-append "jbr-release-" version))))
2021 (file-name (string-append name "-" version "-checkout"))
2022 (sha256
2023 (base32
2024 "1m7pbz2ypx4h7n6iqss8bgymg9hqn14lnis4a2w87mgy51p370ib"))
2025 (patches
2026 (search-patches "jbr-25-wayland-drag-icon.patch"
2027 "jbr-25-wayland-intra-jvm-dnd.patch"))))
2028 (inputs
2029 `(("wayland" ,wayland)
2030 ("libxkbcommon" ,libxkbcommon) ; for wayland
2031 ,@(package-inputs openjdk25)))
2032 (native-inputs
2033 (modify-inputs (package-native-inputs openjdk25)
2034 (append wayland-protocols)))
2035 (arguments
2036 (substitute-keyword-arguments (package-arguments openjdk25)
2037 ((#:configure-flags configure-flags)
2038 #~(append #$configure-flags
2039 (list "--disable-warnings-as-errors"
2040 "--disable-java-warnings-as-errors"
2041 "--with-jvm-features=shenandoahgc"
2042 "--enable-cds=yes"
2043 (string-append "--with-wayland-protocols="
2044 (assoc-ref %build-inputs "wayland-protocols")
2045 "/share/wayland-protocols")
2046 "--with-vendor-name=JetBrains s.r.o"
2047 "--with-vendor-url=https://www.jetbrains.com/"
2048 "--with-vendor-bug-url=https://youtrack.jetbrains.com/issues/JBR")))
2049 ((#:phases phases)
2050 #~(modify-phases #$phases
2051 ;; JBR adds a 'static-jdk-image' target to 'make all' that
2052 ;; statically links both the Wayland and X11 AWT backends,
2053 ;; causing duplicate symbol errors. Build only what we need.
2054 ;; See also their workaround BROKEN_STATIC_LIBS make variable.
2055 (replace 'build
2056 (lambda* (#:key make-flags parallel-build? #:allow-other-keys)
2057 (apply invoke "make" "product-images" "all-docs-images"
2058 (format #f "JOBS=~a" (if parallel-build?
2059 (parallel-job-count)
2060 1))
2061 make-flags)))))))
2062 (synopsis "JetBrains Java Runtime")
2063 (description "This package provides a Java runtime environment for
2064and Java development kit. It supports enhanced class redefinition (DCEVM),
2065includes a number of improvements in font rendering, keyboards support,
2066windowing/focus subsystems, Wayland, HiDPI, accessibility, and performance,
2067provides better desktop integration and bugfixes not yet present in
2068OpenJDK.")
2069 (home-page "https://www.jetbrains.com/")
2070 (license license:gpl2+)))
2071
2011 2072
2012(define-public ant/java8 2073(define-public ant/java8
2013 (package 2074 (package
diff --git a/gnu/packages/patches/jbr-25-wayland-drag-icon.patch b/gnu/packages/patches/jbr-25-wayland-drag-icon.patch
new file mode 100644
index 00000000000..62090e90b85
--- /dev/null
+++ b/gnu/packages/patches/jbr-25-wayland-drag-icon.patch
@@ -0,0 +1,508 @@
1From 0602a477881f7d4047edd2ffe2b6f5157fda9bc9 Mon Sep 17 00:00:00 2001
2From: Danny Milosavljevic <dannym@nova.friendly-machines.com>
3Date: Tue, 31 Mar 2026 05:11:27 +0000
4Subject: [PATCH 1/2] Wayland: fix drag icon updates during drag-and-drop
5
6Expose the actual cursor image and size to the Wayland peer, provide DnD cursor desktop properties, and use the Wayland drag icon surface as the drag-and-drop status channel. Preserve application-supplied drag images, update cursor-derived fallback icons during action changes, pad drag icon buffers for compositor scale requirements, and reuse the drag icon surface so later status changes reach the compositor.
7---
8 .../share/classes/sun/awt/CustomCursor.java | 21 ++-
9 .../classes/sun/awt/wl/WLCursorManager.java | 30 +++++
10 .../unix/classes/sun/awt/wl/WLDataSource.java | 32 +++--
11 .../sun/awt/wl/WLDragSourceContextPeer.java | 122 ++++++++++++++++--
12 .../unix/classes/sun/awt/wl/WLToolkit.java | 11 +-
13 .../unix/native/libawt_wlawt/WLCursor.c | 14 ++
14 .../unix/native/libawt_wlawt/WLDataDevice.c | 37 +++++-
15 7 files changed, 243 insertions(+), 24 deletions(-)
16
17diff --git a/src/java.desktop/share/classes/sun/awt/CustomCursor.java b/src/java.desktop/share/classes/sun/awt/CustomCursor.java
18index 2b65c5dbe..aa7e5559d 100644
19--- a/src/java.desktop/share/classes/sun/awt/CustomCursor.java
20+++ b/src/java.desktop/share/classes/sun/awt/CustomCursor.java
21@@ -37,6 +37,8 @@
22 public abstract class CustomCursor extends Cursor {
23
24 protected Image image;
25+ private Image cursorImage;
26+ private Point hotSpot;
27
28 public CustomCursor(Image cursor, Point hotSpot, String name)
29 throws IndexOutOfBoundsException {
30@@ -80,6 +82,9 @@ public CustomCursor(Image cursor, Point hotSpot, String name)
31 throw new IndexOutOfBoundsException("invalid hotSpot");
32 }
33
34+ cursorImage = cursor;
35+ this.hotSpot = new Point(hotSpot);
36+
37 /* Extract ARGB array from image.
38 *
39 * A transparency mask can be created in native code by checking
40@@ -96,10 +101,24 @@ public CustomCursor(Image cursor, Point hotSpot, String name)
41 } catch (InterruptedException e) {
42 }
43
44- createNativeCursor(image, pixels, width, height, hotSpot.x, hotSpot.y);
45+ createNativeCursor(cursorImage, pixels, width, height, hotSpot.x, hotSpot.y);
46 }
47
48 protected abstract void createNativeCursor(Image im, int[] pixels,
49 int width, int height,
50 int xHotSpot, int yHotSpot);
51+
52+ /**
53+ * Returns the image used to create the native cursor.
54+ */
55+ public Image getImage() {
56+ return cursorImage;
57+ }
58+
59+ /**
60+ * Returns the hotspot used to create the native cursor.
61+ */
62+ public Point getHotSpot() {
63+ return new Point(hotSpot);
64+ }
65 }
66diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLCursorManager.java b/src/java.desktop/unix/classes/sun/awt/wl/WLCursorManager.java
67index 75927e762..0b1ef8d57 100644
68--- a/src/java.desktop/unix/classes/sun/awt/wl/WLCursorManager.java
69+++ b/src/java.desktop/unix/classes/sun/awt/wl/WLCursorManager.java
70@@ -29,6 +29,7 @@
71 import sun.awt.AWTAccessor;
72 import sun.util.logging.PlatformLogger;
73
74+import java.awt.Dimension;
75 import java.awt.Cursor;
76 import java.awt.GraphicsConfiguration;
77
78@@ -139,7 +140,36 @@ private long createNativeCursor(int type, int scale) {
79 return 0;
80 }
81
82+ /**
83+ * Returns the width and height in pixels of the given cursor at the
84+ * specified display scale. Returns null if the cursor is unavailable.
85+ */
86+ public static Dimension getCursorSize(Cursor cursor, int scale) {
87+ if (cursor == null) {
88+ return null;
89+ }
90+
91+ synchronized (instance) {
92+ long pData = AWTAccessor.getCursorAccessor().getPData(cursor, scale);
93+ if (pData == 0 && cursor.getType() == Cursor.CUSTOM_CURSOR) {
94+ pData = AWTAccessor.getCursorAccessor().getPData(cursor);
95+ }
96+ if (pData <= 0) {
97+ return null;
98+ }
99+
100+ int w = nativeGetCursorWidth(pData);
101+ int h = nativeGetCursorHeight(pData);
102+ if (w <= 0 || h <= 0) {
103+ return null;
104+ }
105+ return new Dimension(w, h);
106+ }
107+ }
108+
109 private static native void nativeSetCursor(long pData, int scale, long pointerEnterSerial);
110 private static native long nativeGetPredefinedCursor(String name, int scale);
111 private static native void nativeDestroyPredefinedCursor(long pData);
112+ private static native int nativeGetCursorWidth(long pData);
113+ private static native int nativeGetCursorHeight(long pData);
114 }
115diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLDataSource.java b/src/java.desktop/unix/classes/sun/awt/wl/WLDataSource.java
116index 260aab964..2ecc52619 100644
117--- a/src/java.desktop/unix/classes/sun/awt/wl/WLDataSource.java
118+++ b/src/java.desktop/unix/classes/sun/awt/wl/WLDataSource.java
119@@ -103,22 +103,38 @@ public void setDnDIcon(Image image, int scale, int offsetX, int offsetY) {
120
121 int width = image.getWidth(null);
122 int height = image.getHeight(null);
123- int[] pixels = new int[width * height];
124+ int paddedWidth = width;
125+ int paddedHeight = height;
126+ Image iconImage = image;
127+
128+ if (scale > 1) {
129+ paddedWidth = ((width + scale - 1) / scale) * scale;
130+ paddedHeight = ((height + scale - 1) / scale) * scale;
131+ if (paddedWidth != width || paddedHeight != height) {
132+ BufferedImage paddedImage = new BufferedImage(paddedWidth, paddedHeight, BufferedImage.TYPE_INT_ARGB);
133+ Graphics2D g = paddedImage.createGraphics();
134+ g.drawImage(image, 0, 0, null);
135+ g.dispose();
136+ iconImage = paddedImage;
137+ }
138+ }
139+
140+ int[] pixels = new int[paddedWidth * paddedHeight];
141
142- if (image instanceof BufferedImage) {
143+ if (iconImage instanceof BufferedImage) {
144 // NOTE: no need to ensure that the BufferedImage is TYPE_INT_ARGB,
145 // getRGB() does pixel format conversion automatically
146- ((BufferedImage) image).getRGB(0, 0, width, height, pixels, 0, width);
147+ ((BufferedImage) iconImage).getRGB(0, 0, paddedWidth, paddedHeight, pixels, 0, paddedWidth);
148 } else {
149- BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
150+ BufferedImage bufferedImage = new BufferedImage(paddedWidth, paddedHeight, BufferedImage.TYPE_INT_ARGB);
151 Graphics2D g = bufferedImage.createGraphics();
152- g.drawImage(image, 0, 0, null);
153+ g.drawImage(iconImage, 0, 0, null);
154 g.dispose();
155
156- bufferedImage.getRGB(0, 0, width, height, pixels, 0, width);
157+ bufferedImage.getRGB(0, 0, paddedWidth, paddedHeight, pixels, 0, paddedWidth);
158 }
159
160- setDnDIconImpl(nativePtr, scale, width, height, offsetX, offsetY, pixels);
161+ setDnDIconImpl(nativePtr, scale, paddedWidth, paddedHeight, offsetX, offsetY, pixels);
162 }
163
164 public void offerExtraMime(String mime) {
165@@ -152,4 +168,4 @@ protected void handleDnDDropPerformed() {}
166 protected void handleDnDFinished() {}
167
168 protected void handleDnDAction(int action) {}
169-}
170\ No newline at end of file
171+}
172diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLDragSourceContextPeer.java b/src/java.desktop/unix/classes/sun/awt/wl/WLDragSourceContextPeer.java
173index afd727d1a..46747ddca 100644
174--- a/src/java.desktop/unix/classes/sun/awt/wl/WLDragSourceContextPeer.java
175+++ b/src/java.desktop/unix/classes/sun/awt/wl/WLDragSourceContextPeer.java
176@@ -26,9 +26,13 @@
177 package sun.awt.wl;
178
179 import sun.awt.AWTAccessor;
180+import sun.awt.CustomCursor;
181 import sun.awt.dnd.SunDragSourceContextPeer;
182
183 import java.awt.Cursor;
184+import java.awt.Dimension;
185+import java.awt.Image;
186+import java.awt.Point;
187 import java.awt.datatransfer.DataFlavor;
188 import java.awt.datatransfer.Transferable;
189 import java.awt.dnd.DragGestureEvent;
190@@ -36,6 +40,8 @@
191
192 public class WLDragSourceContextPeer extends SunDragSourceContextPeer {
193 private final WLDataDevice dataDevice;
194+ private WLDragSource activeDragSource;
195+ private boolean activeDragSourceUsesCursorFallback;
196
197 private class WLDragSource extends WLDataSource {
198 private int action;
199@@ -47,7 +53,7 @@ private class WLDragSource extends WLDataSource {
200 super(dataDevice, WLDataDevice.DATA_TRANSFER_PROTOCOL_WAYLAND, data);
201 }
202
203- private void sendFinishedEvent() {
204+ private synchronized void sendFinishedEvent() {
205 if (didSendFinishedEvent) {
206 return;
207 }
208@@ -67,7 +73,11 @@ protected synchronized void handleDnDAction(int action) {
209 // except for maybe action(dnd_ask), but since we do not announce support for dnd_ask,
210 // we don't need to worry about it.
211 if (!didSucceed) {
212+ int previousAction = this.action;
213 this.action = action;
214+ if (previousAction != action) {
215+ WLDragSourceContextPeer.this.handleSourceActionChanged(previousAction, action);
216+ }
217 }
218 }
219
220@@ -78,8 +88,8 @@ protected synchronized void handleDnDDropPerformed() {
221
222 @Override
223 protected synchronized void handleDnDFinished() {
224+ WLDragSourceContextPeer.this.finishDragSource(this);
225 sendFinishedEvent();
226- destroy();
227 }
228
229 @Override
230@@ -88,9 +98,9 @@ protected synchronized void handleTargetAcceptsMime(String mime) {
231 }
232
233 @Override
234- protected void handleCancelled() {
235+ protected synchronized void handleCancelled() {
236+ WLDragSourceContextPeer.this.finishDragSource(this);
237 sendFinishedEvent();
238- super.handleCancelled();
239 }
240 }
241
242@@ -119,6 +129,35 @@ private WLMainSurface getSurface() {
243 return null;
244 }
245
246+ private synchronized void activateDragSource(WLDragSource dragSource) {
247+ activeDragSource = dragSource;
248+ }
249+
250+ private synchronized void finishDragSource(WLDragSource dragSource) {
251+ if (activeDragSource == dragSource) {
252+ activeDragSource = null;
253+ activeDragSourceUsesCursorFallback = false;
254+ }
255+ dragSource.destroy();
256+ }
257+
258+ private void handleSourceActionChanged(int previousWaylandAction, int waylandAction) {
259+ var inputState = WLToolkit.getInputState();
260+ int x = inputState.getPointerX();
261+ int y = inputState.getPointerY();
262+ int modifiers = inputState.getModifiers();
263+ int javaAction = WLDataDevice.waylandActionsToJava(waylandAction);
264+ int previousJavaAction = WLDataDevice.waylandActionsToJava(previousWaylandAction);
265+
266+ if (previousJavaAction == 0 && javaAction != 0) {
267+ postDragSourceDragEvent(javaAction, modifiers, x, y, DISPATCH_ENTER);
268+ } else if (previousJavaAction != 0 && javaAction == 0) {
269+ dragExit(x, y);
270+ } else if (previousJavaAction != 0) {
271+ postDragSourceDragEvent(javaAction, modifiers, x, y, DISPATCH_CHANGED);
272+ }
273+ }
274+
275 @Override
276 protected void startDrag(Transferable trans, long[] formats, Map<Long, DataFlavor> formatMap) {
277 var mainSurface = getSurface();
278@@ -134,21 +173,88 @@ protected void startDrag(Transferable trans, long[] formats, Map<Long, DataFlavo
279
280 source.setDnDActions(waylandActions);
281
282+ int scale = mainSurface.getGraphicsDevice().getDisplayScale();
283 var dragImage = getDragImage();
284+ Point dragImageOffset = null;
285+ boolean usesCursorFallback = false;
286+ if (dragImage == null) {
287+ dragImage = cursorToImage(getDragSourceContext().getCursor());
288+ if (dragImage != null) {
289+ dragImageOffset = cursorToDragImageOffset(getDragSourceContext().getCursor(), scale);
290+ usesCursorFallback = true;
291+ }
292+ } else {
293+ dragImageOffset = getDragImageOffset();
294+ }
295 if (dragImage != null) {
296- var dragImageOffset = getDragImageOffset();
297 source.setDnDIcon(dragImage,
298- mainSurface.getGraphicsDevice().getDisplayScale(),
299+ scale,
300 dragImageOffset.x, dragImageOffset.y);
301 }
302
303 long eventSerial = WLToolkit.getInputState().pointerButtonSerial();
304
305- dataDevice.startDrag(source, mainSurface.getWlSurfacePtr(), eventSerial);
306+ synchronized (this) {
307+ activateDragSource(source);
308+ activeDragSourceUsesCursorFallback = usesCursorFallback;
309+ }
310+ try {
311+ dataDevice.startDrag(source, mainSurface.getWlSurfacePtr(), eventSerial);
312+ } catch (RuntimeException | Error e) {
313+ finishDragSource(source);
314+ throw e;
315+ }
316 }
317
318 @Override
319 protected void setNativeCursor(long nativeCtxt, Cursor c, int cType) {
320- // TODO: setting cursor here doesn't seem to be required on Wayland?
321+ // On Wayland, cursor changes during a drag are expressed by
322+ // updating the drag icon surface content.
323+ var mainSurface = getSurface();
324+ int scale = mainSurface != null ? mainSurface.getGraphicsDevice().getDisplayScale() : 1;
325+
326+ synchronized (this) {
327+ if (activeDragSource == null) {
328+ return;
329+ }
330+ if (!activeDragSourceUsesCursorFallback) {
331+ return;
332+ }
333+
334+ Image image = cursorToImage(c);
335+ if (image == null) {
336+ return;
337+ }
338+
339+ Point dragImageOffset = cursorToDragImageOffset(c, scale);
340+ activeDragSource.setDnDIcon(image, scale, dragImageOffset.x, dragImageOffset.y);
341+ }
342+ }
343+
344+ private static Image cursorToImage(Cursor cursor) {
345+ if (cursor instanceof CustomCursor cc) {
346+ return cc.getImage();
347+ }
348+ return null;
349+ }
350+
351+ private static Point cursorToDragImageOffset(Cursor cursor, int scale) {
352+ Dimension cursorSize = WLCursorManager.getCursorSize(cursor, scale);
353+ if (cursorSize == null) {
354+ Image image = cursorToImage(cursor);
355+ if (image != null) {
356+ cursorSize = new Dimension(image.getWidth(null), image.getHeight(null));
357+ }
358+ }
359+
360+ if (cursorSize == null) {
361+ return new Point(0, 0);
362+ }
363+
364+ // This is only for the Wayland fallback path where no application drag
365+ // image was supplied and we reuse DnD cursor artwork as the drag icon.
366+ // Keep that fallback icon visible by placing it one cursor width/height
367+ // away from the pointer hotspot, expressed in surface-local coordinates.
368+ return new Point(cursorSize.width / scale, cursorSize.height / scale);
369 }
370 }
371diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLToolkit.java b/src/java.desktop/unix/classes/sun/awt/wl/WLToolkit.java
372index 058306539..e768cbfd8 100644
373--- a/src/java.desktop/unix/classes/sun/awt/wl/WLToolkit.java
374+++ b/src/java.desktop/unix/classes/sun/awt/wl/WLToolkit.java
375@@ -983,9 +983,18 @@ public int getNumberOfButtons(){
376 return MOUSE_BUTTONS_COUNT;
377 }
378
379+ private static final String DND_CURSOR_PREFIX = "DnD.Cursor.";
380+
381 @Override
382 protected Object lazilyLoadDesktopProperty(String name) {
383- if (log.isLoggable(PlatformLogger.Level.FINE)) {
384+ if (name.startsWith(DND_CURSOR_PREFIX)) {
385+ String cursorName = name.substring(DND_CURSOR_PREFIX.length()) + ".32x32";
386+ try {
387+ return Cursor.getSystemCustomCursor(cursorName);
388+ } catch (AWTException awte) {
389+ throw new RuntimeException("cannot load system cursor: " + cursorName, awte);
390+ }
391+ } else if (log.isLoggable(PlatformLogger.Level.FINE)) {
392 log.fine("Not implemented: WLToolkit.lazilyLoadDesktopProperty()");
393 }
394 return null;
395diff --git a/src/java.desktop/unix/native/libawt_wlawt/WLCursor.c b/src/java.desktop/unix/native/libawt_wlawt/WLCursor.c
396index f56f39d16..6d1a95460 100644
397--- a/src/java.desktop/unix/native/libawt_wlawt/WLCursor.c
398+++ b/src/java.desktop/unix/native/libawt_wlawt/WLCursor.c
399@@ -100,6 +100,20 @@ JNIEXPORT void JNICALL Java_sun_awt_wl_WLCursorManager_nativeDestroyPredefinedCu
400 free(cursor);
401 }
402
403+JNIEXPORT jint JNICALL Java_sun_awt_wl_WLCursorManager_nativeGetCursorWidth
404+ (JNIEnv *env, jclass cls, jlong cursorPtr)
405+{
406+ struct WLCursor *cursor = jlong_to_ptr(cursorPtr);
407+ return cursor ? (jint) cursor->width : 0;
408+}
409+
410+JNIEXPORT jint JNICALL Java_sun_awt_wl_WLCursorManager_nativeGetCursorHeight
411+ (JNIEnv *env, jclass cls, jlong cursorPtr)
412+{
413+ struct WLCursor *cursor = jlong_to_ptr(cursorPtr);
414+ return cursor ? (jint) cursor->height : 0;
415+}
416+
417 JNIEXPORT jlong JNICALL Java_sun_awt_wl_WLCustomCursor_nativeCreateCustomCursor
418 (JNIEnv *env, jclass cls, jintArray pixels, jint width, jint height, jint xHotSpot, jint yHotSpot)
419 {
420diff --git a/src/java.desktop/unix/native/libawt_wlawt/WLDataDevice.c b/src/java.desktop/unix/native/libawt_wlawt/WLDataDevice.c
421index 5d0095dae..7e6311529 100644
422--- a/src/java.desktop/unix/native/libawt_wlawt/WLDataDevice.c
423+++ b/src/java.desktop/unix/native/libawt_wlawt/WLDataDevice.c
424@@ -86,6 +86,8 @@ struct DataSource
425
426 struct wl_surface* dragIcon;
427 struct wl_buffer* dragIconBuffer;
428+ int dragIconOffsetX;
429+ int dragIconOffsetY;
430
431 struct DataSource* nextDel;
432 };
433@@ -673,7 +675,10 @@ zwp_primary_selection_source_handle_cancelled(void *user,
434 static void
435 wl_data_offer_handle_offer(void *user, struct wl_data_offer *wl_data_offer, const char *mime)
436 {
437- DataOffer_callOfferHandler((struct DataOffer *) user, mime);
438+ struct DataOffer *offer = user;
439+ assert(offer != NULL);
440+
441+ DataOffer_callOfferHandler(offer, mime);
442 }
443
444 static void
445@@ -1149,6 +1154,14 @@ JNIEXPORT void JNICALL Java_sun_awt_wl_WLDataSource_setDnDIconImpl
446 jint width, jint height, jint offsetX, jint offsetY, jintArray pixels)
447 {
448 struct DataSource *source = jlong_to_ptr(nativePtr);
449+ bool updating_existing_icon = source->dragIcon != NULL;
450+ struct wl_buffer *oldBuffer = source->dragIconBuffer;
451+ // Drag-icon updates reuse the same wl_surface after start_drag().
452+ // For that surface, wl_surface.offset (or pre-v5 nonzero attach offsets)
453+ // is relative to the current buffer position, not an absolute placement.
454+ // Keep the last requested absolute offset and send only the delta here.
455+ int deltaOffsetX = offsetX - source->dragIconOffsetX;
456+ int deltaOffsetY = offsetY - source->dragIconOffsetY;
457
458 size_t pixelCount = (size_t)((*env)->GetArrayLength(env, pixels));
459 size_t byteSize = pixelCount * 4U;
460@@ -1180,7 +1193,9 @@ JNIEXPORT void JNICALL Java_sun_awt_wl_WLDataSource_setDnDIconImpl
461 return;
462 }
463
464- source->dragIcon = wl_compositor_create_surface(wl_compositor);
465+ if (!source->dragIcon) {
466+ source->dragIcon = wl_compositor_create_surface(wl_compositor);
467+ }
468 if (!source->dragIcon) {
469 wl_buffer_destroy(source->dragIconBuffer);
470 source->dragIconBuffer = NULL;
471@@ -1191,12 +1206,12 @@ JNIEXPORT void JNICALL Java_sun_awt_wl_WLDataSource_setDnDIconImpl
472 int wl_compositor_version = wl_compositor_get_version(wl_compositor);
473 if (wl_compositor_version >= 5) {
474 wl_surface_attach(source->dragIcon, source->dragIconBuffer, 0, 0);
475- wl_surface_offset(source->dragIcon, offsetX, offsetY);
476+ wl_surface_offset(source->dragIcon, deltaOffsetX, deltaOffsetY);
477 } else {
478- wl_surface_attach(source->dragIcon, source->dragIconBuffer, offsetX, offsetY);
479+ wl_surface_attach(source->dragIcon, source->dragIconBuffer, deltaOffsetX, deltaOffsetY);
480 }
481 #else
482- wl_surface_attach(source->dragIcon, source->dragIconBuffer, offsetX, offsetY);
483+ wl_surface_attach(source->dragIcon, source->dragIconBuffer, deltaOffsetX, deltaOffsetY);
484 #endif
485
486 if (scale >= 1) {
487@@ -1205,7 +1220,17 @@ JNIEXPORT void JNICALL Java_sun_awt_wl_WLDataSource_setDnDIconImpl
488
489 wl_surface_damage_buffer(source->dragIcon, 0, 0, width, height);
490
491- // NOTE: we still need to commit the surface, this is done immediately after start_drag
492+ if (oldBuffer) {
493+ wl_buffer_destroy(oldBuffer);
494+ }
495+
496+ source->dragIconOffsetX = offsetX;
497+ source->dragIconOffsetY = offsetY;
498+
499+ if (updating_existing_icon) {
500+ wl_surface_commit(source->dragIcon);
501+ wlFlushToServer(env);
502+ }
503 }
504
505 JNIEXPORT void JNICALL
506--
5072.52.0
508
diff --git a/gnu/packages/patches/jbr-25-wayland-intra-jvm-dnd.patch b/gnu/packages/patches/jbr-25-wayland-intra-jvm-dnd.patch
new file mode 100644
index 00000000000..889014653d0
--- /dev/null
+++ b/gnu/packages/patches/jbr-25-wayland-intra-jvm-dnd.patch
@@ -0,0 +1,144 @@
1From 6f12ff22c3a902db64ef2bc4f94cb2ad4b26f4ce Mon Sep 17 00:00:00 2001
2From: Danny Milosavljevic <dannym@nova.friendly-machines.com>
3Date: Tue, 31 Mar 2026 05:24:04 +0000
4Subject: [PATCH 2/2] Wayland: enable intra-JVM drag-and-drop transfers
5
6Advertise JAVA_DATAFLAVOR natives for local drags, register the current JVM-local Transferable before starting the drag, and let the drop target fall back to the source actions when Wayland MIME negotiation stays at zero for a local Java-object transfer.
7---
8 .../sun/awt/wl/WLDragSourceContextPeer.java | 42 +++++++++++++++++--
9 .../sun/awt/wl/WLDropTargetContextPeer.java | 11 ++++-
10 2 files changed, 48 insertions(+), 5 deletions(-)
11
12diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLDragSourceContextPeer.java b/src/java.desktop/unix/classes/sun/awt/wl/WLDragSourceContextPeer.java
13index 46747ddca..3490b9f2b 100644
14--- a/src/java.desktop/unix/classes/sun/awt/wl/WLDragSourceContextPeer.java
15+++ b/src/java.desktop/unix/classes/sun/awt/wl/WLDragSourceContextPeer.java
16@@ -28,6 +28,7 @@
17 import sun.awt.AWTAccessor;
18 import sun.awt.CustomCursor;
19 import sun.awt.dnd.SunDragSourceContextPeer;
20+import sun.awt.dnd.SunDropTargetContextPeer;
21
22 import java.awt.Cursor;
23 import java.awt.Dimension;
24@@ -35,7 +36,9 @@
25 import java.awt.Point;
26 import java.awt.datatransfer.DataFlavor;
27 import java.awt.datatransfer.Transferable;
28+import java.awt.dnd.DnDConstants;
29 import java.awt.dnd.DragGestureEvent;
30+import java.util.HashSet;
31 import java.util.Map;
32
33 public class WLDragSourceContextPeer extends SunDragSourceContextPeer {
34@@ -59,7 +62,9 @@ private synchronized void sendFinishedEvent() {
35 }
36 didSendFinishedEvent = true;
37
38- final int javaAction = didSucceed ? WLDataDevice.waylandActionsToJava(action) : 0;
39+ final int javaAction = didSucceed
40+ ? WLDataDevice.waylandActionsToJava(action)
41+ : DnDConstants.ACTION_NONE;
42 final int x = WLToolkit.getInputState().getPointerX();
43 final int y = WLToolkit.getInputState().getPointerY();
44 WLDragSourceContextPeer.this.dragDropFinished(didSucceed, javaAction, x, y);
45@@ -149,15 +154,41 @@ private void handleSourceActionChanged(int previousWaylandAction, int waylandAct
46 int javaAction = WLDataDevice.waylandActionsToJava(waylandAction);
47 int previousJavaAction = WLDataDevice.waylandActionsToJava(previousWaylandAction);
48
49- if (previousJavaAction == 0 && javaAction != 0) {
50+ if (previousJavaAction == DnDConstants.ACTION_NONE
51+ && javaAction != DnDConstants.ACTION_NONE) {
52 postDragSourceDragEvent(javaAction, modifiers, x, y, DISPATCH_ENTER);
53- } else if (previousJavaAction != 0 && javaAction == 0) {
54+ } else if (previousJavaAction != DnDConstants.ACTION_NONE
55+ && javaAction == DnDConstants.ACTION_NONE) {
56 dragExit(x, y);
57- } else if (previousJavaAction != 0) {
58+ } else if (previousJavaAction != DnDConstants.ACTION_NONE) {
59 postDragSourceDragEvent(javaAction, modifiers, x, y, DISPATCH_CHANGED);
60 }
61 }
62
63+ private void offerJavaFlavorMimes(WLDragSource source, Transferable trans) {
64+ var transferer = (WLDataTransferer) WLDataTransferer.getInstance();
65+ var flavorTable = transferer.getFlavorTable();
66+ var extraMimes = new HashSet<String>();
67+ var flavors = trans.getTransferDataFlavors();
68+
69+ if (flavors == null) {
70+ return;
71+ }
72+
73+ for (DataFlavor flavor : flavors) {
74+ if (flavor == null) {
75+ continue;
76+ }
77+ for (String nativeName : flavorTable.getNativesForFlavor(flavor)) {
78+ if (nativeName != null
79+ && nativeName.startsWith("JAVA_DATAFLAVOR:")
80+ && extraMimes.add(nativeName)) {
81+ source.offerExtraMime(nativeName);
82+ }
83+ }
84+ }
85+ }
86+
87 @Override
88 protected void startDrag(Transferable trans, long[] formats, Map<Long, DataFlavor> formatMap) {
89 var mainSurface = getSurface();
90@@ -167,6 +198,7 @@ protected void startDrag(Transferable trans, long[] formats, Map<Long, DataFlavo
91
92 // formats and formatMap are unused, because WLDataSource already references the same DataTransferer singleton
93 var source = new WLDragSource(trans);
94+ offerJavaFlavorMimes(source, trans);
95
96 var actions = getDragSourceContext().getSourceActions();
97 int waylandActions = WLDataDevice.javaActionsToWayland(actions);
98@@ -199,9 +231,11 @@ protected void startDrag(Transferable trans, long[] formats, Map<Long, DataFlavo
99 activeDragSourceUsesCursorFallback = usesCursorFallback;
100 }
101 try {
102+ SunDropTargetContextPeer.setCurrentJVMLocalSourceTransferable(trans);
103 dataDevice.startDrag(source, mainSurface.getWlSurfacePtr(), eventSerial);
104 } catch (RuntimeException | Error e) {
105 finishDragSource(source);
106+ SunDropTargetContextPeer.setCurrentJVMLocalSourceTransferable(null);
107 throw e;
108 }
109 }
110diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLDropTargetContextPeer.java b/src/java.desktop/unix/classes/sun/awt/wl/WLDropTargetContextPeer.java
111index 76e47a133..757e20652 100644
112--- a/src/java.desktop/unix/classes/sun/awt/wl/WLDropTargetContextPeer.java
113+++ b/src/java.desktop/unix/classes/sun/awt/wl/WLDropTargetContextPeer.java
114@@ -31,6 +31,7 @@
115 import sun.awt.dnd.SunDropTargetEvent;
116
117 import java.awt.Component;
118+import java.awt.dnd.DnDConstants;
119 import java.awt.event.MouseEvent;
120 import java.io.IOException;
121
122@@ -67,10 +68,18 @@ private synchronized void postEvent(int event) {
123 var x = peer.surfaceUnitsToJavaUnits((int) currentX);
124 var y = peer.surfaceUnitsToJavaUnits((int) currentY);
125 var actions = WLDataDevice.waylandActionsToJava(currentOffer.getSourceActions());
126- int dropAction = 0;
127+ int dropAction = DnDConstants.ACTION_NONE;
128
129 if (hasTarget() && event != MouseEvent.MOUSE_EXITED) {
130+ // For intra-JVM transfers, the Wayland-level action negotiation
131+ // may fail (no matching MIME types for Java object flavors).
132+ // Fall back to the source actions directly.
133 dropAction = WLDataDevice.waylandActionsToJava(currentOffer.getSelectedAction());
134+ if (dropAction == DnDConstants.ACTION_NONE
135+ && currentJVMLocalSourceTransferable != null) {
136+ dropAction = SunDragSourceContextPeer.convertModifiersToDropAction(
137+ WLToolkit.getInputState().getModifiers(), actions);
138+ }
139 }
140
141 postDropTargetEvent(
142--
1432.52.0
144