Skip to content

Commit b942c4c

Browse files
committed
[GTK4] Implement Clipboard supporting copy and pasting to other applications
The pre-existing implementation of Clipboard for GTK4 only allowed copying and pasting within the application. Fixes #2126
1 parent 4a6ab2a commit b942c4c

File tree

21 files changed

+2287
-232
lines changed

21 files changed

+2287
-232
lines changed

bundles/org.eclipse.swt/Eclipse SWT Drag and Drop/gtk/org/eclipse/swt/dnd/Clipboard.java

Lines changed: 156 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
package org.eclipse.swt.dnd;
1515

1616

17+
import java.time.*;
18+
import java.util.*;
19+
import java.util.List;
1720
import java.util.concurrent.*;
1821

1922
import org.eclipse.swt.*;
20-
import org.eclipse.swt.graphics.*;
2123
import org.eclipse.swt.internal.*;
2224
import org.eclipse.swt.internal.gtk.*;
2325
import org.eclipse.swt.internal.gtk3.*;
@@ -41,15 +43,24 @@ public class Clipboard {
4143

4244
static long GTKCLIPBOARD;
4345
static long GTKPRIMARYCLIPBOARD;
46+
/**
47+
* GTK3 only
48+
*/
4449
private static long TARGET;
4550

4651
static {
47-
GTKCLIPBOARD = GTK.GTK4 ? GDK.gdk_display_get_clipboard(GDK.gdk_display_get_default()) : GTK3.gtk_clipboard_get (GDK.GDK_NONE);
48-
byte[] buffer = Converter.wcsToMbcs("PRIMARY", true);
49-
long primary = GTK.GTK4 ? 0 : GDK.gdk_atom_intern(buffer, false);
50-
GTKPRIMARYCLIPBOARD = GTK.GTK4 ? GDK.gdk_display_get_primary_clipboard(GDK.gdk_display_get_default()) : GTK3.gtk_clipboard_get(primary);
51-
buffer = Converter.wcsToMbcs("TARGETS", true);
52-
TARGET = GTK.GTK4 ? 0 : GDK.gdk_atom_intern(buffer, false);
52+
if (GTK.GTK4) {
53+
GTKCLIPBOARD = GDK.gdk_display_get_clipboard(GDK.gdk_display_get_default());
54+
GTKPRIMARYCLIPBOARD = GDK.gdk_display_get_primary_clipboard(GDK.gdk_display_get_default());
55+
TARGET = 0;
56+
} else {
57+
GTKCLIPBOARD = GTK3.gtk_clipboard_get(GDK.GDK_NONE);
58+
byte[] buffer = Converter.wcsToMbcs("PRIMARY", true);
59+
long primary = GDK.gdk_atom_intern(buffer, false);
60+
GTKPRIMARYCLIPBOARD = GTK3.gtk_clipboard_get(primary);
61+
buffer = Converter.wcsToMbcs("TARGETS", true);
62+
TARGET = GDK.gdk_atom_intern(buffer, false);
63+
}
5364
}
5465

5566
/**
@@ -190,8 +201,13 @@ public void clearContents() {
190201
*/
191202
public void clearContents(int clipboards) {
192203
checkWidget();
193-
ClipboardProxy proxy = ClipboardProxy._getInstance(display);
194-
proxy.clear(this, clipboards);
204+
if (GTK.GTK4) {
205+
ClipboardProxyGTK4 proxy = ClipboardProxyGTK4._getInstance(display);
206+
proxy.clear(this, clipboards, false);
207+
} else {
208+
ClipboardProxy proxy = ClipboardProxy._getInstance(display);
209+
proxy.clear(this, clipboards);
210+
}
195211
}
196212

197213
/**
@@ -302,24 +318,40 @@ public Object getContents(Transfer transfer) {
302318
* @since 3.1
303319
*/
304320
public Object getContents(Transfer transfer, int clipboards) {
305-
checkWidget();
306-
if (transfer == null) DND.error(SWT.ERROR_NULL_ARGUMENT);
307-
308-
if(GTK.GTK4) {
309-
Object result = getContents_gtk4(transfer, clipboards);
310-
return result;
321+
if (GTK.GTK4) {
322+
CompletableFuture<Object> contentsAsync = gtk4_getContentsAsync(transfer, clipboards);
323+
try {
324+
// We limit how long the clipboard has to respond to 5 seconds
325+
// based on similar decisions in GTK3, see
326+
// https://github.com/eclipse-platform/eclipse.platform.swt/blob/d2de8e31f846d655949e81fa006ad2ebcc542b2f/bundles/org.eclipse.swt/Eclipse%20SWT%20Drag%20and%20Drop/gtk/org/eclipse/swt/dnd/Clipboard.java#L719
327+
return GTK4GlibFuture.get(display, contentsAsync, Duration.ofSeconds(5));
328+
} catch (InterruptedException e) {
329+
Thread.currentThread().interrupt();
330+
return null;
331+
} catch (ExecutionException | TimeoutException e) {
332+
// Bug 241957: In case of timeout take clipboard ownership to unblock future calls
333+
ClipboardProxyGTK4 proxy = ClipboardProxyGTK4._getInstance(display);
334+
proxy.clear(this, clipboards, true);
335+
return null;
336+
}
311337
}
312338

339+
return gtk3_getContents(transfer, clipboards);
340+
}
341+
342+
private Object gtk3_getContents(Transfer transfer, int clipboards) {
343+
checkWidget();
344+
if (transfer == null) DND.error(SWT.ERROR_NULL_ARGUMENT);
313345
long selection_data = 0;
314346
int[] typeIds = transfer.getTypeIds();
315347
boolean textTransfer = transfer.getTypeNames()[0].equals("UTF8_STRING");
316348
Object result = null;
317349
for (int i = 0; i < typeIds.length; i++) {
318350
if ((clipboards & DND.CLIPBOARD) != 0) {
319-
selection_data = gtk_clipboard_wait_for_contents(GTKCLIPBOARD, typeIds[i]);
351+
selection_data = gtk3_clipboard_wait_for_contents(GTKCLIPBOARD, typeIds[i]);
320352
}
321353
if (selection_data == 0 && (clipboards & DND.SELECTION_CLIPBOARD) != 0) {
322-
selection_data = gtk_clipboard_wait_for_contents(GTKPRIMARYCLIPBOARD, typeIds[i]);
354+
selection_data = gtk3_clipboard_wait_for_contents(GTKPRIMARYCLIPBOARD, typeIds[i]);
323355
}
324356
if (selection_data != 0) {
325357
TransferData tdata = new TransferData();
@@ -419,69 +451,20 @@ public CompletableFuture<Object> getContentsAsync(Transfer transfer) {
419451
* @since 3.132
420452
*/
421453
public CompletableFuture<Object> getContentsAsync(Transfer transfer, int clipboards) {
422-
return CompletableFuture.completedFuture(getContents(transfer, clipboards));
454+
if (GTK.GTK4) {
455+
return gtk4_getContentsAsync(transfer, clipboards);
456+
}
457+
458+
return CompletableFuture.completedFuture(gtk3_getContents(transfer, clipboards));
423459
}
424460

425-
private Object getContents_gtk4(Transfer transfer, int clipboards) {
426-
427-
long contents = GTK4.gdk_clipboard_get_content(Clipboard.GTKCLIPBOARD);
428-
if(contents == 0) return null;
429-
long value = OS.g_malloc (OS.GValue_sizeof ());
430-
C.memset (value, 0, OS.GValue_sizeof ());
431-
432-
//Pasting of text (TextTransfer/RTFTransfer)
433-
if(transfer.getTypeNames()[0].equals("text/plain") || transfer.getTypeNames()[0].equals("text/rtf")) {
434-
OS.g_value_init(value, OS.G_TYPE_STRING());
435-
if (!GTK4.gdk_content_provider_get_value (contents, value, null)) return null;
436-
long cStr = OS.g_value_get_string(value);
437-
long [] items_written = new long [1];
438-
long utf16Ptr = OS.g_utf8_to_utf16(cStr, -1, null, items_written, null);
439-
OS.g_free(cStr);
440-
if (utf16Ptr == 0) return null;
441-
int length = (int)items_written[0];
442-
char[] buffer = new char[length];
443-
C.memmove(buffer, utf16Ptr, length * 2);
444-
OS.g_free(utf16Ptr);
445-
String str = new String(buffer);
446-
if(transfer.getTypeNames()[0].equals("text/rtf") && !str.contains("{\\rtf1")) {
447-
return null;
448-
}
449-
if(transfer.getTypeNames()[0].equals("text/plain") && str.contains("{\\rtf1")){
450-
return null;
451-
}
452-
return str;
453-
}
454-
//Pasting of Image
455-
if(transfer.getTypeIds()[0] == (int)GDK.GDK_TYPE_PIXBUF()) {
456-
ImageData imgData = null;
457-
OS.g_value_init(value, GDK.GDK_TYPE_PIXBUF());
458-
if (!GTK4.gdk_content_provider_get_value (contents, value, null)) return null;
459-
long pixbufObj = OS.g_value_get_object(value);
460-
if (pixbufObj != 0) {
461-
Image img = Image.gtk_new_from_pixbuf(Display.getCurrent(), SWT.BITMAP, pixbufObj);
462-
imgData = img.getImageData();
463-
img.dispose();
464-
}
465-
return imgData;
466-
}
467-
//Pasting of HTML
468-
if(transfer.getTypeNames()[0].equals("text/html")) {
469-
OS.g_value_init(value, OS.G_TYPE_STRING());
470-
if (!GTK4.gdk_content_provider_get_value (contents, value, null)) return null;
471-
long cStr = OS.g_value_get_string(value);
472-
long [] items_written = new long [1];
473-
long utf16Ptr = OS.g_utf8_to_utf16(cStr, -1, null, items_written, null);
474-
OS.g_free(cStr);
475-
if (utf16Ptr == 0) return null;
476-
int length = (int)items_written[0];
477-
char[] buffer = new char[length];
478-
C.memmove(buffer, utf16Ptr, length * 2);
479-
OS.g_free(utf16Ptr);
480-
String str = new String(buffer);
481-
return str;
482-
}
483-
//TODO: [GTK4] Other cases
484-
return null;
461+
private CompletableFuture<Object> gtk4_getContentsAsync(Transfer transfer, int clipboards) {
462+
checkWidget();
463+
if (transfer == null)
464+
DND.error(SWT.ERROR_NULL_ARGUMENT);
465+
466+
ClipboardProxyGTK4 proxy = ClipboardProxyGTK4._getInstance(display);
467+
return proxy.getData(this, transfer, clipboards);
485468
}
486469

487470
/**
@@ -621,9 +604,16 @@ public void setContents(Object[] data, Transfer[] dataTypes, int clipboards) {
621604
DND.error(SWT.ERROR_INVALID_ARGUMENT);
622605
}
623606
}
624-
ClipboardProxy proxy = ClipboardProxy._getInstance(display);
625-
if (!proxy.setData(this, data, dataTypes, clipboards)) {
626-
DND.error(DND.ERROR_CANNOT_SET_CLIPBOARD);
607+
if (GTK.GTK4) {
608+
ClipboardProxyGTK4 proxy = ClipboardProxyGTK4._getInstance(display);
609+
if (!proxy.setData(this, data, dataTypes, clipboards)) {
610+
DND.error(DND.ERROR_CANNOT_SET_CLIPBOARD);
611+
}
612+
} else {
613+
ClipboardProxy proxy = ClipboardProxy._getInstance(display);
614+
if (!proxy.setData(this, data, dataTypes, clipboards)) {
615+
DND.error(DND.ERROR_CANNOT_SET_CLIPBOARD);
616+
}
627617
}
628618
}
629619

@@ -671,18 +661,21 @@ public TransferData[] getAvailableTypes() {
671661
*/
672662
public TransferData[] getAvailableTypes(int clipboards) {
673663
checkWidget();
664+
if(GTK.GTK4) {
665+
return gtk4_getAvailableTypes(clipboards);
666+
}
674667

675668
TransferData[] result = null;
676669
if ((clipboards & DND.CLIPBOARD) != 0) {
677-
int[] types = getAvailableClipboardTypes();
670+
int[] types = gtk3_getAvailableTypes(GTKCLIPBOARD);
678671
result = new TransferData[types.length];
679672
for (int i = 0; i < types.length; i++) {
680673
result[i] = new TransferData();
681674
result[i].type = types[i];
682675
}
683676
}
684677
if ((clipboards & DND.SELECTION_CLIPBOARD) != 0) {
685-
int[] types = getAvailablePrimaryTypes();
678+
int[] types = gtk3_getAvailableTypes(GTKPRIMARYCLIPBOARD);
686679
int offset = 0;
687680
if (result != null) {
688681
TransferData[] newResult = new TransferData[result.length + types.length];
@@ -719,13 +712,10 @@ public TransferData[] getAvailableTypes(int clipboards) {
719712
public String[] getAvailableTypeNames() {
720713
checkWidget();
721714
if(GTK.GTK4) {
722-
long formatsCStr = GTK4.gdk_content_formats_to_string(GTK4.gdk_clipboard_get_formats(Clipboard.GTKCLIPBOARD));
723-
String formatsStr = Converter.cCharPtrToJavaString(formatsCStr, true);
724-
String[] types = formatsStr.split(" ");
725-
return types;
715+
return gtk4_getAvailableTypeNames();
726716
}
727-
int[] types1 = getAvailableClipboardTypes();
728-
int[] types2 = getAvailablePrimaryTypes();
717+
int[] types1 = gtk3_getAvailableTypes(GTKCLIPBOARD);
718+
int[] types2 = gtk3_getAvailableTypes(GTKPRIMARYCLIPBOARD);
729719
String[] result = new String[types1.length + types2.length];
730720
int count = 0;
731721
for (int i = 0; i < types1.length; i++) {
@@ -756,37 +746,90 @@ public String[] getAvailableTypeNames() {
756746
return result;
757747
}
758748

759-
private int[] getAvailablePrimaryTypes() {
760-
if (GTK.GTK4) {
761-
return gtk4_getAvailableTypes(GTKPRIMARYCLIPBOARD);
762-
}
763-
return gtk3_getAvailableTypes(GTKPRIMARYCLIPBOARD);
749+
private TransferData[] gtk4_getAvailableTypes(int clipboards) {
750+
/*
751+
* The first time we see a type name (mime type) may be when asking the
752+
* clipboard. The user may not load the specific Transfer class until after
753+
* getting the available types.
754+
*
755+
* Therefore we need to register any type names we see with the transfer system.
756+
*/
757+
return Arrays.stream(gtk4_getAvailableTypeNames(clipboards)).map(Transfer::registerType).map((type) -> {
758+
TransferData transferData = new TransferData();
759+
transferData.type = type;
760+
return transferData;
761+
}).toArray(TransferData[]::new);
764762
}
765-
private int[] getAvailableClipboardTypes () {
766-
if (GTK.GTK4) {
767-
return gtk4_getAvailableTypes(GTKCLIPBOARD);
768-
}
769-
return gtk3_getAvailableTypes(GTKCLIPBOARD);
763+
764+
private String[] gtk4_getAvailableTypeNames() {
765+
Set<String> all = new LinkedHashSet<>();
766+
Arrays.stream(gtk4_getAvailableTypeNames(DND.CLIPBOARD)).map(n -> "GTKCLIPBOARD "+ n).forEachOrdered(all::add);
767+
Arrays.stream(gtk4_getAvailableTypeNames(DND.SELECTION_CLIPBOARD)).map(n -> "GTKPRIMARYCLIPBOARD "+ n).forEachOrdered(all::add);
768+
return all.toArray(String[]::new);
770769
}
771770

772-
private int[] gtk4_getAvailableTypes(long clipboard) {
773-
long formats = GTK4.gdk_clipboard_get_formats(clipboard);
774-
long[] n_gtypes = new long[1];
775-
long gtypes = GTK4.gdk_content_formats_get_gtypes(formats, n_gtypes);
776-
777-
int gtypes_length = (int) n_gtypes[0];
778-
int[] types = new int[gtypes_length];
779-
for (int i = 0 ; i < gtypes_length ; ++i) {
780-
long[] ptr = new long[1];
781-
C.memmove(ptr, gtypes + i * C.PTR_SIZEOF, C.PTR_SIZEOF);
782-
types[i] = (int) ptr[0];
771+
private String[] gtk4_getAvailableTypeNames(int clipboards) {
772+
long gtkClipboard = ((clipboards & DND.CLIPBOARD) != 0) ? Clipboard.GTKCLIPBOARD : Clipboard.GTKPRIMARYCLIPBOARD;
773+
774+
List<String> names = new ArrayList<>();
775+
776+
long formats = GTK4.gdk_clipboard_get_formats(gtkClipboard);
777+
778+
{
779+
long[] n_gtypes = new long[1];
780+
long gtypes = GTK4.gdk_content_formats_get_gtypes(formats, n_gtypes);
781+
if (gtypes != 0) {
782+
int gtypes_length = (int) n_gtypes[0];
783+
784+
for (int i = 0; i < gtypes_length; i++) {
785+
long[] ptr = new long[1];
786+
C.memmove(ptr, gtypes + i * C.PTR_SIZEOF, C.PTR_SIZEOF);
787+
long gtype = ptr[0];
788+
if (gtype == 0) {
789+
// End reached of null terminated array
790+
break;
791+
}
792+
if (gtype != OS.G_TYPE_INVALID()) {
793+
long g_type_name = OS.g_type_name(gtype);
794+
if (g_type_name == 0) {
795+
continue;
796+
}
797+
String gtypeName = Converter.cCharPtrToJavaString(g_type_name, false);
798+
names.add(gtypeName);
799+
}
800+
}
801+
}
802+
}
803+
804+
{
805+
long[] n_mime_types = new long[1];
806+
long mime_types = GTK4.gdk_content_formats_get_mime_types(formats, n_mime_types);
807+
if (mime_types != 0) {
808+
int mime_types_length = (int) n_mime_types[0];
809+
810+
for (int i = 0; i < mime_types_length; i++) {
811+
long[] ptr = new long[1];
812+
C.memmove(ptr, mime_types + i * C.PTR_SIZEOF, C.PTR_SIZEOF);
813+
long mime_type = ptr[0];
814+
if (mime_type == 0) {
815+
// End reached of null terminated array
816+
break;
817+
}
818+
819+
String typeName = Converter.cCharPtrToJavaString(mime_type, false);
820+
if (!typeName.isBlank()) {
821+
names.add(typeName);
822+
}
823+
}
824+
}
783825
}
784-
return types;
826+
827+
return names.toArray(String[]::new);
785828
}
786829

787830
private int[] gtk3_getAvailableTypes(long clipboard) {
788831
int[] types = new int[0];
789-
long selection_data = gtk_clipboard_wait_for_contents(clipboard, TARGET);
832+
long selection_data = gtk3_clipboard_wait_for_contents(clipboard, TARGET);
790833
if (selection_data != 0) {
791834
try {
792835
int length = GTK3.gtk_selection_data_get_length(selection_data);
@@ -803,7 +846,7 @@ private int[] gtk3_getAvailableTypes(long clipboard) {
803846
return types;
804847
}
805848

806-
long gtk_clipboard_wait_for_contents(long clipboard, long target) {
849+
private long gtk3_clipboard_wait_for_contents(long clipboard, long target) {
807850
long startTime = System.currentTimeMillis();
808851
String key = "org.eclipse.swt.internal.gtk.dispatchEvent";
809852
Display display = this.display;

0 commit comments

Comments
 (0)