1414package org .eclipse .swt .dnd ;
1515
1616
17+ import java .time .*;
18+ import java .util .*;
19+ import java .util .List ;
1720import java .util .concurrent .*;
1821
1922import org .eclipse .swt .*;
20- import org .eclipse .swt .graphics .*;
2123import org .eclipse .swt .internal .*;
2224import org .eclipse .swt .internal .gtk .*;
2325import 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 */
191202public 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 */
304320public 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 */
421453public 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 */
672662public 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) {
719712public 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
787830private 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