diff options
| author | Danny Milosavljevic <dannym@friendly-machines.com> | 2026-04-01 01:19:29 +0200 |
|---|---|---|
| committer | Danny Milosavljevic <dannym@friendly-machines.com> | 2026-05-03 02:30:27 +0200 |
| commit | 95d5ecee8121145a51ab6844645b73a8c35114f5 (patch) | |
| tree | 30991c58800098cb223ccc24be707946e8b6a065 /gnu | |
| parent | 472c94ff67ba84676be980ca2ec58f8f4b296552 (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.mk | 2 | ||||
| -rw-r--r-- | gnu/packages/java.scm | 61 | ||||
| -rw-r--r-- | gnu/packages/patches/jbr-25-wayland-drag-icon.patch | 508 | ||||
| -rw-r--r-- | gnu/packages/patches/jbr-25-wayland-intra-jvm-dnd.patch | 144 |
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 | ||
| 2064 | and Java development kit. It supports enhanced class redefinition (DCEVM), | ||
| 2065 | includes a number of improvements in font rendering, keyboards support, | ||
| 2066 | windowing/focus subsystems, Wayland, HiDPI, accessibility, and performance, | ||
| 2067 | provides better desktop integration and bugfixes not yet present in | ||
| 2068 | OpenJDK.") | ||
| 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 @@ | |||
| 1 | From 0602a477881f7d4047edd2ffe2b6f5157fda9bc9 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Danny Milosavljevic <dannym@nova.friendly-machines.com> | ||
| 3 | Date: Tue, 31 Mar 2026 05:11:27 +0000 | ||
| 4 | Subject: [PATCH 1/2] Wayland: fix drag icon updates during drag-and-drop | ||
| 5 | |||
| 6 | Expose 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 | |||
| 17 | diff --git a/src/java.desktop/share/classes/sun/awt/CustomCursor.java b/src/java.desktop/share/classes/sun/awt/CustomCursor.java | ||
| 18 | index 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 | } | ||
| 66 | diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLCursorManager.java b/src/java.desktop/unix/classes/sun/awt/wl/WLCursorManager.java | ||
| 67 | index 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 | } | ||
| 115 | diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLDataSource.java b/src/java.desktop/unix/classes/sun/awt/wl/WLDataSource.java | ||
| 116 | index 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 | +} | ||
| 172 | diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLDragSourceContextPeer.java b/src/java.desktop/unix/classes/sun/awt/wl/WLDragSourceContextPeer.java | ||
| 173 | index 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 | } | ||
| 371 | diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLToolkit.java b/src/java.desktop/unix/classes/sun/awt/wl/WLToolkit.java | ||
| 372 | index 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; | ||
| 395 | diff --git a/src/java.desktop/unix/native/libawt_wlawt/WLCursor.c b/src/java.desktop/unix/native/libawt_wlawt/WLCursor.c | ||
| 396 | index 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 | { | ||
| 420 | diff --git a/src/java.desktop/unix/native/libawt_wlawt/WLDataDevice.c b/src/java.desktop/unix/native/libawt_wlawt/WLDataDevice.c | ||
| 421 | index 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 | -- | ||
| 507 | 2.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 @@ | |||
| 1 | From 6f12ff22c3a902db64ef2bc4f94cb2ad4b26f4ce Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Danny Milosavljevic <dannym@nova.friendly-machines.com> | ||
| 3 | Date: Tue, 31 Mar 2026 05:24:04 +0000 | ||
| 4 | Subject: [PATCH 2/2] Wayland: enable intra-JVM drag-and-drop transfers | ||
| 5 | |||
| 6 | Advertise 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 | |||
| 12 | diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLDragSourceContextPeer.java b/src/java.desktop/unix/classes/sun/awt/wl/WLDragSourceContextPeer.java | ||
| 13 | index 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 | } | ||
| 110 | diff --git a/src/java.desktop/unix/classes/sun/awt/wl/WLDropTargetContextPeer.java b/src/java.desktop/unix/classes/sun/awt/wl/WLDropTargetContextPeer.java | ||
| 111 | index 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 | -- | ||
| 143 | 2.52.0 | ||
| 144 | |||
