mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 09:29:38 +01:00
JBR-8833: Refactor Wayland data device abstraction [WLToolkit]
This commit is contained in:
@@ -24,101 +24,74 @@
|
||||
*/
|
||||
package sun.awt.wl;
|
||||
|
||||
import jdk.internal.misc.InnocuousThread;
|
||||
import sun.awt.datatransfer.DataTransferer;
|
||||
import sun.awt.datatransfer.SunClipboard;
|
||||
import sun.util.logging.PlatformLogger;
|
||||
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.FlavorTable;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.SortedMap;
|
||||
|
||||
public final class WLClipboard extends SunClipboard {
|
||||
private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.wl.WLClipboard");
|
||||
|
||||
public static final int INITIAL_MIME_FORMATS_COUNT = 10;
|
||||
private static final int DEFAULT_BUFFER_SIZE = 4096;
|
||||
private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
|
||||
|
||||
// A native handle of a Wayland queue dedicated to handling
|
||||
// data offer-related events
|
||||
private static final long dataOfferQueuePtr;
|
||||
|
||||
private final long ID;
|
||||
private final WLDataDevice dataDevice;
|
||||
|
||||
// true if this is the "primary selection" clipboard,
|
||||
// false otherwise (the regular clipboard).
|
||||
private final boolean isPrimary; // used by native
|
||||
private final boolean isPrimary;
|
||||
|
||||
private final Object dataLock = new Object();
|
||||
// A handle to the native clipboard representation, 0 if not available.
|
||||
private long clipboardNativePtr; // guarded by dataLock
|
||||
private long ourOfferNativePtr; // guarded by dataLock
|
||||
|
||||
// The list of numeric format IDs the current clipboard is available in;
|
||||
// could be null or empty.
|
||||
private List<Long> clipboardFormats; // guarded by dataLock
|
||||
// Latest active data offer to us, containing up-to-date clipboard content,
|
||||
// as provided by the Wayland compositor.
|
||||
// If null, there's no clipboard data available.
|
||||
// Guarded by dataLock.
|
||||
private WLDataOffer clipboardDataOfferedToUs;
|
||||
|
||||
// The "current" list formats for the new clipboard contents that is about
|
||||
// to be received from Wayland. Could be empty, but never null.
|
||||
private List<Long> newClipboardFormats = new ArrayList<>(INITIAL_MIME_FORMATS_COUNT); // guarded by dataLock
|
||||
// Clipboard data source we are providing to the Wayland compositor.
|
||||
// If null, we are not offering any clipboard data at the moment.
|
||||
// Guarded by dataLock.
|
||||
private WLDataSource ourDataSource;
|
||||
|
||||
private static final Thread clipboardDispatcherThread;
|
||||
static {
|
||||
initIDs();
|
||||
dataOfferQueuePtr = createDataOfferQueue();
|
||||
flavorTable = DataTransferer.adaptFlavorMap(getDefaultFlavorTable());
|
||||
|
||||
Thread t = InnocuousThread.newThread(
|
||||
"AWT-Wayland-clipboard-dispatcher",
|
||||
WLClipboard::dispatchDataOfferQueue);
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
clipboardDispatcherThread = t;
|
||||
}
|
||||
|
||||
private final static FlavorTable flavorTable;
|
||||
|
||||
public WLClipboard(String name, boolean isPrimary) {
|
||||
public WLClipboard(WLDataDevice dataDevice, String name, boolean isPrimary) {
|
||||
super(name);
|
||||
this.ID = initNative(isPrimary);
|
||||
this.isPrimary = isPrimary;
|
||||
this.dataDevice = dataDevice;
|
||||
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("Clipboard: Created " + this);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dispatchDataOfferQueue() {
|
||||
dispatchDataOfferQueueImpl(dataOfferQueuePtr); // does not return until error or server disconnect
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("Clipboard: data offer dispatcher exited");
|
||||
private int getProtocol() {
|
||||
if (isPrimary) {
|
||||
return WLDataDevice.DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION;
|
||||
} else {
|
||||
return WLDataDevice.DATA_TRANSFER_PROTOCOL_WAYLAND;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Clipboard: Wayland %s (%x)", (isPrimary ? "selection clipboard" : "clipboard"), ID);
|
||||
return String.format("Clipboard: Wayland %s", (isPrimary ? "selection clipboard" : "clipboard"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getID() {
|
||||
return ID;
|
||||
return isPrimary ? 2 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we loose ownership of the clipboard.
|
||||
* Called when we lose ownership of the clipboard.
|
||||
*/
|
||||
@Override
|
||||
protected void clearNativeContext() {
|
||||
@@ -164,34 +137,20 @@ public final class WLClipboard extends SunClipboard {
|
||||
notifyOfNewFormats(formats);
|
||||
|
||||
if (formats.length > 0) {
|
||||
String[] mime = new String[formats.length];
|
||||
for (int i = 0; i < formats.length; i++) {
|
||||
mime[i] = wlDataTransferer.getNativeForFormat(formats[i]);
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("Clipboard: formats mapping " + formats[i] + " -> " + mime[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("Clipboard: Offering new contents (" + contents + ") in these MIME formats: " + Arrays.toString(mime));
|
||||
log.fine("Clipboard: Offering new contents (" + contents + ")");
|
||||
}
|
||||
|
||||
WLDataSource newOffer = null;
|
||||
newOffer = dataDevice.createDataSourceFromTransferable(getProtocol(), contents);
|
||||
|
||||
synchronized (dataLock) {
|
||||
if (ourOfferNativePtr != 0) {
|
||||
cancelOffer(ourOfferNativePtr);
|
||||
ourOfferNativePtr = 0;
|
||||
if (ourDataSource != null) {
|
||||
ourDataSource.destroy();
|
||||
}
|
||||
ourDataSource = newOffer;
|
||||
dataDevice.setSelection(getProtocol(), newOffer, eventSerial);
|
||||
}
|
||||
long newOfferPtr = offerData(eventSerial, mime, contents, dataOfferQueuePtr);
|
||||
synchronized (dataLock) {
|
||||
ourOfferNativePtr = newOfferPtr;
|
||||
}
|
||||
|
||||
// Once we have offered the data, someone may come back and ask to provide them.
|
||||
// In that event, the transferContentsWithType() will be called from native on the
|
||||
// clipboard dispatch thread.
|
||||
// A reference to contents is retained until we are notified of the new contents
|
||||
// by the Wayland server.
|
||||
}
|
||||
} else {
|
||||
this.owner = null;
|
||||
@@ -199,75 +158,26 @@ public final class WLClipboard extends SunClipboard {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from native on EDT when a client has asked to provide the actual data for
|
||||
* the clipboard that we own in the given format to the given file.
|
||||
* NB: that client could be us, but we aren't necessarily aware of that once we
|
||||
* lost keyboard focus at least once after Ctrl-C.
|
||||
*
|
||||
* @param contents a reference to the clipboard's contents to be transferred
|
||||
* @param mime transfer the contents in this MIME format
|
||||
* @param destFD transfer the contents to this file descriptor and close it afterward
|
||||
*
|
||||
* @throws IOException in case writing to the given file descriptor failed
|
||||
*/
|
||||
private void transferContentsWithType(Transferable contents, String mime, int destFD) throws IOException {
|
||||
assert (Thread.currentThread() == clipboardDispatcherThread);
|
||||
|
||||
Objects.requireNonNull(contents);
|
||||
Objects.requireNonNull(mime);
|
||||
|
||||
WLDataTransferer wlDataTransferer = (WLDataTransferer) DataTransferer.getInstance();
|
||||
SortedMap<Long,DataFlavor> formatMap =
|
||||
wlDataTransferer.getFormatsForTransferable(contents, flavorTable);
|
||||
|
||||
long targetFormat = wlDataTransferer.getFormatForNativeAsLong(mime);
|
||||
DataFlavor flavor = formatMap.get(targetFormat);
|
||||
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("Clipboard: will write contents (" + contents + ") in format " + mime + " to fd=" + destFD);
|
||||
log.fine("Clipboard: data flavor: " + flavor);
|
||||
}
|
||||
|
||||
if (flavor != null) {
|
||||
byte[] bytes = wlDataTransferer.translateTransferable(contents, flavor, targetFormat);
|
||||
if (bytes == null) return;
|
||||
FileDescriptor javaDestFD = new FileDescriptor();
|
||||
jdk.internal.access.SharedSecrets.getJavaIOFileDescriptorAccess().set(javaDestFD, destFD);
|
||||
try (var out = new FileOutputStream(javaDestFD)) {
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("Clipboard: about to write " + bytes.length + " bytes to " + out);
|
||||
}
|
||||
|
||||
FileChannel ch = out.getChannel();
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes);
|
||||
// Need to write with retries because when a pipe is in the non-blocking mode
|
||||
// writing more than its capacity (usually 16 pages or 64K) fails with EAGAIN.
|
||||
// Since we receive destFD from the Wayland sever, we can't assume it
|
||||
// to always be in the blocking mode.
|
||||
while (buffer.hasRemaining()) {
|
||||
ch.write(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return formats the current clipboard is available in; could be null
|
||||
*/
|
||||
@Override
|
||||
protected long[] getClipboardFormats() {
|
||||
WLDataTransferer wlDataTransferer = (WLDataTransferer) DataTransferer.getInstance();
|
||||
List<String> mimes;
|
||||
synchronized (dataLock) {
|
||||
if (clipboardFormats != null && !clipboardFormats.isEmpty()) {
|
||||
long[] res = new long[clipboardFormats.size()];
|
||||
for (int i = 0; i < res.length; i++) {
|
||||
res[i] = clipboardFormats.get(i);
|
||||
}
|
||||
return res;
|
||||
} else {
|
||||
if (clipboardDataOfferedToUs == null || !clipboardDataOfferedToUs.isValid()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
mimes = clipboardDataOfferedToUs.getMimes();
|
||||
}
|
||||
|
||||
long[] formats = new long[mimes.size()];
|
||||
for (int i = 0; i < mimes.size(); ++i) {
|
||||
formats[i] = wlDataTransferer.getFormatForNativeAsLong(mimes.get(i));
|
||||
}
|
||||
return formats;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,100 +189,10 @@ public final class WLClipboard extends SunClipboard {
|
||||
*/
|
||||
@Override
|
||||
protected byte[] getClipboardData(long format) throws IOException {
|
||||
int fd = getClipboardFDIn(format);
|
||||
if (fd >= 0) {
|
||||
FileDescriptor javaFD = new FileDescriptor();
|
||||
jdk.internal.access.SharedSecrets.getJavaIOFileDescriptorAccess().set(javaFD, fd);
|
||||
try (var in = new FileInputStream(javaFD)) {
|
||||
byte[] bytes = readAllBytesFrom(in);
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("Clipboard: read data from " + fd + ": "
|
||||
+ (bytes != null ? bytes.length : 0) + " bytes");
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private int getClipboardFDIn(long format) {
|
||||
synchronized (dataLock) {
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("Clipboard: requested content of clipboard with handle "
|
||||
+ clipboardNativePtr + " in format " + format);
|
||||
}
|
||||
if (clipboardNativePtr != 0) {
|
||||
WLDataTransferer wlDataTransferer = (WLDataTransferer) DataTransferer.getInstance();
|
||||
String mime = wlDataTransferer.getNativeForFormat(format);
|
||||
int fd = requestDataInFormat(clipboardNativePtr, mime);
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("Clipboard: will read data from " + fd + " in format " + mime);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from native to notify us of the availability of a new clipboard
|
||||
* denoted by the native handle in a specific MIME format.
|
||||
* This method is usually called repeatedly with the same nativePtr and
|
||||
* different formats. When all formats are announces in this way,
|
||||
* handleNewClipboard() is called.
|
||||
*
|
||||
* @param nativePtr a native handle to the clipboard
|
||||
* @param mime the MIME format in which this clipboard is available.
|
||||
*/
|
||||
private void handleClipboardFormat(long nativePtr, String mime) {
|
||||
WLDataTransferer wlDataTransferer = (WLDataTransferer) DataTransferer.getInstance();
|
||||
Long format = wlDataTransferer.getFormatForNativeAsLong(mime);
|
||||
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("Clipboard: new format is available for " + nativePtr + ": " + mime);
|
||||
}
|
||||
|
||||
String mime = wlDataTransferer.getNativeForFormat(format);
|
||||
synchronized (dataLock) {
|
||||
newClipboardFormats.add(format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from native to notify us that a new clipboard content
|
||||
* has been made available. The list of supported formats
|
||||
* should have already been received and saved in newClipboardFormats.
|
||||
*
|
||||
* @param newClipboardNativePtr a native handle to the clipboard
|
||||
*/
|
||||
private void handleNewClipboard(long newClipboardNativePtr) {
|
||||
// Since we have a new clipboard, the existing one is no longer valid.
|
||||
// We have no way of knowing whether this "new" one is the same as the "old" one.
|
||||
lostOwnershipNow(null);
|
||||
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("Clipboard: new clipboard is available: " + newClipboardNativePtr);
|
||||
}
|
||||
|
||||
synchronized (dataLock) {
|
||||
long oldClipboardNativePtr = clipboardNativePtr;
|
||||
if (oldClipboardNativePtr != 0) {
|
||||
// "The client must destroy the previous selection data_offer, if any, upon receiving this event."
|
||||
destroyClipboard(oldClipboardNativePtr);
|
||||
}
|
||||
clipboardFormats = newClipboardFormats;
|
||||
clipboardNativePtr = newClipboardNativePtr; // Could be NULL
|
||||
|
||||
newClipboardFormats = new ArrayList<>(INITIAL_MIME_FORMATS_COUNT);
|
||||
}
|
||||
|
||||
notifyOfNewFormats(getClipboardFormats());
|
||||
}
|
||||
|
||||
private void handleOfferCancelled(long offerNativePtr) {
|
||||
synchronized (dataLock) {
|
||||
assert offerNativePtr == ourOfferNativePtr;
|
||||
ourOfferNativePtr = 0;
|
||||
return clipboardDataOfferedToUs.receiveData(mime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,76 +217,14 @@ public final class WLClipboard extends SunClipboard {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the given input stream until EOF and returns its contents as an array of bytes.
|
||||
*/
|
||||
private byte[] readAllBytesFrom(FileInputStream inputStream) throws IOException {
|
||||
int len = Integer.MAX_VALUE;
|
||||
List<byte[]> bufs = null;
|
||||
byte[] result = null;
|
||||
int total = 0;
|
||||
int remaining = len;
|
||||
int n;
|
||||
do {
|
||||
byte[] buf = new byte[Math.min(remaining, DEFAULT_BUFFER_SIZE)];
|
||||
int nread = 0;
|
||||
void handleClipboardOffer(WLDataOffer offer /* nullable */) {
|
||||
lostOwnershipNow(null);
|
||||
|
||||
while ((n = inputStream.read(buf, nread,
|
||||
Math.min(buf.length - nread, remaining))) > 0) {
|
||||
nread += n;
|
||||
remaining -= n;
|
||||
synchronized (dataLock) {
|
||||
if (clipboardDataOfferedToUs != null && clipboardDataOfferedToUs.isValid()) {
|
||||
clipboardDataOfferedToUs.destroy();
|
||||
}
|
||||
|
||||
if (nread > 0) {
|
||||
if (MAX_BUFFER_SIZE - total < nread) {
|
||||
throw new OutOfMemoryError("Required array size too large");
|
||||
}
|
||||
if (nread < buf.length) {
|
||||
buf = Arrays.copyOfRange(buf, 0, nread);
|
||||
}
|
||||
total += nread;
|
||||
if (result == null) {
|
||||
result = buf;
|
||||
} else {
|
||||
if (bufs == null) {
|
||||
bufs = new ArrayList<>();
|
||||
bufs.add(result);
|
||||
}
|
||||
bufs.add(buf);
|
||||
}
|
||||
}
|
||||
// if the last call to read returned -1 or the number of bytes
|
||||
// requested have been read then break
|
||||
} while (n >= 0 && remaining > 0);
|
||||
|
||||
if (bufs == null) {
|
||||
if (result == null) {
|
||||
return new byte[0];
|
||||
}
|
||||
return result.length == total ?
|
||||
result : Arrays.copyOf(result, total);
|
||||
clipboardDataOfferedToUs = offer;
|
||||
}
|
||||
|
||||
result = new byte[total];
|
||||
int offset = 0;
|
||||
remaining = total;
|
||||
for (byte[] b : bufs) {
|
||||
int count = Math.min(b.length, remaining);
|
||||
System.arraycopy(b, 0, result, offset, count);
|
||||
offset += count;
|
||||
remaining -= count;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static native void initIDs();
|
||||
private static native long createDataOfferQueue();
|
||||
private static native void dispatchDataOfferQueueImpl(long dataOfferQueuePtr);
|
||||
private native long initNative(boolean isPrimary);
|
||||
private native long offerData(long eventSerial, String[] mime, Object data, long dataOfferQueuePtr);
|
||||
private native void cancelOffer(long offerNativePtr);
|
||||
|
||||
private native int requestDataInFormat(long clipboardNativePtr, String mime);
|
||||
private native void destroyClipboard(long clipboardNativePtr);
|
||||
}
|
||||
249
src/java.desktop/unix/classes/sun/awt/wl/WLDataDevice.java
Normal file
249
src/java.desktop/unix/classes/sun/awt/wl/WLDataDevice.java
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright 2025 JetBrains s.r.o.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package sun.awt.wl;
|
||||
|
||||
import jdk.internal.misc.InnocuousThread;
|
||||
import sun.util.logging.PlatformLogger;
|
||||
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.SortedMap;
|
||||
|
||||
public class WLDataDevice {
|
||||
private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.wl.WLDataDevice");
|
||||
|
||||
private long nativePtr;
|
||||
private final WLClipboard systemClipboard;
|
||||
private final WLClipboard primarySelectionClipboard;
|
||||
|
||||
public static final int DND_COPY = 0x01;
|
||||
public static final int DND_MOVE = 0x02;
|
||||
public static final int DND_ASK = 0x04;
|
||||
public static final int DND_ALL = DND_COPY | DND_MOVE | DND_ASK;
|
||||
|
||||
public static final int DATA_TRANSFER_PROTOCOL_WAYLAND = 1;
|
||||
public static final int DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION = 2;
|
||||
|
||||
WLDataDevice(long wlSeatNativePtr) {
|
||||
nativePtr = initNative(wlSeatNativePtr);
|
||||
|
||||
var queueThread = InnocuousThread.newThread("AWT-Wayland-data-transferer-dispatcher", () -> {
|
||||
dispatchDataTransferQueueImpl(nativePtr);
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("data transfer dispatcher exited");
|
||||
}
|
||||
});
|
||||
queueThread.setDaemon(true);
|
||||
queueThread.start();
|
||||
|
||||
systemClipboard = new WLClipboard(this, "System", false);
|
||||
if (isProtocolSupported(DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION)) {
|
||||
primarySelectionClipboard = new WLClipboard(this, "Selection", true);
|
||||
} else {
|
||||
primarySelectionClipboard = null;
|
||||
}
|
||||
}
|
||||
|
||||
private native static void initIDs();
|
||||
static {
|
||||
initIDs();
|
||||
}
|
||||
|
||||
private native long initNative(long wlSeatNativePtr);
|
||||
private static native boolean isProtocolSupportedImpl(long nativePtr, int protocol);
|
||||
private static native void dispatchDataTransferQueueImpl(long nativePtr);
|
||||
private static native void setSelectionImpl(int protocol, long nativePtr, long dataOfferNativePtr, long serial);
|
||||
|
||||
public WLDataSource createDataSourceFromTransferable(int protocol, Transferable transferable) {
|
||||
return new WLDataSource(nativePtr, protocol, transferable);
|
||||
}
|
||||
|
||||
public boolean isProtocolSupported(int protocol) {
|
||||
return isProtocolSupportedImpl(nativePtr, protocol);
|
||||
}
|
||||
|
||||
public void setSelection(int protocol, WLDataSource source, long serial) {
|
||||
setSelectionImpl(protocol, nativePtr, source.getNativePtr(), serial);
|
||||
}
|
||||
|
||||
public WLClipboard getSystemClipboard() {
|
||||
return systemClipboard;
|
||||
}
|
||||
|
||||
public WLClipboard getPrimarySelectionClipboard() {
|
||||
return primarySelectionClipboard;
|
||||
}
|
||||
|
||||
static void transferContentsWithType(Transferable contents, String mime, int fd) {
|
||||
Objects.requireNonNull(contents);
|
||||
Objects.requireNonNull(mime);
|
||||
|
||||
WLDataTransferer wlDataTransferer = (WLDataTransferer) WLDataTransferer.getInstance();
|
||||
|
||||
SortedMap<Long, DataFlavor> formatMap = wlDataTransferer.getFormatsForTransferable(contents, wlDataTransferer.getFlavorTable());
|
||||
|
||||
long targetFormat = wlDataTransferer.getFormatForNativeAsLong(mime);
|
||||
DataFlavor flavor = formatMap.get(targetFormat);
|
||||
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("will write contents (" + contents + ") in format " + mime + " to fd=" + fd);
|
||||
log.fine("data flavor: " + flavor);
|
||||
}
|
||||
|
||||
if (flavor != null) {
|
||||
try {
|
||||
byte[] bytes = wlDataTransferer.translateTransferable(contents, flavor, targetFormat);
|
||||
if (bytes == null) return;
|
||||
FileDescriptor javaDestFD = new FileDescriptor();
|
||||
jdk.internal.access.SharedSecrets.getJavaIOFileDescriptorAccess().set(javaDestFD, fd);
|
||||
try (var out = new FileOutputStream(javaDestFD)) {
|
||||
if (log.isLoggable(PlatformLogger.Level.FINE)) {
|
||||
log.fine("about to write " + bytes.length + " bytes to " + out);
|
||||
}
|
||||
|
||||
FileChannel ch = out.getChannel();
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes);
|
||||
// Need to write with retries because when a pipe is in the non-blocking mode
|
||||
// writing more than its capacity (usually 16 pages or 64K) fails with EAGAIN.
|
||||
// Since we receive fd from the Wayland sever, we can't assume it
|
||||
// to always be in the blocking mode.
|
||||
while (buffer.hasRemaining()) {
|
||||
ch.write(buffer);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warning("failed to write contents (" + contents + ") in format " + mime + " to fd=" + fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final int DEFAULT_BUFFER_SIZE = 4096;
|
||||
private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
|
||||
|
||||
/**
|
||||
* Reads the given input stream until EOF and returns its contents as an array of bytes.
|
||||
*/
|
||||
static byte[] readAllBytesFrom(FileInputStream inputStream) throws IOException {
|
||||
int len = Integer.MAX_VALUE;
|
||||
List<byte[]> bufs = null;
|
||||
byte[] result = null;
|
||||
int total = 0;
|
||||
int remaining = len;
|
||||
int n;
|
||||
do {
|
||||
byte[] buf = new byte[Math.min(remaining, DEFAULT_BUFFER_SIZE)];
|
||||
int nread = 0;
|
||||
|
||||
while ((n = inputStream.read(buf, nread,
|
||||
Math.min(buf.length - nread, remaining))) > 0) {
|
||||
nread += n;
|
||||
remaining -= n;
|
||||
}
|
||||
|
||||
if (nread > 0) {
|
||||
if (MAX_BUFFER_SIZE - total < nread) {
|
||||
throw new OutOfMemoryError("Required array size too large");
|
||||
}
|
||||
if (nread < buf.length) {
|
||||
buf = Arrays.copyOfRange(buf, 0, nread);
|
||||
}
|
||||
total += nread;
|
||||
if (result == null) {
|
||||
result = buf;
|
||||
} else {
|
||||
if (bufs == null) {
|
||||
bufs = new ArrayList<>();
|
||||
bufs.add(result);
|
||||
}
|
||||
bufs.add(buf);
|
||||
}
|
||||
}
|
||||
// if the last call to read returned -1 or the number of bytes
|
||||
// requested have been read then break
|
||||
} while (n >= 0 && remaining > 0);
|
||||
|
||||
if (bufs == null) {
|
||||
if (result == null) {
|
||||
return new byte[0];
|
||||
}
|
||||
return result.length == total ?
|
||||
result : Arrays.copyOf(result, total);
|
||||
}
|
||||
|
||||
result = new byte[total];
|
||||
int offset = 0;
|
||||
remaining = total;
|
||||
for (byte[] b : bufs) {
|
||||
int count = Math.min(b.length, remaining);
|
||||
System.arraycopy(b, 0, result, offset, count);
|
||||
offset += count;
|
||||
remaining -= count;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private WLDataOffer currentDnDOffer = null;
|
||||
|
||||
// Event handlers, called from native on the EDT
|
||||
private void handleDnDEnter(WLDataOffer offer, long serial, long surfacePtr, double x, double y) {
|
||||
if (currentDnDOffer != null) {
|
||||
currentDnDOffer.destroy();
|
||||
}
|
||||
currentDnDOffer = offer;
|
||||
}
|
||||
|
||||
private void handleDnDLeave() {
|
||||
if (currentDnDOffer != null) {
|
||||
currentDnDOffer.destroy();
|
||||
currentDnDOffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDnDMotion(long timestamp, double x, double y) {
|
||||
}
|
||||
|
||||
private void handleDnDDrop() {
|
||||
}
|
||||
|
||||
private void handleSelection(WLDataOffer offer /* nullable */, int protocol) {
|
||||
WLClipboard clipboard = (protocol == DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION) ? primarySelectionClipboard : systemClipboard;
|
||||
|
||||
if (clipboard != null) {
|
||||
clipboard.handleClipboardOffer(offer);
|
||||
}
|
||||
}
|
||||
}
|
||||
126
src/java.desktop/unix/classes/sun/awt/wl/WLDataOffer.java
Normal file
126
src/java.desktop/unix/classes/sun/awt/wl/WLDataOffer.java
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2025 JetBrains s.r.o.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package sun.awt.wl;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class WLDataOffer {
|
||||
private long nativePtr;
|
||||
private final List<String> mimes = new ArrayList<>();
|
||||
private int sourceActions = -1;
|
||||
private int selectedAction = -1;
|
||||
|
||||
private static native void destroyImpl(long nativePtr);
|
||||
|
||||
private static native void acceptImpl(long nativePtr, long serial, String mime);
|
||||
|
||||
private static native int openReceivePipe(long nativePtr, String mime);
|
||||
|
||||
private static native void finishDnDImpl(long nativePtr);
|
||||
|
||||
private static native void setDnDActionsImpl(long nativePtr, int actions, int preferredAction);
|
||||
|
||||
private WLDataOffer(long nativePtr) {
|
||||
this.nativePtr = nativePtr;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return nativePtr != 0;
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (nativePtr != 0) {
|
||||
destroyImpl(nativePtr);
|
||||
nativePtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] receiveData(String mime) throws IOException {
|
||||
if (nativePtr == 0) {
|
||||
throw new IllegalStateException("nativePtr is 0");
|
||||
}
|
||||
|
||||
int fd = openReceivePipe(nativePtr, mime);
|
||||
assert(fd != -1); // Otherwise an exception should be thrown from native code
|
||||
|
||||
FileDescriptor javaFD = new FileDescriptor();
|
||||
jdk.internal.access.SharedSecrets.getJavaIOFileDescriptorAccess().set(javaFD, fd);
|
||||
try (var in = new FileInputStream(javaFD)) {
|
||||
return WLDataDevice.readAllBytesFrom(in);
|
||||
}
|
||||
}
|
||||
|
||||
public void accept(long serial, String mime) {
|
||||
if (nativePtr == 0) {
|
||||
throw new IllegalStateException("nativePtr is 0");
|
||||
}
|
||||
|
||||
acceptImpl(nativePtr, serial, mime);
|
||||
}
|
||||
|
||||
public void finishDnD() {
|
||||
if (nativePtr == 0) {
|
||||
throw new IllegalStateException("nativePtr is 0");
|
||||
}
|
||||
|
||||
finishDnDImpl(nativePtr);
|
||||
}
|
||||
|
||||
public void setDnDActions(int actions, int preferredAction) {
|
||||
if (nativePtr == 0) {
|
||||
throw new IllegalStateException("nativePtr is 0");
|
||||
}
|
||||
|
||||
if (actions != 0) {
|
||||
if ((actions & preferredAction) == 0) {
|
||||
throw new IllegalArgumentException("preferredAction is not a valid action");
|
||||
}
|
||||
}
|
||||
|
||||
setDnDActionsImpl(nativePtr, actions, preferredAction);
|
||||
}
|
||||
|
||||
public List<String> getMimes() {
|
||||
return mimes;
|
||||
}
|
||||
|
||||
// Event handlers, called from native code on the data device dispatch thread
|
||||
private void handleOfferMime(String mime) {
|
||||
mimes.add(mime);
|
||||
}
|
||||
|
||||
private void handleSourceActions(int actions) {
|
||||
sourceActions = actions;
|
||||
}
|
||||
|
||||
private void handleAction(int action) {
|
||||
selectedAction = action;
|
||||
}
|
||||
}
|
||||
112
src/java.desktop/unix/classes/sun/awt/wl/WLDataSource.java
Normal file
112
src/java.desktop/unix/classes/sun/awt/wl/WLDataSource.java
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2025 JetBrains s.r.o.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package sun.awt.wl;
|
||||
|
||||
import java.awt.datatransfer.Transferable;
|
||||
|
||||
public class WLDataSource {
|
||||
private long nativePtr;
|
||||
private final Transferable data;
|
||||
|
||||
private native long initNative(long dataDeviceNativePtr, int protocol);
|
||||
|
||||
private static native void offerMimeImpl(long nativePtr, String mime);
|
||||
|
||||
private static native void destroyImpl(long nativePtr);
|
||||
|
||||
private static native void setDnDActionsImpl(long nativePtr, int actions);
|
||||
|
||||
WLDataSource(long dataDeviceNativePtr, int protocol, Transferable data) {
|
||||
var wlDataTransferer = (WLDataTransferer) WLDataTransferer.getInstance();
|
||||
|
||||
nativePtr = initNative(dataDeviceNativePtr, protocol);
|
||||
assert nativePtr != 0; // should've already thrown in native
|
||||
this.data = data;
|
||||
|
||||
try {
|
||||
if (data != null) {
|
||||
long[] formats = wlDataTransferer.getFormatsForTransferableAsArray(data, wlDataTransferer.getFlavorTable());
|
||||
for (long format : formats) {
|
||||
String mime = wlDataTransferer.getNativeForFormat(format);
|
||||
offerMime(mime);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
destroyImpl(nativePtr);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return nativePtr != 0;
|
||||
}
|
||||
|
||||
public long getNativePtr() {
|
||||
return nativePtr;
|
||||
}
|
||||
|
||||
public void offerMime(String mime) {
|
||||
if (nativePtr == 0) {
|
||||
throw new IllegalStateException("Native pointer is null");
|
||||
}
|
||||
offerMimeImpl(nativePtr, mime);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (nativePtr != 0) {
|
||||
destroyImpl(nativePtr);
|
||||
nativePtr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void setDnDActions(int actions) {
|
||||
if (nativePtr == 0) {
|
||||
throw new IllegalStateException("Native pointer is null");
|
||||
}
|
||||
setDnDActionsImpl(nativePtr, actions);
|
||||
}
|
||||
|
||||
// Event handlers, called from native code on the data transferer dispatch thread
|
||||
protected void handleTargetAcceptsMime(String mime) {
|
||||
}
|
||||
|
||||
protected void handleSend(String mime, int fd) {
|
||||
WLDataDevice.transferContentsWithType(data, mime, fd);
|
||||
}
|
||||
|
||||
protected void handleCancelled() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
protected void handleDnDDropPerformed() {
|
||||
}
|
||||
|
||||
protected void handleDnDFinished() {
|
||||
}
|
||||
|
||||
protected void handleDnDAction(int action) {
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023 JetBrains s.r.o.
|
||||
* Copyright 2025 JetBrains s.r.o.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
package sun.awt.wl;
|
||||
|
||||
import jdk.internal.misc.InnocuousThread;
|
||||
import sun.awt.datatransfer.DataTransferer;
|
||||
import sun.awt.datatransfer.ToolkitThreadBlockedHandler;
|
||||
import sun.datatransfer.DataFlavorUtil;
|
||||
@@ -39,24 +40,18 @@ import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.FlavorTable;
|
||||
import java.awt.datatransfer.SystemFlavorMap;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Facilitates data conversion between formats for the use with the clipboard.
|
||||
@@ -66,8 +61,6 @@ import java.util.Objects;
|
||||
* once established, this mapping never changes
|
||||
*/
|
||||
public class WLDataTransferer extends DataTransferer {
|
||||
private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.wl.WLDataTransferer");
|
||||
|
||||
private static ImageTypeSpecifier defaultImageSpec = null;
|
||||
|
||||
// Maps the "native" format (MIME) to its numeric ID
|
||||
@@ -79,6 +72,12 @@ public class WLDataTransferer extends DataTransferer {
|
||||
private final Map<Long, Boolean> imageFormats = new HashMap<>();
|
||||
private final Map<Long, Boolean> textFormats = new HashMap<>();
|
||||
|
||||
private final FlavorTable flavorTable;
|
||||
|
||||
public FlavorTable getFlavorTable() {
|
||||
return flavorTable;
|
||||
}
|
||||
|
||||
private static class HOLDER {
|
||||
static WLDataTransferer instance = new WLDataTransferer();
|
||||
}
|
||||
@@ -87,6 +86,10 @@ public class WLDataTransferer extends DataTransferer {
|
||||
return HOLDER.instance;
|
||||
}
|
||||
|
||||
private WLDataTransferer() {
|
||||
flavorTable = adaptFlavorMap(SystemFlavorMap.getDefaultFlavorMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultUnicodeEncoding() {
|
||||
return "UTF-8";
|
||||
|
||||
@@ -156,8 +156,7 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
|
||||
|
||||
private static boolean initialized = false;
|
||||
private static Thread toolkitThread;
|
||||
private final WLClipboard clipboard;
|
||||
private final WLClipboard selection;
|
||||
private final WLDataDevice dataDevice;
|
||||
|
||||
private static Cursor currentCursor;
|
||||
|
||||
@@ -191,17 +190,9 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
|
||||
toolkitSystemThread.setDaemon(true);
|
||||
toolkitSystemThread.start();
|
||||
|
||||
WLClipboard selectionClipboard = null;
|
||||
try {
|
||||
selectionClipboard = new WLClipboard("Selection", true);
|
||||
} catch (UnsupportedOperationException ignored) {
|
||||
}
|
||||
|
||||
clipboard = new WLClipboard("System", false);
|
||||
selection = selectionClipboard;
|
||||
dataDevice = new WLDataDevice(0); // TODO: for multiseat support pass wl_seat pointer here
|
||||
} else {
|
||||
clipboard = null;
|
||||
selection = null;
|
||||
dataDevice = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -812,7 +803,7 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
|
||||
security.checkPermission(AWTPermissions.ACCESS_CLIPBOARD_PERMISSION);
|
||||
}
|
||||
|
||||
return clipboard;
|
||||
return dataDevice.getSystemClipboard();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -822,7 +813,7 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
|
||||
if (security != null) {
|
||||
security.checkPermission(AWTPermissions.ACCESS_CLIPBOARD_PERMISSION);
|
||||
}
|
||||
return selection;
|
||||
return dataDevice.getPrimarySelectionClipboard();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,724 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023, JetBrains s.r.o.. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "JNIUtilities.h"
|
||||
#include "sun_awt_wl_WLClipboard.h"
|
||||
#include "wayland-client-protocol.h"
|
||||
#include "WLToolkit.h"
|
||||
|
||||
// A type both zwp_primary_selection_source_v1* and wl_data_source*
|
||||
// are convertible to.
|
||||
typedef void* data_source_t;
|
||||
|
||||
static jmethodID transferContentsWithTypeMID; // WLCipboard.transferContentsWithType()
|
||||
static jmethodID handleClipboardFormatMID; // WLCipboard.handleClipboardFormat()
|
||||
static jmethodID handleNewClipboardMID; // WLCipboard.handleNewClipboard()
|
||||
static jmethodID handleOfferCancelledMID; // WLCipboard.handleOfferCancelled()
|
||||
static jfieldID isPrimaryFID; // WLClipboard.isPrimary
|
||||
|
||||
typedef struct DataSourcePayload {
|
||||
data_source_t source;
|
||||
jobject clipboard; // a global reference to WLClipboard
|
||||
jobject content; // a global reference to Transferable
|
||||
jboolean isPrimary;
|
||||
} DataSourcePayload;
|
||||
|
||||
static DataSourcePayload *
|
||||
DataSourcePayload_Create(jobject clipboard, jobject content)
|
||||
{
|
||||
DataSourcePayload * payload = malloc(sizeof(struct DataSourcePayload));
|
||||
if (payload) {
|
||||
payload->source = NULL;
|
||||
payload->clipboard = clipboard;
|
||||
payload->content = content;
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
static void
|
||||
DataSourcePayload_Destroy(DataSourcePayload* payload)
|
||||
{
|
||||
free(payload);
|
||||
}
|
||||
|
||||
typedef struct DataOfferPayload {
|
||||
jobject clipboard; // a global reference to WLClipboard
|
||||
} DataOfferPayload;
|
||||
|
||||
static DataOfferPayload *
|
||||
DataOfferPayload_Create(jobject clipboard)
|
||||
{
|
||||
// NB: this payload is associated with the clipboard and, once created,
|
||||
// is never destroyed, much like the clipboard itself.
|
||||
DataOfferPayload * payload = malloc(sizeof(struct DataOfferPayload));
|
||||
if (payload) {
|
||||
payload->clipboard = clipboard;
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
// Clipboard "devices", one for the actual clipboard and one for the selection clipboard.
|
||||
// Implicitly assumed that WLClipboard can only create once instance of each.
|
||||
static struct wl_data_device *wl_data_device;
|
||||
static struct zwp_primary_selection_device_v1 *zwp_selection_device;
|
||||
|
||||
static void data_device_handle_data_offer(
|
||||
void *data,
|
||||
struct wl_data_device *data_device,
|
||||
struct wl_data_offer *offer);
|
||||
|
||||
static void data_device_handle_selection(
|
||||
void *data,
|
||||
struct wl_data_device *data_device,
|
||||
struct wl_data_offer *offer);
|
||||
|
||||
static void
|
||||
data_device_handle_enter(
|
||||
void *data,
|
||||
struct wl_data_device *wl_data_device,
|
||||
uint32_t serial,
|
||||
struct wl_surface *surface,
|
||||
wl_fixed_t x,
|
||||
wl_fixed_t y,
|
||||
struct wl_data_offer *id)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
static void
|
||||
data_device_handle_leave(void *data, struct wl_data_device *wl_data_device)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
static void
|
||||
data_device_handle_motion(
|
||||
void *data,
|
||||
struct wl_data_device *wl_data_device,
|
||||
uint32_t time,
|
||||
wl_fixed_t x,
|
||||
wl_fixed_t y)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
static void
|
||||
data_device_handle_drop(void *data, struct wl_data_device *wl_data_device)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
static const struct wl_data_device_listener wl_data_device_listener = {
|
||||
.data_offer = data_device_handle_data_offer,
|
||||
.selection = data_device_handle_selection,
|
||||
.enter = data_device_handle_enter,
|
||||
.leave = data_device_handle_leave,
|
||||
.motion = data_device_handle_motion,
|
||||
.drop = data_device_handle_drop
|
||||
};
|
||||
|
||||
static void
|
||||
RegisterDataOfferWithMimeType(DataOfferPayload *payload, void *offer, const char *mime_type)
|
||||
{
|
||||
JNIEnv *env = getEnv();
|
||||
jstring mimeTypeString = (*env)->NewStringUTF(env, mime_type);
|
||||
EXCEPTION_CLEAR(env);
|
||||
if (mimeTypeString) {
|
||||
(*env)->CallVoidMethod(env, payload->clipboard, handleClipboardFormatMID, ptr_to_jlong(offer), mimeTypeString);
|
||||
EXCEPTION_CLEAR(env);
|
||||
(*env)->DeleteLocalRef(env, mimeTypeString);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
RegisterDataOffer(DataOfferPayload *payload, void *offer)
|
||||
{
|
||||
JNIEnv *env = getEnv();
|
||||
(*env)->CallVoidMethod(env, payload->clipboard, handleNewClipboardMID, ptr_to_jlong(offer));
|
||||
EXCEPTION_CLEAR(env);
|
||||
}
|
||||
|
||||
static void
|
||||
zwp_selection_offer(
|
||||
void *data,
|
||||
struct zwp_primary_selection_offer_v1 *offer,
|
||||
const char *mime_type)
|
||||
{
|
||||
assert (data != NULL);
|
||||
RegisterDataOfferWithMimeType(data, offer, mime_type);
|
||||
}
|
||||
|
||||
const struct zwp_primary_selection_offer_v1_listener zwp_selection_offer_listener = {
|
||||
.offer = zwp_selection_offer
|
||||
};
|
||||
|
||||
static void
|
||||
zwp_selection_device_handle_data_offer(
|
||||
void *data,
|
||||
struct zwp_primary_selection_device_v1 *device,
|
||||
struct zwp_primary_selection_offer_v1 *offer)
|
||||
{
|
||||
zwp_primary_selection_offer_v1_add_listener(offer, &zwp_selection_offer_listener, data);
|
||||
}
|
||||
|
||||
static void
|
||||
zwp_selection_device_handle_selection(
|
||||
void *data,
|
||||
struct zwp_primary_selection_device_v1 *device,
|
||||
struct zwp_primary_selection_offer_v1 *offer)
|
||||
{
|
||||
assert (data != NULL);
|
||||
RegisterDataOffer(data, offer);
|
||||
}
|
||||
|
||||
static const struct zwp_primary_selection_device_v1_listener zwp_selection_device_listener = {
|
||||
.data_offer = zwp_selection_device_handle_data_offer,
|
||||
.selection = zwp_selection_device_handle_selection
|
||||
};
|
||||
|
||||
static void
|
||||
wl_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action)
|
||||
{
|
||||
// TODO: this is for DnD
|
||||
}
|
||||
|
||||
static void
|
||||
wl_offer(void *data, struct wl_data_offer *offer, const char *mime_type)
|
||||
{
|
||||
assert (data != NULL);
|
||||
RegisterDataOfferWithMimeType(data, offer, mime_type);
|
||||
}
|
||||
|
||||
static void
|
||||
wl_source_actions(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions)
|
||||
{
|
||||
// TODO: this is for DnD
|
||||
}
|
||||
|
||||
static const struct wl_data_offer_listener wl_data_offer_listener = {
|
||||
.action = wl_action,
|
||||
.offer = wl_offer,
|
||||
.source_actions = wl_source_actions
|
||||
};
|
||||
|
||||
static void
|
||||
data_device_handle_data_offer(
|
||||
void *data,
|
||||
struct wl_data_device *data_device,
|
||||
struct wl_data_offer *offer)
|
||||
{
|
||||
wl_data_offer_add_listener(offer, &wl_data_offer_listener, data);
|
||||
}
|
||||
|
||||
static void data_device_handle_selection(
|
||||
void *data,
|
||||
struct wl_data_device *data_device,
|
||||
struct wl_data_offer *offer)
|
||||
{
|
||||
assert (data != NULL);
|
||||
RegisterDataOffer(data, offer);
|
||||
}
|
||||
|
||||
static void
|
||||
SendClipboardToFD(DataSourcePayload *payload, const char *mime_type, int fd)
|
||||
{
|
||||
JNIEnv *env = getEnv();
|
||||
jstring mime_type_string = (*env)->NewStringUTF(env, mime_type);
|
||||
EXCEPTION_CLEAR(env);
|
||||
if (payload->clipboard != NULL && payload->content != NULL && mime_type_string != NULL && fd >= 0) {
|
||||
(*env)->CallVoidMethod(env,
|
||||
payload->clipboard,
|
||||
transferContentsWithTypeMID,
|
||||
payload->content,
|
||||
mime_type_string,
|
||||
fd);
|
||||
EXCEPTION_CLEAR(env);
|
||||
} else {
|
||||
// The file is normally closed on the Java side, so only close here
|
||||
// if the Java side wasn't involved.
|
||||
close(fd);
|
||||
}
|
||||
if (mime_type_string != NULL) {
|
||||
(*env)->DeleteLocalRef(env, mime_type_string);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
CleanupClipboard(DataSourcePayload *payload)
|
||||
{
|
||||
if (payload != NULL) {
|
||||
JNIEnv* env = getEnv();
|
||||
|
||||
if (payload->clipboard != NULL) (*env)->DeleteGlobalRef(env, payload->clipboard);
|
||||
if (payload->content != NULL) (*env)->DeleteGlobalRef(env, payload->content);
|
||||
if (payload->source != NULL) {
|
||||
if (payload->isPrimary) {
|
||||
zwp_primary_selection_source_v1_destroy(payload->source);
|
||||
} else {
|
||||
wl_data_source_destroy(payload->source);
|
||||
}
|
||||
}
|
||||
DataSourcePayload_Destroy(payload);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
OfferCancelled(DataSourcePayload *payload) {
|
||||
JNIEnv *env = getEnv();
|
||||
(*env)->CallVoidMethod(env, payload->clipboard, handleOfferCancelledMID, ptr_to_jlong(payload));
|
||||
EXCEPTION_CLEAR(env);
|
||||
CleanupClipboard(payload);
|
||||
}
|
||||
|
||||
static void
|
||||
wl_data_source_target(void *data,
|
||||
struct wl_data_source *wl_data_source,
|
||||
const char *mime_type)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
static void
|
||||
wl_data_source_handle_send(
|
||||
void *data,
|
||||
struct wl_data_source *source,
|
||||
const char *mime_type,
|
||||
int fd)
|
||||
{
|
||||
assert(data);
|
||||
SendClipboardToFD(data, mime_type, fd);
|
||||
}
|
||||
|
||||
static void
|
||||
wl_data_source_handle_cancelled(
|
||||
void *data,
|
||||
struct wl_data_source *source)
|
||||
{
|
||||
JNU_RUNTIME_ASSERT(getEnv(), data != NULL && source == ((DataSourcePayload*)data)->source, "Unexpected data source cancelled");
|
||||
|
||||
OfferCancelled(data);
|
||||
}
|
||||
|
||||
static const struct wl_data_source_listener wl_data_source_listener = {
|
||||
.target = wl_data_source_target,
|
||||
.send = wl_data_source_handle_send,
|
||||
.cancelled = wl_data_source_handle_cancelled
|
||||
};
|
||||
|
||||
static void
|
||||
zwp_selection_source_handle_send(
|
||||
void *data,
|
||||
struct zwp_primary_selection_source_v1 *source,
|
||||
const char *mime_type,
|
||||
int32_t fd)
|
||||
{
|
||||
DataSourcePayload * payload = data;
|
||||
assert(payload);
|
||||
|
||||
SendClipboardToFD(payload, mime_type, fd);
|
||||
}
|
||||
|
||||
static void
|
||||
zwp_selection_source_handle_cancelled(
|
||||
void *data,
|
||||
struct zwp_primary_selection_source_v1 *source)
|
||||
{
|
||||
JNU_RUNTIME_ASSERT(getEnv(), data != NULL && source == ((DataSourcePayload*)data)->source, "Unexpected selection source cancelled");
|
||||
|
||||
OfferCancelled(data);
|
||||
}
|
||||
|
||||
static const struct zwp_primary_selection_source_v1_listener zwp_selection_source_listener = {
|
||||
.send = zwp_selection_source_handle_send,
|
||||
.cancelled = zwp_selection_source_handle_cancelled
|
||||
};
|
||||
|
||||
static jboolean
|
||||
initJavaRefs(JNIEnv* env, jclass wlClipboardClass)
|
||||
{
|
||||
GET_METHOD_RETURN(transferContentsWithTypeMID,
|
||||
wlClipboardClass,
|
||||
"transferContentsWithType",
|
||||
"(Ljava/awt/datatransfer/Transferable;Ljava/lang/String;I)V",
|
||||
JNI_FALSE);
|
||||
|
||||
GET_METHOD_RETURN(handleClipboardFormatMID,
|
||||
wlClipboardClass,
|
||||
"handleClipboardFormat",
|
||||
"(JLjava/lang/String;)V",
|
||||
JNI_FALSE);
|
||||
|
||||
GET_METHOD_RETURN(handleNewClipboardMID,
|
||||
wlClipboardClass,
|
||||
"handleNewClipboard",
|
||||
"(J)V",
|
||||
JNI_FALSE);
|
||||
|
||||
GET_METHOD_RETURN(handleOfferCancelledMID,
|
||||
wlClipboardClass,
|
||||
"handleOfferCancelled",
|
||||
"(J)V",
|
||||
JNI_FALSE);
|
||||
|
||||
GET_FIELD_RETURN(isPrimaryFID,
|
||||
wlClipboardClass,
|
||||
"isPrimary",
|
||||
"Z",
|
||||
JNI_FALSE);
|
||||
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns JNI_TRUE if the WLClipboard referred to by wlClipboard
|
||||
* corresponds to the "primary selection" clipboard and JNI_FALSE
|
||||
* otherwise.
|
||||
* Depending on that, a different protocol must be used to communicate
|
||||
* with Wayland (zwp_primary_selection_device_manager_v1 and
|
||||
* wl_data_device_manager correspondingly).
|
||||
*/
|
||||
static jboolean
|
||||
isPrimarySelectionClipboard(JNIEnv* env, jobject wlClipboard)
|
||||
{
|
||||
return (*env)->GetBooleanField(env, wlClipboard, isPrimaryFID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes data common to all clipboard objects. Called once in
|
||||
* static initialization time of the WLClipboard class.
|
||||
*
|
||||
* Throws InternalError in case of any errors.
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_sun_awt_wl_WLClipboard_initIDs(
|
||||
JNIEnv *env,
|
||||
jclass wlClipboardClass)
|
||||
{
|
||||
if (!initJavaRefs(env, wlClipboardClass)) {
|
||||
JNU_ThrowInternalError(env, "Failed to find WLClipboard members");
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_sun_awt_wl_WLClipboard_createDataOfferQueue(
|
||||
JNIEnv *env,
|
||||
jclass wlClipboardClass)
|
||||
{
|
||||
struct wl_event_queue * queue = wl_display_create_queue(wl_display);
|
||||
if (queue == NULL) {
|
||||
JNU_ThrowInternalError(env, "Couldn't create an event queue for the clipboard");
|
||||
}
|
||||
|
||||
return ptr_to_jlong(queue);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_sun_awt_wl_WLClipboard_dispatchDataOfferQueueImpl(
|
||||
JNIEnv *env,
|
||||
jclass wlClipboardClass,
|
||||
jlong dataOfferQueuePtr)
|
||||
{
|
||||
struct wl_event_queue * queue = jlong_to_ptr(dataOfferQueuePtr);
|
||||
assert (queue != NULL);
|
||||
|
||||
while (wl_display_dispatch_queue(wl_display, queue) != -1) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes data for a specific clipboard object (the primary selection
|
||||
* or the regular one). Called once per clipboard type.
|
||||
*
|
||||
* Returns the native handle to the corresponding clipboard.
|
||||
*
|
||||
* Throws UnsupportedOperationException in case of the primary selection
|
||||
* clipboard is not available and isPrimary is true.
|
||||
* Throws IllegalStateException in case of double-initialization.
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_sun_awt_wl_WLClipboard_initNative(
|
||||
JNIEnv *env,
|
||||
jobject obj,
|
||||
jboolean isPrimary)
|
||||
{
|
||||
if (!isPrimary) {
|
||||
if (wl_data_device != NULL) {
|
||||
JNU_ThrowByName(env, "java/lang/IllegalStateException", "one data device has already been created");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (zwp_selection_device != NULL) {
|
||||
JNU_ThrowByName(env,
|
||||
"java/lang/IllegalStateException",
|
||||
"one primary selection device has already been created");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
jobject clipboardGlobalRef = (*env)->NewGlobalRef(env, obj); // normally never deleted
|
||||
CHECK_NULL_RETURN(clipboardGlobalRef, 0);
|
||||
DataOfferPayload *payload = DataOfferPayload_Create(clipboardGlobalRef);
|
||||
if (payload == NULL) {
|
||||
(*env)->DeleteGlobalRef(env, clipboardGlobalRef);
|
||||
}
|
||||
CHECK_NULL_THROW_OOME_RETURN(env, payload, "Failed to allocate memory for DataOfferPayload", 0);
|
||||
|
||||
if (!isPrimary) {
|
||||
// TODO: may be needed by DnD also, initialize in a common place
|
||||
wl_data_device = wl_data_device_manager_get_data_device(wl_ddm, wl_seat);
|
||||
if (wl_data_device == NULL) {
|
||||
(*env)->DeleteGlobalRef(env, clipboardGlobalRef);
|
||||
JNU_ThrowByName(env,
|
||||
"java/awt/AWTError",
|
||||
"wl_data_device_manager_get_data_device() failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
wl_data_device_add_listener(wl_data_device, &wl_data_device_listener, payload);
|
||||
} else {
|
||||
if (zwp_selection_dm != NULL) {
|
||||
zwp_selection_device = zwp_primary_selection_device_manager_v1_get_device(zwp_selection_dm, wl_seat);
|
||||
if (zwp_selection_device == NULL) {
|
||||
(*env)->DeleteGlobalRef(env, clipboardGlobalRef);
|
||||
JNU_ThrowByName(env,
|
||||
"java/awt/AWTError",
|
||||
"zwp_primary_selection_device_manager_v1_get_device() failed");
|
||||
return 0;
|
||||
}
|
||||
zwp_primary_selection_device_v1_add_listener(zwp_selection_device, &zwp_selection_device_listener, payload);
|
||||
} else {
|
||||
(*env)->DeleteGlobalRef(env, clipboardGlobalRef);
|
||||
JNU_ThrowByName(env,
|
||||
"java/lang/UnsupportedOperationException",
|
||||
"zwp_primary_selection_device_manager_v1 not available");
|
||||
}
|
||||
}
|
||||
|
||||
return ptr_to_jlong(wl_data_device);
|
||||
}
|
||||
|
||||
static jboolean
|
||||
announceMimeTypesForSource(
|
||||
JNIEnv * env,
|
||||
jboolean isPrimary,
|
||||
jobjectArray mimeTypes,
|
||||
data_source_t source)
|
||||
{
|
||||
jint length = (*env)->GetArrayLength(env, mimeTypes);
|
||||
for (jint i = 0; i < length; i++) {
|
||||
jstring s = (*env)->GetObjectArrayElement(env, mimeTypes, i);
|
||||
JNU_CHECK_EXCEPTION_RETURN(env, JNI_FALSE);
|
||||
const char *mimeType = (*env)->GetStringUTFChars(env, s, JNI_FALSE);
|
||||
CHECK_NULL_RETURN(mimeType, JNI_FALSE);
|
||||
|
||||
if (isPrimary) {
|
||||
zwp_primary_selection_source_v1_offer((struct zwp_primary_selection_source_v1 *)source, mimeType);
|
||||
} else {
|
||||
wl_data_source_offer((struct wl_data_source *)source, mimeType);
|
||||
}
|
||||
(*env)->ReleaseStringUTFChars(env, s, mimeType);
|
||||
(*env)->DeleteLocalRef(env, s);
|
||||
}
|
||||
wlFlushToServer(env);
|
||||
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
static jboolean
|
||||
offerData(
|
||||
JNIEnv* env,
|
||||
DataSourcePayload * payload,
|
||||
jboolean isPrimary,
|
||||
jlong eventSerial,
|
||||
jobjectArray mimeTypes,
|
||||
jlong dataOfferQueuePtr)
|
||||
{
|
||||
data_source_t source = isPrimary
|
||||
? (data_source_t)zwp_primary_selection_device_manager_v1_create_source(zwp_selection_dm)
|
||||
: (data_source_t)wl_data_device_manager_create_data_source(wl_ddm);
|
||||
|
||||
if (source != NULL) {
|
||||
payload->source = source;
|
||||
payload->isPrimary = isPrimary;
|
||||
|
||||
wl_proxy_set_queue((struct wl_proxy*)source, jlong_to_ptr(dataOfferQueuePtr));
|
||||
|
||||
if (isPrimary) {
|
||||
zwp_primary_selection_source_v1_add_listener(
|
||||
(struct zwp_primary_selection_source_v1 *)source,
|
||||
&zwp_selection_source_listener,
|
||||
payload);
|
||||
} else {
|
||||
wl_data_source_add_listener(
|
||||
(struct wl_data_source *)source,
|
||||
&wl_data_source_listener,
|
||||
payload);
|
||||
}
|
||||
|
||||
if (mimeTypes != NULL) {
|
||||
if (!announceMimeTypesForSource(env, isPrimary, mimeTypes, source)) {
|
||||
if (isPrimary) {
|
||||
zwp_primary_selection_source_v1_destroy(source);
|
||||
} else {
|
||||
wl_data_source_destroy(source);
|
||||
}
|
||||
return JNI_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPrimary) {
|
||||
zwp_primary_selection_device_v1_set_selection(
|
||||
zwp_selection_device,
|
||||
(struct zwp_primary_selection_source_v1 *)source,
|
||||
eventSerial);
|
||||
}
|
||||
else {
|
||||
wl_data_device_set_selection(
|
||||
wl_data_device,
|
||||
(struct wl_data_source *)source,
|
||||
eventSerial);
|
||||
}
|
||||
wlFlushToServer(env);
|
||||
}
|
||||
|
||||
return source != NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes Wayland aware of the availability of new clipboard content in the
|
||||
* given MIME formats.
|
||||
* Retains the reference to clipboard content for the further use when the actual
|
||||
* clipboard data get requested.
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_sun_awt_wl_WLClipboard_offerData(
|
||||
JNIEnv *env,
|
||||
jobject obj,
|
||||
jlong eventSerial,
|
||||
jobjectArray mimeTypes,
|
||||
jobject content,
|
||||
jlong dataOfferQueuePtr)
|
||||
{
|
||||
jobject clipboardGlobalRef = (*env)->NewGlobalRef(env, obj); // deleted by ...source_handle_cancelled()
|
||||
CHECK_NULL_RETURN(clipboardGlobalRef, 0);
|
||||
jobject contentGlobalRef = (*env)->NewGlobalRef(env, content); // deleted by ...source_handle_cancelled()
|
||||
CHECK_NULL_RETURN(contentGlobalRef, 0);
|
||||
|
||||
DataSourcePayload * payload = DataSourcePayload_Create(clipboardGlobalRef, contentGlobalRef);
|
||||
if (payload == NULL) {
|
||||
(*env)->DeleteGlobalRef(env, clipboardGlobalRef);
|
||||
(*env)->DeleteGlobalRef(env, contentGlobalRef);
|
||||
}
|
||||
CHECK_NULL_THROW_OOME_RETURN(env, payload, "failed to allocate memory for DataSourcePayload", 0);
|
||||
|
||||
const jboolean isPrimary = isPrimarySelectionClipboard(env, obj);
|
||||
if (!offerData(env, payload, isPrimary, eventSerial, mimeTypes, dataOfferQueuePtr)) {
|
||||
// Failed to create a data source; give up and cleanup.
|
||||
CleanupClipboard(payload);
|
||||
}
|
||||
|
||||
return ptr_to_jlong(payload);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_sun_awt_wl_WLClipboard_cancelOffer(
|
||||
JNIEnv *env,
|
||||
jobject obj,
|
||||
jlong payloadNativePtr)
|
||||
{
|
||||
JNU_RUNTIME_ASSERT(env, payloadNativePtr != 0, "NULL pointer to clipboard data source");
|
||||
|
||||
CleanupClipboard(jlong_to_ptr(payloadNativePtr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks Wayland to provide the data for the clipboard in the given MIME
|
||||
* format.
|
||||
*
|
||||
* Returns the file descriptor from which the data must be read or -1
|
||||
* in case of an error.
|
||||
*
|
||||
* NB: the returned file descriptor must be closed by the caller.
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_sun_awt_wl_WLClipboard_requestDataInFormat(
|
||||
JNIEnv *env,
|
||||
jobject obj,
|
||||
jlong clipboardNativePtr,
|
||||
jstring mimeTypeJava)
|
||||
{
|
||||
assert (clipboardNativePtr != 0);
|
||||
const jboolean isPrimary = isPrimarySelectionClipboard(env, obj);
|
||||
|
||||
int fd = -1; // The file descriptor the clipboard data will be read from by Java
|
||||
|
||||
const char * mimeType = (*env)->GetStringUTFChars(env, mimeTypeJava, NULL);
|
||||
if (mimeType) {
|
||||
int fds[2];
|
||||
int rc = pipe2(fds, O_CLOEXEC);
|
||||
if (rc == 0) {
|
||||
if (isPrimary) {
|
||||
struct zwp_primary_selection_offer_v1 * offer = jlong_to_ptr(clipboardNativePtr);
|
||||
zwp_primary_selection_offer_v1_receive(offer, mimeType, fds[1]);
|
||||
} else {
|
||||
struct wl_data_offer * offer = jlong_to_ptr(clipboardNativePtr);
|
||||
wl_data_offer_receive(offer, mimeType, fds[1]);
|
||||
}
|
||||
// Since the request for the clipboard contents can and usually is blocking,
|
||||
// make sure that the server has received it right away.
|
||||
wlFlushToServer(env);
|
||||
|
||||
close(fds[1]); // close the "sender" end of the pipe
|
||||
fd = fds[0];
|
||||
}
|
||||
(*env)->ReleaseStringUTFChars(env, mimeTypeJava, mimeType);
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the corresponding Wayland proxy objects pointed to by clipboardNativePtr.
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_sun_awt_wl_WLClipboard_destroyClipboard(
|
||||
JNIEnv *env,
|
||||
jobject obj,
|
||||
jlong clipboardNativePtr)
|
||||
{
|
||||
assert(clipboardNativePtr != 0);
|
||||
|
||||
if (isPrimarySelectionClipboard(env, obj)) {
|
||||
struct zwp_primary_selection_offer_v1 * offer = jlong_to_ptr(clipboardNativePtr);
|
||||
zwp_primary_selection_offer_v1_destroy(offer);
|
||||
} else {
|
||||
struct wl_data_offer * offer = jlong_to_ptr(clipboardNativePtr);
|
||||
wl_data_offer_destroy(offer);
|
||||
}
|
||||
}
|
||||
965
src/java.desktop/unix/native/libawt_wlawt/WLDataDevice.c
Normal file
965
src/java.desktop/unix/native/libawt_wlawt/WLDataDevice.c
Normal file
@@ -0,0 +1,965 @@
|
||||
/*
|
||||
* Copyright (c) 2025, JetBrains s.r.o.. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "JNIUtilities.h"
|
||||
#include "WLToolkit.h"
|
||||
#include "sun_awt_wl_WLDataDevice.h"
|
||||
#include "sun_awt_wl_WLDataSource.h"
|
||||
#include "sun_awt_wl_WLDataOffer.h"
|
||||
#include "wayland-client-protocol.h"
|
||||
|
||||
|
||||
// Types
|
||||
|
||||
enum DataTransferProtocol {
|
||||
DATA_TRANSFER_PROTOCOL_WAYLAND = sun_awt_wl_WLDataDevice_DATA_TRANSFER_PROTOCOL_WAYLAND,
|
||||
DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION = sun_awt_wl_WLDataDevice_DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION,
|
||||
};
|
||||
|
||||
// native part of WLDataDevice, one instance per seat
|
||||
// seat's wl_data_device and zwp_primary_selection_device_v1 have user pointers to this struct
|
||||
struct DataDevice {
|
||||
// global reference to the corresponding WLDataDevice object
|
||||
jobject javaObject;
|
||||
|
||||
struct wl_event_queue *dataTransferQueue;
|
||||
struct wl_data_device *wlDataDevice;
|
||||
struct zwp_primary_selection_device_v1 *zwpPrimarySelectionDevice;
|
||||
};
|
||||
|
||||
// native part of WLDataSource, remains alive until WLDataSource.destroy() is called
|
||||
// pointer to this structure is the wl_data_source's (zwp_primary_selection_source_v1's) user pointer
|
||||
struct DataSource {
|
||||
enum DataTransferProtocol protocol;
|
||||
// global reference to the corresponding WLDataSource object
|
||||
jobject javaObject;
|
||||
|
||||
union {
|
||||
void *anyPtr;
|
||||
|
||||
struct wl_data_source *wlDataSource;
|
||||
struct zwp_primary_selection_source_v1 *zwpPrimarySelectionSource;
|
||||
};
|
||||
};
|
||||
|
||||
// native part of WLDataOffer, remains alive until WLDataOffer.destroy() is called
|
||||
// pointer to this structure is the wl_data_offer's (zwp_primary_selection_offer_v1's) user pointer
|
||||
struct DataOffer {
|
||||
enum DataTransferProtocol protocol;
|
||||
// global reference to the corresponding WLDataOffer object
|
||||
jobject javaObject;
|
||||
|
||||
union {
|
||||
void *anyPtr;
|
||||
|
||||
struct wl_data_offer *wlDataOffer;
|
||||
struct zwp_primary_selection_offer_v1 *zwpPrimarySelectionOffer;
|
||||
};
|
||||
};
|
||||
|
||||
// Java refs
|
||||
|
||||
static jclass wlDataOfferClass;
|
||||
static jmethodID wlDataDeviceHandleDnDEnterMID;
|
||||
static jmethodID wlDataDeviceHandleDnDLeaveMID;
|
||||
static jmethodID wlDataDeviceHandleDnDMotionMID;
|
||||
static jmethodID wlDataDeviceHandleDnDDropMID;
|
||||
static jmethodID wlDataDeviceHandleSelectionMID;
|
||||
static jmethodID wlDataSourceHandleTargetAcceptsMimeMID;
|
||||
static jmethodID wlDataSourceHandleSendMID;
|
||||
static jmethodID wlDataSourceHandleCancelledMID;
|
||||
static jmethodID wlDataSourceHandleDnDDropPerformedMID;
|
||||
static jmethodID wlDataSourceHandleDnDFinishedMID;
|
||||
static jmethodID wlDataSourceHandleDnDActionMID;
|
||||
static jmethodID wlDataOfferConstructorMID;
|
||||
static jmethodID wlDataOfferHandleOfferMimeMID;
|
||||
static jmethodID wlDataOfferHandleSourceActionsMID;
|
||||
static jmethodID wlDataOfferHandleActionMID;
|
||||
|
||||
static bool initJavaRefs(JNIEnv *env) {
|
||||
jclass wlDataDeviceClass = NULL;
|
||||
jclass wlDataSourceClass = NULL;
|
||||
|
||||
GET_CLASS_RETURN(wlDataDeviceClass, "sun/awt/wl/WLDataDevice", false);
|
||||
GET_CLASS_RETURN(wlDataSourceClass, "sun/awt/wl/WLDataSource", false);
|
||||
GET_CLASS_RETURN(wlDataOfferClass, "sun/awt/wl/WLDataOffer", false);
|
||||
|
||||
GET_METHOD_RETURN(wlDataDeviceHandleDnDEnterMID, wlDataDeviceClass, "handleDnDEnter",
|
||||
"(Lsun/awt/wl/WLDataOffer;JJDD)V", false);
|
||||
GET_METHOD_RETURN(wlDataDeviceHandleDnDLeaveMID, wlDataDeviceClass, "handleDnDLeave", "()V", false);
|
||||
GET_METHOD_RETURN(wlDataDeviceHandleDnDMotionMID, wlDataDeviceClass, "handleDnDMotion", "(JDD)V", false);
|
||||
GET_METHOD_RETURN(wlDataDeviceHandleDnDDropMID, wlDataDeviceClass, "handleDnDDrop", "()V", false);
|
||||
GET_METHOD_RETURN(wlDataDeviceHandleSelectionMID, wlDataDeviceClass, "handleSelection",
|
||||
"(Lsun/awt/wl/WLDataOffer;I)V", false);
|
||||
GET_METHOD_RETURN(wlDataSourceHandleTargetAcceptsMimeMID, wlDataSourceClass, "handleTargetAcceptsMime",
|
||||
"(Ljava/lang/String;)V", false);
|
||||
GET_METHOD_RETURN(wlDataSourceHandleSendMID, wlDataSourceClass, "handleSend", "(Ljava/lang/String;I)V", false);
|
||||
GET_METHOD_RETURN(wlDataSourceHandleCancelledMID, wlDataSourceClass, "handleCancelled", "()V", false);
|
||||
GET_METHOD_RETURN(wlDataSourceHandleDnDDropPerformedMID, wlDataSourceClass, "handleDnDDropPerformed", "()V",
|
||||
false);
|
||||
GET_METHOD_RETURN(wlDataSourceHandleDnDFinishedMID, wlDataSourceClass, "handleDnDFinished", "()V", false);
|
||||
GET_METHOD_RETURN(wlDataSourceHandleDnDActionMID, wlDataSourceClass, "handleDnDAction", "(I)V", false);
|
||||
GET_METHOD_RETURN(wlDataOfferConstructorMID, wlDataOfferClass, "<init>", "(J)V", false);
|
||||
GET_METHOD_RETURN(wlDataOfferHandleOfferMimeMID, wlDataOfferClass, "handleOfferMime", "(Ljava/lang/String;)V",
|
||||
false);
|
||||
GET_METHOD_RETURN(wlDataOfferHandleSourceActionsMID, wlDataOfferClass, "handleSourceActions", "(I)V", false);
|
||||
GET_METHOD_RETURN(wlDataOfferHandleActionMID, wlDataOfferClass, "handleAction", "(I)V", false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Event handlers declarations
|
||||
|
||||
static void wl_data_source_handle_target(void *user, struct wl_data_source *source, const char *mime);
|
||||
static void wl_data_source_handle_send(void *user, struct wl_data_source *source, const char *mime, int32_t fd);
|
||||
static void wl_data_source_handle_cancelled(void *user, struct wl_data_source *source);
|
||||
static void wl_data_source_handle_dnd_drop_performed(void *user, struct wl_data_source *source);
|
||||
static void wl_data_source_handle_dnd_finished(void *user, struct wl_data_source *source);
|
||||
static void wl_data_source_handle_action(void *user, struct wl_data_source *source, uint32_t action);
|
||||
|
||||
static const struct wl_data_source_listener wl_data_source_listener = {
|
||||
.target = wl_data_source_handle_target,
|
||||
.send = wl_data_source_handle_send,
|
||||
.cancelled = wl_data_source_handle_cancelled,
|
||||
.dnd_drop_performed = wl_data_source_handle_dnd_drop_performed,
|
||||
.dnd_finished = wl_data_source_handle_dnd_finished,
|
||||
.action = wl_data_source_handle_action,
|
||||
};
|
||||
|
||||
static void zwp_primary_selection_source_handle_send(void *user,
|
||||
struct zwp_primary_selection_source_v1 *source,
|
||||
const char *mime,
|
||||
int32_t fd);
|
||||
static void zwp_primary_selection_source_handle_cancelled(void *user, struct zwp_primary_selection_source_v1 *source);
|
||||
|
||||
static const struct zwp_primary_selection_source_v1_listener zwp_primary_selection_source_v1_listener = {
|
||||
.send = zwp_primary_selection_source_handle_send,
|
||||
.cancelled = zwp_primary_selection_source_handle_cancelled,
|
||||
};
|
||||
|
||||
static void wl_data_offer_handle_offer(void *user, struct wl_data_offer *offer, const char *mime);
|
||||
static void wl_data_offer_handle_source_actions(void *user, struct wl_data_offer *offer, uint32_t source_actions);
|
||||
static void wl_data_offer_handle_action(void *user, struct wl_data_offer *offer, uint32_t action);
|
||||
|
||||
static const struct wl_data_offer_listener wlDataOfferListener = {
|
||||
.offer = wl_data_offer_handle_offer,
|
||||
.source_actions = wl_data_offer_handle_source_actions,
|
||||
.action = wl_data_offer_handle_action,
|
||||
};
|
||||
|
||||
static void
|
||||
zwp_primary_selection_offer_handle_offer(void *user, struct zwp_primary_selection_offer_v1 *offer, const char *mime);
|
||||
|
||||
static const struct zwp_primary_selection_offer_v1_listener zwpPrimarySelectionOfferListener = {
|
||||
.offer = zwp_primary_selection_offer_handle_offer,
|
||||
};
|
||||
|
||||
static void
|
||||
wl_data_device_handle_data_offer(void *user, struct wl_data_device *wl_data_device, struct wl_data_offer *id);
|
||||
static void wl_data_device_handle_enter(void *user,
|
||||
struct wl_data_device *wl_data_device,
|
||||
uint32_t serial,
|
||||
struct wl_surface *surface,
|
||||
wl_fixed_t x,
|
||||
wl_fixed_t y,
|
||||
struct wl_data_offer *id);
|
||||
static void wl_data_device_handle_leave(void *user, struct wl_data_device *wl_data_device);
|
||||
static void wl_data_device_handle_motion(
|
||||
void *user, struct wl_data_device *wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y);
|
||||
static void wl_data_device_handle_drop(void *user, struct wl_data_device *wl_data_device);
|
||||
static void
|
||||
wl_data_device_handle_selection(void *user, struct wl_data_device *wl_data_device, struct wl_data_offer *id);
|
||||
|
||||
static const struct wl_data_device_listener wlDataDeviceListener = {
|
||||
.data_offer = wl_data_device_handle_data_offer,
|
||||
.enter = wl_data_device_handle_enter,
|
||||
.leave = wl_data_device_handle_leave,
|
||||
.motion = wl_data_device_handle_motion,
|
||||
.drop = wl_data_device_handle_drop,
|
||||
.selection = wl_data_device_handle_selection,
|
||||
};
|
||||
|
||||
static void zwp_primary_selection_device_handle_data_offer(void *user,
|
||||
struct zwp_primary_selection_device_v1 *device,
|
||||
struct zwp_primary_selection_offer_v1 *id);
|
||||
static void zwp_primary_selection_device_handle_selection(void *user,
|
||||
struct zwp_primary_selection_device_v1 *device,
|
||||
struct zwp_primary_selection_offer_v1 *id);
|
||||
|
||||
static const struct zwp_primary_selection_device_v1_listener zwpPrimarySelectionDeviceListener = {
|
||||
.data_offer = zwp_primary_selection_device_handle_data_offer,
|
||||
.selection = zwp_primary_selection_device_handle_selection,
|
||||
};
|
||||
|
||||
static void DataSource_offer(const struct DataSource *source, const char *mime);
|
||||
static void DataSource_setDnDActions(const struct DataSource *source, uint32_t actions);
|
||||
|
||||
static struct DataOffer *DataOffer_create(struct DataDevice* dataDevice, enum DataTransferProtocol protocol, void *waylandObject);
|
||||
static void DataOffer_destroy(struct DataOffer *offer);
|
||||
static void DataOffer_receive(struct DataOffer *offer, const char *mime, int fd);
|
||||
static void DataOffer_accept(struct DataOffer *offer, uint32_t serial, const char *mime);
|
||||
static void DataOffer_finishDnD(struct DataOffer *offer);
|
||||
static void DataOffer_setDnDActions(struct DataOffer *offer, int dnd_actions, int preferred_action);
|
||||
static void DataOffer_callOfferHandler(struct DataOffer *offer, const char *mime);
|
||||
static void DataOffer_callSelectionHandler(struct DataDevice* dataDevice, struct DataOffer *offer, enum DataTransferProtocol protocol);
|
||||
|
||||
// Implementation
|
||||
|
||||
static void DataSource_offer(const struct DataSource *source, const char *mime) {
|
||||
if (source->protocol == DATA_TRANSFER_PROTOCOL_WAYLAND) {
|
||||
wl_data_source_offer(source->wlDataSource, mime);
|
||||
} else if (source->protocol == DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION) {
|
||||
zwp_primary_selection_source_v1_offer(source->zwpPrimarySelectionSource, mime);
|
||||
}
|
||||
}
|
||||
|
||||
static void DataSource_setDnDActions(const struct DataSource *source, uint32_t actions) {
|
||||
if (source->protocol == DATA_TRANSFER_PROTOCOL_WAYLAND) {
|
||||
wl_data_source_set_actions(source->wlDataSource, actions);
|
||||
}
|
||||
}
|
||||
|
||||
static struct DataOffer *DataOffer_create(struct DataDevice* dataDevice, enum DataTransferProtocol protocol, void *waylandObject) {
|
||||
struct DataOffer *offer = calloc(1, sizeof(struct DataOffer));
|
||||
if (offer == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JNIEnv* env = getEnv();
|
||||
assert(env != NULL);
|
||||
|
||||
jobject obj = (*env)->NewObject(env, wlDataOfferClass, wlDataOfferConstructorMID, ptr_to_jlong(offer));
|
||||
|
||||
// Can't throw Java exceptions during Wayland event dispatch
|
||||
EXCEPTION_CLEAR(env);
|
||||
|
||||
if (obj == NULL) {
|
||||
free(offer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Cleared in DataOffer_destroy
|
||||
jobject globalRef = (*env)->NewGlobalRef(env, obj);
|
||||
|
||||
EXCEPTION_CLEAR(env);
|
||||
if (globalRef == NULL) {
|
||||
(*env)->DeleteLocalRef(env, obj);
|
||||
free(offer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
offer->javaObject = globalRef;
|
||||
|
||||
if (protocol == DATA_TRANSFER_PROTOCOL_WAYLAND) {
|
||||
struct wl_data_offer *wlDataOffer = waylandObject;
|
||||
|
||||
wl_proxy_set_queue((struct wl_proxy *)wlDataOffer, dataDevice->dataTransferQueue);
|
||||
wl_data_offer_add_listener(wlDataOffer, &wlDataOfferListener, offer);
|
||||
|
||||
offer->wlDataOffer = wlDataOffer;
|
||||
offer->protocol = DATA_TRANSFER_PROTOCOL_WAYLAND;
|
||||
}
|
||||
|
||||
if (protocol == DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION) {
|
||||
struct zwp_primary_selection_offer_v1 *zwpPrimarySelectionOffer = waylandObject;
|
||||
|
||||
wl_proxy_set_queue((struct wl_proxy *)zwpPrimarySelectionOffer, dataDevice->dataTransferQueue);
|
||||
zwp_primary_selection_offer_v1_add_listener(zwpPrimarySelectionOffer, &zwpPrimarySelectionOfferListener, offer);
|
||||
offer->zwpPrimarySelectionOffer = zwpPrimarySelectionOffer;
|
||||
offer->protocol = DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION;
|
||||
}
|
||||
|
||||
return offer;
|
||||
}
|
||||
|
||||
static void DataOffer_destroy(struct DataOffer *offer) {
|
||||
if (offer == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (offer->javaObject != NULL) {
|
||||
JNIEnv* env = getEnv();
|
||||
assert(env != NULL);
|
||||
(*env)->DeleteGlobalRef(env, offer->javaObject);
|
||||
offer->javaObject = NULL;
|
||||
}
|
||||
|
||||
if (offer->protocol == DATA_TRANSFER_PROTOCOL_WAYLAND) {
|
||||
wl_data_offer_destroy(offer->wlDataOffer);
|
||||
} else if (offer->protocol == DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION) {
|
||||
zwp_primary_selection_offer_v1_destroy(offer->zwpPrimarySelectionOffer);
|
||||
}
|
||||
|
||||
free(offer);
|
||||
}
|
||||
|
||||
static void DataOffer_receive(struct DataOffer *offer, const char *mime, int fd) {
|
||||
if (offer->protocol == DATA_TRANSFER_PROTOCOL_WAYLAND) {
|
||||
wl_data_offer_receive(offer->wlDataOffer, mime, fd);
|
||||
} else if (offer->protocol == DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION) {
|
||||
zwp_primary_selection_offer_v1_receive(offer->zwpPrimarySelectionOffer, mime, fd);
|
||||
}
|
||||
}
|
||||
|
||||
static void DataOffer_accept(struct DataOffer *offer, uint32_t serial, const char *mime) {
|
||||
if (offer->protocol == DATA_TRANSFER_PROTOCOL_WAYLAND) {
|
||||
wl_data_offer_accept(offer->wlDataOffer, serial, mime);
|
||||
}
|
||||
}
|
||||
|
||||
static void DataOffer_finishDnD(struct DataOffer *offer) {
|
||||
if (offer->protocol == DATA_TRANSFER_PROTOCOL_WAYLAND) {
|
||||
wl_data_offer_finish(offer->wlDataOffer);
|
||||
}
|
||||
}
|
||||
|
||||
static void DataOffer_setDnDActions(struct DataOffer *offer, int dnd_actions, int preferred_action) {
|
||||
if (offer->protocol == DATA_TRANSFER_PROTOCOL_WAYLAND) {
|
||||
wl_data_offer_set_actions(offer->wlDataOffer, dnd_actions, preferred_action);
|
||||
}
|
||||
}
|
||||
|
||||
static void DataOffer_callOfferHandler(struct DataOffer *offer, const char *mime) {
|
||||
assert(offer != NULL);
|
||||
|
||||
if (offer->javaObject == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
|
||||
jstring mimeJavaString = (*env)->NewStringUTF(env, mime);
|
||||
EXCEPTION_CLEAR(env);
|
||||
if (mimeJavaString != NULL) {
|
||||
(*env)->CallVoidMethod(env, offer->javaObject, wlDataOfferHandleOfferMimeMID, mimeJavaString);
|
||||
EXCEPTION_CLEAR(env);
|
||||
(*env)->DeleteLocalRef(env, mimeJavaString);
|
||||
}
|
||||
}
|
||||
|
||||
static void DataOffer_callSelectionHandler(struct DataDevice* dataDevice, struct DataOffer *offer, enum DataTransferProtocol protocol) {
|
||||
assert(dataDevice != NULL);
|
||||
// offer can be NULL, this means that the selection was cleared
|
||||
jobject offerObject = NULL;
|
||||
if (offer != NULL) {
|
||||
offerObject = offer->javaObject;
|
||||
}
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
|
||||
(*env)->CallVoidMethod(env, dataDevice->javaObject, wlDataDeviceHandleSelectionMID, offerObject, protocol);
|
||||
EXCEPTION_CLEAR(env);
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
static void wl_data_source_handle_target(void *user, struct wl_data_source *wl_data_source, const char *mime) {
|
||||
struct DataSource *source = user;
|
||||
assert(source != NULL);
|
||||
|
||||
if (source->javaObject == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
|
||||
jstring mimeJavaString = (*env)->NewStringUTF(env, mime);
|
||||
EXCEPTION_CLEAR(env);
|
||||
if (mimeJavaString != NULL) {
|
||||
(*env)->CallVoidMethod(env, source->javaObject, wlDataSourceHandleTargetAcceptsMimeMID, mimeJavaString);
|
||||
EXCEPTION_CLEAR(env);
|
||||
(*env)->DeleteLocalRef(env, mimeJavaString);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
wl_data_source_handle_send(void *user, struct wl_data_source *wl_data_source, const char *mime, int32_t fd) {
|
||||
struct DataSource *source = user;
|
||||
assert(source != NULL);
|
||||
|
||||
if (source->javaObject == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
|
||||
jstring mimeJavaString = (*env)->NewStringUTF(env, mime);
|
||||
EXCEPTION_CLEAR(env);
|
||||
if (mimeJavaString != NULL) {
|
||||
(*env)->CallVoidMethod(env, source->javaObject, wlDataSourceHandleSendMID, mimeJavaString, fd);
|
||||
EXCEPTION_CLEAR(env);
|
||||
(*env)->DeleteLocalRef(env, mimeJavaString);
|
||||
}
|
||||
}
|
||||
|
||||
static void wl_data_source_handle_cancelled(void *user, struct wl_data_source *wl_data_source) {
|
||||
struct DataSource *source = user;
|
||||
assert(source != NULL);
|
||||
|
||||
if (source->javaObject == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
|
||||
(*env)->CallVoidMethod(env, source->javaObject, wlDataSourceHandleCancelledMID);
|
||||
EXCEPTION_CLEAR(env);
|
||||
}
|
||||
|
||||
static void wl_data_source_handle_dnd_drop_performed(void *user, struct wl_data_source *wl_data_source) {
|
||||
struct DataSource *source = user;
|
||||
assert(source != NULL);
|
||||
|
||||
if (source->javaObject == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
|
||||
(*env)->CallVoidMethod(env, source->javaObject, wlDataSourceHandleDnDDropPerformedMID);
|
||||
EXCEPTION_CLEAR(env);
|
||||
}
|
||||
|
||||
static void wl_data_source_handle_dnd_finished(void *user, struct wl_data_source *wl_data_source) {
|
||||
struct DataSource *source = user;
|
||||
assert(source != NULL);
|
||||
|
||||
if (source->javaObject == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
|
||||
(*env)->CallVoidMethod(env, source->javaObject, wlDataSourceHandleDnDFinishedMID);
|
||||
EXCEPTION_CLEAR(env);
|
||||
}
|
||||
|
||||
static void wl_data_source_handle_action(void *user, struct wl_data_source *wl_data_source, uint32_t action) {
|
||||
struct DataSource *source = user;
|
||||
assert(source != NULL);
|
||||
|
||||
if (source->javaObject == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
|
||||
(*env)->CallVoidMethod(env, source->javaObject, wlDataSourceHandleDnDActionMID, action);
|
||||
EXCEPTION_CLEAR(env);
|
||||
}
|
||||
|
||||
static void zwp_primary_selection_source_handle_send(
|
||||
void *user, struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1, const char *mime, int32_t fd) {
|
||||
struct DataSource *source = user;
|
||||
assert(source != NULL);
|
||||
|
||||
if (source->javaObject == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
|
||||
jstring mimeJavaString = (*env)->NewStringUTF(env, mime);
|
||||
EXCEPTION_CLEAR(env);
|
||||
if (mimeJavaString != NULL) {
|
||||
(*env)->CallVoidMethod(env, source->javaObject, wlDataSourceHandleSendMID, mimeJavaString, fd);
|
||||
EXCEPTION_CLEAR(env);
|
||||
(*env)->DeleteLocalRef(env, mimeJavaString);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
zwp_primary_selection_source_handle_cancelled(void *user,
|
||||
struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1) {
|
||||
struct DataSource *source = user;
|
||||
assert(source != NULL);
|
||||
|
||||
if (source->javaObject == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
|
||||
(*env)->CallVoidMethod(env, source->javaObject, wlDataSourceHandleCancelledMID);
|
||||
EXCEPTION_CLEAR(env);
|
||||
}
|
||||
|
||||
static void wl_data_offer_handle_offer(void *user, struct wl_data_offer *wl_data_offer, const char *mime) {
|
||||
DataOffer_callOfferHandler((struct DataOffer *)user, mime);
|
||||
}
|
||||
|
||||
static void
|
||||
wl_data_offer_handle_source_actions(void *user, struct wl_data_offer *wl_data_offer, uint32_t source_actions) {
|
||||
struct DataOffer *offer = user;
|
||||
assert(offer != NULL);
|
||||
|
||||
if (offer->javaObject == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
|
||||
(*env)->CallVoidMethod(env, offer->javaObject, wlDataOfferHandleSourceActionsMID, (jint)source_actions);
|
||||
EXCEPTION_CLEAR(env);
|
||||
}
|
||||
|
||||
static void wl_data_offer_handle_action(void *user, struct wl_data_offer *wl_data_offer, uint32_t action) {
|
||||
struct DataOffer *offer = user;
|
||||
assert(offer != NULL);
|
||||
|
||||
if (offer->javaObject == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
|
||||
(*env)->CallVoidMethod(env, offer->javaObject, wlDataOfferHandleActionMID, (jint)action);
|
||||
EXCEPTION_CLEAR(env);
|
||||
}
|
||||
|
||||
static void zwp_primary_selection_offer_handle_offer(
|
||||
void *user, struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1, const char *mime) {
|
||||
DataOffer_callOfferHandler((struct DataOffer *)user, mime);
|
||||
}
|
||||
|
||||
static void
|
||||
wl_data_device_handle_data_offer(void *user, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {
|
||||
struct DataDevice* dataDevice = user;
|
||||
assert(dataDevice != NULL);
|
||||
|
||||
struct DataOffer *offer = DataOffer_create(dataDevice, DATA_TRANSFER_PROTOCOL_WAYLAND, id);
|
||||
if (offer == NULL) {
|
||||
// This can only happen in OOM scenarios.
|
||||
// We can't throw a Java exception here.
|
||||
// Destroy the offer, since we won't be able to do anything useful with it.
|
||||
wl_data_offer_destroy(id);
|
||||
return;
|
||||
}
|
||||
// no memory leak here: allocated DataOffer object will be
|
||||
// associated with the wl_data_offer
|
||||
}
|
||||
|
||||
static void wl_data_device_handle_enter(void *user,
|
||||
struct wl_data_device *wl_data_device,
|
||||
uint32_t serial,
|
||||
struct wl_surface *surface,
|
||||
wl_fixed_t x,
|
||||
wl_fixed_t y,
|
||||
struct wl_data_offer *id) {
|
||||
struct DataDevice* dataDevice = user;
|
||||
assert(dataDevice != NULL);
|
||||
struct DataOffer *offer = (struct DataOffer *)wl_data_offer_get_user_data(id);
|
||||
assert(offer != NULL);
|
||||
|
||||
if (offer->javaObject == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
|
||||
(*env)->CallVoidMethod(env, dataDevice->javaObject, wlDataDeviceHandleDnDEnterMID, offer->javaObject, (jlong)serial,
|
||||
ptr_to_jlong(surface), wl_fixed_to_double(x), wl_fixed_to_double(y));
|
||||
EXCEPTION_CLEAR(env);
|
||||
}
|
||||
|
||||
static void wl_data_device_handle_leave(void *user, struct wl_data_device *wl_data_device) {
|
||||
struct DataDevice* dataDevice = user;
|
||||
assert(dataDevice != NULL);
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
(*env)->CallVoidMethod(env, dataDevice->javaObject, wlDataDeviceHandleDnDLeaveMID);
|
||||
EXCEPTION_CLEAR(env);
|
||||
}
|
||||
|
||||
static void wl_data_device_handle_motion(
|
||||
void *user, struct wl_data_device *wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y) {
|
||||
struct DataDevice* dataDevice = user;
|
||||
assert(dataDevice != NULL);
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
(*env)->CallVoidMethod(env, dataDevice->javaObject, wlDataDeviceHandleDnDMotionMID, (jlong)time, (jdouble)x / 256.0,
|
||||
(jdouble)y / 256.0);
|
||||
EXCEPTION_CLEAR(env);
|
||||
}
|
||||
|
||||
static void wl_data_device_handle_drop(void *user, struct wl_data_device *wl_data_device) {
|
||||
struct DataDevice* dataDevice = user;
|
||||
assert(dataDevice != NULL);
|
||||
|
||||
JNIEnv *env = getEnv();
|
||||
assert(env != NULL);
|
||||
(*env)->CallVoidMethod(env, dataDevice->javaObject, wlDataDeviceHandleDnDDropMID);
|
||||
EXCEPTION_CLEAR(env);
|
||||
}
|
||||
|
||||
static void
|
||||
wl_data_device_handle_selection(void *user, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {
|
||||
struct DataDevice* dataDevice = user;
|
||||
assert(dataDevice != NULL);
|
||||
struct DataOffer *offer = NULL;
|
||||
|
||||
// id can be NULL, this means that the selection was cleared
|
||||
if (id != NULL) {
|
||||
offer = (struct DataOffer *)wl_data_offer_get_user_data(id);
|
||||
assert(offer != NULL);
|
||||
}
|
||||
|
||||
DataOffer_callSelectionHandler(dataDevice, offer, DATA_TRANSFER_PROTOCOL_WAYLAND);
|
||||
}
|
||||
|
||||
static void
|
||||
zwp_primary_selection_device_handle_data_offer(void *user,
|
||||
struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1,
|
||||
struct zwp_primary_selection_offer_v1 *id) {
|
||||
struct DataDevice* dataDevice = user;
|
||||
struct DataOffer *offer = DataOffer_create(dataDevice, DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION, id);
|
||||
if (offer == NULL) {
|
||||
// This can only happen in OOM scenarios.
|
||||
// We can't throw a Java exception here.
|
||||
// Destroy the offer, since we won't be able to do anything useful with it.
|
||||
zwp_primary_selection_offer_v1_destroy(id);
|
||||
return;
|
||||
}
|
||||
// no memory leak here: allocated DataOffer object will be
|
||||
// associated with the zwp_primary_selection_offer_v1
|
||||
}
|
||||
|
||||
static void
|
||||
zwp_primary_selection_device_handle_selection(void *user,
|
||||
struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1,
|
||||
struct zwp_primary_selection_offer_v1 *id) {
|
||||
struct DataDevice* dataDevice = user;
|
||||
assert(dataDevice != NULL);
|
||||
|
||||
struct DataOffer *offer = NULL;
|
||||
|
||||
// id can be NULL, this means that the selection was cleared
|
||||
if (id != NULL) {
|
||||
offer = (struct DataOffer *)zwp_primary_selection_offer_v1_get_user_data(id);
|
||||
assert(offer != NULL);
|
||||
}
|
||||
|
||||
DataOffer_callSelectionHandler(dataDevice, offer, DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION);
|
||||
}
|
||||
|
||||
// JNI functions
|
||||
|
||||
JNIEXPORT void JNICALL Java_sun_awt_wl_WLDataDevice_initIDs(JNIEnv* env, jclass clazz)
|
||||
{
|
||||
if (!initJavaRefs(env)) {
|
||||
JNU_ThrowInternalError(env, "Failed to initialize WLDataDevice java refs");
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL Java_sun_awt_wl_WLDataDevice_initNative(JNIEnv *env, jobject obj, jlong wlSeatPtr) {
|
||||
struct wl_seat* seat = (wlSeatPtr == 0) ? wl_seat : (struct wl_seat*)jlong_to_ptr(wlSeatPtr);
|
||||
|
||||
struct DataDevice *dataDevice = calloc(1, sizeof(struct DataDevice));
|
||||
if (dataDevice == NULL) {
|
||||
JNU_ThrowOutOfMemoryError(env, "Failed to allocate DataDevice");
|
||||
return 0;
|
||||
}
|
||||
|
||||
dataDevice->javaObject = (*env)->NewGlobalRef(env, obj);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
goto error_cleanup;
|
||||
}
|
||||
if (dataDevice->javaObject == NULL) {
|
||||
JNU_ThrowInternalError(env, "Failed to initialize WLDataDevice");
|
||||
goto error_cleanup;
|
||||
}
|
||||
|
||||
dataDevice->wlDataDevice = wl_data_device_manager_get_data_device(wl_ddm, seat);
|
||||
if (dataDevice->wlDataDevice == NULL) {
|
||||
JNU_ThrowInternalError(env, "Couldn't get a Wayland data device");
|
||||
goto error_cleanup;
|
||||
}
|
||||
|
||||
dataDevice->zwpPrimarySelectionDevice = NULL;
|
||||
if (zwp_selection_dm != NULL) {
|
||||
dataDevice->zwpPrimarySelectionDevice = zwp_primary_selection_device_manager_v1_get_device(zwp_selection_dm, seat);
|
||||
if (dataDevice->zwpPrimarySelectionDevice == NULL) {
|
||||
JNU_ThrowInternalError(env, "Couldn't get zwp_primary_selection_device");
|
||||
goto error_cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
dataDevice->dataTransferQueue = wl_display_create_queue(wl_display);
|
||||
if (dataDevice->dataTransferQueue == NULL) {
|
||||
JNU_ThrowInternalError(env, "Couldn't create an event queue for the data device");
|
||||
goto error_cleanup;
|
||||
}
|
||||
|
||||
wl_data_device_add_listener(dataDevice->wlDataDevice, &wlDataDeviceListener, dataDevice);
|
||||
|
||||
if (dataDevice->zwpPrimarySelectionDevice != NULL) {
|
||||
zwp_primary_selection_device_v1_add_listener(dataDevice->zwpPrimarySelectionDevice, &zwpPrimarySelectionDeviceListener,
|
||||
dataDevice);
|
||||
}
|
||||
|
||||
return ptr_to_jlong(dataDevice);
|
||||
|
||||
error_cleanup:
|
||||
if (dataDevice->dataTransferQueue != NULL) {
|
||||
wl_event_queue_destroy(dataDevice->dataTransferQueue);
|
||||
}
|
||||
|
||||
if (dataDevice->zwpPrimarySelectionDevice != NULL) {
|
||||
zwp_primary_selection_device_v1_destroy(dataDevice->zwpPrimarySelectionDevice);
|
||||
}
|
||||
|
||||
if (dataDevice->wlDataDevice != NULL) {
|
||||
wl_data_device_destroy(dataDevice->wlDataDevice);
|
||||
}
|
||||
|
||||
if (dataDevice->javaObject != NULL) {
|
||||
(*env)->DeleteGlobalRef(env, dataDevice->javaObject);
|
||||
}
|
||||
|
||||
if (dataDevice != NULL) {
|
||||
free(dataDevice);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_sun_awt_wl_WLDataDevice_isProtocolSupportedImpl(JNIEnv *env, jclass clazz, jlong nativePtr, jint protocol) {
|
||||
struct DataDevice *dataDevice = jlong_to_ptr(nativePtr);
|
||||
assert(dataDevice != NULL);
|
||||
|
||||
if (protocol == DATA_TRANSFER_PROTOCOL_WAYLAND) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (protocol == DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION) {
|
||||
return dataDevice->zwpPrimarySelectionDevice != NULL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_sun_awt_wl_WLDataDevice_dispatchDataTransferQueueImpl(JNIEnv *env, jclass clazz, jlong nativePtr) {
|
||||
struct DataDevice *dataDevice = jlong_to_ptr(nativePtr);
|
||||
assert(dataDevice != NULL);
|
||||
|
||||
while (wl_display_dispatch_queue(wl_display, dataDevice->dataTransferQueue) != -1) {
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_sun_awt_wl_WLDataDevice_setSelectionImpl(JNIEnv *env,
|
||||
jclass clazz,
|
||||
jint protocol,
|
||||
jlong dataDeviceNativePtr,
|
||||
jlong dataSourceNativePtr,
|
||||
jlong serial) {
|
||||
struct DataDevice *dataDevice = jlong_to_ptr(dataDeviceNativePtr);
|
||||
assert(dataDevice != NULL);
|
||||
|
||||
struct DataSource *source = jlong_to_ptr(dataSourceNativePtr);
|
||||
|
||||
if (protocol == DATA_TRANSFER_PROTOCOL_WAYLAND) {
|
||||
wl_data_device_set_selection(dataDevice->wlDataDevice, (source == NULL) ? NULL : source->wlDataSource, serial);
|
||||
} else if (protocol == DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION) {
|
||||
zwp_primary_selection_device_v1_set_selection(
|
||||
dataDevice->zwpPrimarySelectionDevice, (source == NULL) ? NULL : source->zwpPrimarySelectionSource, serial);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL Java_sun_awt_wl_WLDataSource_initNative(JNIEnv *env, jobject javaObject, jlong dataDeviceNativePtr, jint protocol) {
|
||||
struct DataDevice *dataDevice = jlong_to_ptr(dataDeviceNativePtr);
|
||||
assert(dataDevice != NULL);
|
||||
|
||||
struct DataSource *dataSource = calloc(1, sizeof(struct DataSource));
|
||||
|
||||
if (dataSource == NULL) {
|
||||
JNU_ThrowOutOfMemoryError(env, "Failed to allocate DataSource");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// cleaned up in WLDataSource.destroy()
|
||||
dataSource->javaObject = (*env)->NewGlobalRef(env, javaObject);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
free(dataSource);
|
||||
return 0;
|
||||
}
|
||||
if (dataSource->javaObject == NULL) {
|
||||
free(dataSource);
|
||||
JNU_ThrowInternalError(env, "Failed to create a reference to WLDataSource");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (protocol == DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION && !zwp_selection_dm) {
|
||||
protocol = DATA_TRANSFER_PROTOCOL_WAYLAND;
|
||||
}
|
||||
|
||||
if (protocol == DATA_TRANSFER_PROTOCOL_WAYLAND) {
|
||||
struct wl_data_source *wlDataSource = wl_data_device_manager_create_data_source(wl_ddm);
|
||||
if (wlDataSource == NULL) {
|
||||
free(dataSource);
|
||||
JNU_ThrowByName(env, "java/awt/AWTError", "Wayland error creating wl_data_source proxy");
|
||||
return 0;
|
||||
}
|
||||
|
||||
wl_proxy_set_queue((struct wl_proxy *)wlDataSource, dataDevice->dataTransferQueue);
|
||||
wl_data_source_add_listener(wlDataSource, &wl_data_source_listener, dataSource);
|
||||
|
||||
dataSource->protocol = DATA_TRANSFER_PROTOCOL_WAYLAND;
|
||||
dataSource->wlDataSource = wlDataSource;
|
||||
}
|
||||
|
||||
if (protocol == DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION) {
|
||||
struct zwp_primary_selection_source_v1 *zwpPrimarySelectionSource =
|
||||
zwp_primary_selection_device_manager_v1_create_source(zwp_selection_dm);
|
||||
if (zwpPrimarySelectionSource == NULL) {
|
||||
free(dataSource);
|
||||
JNU_ThrowByName(env, "java/awt/AWTError", "Wayland error creating zwp_primary_selection_source_v1 proxy");
|
||||
return 0;
|
||||
}
|
||||
|
||||
wl_proxy_set_queue((struct wl_proxy *)zwpPrimarySelectionSource, dataDevice->dataTransferQueue);
|
||||
zwp_primary_selection_source_v1_add_listener(zwpPrimarySelectionSource,
|
||||
&zwp_primary_selection_source_v1_listener, dataSource);
|
||||
|
||||
dataSource->protocol = DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION;
|
||||
dataSource->zwpPrimarySelectionSource = zwpPrimarySelectionSource;
|
||||
}
|
||||
|
||||
return ptr_to_jlong(dataSource);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_sun_awt_wl_WLDataSource_offerMimeImpl(JNIEnv *env,
|
||||
jclass clazz,
|
||||
jlong nativePtr,
|
||||
jstring mimeJavaString) {
|
||||
struct DataSource *source = jlong_to_ptr(nativePtr);
|
||||
const char *mime = (*env)->GetStringUTFChars(env, mimeJavaString, NULL);
|
||||
JNU_CHECK_EXCEPTION(env);
|
||||
DataSource_offer(source, mime);
|
||||
(*env)->ReleaseStringUTFChars(env, mimeJavaString, mime);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_sun_awt_wl_WLDataSource_destroyImpl(JNIEnv *env, jclass clazz, jlong nativePtr) {
|
||||
struct DataSource *source = jlong_to_ptr(nativePtr);
|
||||
if (source == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source->javaObject != NULL) {
|
||||
(*env)->DeleteGlobalRef(env, source->javaObject);
|
||||
source->javaObject = NULL;
|
||||
}
|
||||
|
||||
if (source->protocol == DATA_TRANSFER_PROTOCOL_WAYLAND) {
|
||||
wl_data_source_destroy(source->wlDataSource);
|
||||
} else if (source->protocol == DATA_TRANSFER_PROTOCOL_PRIMARY_SELECTION) {
|
||||
zwp_primary_selection_source_v1_destroy(source->zwpPrimarySelectionSource);
|
||||
}
|
||||
|
||||
free(source);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_sun_awt_wl_WLDataSource_setDnDActionsImpl(JNIEnv *env,
|
||||
jclass clazz,
|
||||
jlong nativePtr,
|
||||
jint actions) {
|
||||
struct DataSource *source = jlong_to_ptr(nativePtr);
|
||||
DataSource_setDnDActions(source, actions);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_sun_awt_wl_WLDataOffer_destroyImpl(JNIEnv *env, jclass clazz, jlong nativePtr) {
|
||||
struct DataOffer *offer = jlong_to_ptr(nativePtr);
|
||||
DataOffer_destroy(offer);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_sun_awt_wl_WLDataOffer_acceptImpl(
|
||||
JNIEnv *env, jclass clazz, jlong nativePtr, jlong serial, jstring mimeJavaString) {
|
||||
struct DataOffer *offer = jlong_to_ptr(nativePtr);
|
||||
const char *mime = NULL;
|
||||
|
||||
if (mimeJavaString != NULL) {
|
||||
mime = (*env)->GetStringUTFChars(env, mimeJavaString, NULL);
|
||||
JNU_CHECK_EXCEPTION(env);
|
||||
}
|
||||
|
||||
DataOffer_accept(offer, (uint32_t)serial, mime);
|
||||
|
||||
if (mime != NULL) {
|
||||
(*env)->ReleaseStringUTFChars(env, mimeJavaString, mime);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_sun_awt_wl_WLDataOffer_openReceivePipe(JNIEnv *env,
|
||||
jclass clazz,
|
||||
jlong nativePtr,
|
||||
jstring mimeJavaString) {
|
||||
struct DataOffer *offer = jlong_to_ptr(nativePtr);
|
||||
const char *mime = (*env)->GetStringUTFChars(env, mimeJavaString, NULL);
|
||||
JNU_CHECK_EXCEPTION_RETURN(env, -1);
|
||||
|
||||
int fds[2];
|
||||
if (pipe2(fds, O_CLOEXEC) != 0) {
|
||||
JNU_ThrowIOExceptionWithMessageAndLastError(env, "pipe2");
|
||||
return -1;
|
||||
}
|
||||
|
||||
DataOffer_receive(offer, mime, fds[1]);
|
||||
|
||||
// Flush the request to receive
|
||||
wlFlushToServer(env);
|
||||
|
||||
close(fds[1]);
|
||||
(*env)->ReleaseStringUTFChars(env, mimeJavaString, mime);
|
||||
|
||||
return fds[0];
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_sun_awt_wl_WLDataOffer_finishDnDImpl(JNIEnv *env, jclass clazz, jlong nativePtr) {
|
||||
struct DataOffer *offer = jlong_to_ptr(nativePtr);
|
||||
DataOffer_finishDnD(offer);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_sun_awt_wl_WLDataOffer_setDnDActionsImpl(
|
||||
JNIEnv *env, jclass clazz, jlong nativePtr, jint actions, jint preferredAction) {
|
||||
struct DataOffer *offer = jlong_to_ptr(nativePtr);
|
||||
DataOffer_setDnDActions(offer, actions, preferredAction);
|
||||
}
|
||||
Reference in New Issue
Block a user