1 /*
   2  * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 #include "com_sun_glass_ui_gtk_GtkSystemClipboard.h"
  26 #include "glass_general.h"
  27 
  28 #include <gtk/gtk.h>
  29 #include <string.h>
  30 #include <gdk-pixbuf/gdk-pixbuf.h>
  31 
  32 static GdkAtom MIME_TEXT_PLAIN_TARGET;
  33 
  34 static GdkAtom MIME_TEXT_URI_LIST_TARGET;
  35 
  36 static GdkAtom MIME_JAVA_IMAGE;
  37 
  38 static GdkAtom MIME_FILES_TARGET;
  39 
  40 static void init_atoms()
  41 {
  42     static int initialized = 0;
  43 
  44     if (!initialized) {
  45         MIME_TEXT_PLAIN_TARGET = gdk_atom_intern_static_string("text/plain");
  46 
  47         MIME_TEXT_URI_LIST_TARGET = gdk_atom_intern_static_string("text/uri-list");
  48 
  49         MIME_JAVA_IMAGE = gdk_atom_intern_static_string("application/x-java-rawimage");
  50 
  51         MIME_FILES_TARGET = gdk_atom_intern_static_string("application/x-java-file-list");
  52         initialized = 1;
  53     }
  54 }
  55 
  56 
  57 static GtkClipboard* clipboard = NULL;
  58 static gboolean is_clipboard_owner = FALSE;
  59 static gboolean is_clipboard_updated_by_glass = FALSE;
  60 
  61 static GtkClipboard *get_clipboard() {
  62     if (clipboard == NULL) {
  63         clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
  64     }
  65     return clipboard;
  66 }
  67 
  68 static void add_target_from_jstring(JNIEnv *env, GtkTargetList *list, jstring string)
  69 {
  70     const char *gstring = env->GetStringUTFChars(string, NULL);
  71     if (g_strcmp0(gstring, "text/plain") == 0) {
  72         gtk_target_list_add_text_targets(list, 0);
  73     } else if (g_strcmp0(gstring, "application/x-java-rawimage") == 0) {
  74         gtk_target_list_add_image_targets(list, 0, TRUE);
  75     } else if (g_strcmp0(gstring, "application/x-java-file-list") == 0) {
  76         gtk_target_list_add(list, MIME_TEXT_URI_LIST_TARGET, 0, 0);
  77     } else {
  78         gtk_target_list_add(list, gdk_atom_intern(gstring, FALSE), 0, 0);
  79     }
  80 
  81     env->ReleaseStringUTFChars(string, gstring);
  82 }
  83 
  84 static void data_to_targets(JNIEnv *env, jobject data, GtkTargetEntry **targets, gint *ntargets)
  85 {
  86     jobject keys;
  87     jobject keysIterator;
  88     jstring next;
  89 
  90     GtkTargetList *list = gtk_target_list_new(NULL, 0);
  91 
  92     keys = env->CallObjectMethod(data, jMapKeySet, NULL);
  93     CHECK_JNI_EXCEPTION(env)
  94     keysIterator = env->CallObjectMethod(keys, jIterableIterator, NULL);
  95     CHECK_JNI_EXCEPTION(env)
  96 
  97     while (env->CallBooleanMethod(keysIterator, jIteratorHasNext) == JNI_TRUE) {
  98         next = (jstring) env->CallObjectMethod(keysIterator, jIteratorNext, NULL);
  99         add_target_from_jstring(env, list, next);
 100     }
 101     *targets = gtk_target_table_new_from_list(list, ntargets);
 102     gtk_target_list_unref(list);
 103 
 104 }
 105 
 106 static void set_text_data(GtkSelectionData *selection_data, jstring data)
 107 {
 108     const char *text_data = mainEnv->GetStringUTFChars(data, NULL);
 109     guint ntext_data = strlen(text_data);
 110 
 111     gtk_selection_data_set_text(selection_data, text_data, ntext_data);
 112     mainEnv->ReleaseStringUTFChars(data, text_data);
 113 }
 114 
 115 static void set_jstring_data(GtkSelectionData *selection_data, GdkAtom target, jstring data)
 116 {
 117     const char *text_data = mainEnv->GetStringUTFChars(data, NULL);
 118     guint ntext_data = strlen(text_data);
 119 
 120     //XXX is target == type ??
 121     gtk_selection_data_set(selection_data, target, 8, (const guchar *)text_data, ntext_data);
 122     mainEnv->ReleaseStringUTFChars(data, text_data);
 123 }
 124 
 125 static void set_bytebuffer_data(GtkSelectionData *selection_data, GdkAtom target, jobject data)
 126 {
 127     jbyteArray byteArray = (jbyteArray) mainEnv->CallObjectMethod(data, jByteBufferArray);
 128     CHECK_JNI_EXCEPTION(mainEnv)
 129     jbyte* raw = mainEnv->GetByteArrayElements(byteArray, NULL);
 130     jsize nraw = mainEnv->GetArrayLength(byteArray);
 131 
 132     //XXX is target == type ??
 133     gtk_selection_data_set(selection_data, target, 8, (guchar *)raw, (gint)nraw);
 134 
 135     mainEnv->ReleaseByteArrayElements(byteArray, raw, JNI_ABORT);
 136 }
 137 
 138 static void set_uri_data(GtkSelectionData *selection_data, jobject data) {
 139     const gchar* url = NULL;
 140     jstring jurl = NULL;
 141 
 142     jobjectArray files_array = NULL;
 143     gsize files_cnt = 0;
 144 
 145     jstring typeString;
 146 
 147     typeString = mainEnv->NewStringUTF("text/uri-list");
 148     if (mainEnv->ExceptionCheck()) return;
 149     if (mainEnv->CallBooleanMethod(data, jMapContainsKey, typeString, NULL)) {
 150         jurl = (jstring) mainEnv->CallObjectMethod(data, jMapGet, typeString, NULL);
 151         CHECK_JNI_EXCEPTION(mainEnv);
 152         url = mainEnv->GetStringUTFChars(jurl, NULL);
 153     }
 154 
 155     typeString = mainEnv->NewStringUTF("application/x-java-file-list");
 156     if (mainEnv->ExceptionCheck()) return;
 157     if (mainEnv->CallBooleanMethod(data, jMapContainsKey, typeString, NULL)) {
 158         files_array = (jobjectArray) mainEnv->CallObjectMethod(data, jMapGet, typeString, NULL);
 159         CHECK_JNI_EXCEPTION(mainEnv);
 160         if (files_array) {
 161             files_cnt = mainEnv->GetArrayLength(files_array);
 162         }
 163     }
 164 
 165     if (!url && !files_cnt) {
 166         return;
 167     }
 168 
 169     gsize uri_cnt = files_cnt + (url ? 1 : 0);
 170 
 171     gchar **uris =
 172             (gchar**) glass_try_malloc0_n(uri_cnt + 1, // uris must be a NULL-terminated array of strings
 173                                             sizeof(gchar*));
 174     if (!uris) {
 175         if (url) {
 176             mainEnv->ReleaseStringUTFChars(jurl, url);
 177         }
 178         glass_throw_oom(mainEnv, "Failed to allocate uri data");
 179         return;
 180     }
 181 
 182     gsize i = 0;
 183     if (files_cnt > 0) {
 184         for (; i < files_cnt; ++i) {
 185             jstring string = (jstring) mainEnv->GetObjectArrayElement(files_array, i);
 186             const gchar* file = mainEnv->GetStringUTFChars(string, NULL);
 187             uris[i] = g_filename_to_uri(file, NULL, NULL);
 188             mainEnv->ReleaseStringUTFChars(string, file);
 189         }
 190     }
 191 
 192     if (url) {
 193         uris[i] = (gchar*) url;
 194     }
 195     //http://www.ietf.org/rfc/rfc2483.txt
 196     gtk_selection_data_set_uris(selection_data, uris);
 197 
 198     for (i = 0; i < uri_cnt; ++i) {
 199         if (uris[i] != url) {
 200             g_free(uris[i]);
 201         }
 202     }
 203 
 204     if (url) {
 205         mainEnv->ReleaseStringUTFChars(jurl, url);
 206     }
 207     g_free(uris);
 208 }
 209 
 210 static void set_image_data(GtkSelectionData *selection_data, jobject pixels)
 211 {
 212     GdkPixbuf *pixbuf = NULL;
 213 
 214     mainEnv->CallVoidMethod(pixels, jPixelsAttachData, PTR_TO_JLONG(&pixbuf));
 215     if (!EXCEPTION_OCCURED(mainEnv)) {
 216         gtk_selection_data_set_pixbuf(selection_data, pixbuf);
 217     }
 218 
 219     g_object_unref(pixbuf);
 220 }
 221 
 222 static void set_data(GdkAtom target, GtkSelectionData *selection_data, jobject data)
 223 {
 224     gchar *name = gdk_atom_name(target);
 225     jstring typeString;
 226     jobject result;
 227 
 228     if (gtk_targets_include_text(&target, 1)) {
 229         typeString = mainEnv->NewStringUTF("text/plain");
 230         EXCEPTION_OCCURED(mainEnv);
 231         result = mainEnv->CallObjectMethod(data, jMapGet, typeString, NULL);
 232         if (!EXCEPTION_OCCURED(mainEnv) && result != NULL) {
 233             set_text_data(selection_data, (jstring)result);
 234         }
 235     } else if (gtk_targets_include_image(&target, 1, TRUE)) {
 236         typeString = mainEnv->NewStringUTF("application/x-java-rawimage");
 237         EXCEPTION_OCCURED(mainEnv);
 238         result = mainEnv->CallObjectMethod(data, jMapGet, typeString, NULL);
 239         if (!EXCEPTION_OCCURED(mainEnv) && result != NULL) {
 240             set_image_data(selection_data, result);
 241         }
 242     } else if (target == MIME_TEXT_URI_LIST_TARGET) {
 243         set_uri_data(selection_data, data);
 244     } else {
 245         typeString = mainEnv->NewStringUTF(name);
 246         EXCEPTION_OCCURED(mainEnv);
 247         result = mainEnv->CallObjectMethod(data, jMapGet, typeString, NULL);
 248         if (!EXCEPTION_OCCURED(mainEnv) && result != NULL) {
 249             if (mainEnv->IsInstanceOf(result, jStringCls)) {
 250                 set_jstring_data(selection_data, target, (jstring)result);
 251             } else if (mainEnv->IsInstanceOf(result, jByteBufferCls)) {
 252                 set_bytebuffer_data(selection_data, target, result);
 253             }
 254         }
 255     }
 256 
 257     g_free(name);
 258 }
 259 
 260 static void set_data_func(GtkClipboard *clipboard, GtkSelectionData *selection_data,
 261         guint info, gpointer user_data)
 262 {
 263     (void)clipboard;
 264     (void)info;
 265 
 266     jobject data = (jobject) user_data; //HashMap
 267     GdkAtom target;
 268     target = gtk_selection_data_get_target(selection_data);
 269 
 270     set_data(target, selection_data, data);
 271     CHECK_JNI_EXCEPTION(mainEnv);
 272 }
 273 
 274 static void clear_data_func(GtkClipboard *clipboard, gpointer user_data)
 275 {
 276     (void)clipboard;
 277 
 278     jobject data =(jobject) user_data;
 279     mainEnv->DeleteGlobalRef(data);
 280 }
 281 
 282 static jobject get_data_text(JNIEnv *env)
 283 {
 284     gchar *data = gtk_clipboard_wait_for_text(get_clipboard());
 285     if (data == NULL) {
 286         return NULL;
 287     }
 288     jstring jdata = env->NewStringUTF(data);
 289     EXCEPTION_OCCURED(env);
 290     g_free(data);
 291     return jdata;
 292 }
 293 
 294 static jobject get_data_uri_list(JNIEnv *env, gboolean files)
 295 {
 296     return uris_to_java(env, gtk_clipboard_wait_for_uris(get_clipboard()), files);
 297 }
 298 
 299 static jobject get_data_image(JNIEnv* env) {
 300     GdkPixbuf* pixbuf;
 301     guchar *data;
 302     jbyteArray data_array;
 303     jobject buffer, result;
 304     int w,h,stride;
 305 
 306     pixbuf = gtk_clipboard_wait_for_image(get_clipboard());
 307     if (pixbuf == NULL) {
 308         return NULL;
 309     }
 310 
 311     if (!gdk_pixbuf_get_has_alpha(pixbuf)) {
 312         GdkPixbuf *tmp_buf = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
 313         g_object_unref(pixbuf);
 314         pixbuf = tmp_buf;
 315     }
 316     w = gdk_pixbuf_get_width(pixbuf);
 317     h = gdk_pixbuf_get_height(pixbuf);
 318     stride = gdk_pixbuf_get_rowstride(pixbuf);
 319 
 320     data = gdk_pixbuf_get_pixels(pixbuf);
 321 
 322     //Actually, we are converting RGBA to BGRA, but that's the same operation
 323     data = (guchar*) convert_BGRA_to_RGBA((int*)data, stride, h);
 324 
 325     data_array = env->NewByteArray(stride*h);
 326     EXCEPTION_OCCURED(env);
 327     env->SetByteArrayRegion(data_array, 0, stride*h, (jbyte*)data);
 328     EXCEPTION_OCCURED(env);
 329 
 330     buffer = env->CallStaticObjectMethod(jByteBufferCls, jByteBufferWrap, data_array);
 331     result = env->NewObject(jGtkPixelsCls, jGtkPixelsInit, w, h, buffer);
 332     EXCEPTION_OCCURED(env);
 333 
 334     g_free(data);
 335     g_object_unref(pixbuf);
 336 
 337     return result;
 338 
 339 }
 340 
 341 static jobject get_data_raw(JNIEnv *env, const char* mime, gboolean string_data)
 342 {
 343     GtkSelectionData *data;
 344     const guchar *raw_data;
 345     jsize length;
 346     jbyteArray array;
 347     jobject result = NULL;
 348     data = gtk_clipboard_wait_for_contents(get_clipboard(), gdk_atom_intern(mime, FALSE));
 349     if (data != NULL) {
 350         raw_data = glass_gtk_selection_data_get_data_with_length(data, &length);
 351         if (string_data) {
 352             result = env->NewStringUTF((const char*)raw_data);
 353             EXCEPTION_OCCURED(env);
 354         } else {
 355             array = env->NewByteArray(length);
 356             EXCEPTION_OCCURED(env);
 357             env->SetByteArrayRegion(array, 0, length, (const jbyte*)raw_data);
 358             EXCEPTION_OCCURED(env);
 359             result = env->CallStaticObjectMethod(jByteBufferCls, jByteBufferWrap, array);
 360         }
 361         gtk_selection_data_free(data);
 362     }
 363     return result;
 364 }
 365 
 366 static jobject jclipboard = NULL;
 367 static gulong owner_change_handler_id = 0;
 368 
 369 static void clipboard_owner_changed_callback(GtkClipboard *clipboard, GdkEventOwnerChange *event, jobject obj)
 370 {
 371     (void)clipboard;
 372     (void)event;
 373     (void)obj;
 374 
 375     is_clipboard_owner = is_clipboard_updated_by_glass;
 376     is_clipboard_updated_by_glass = FALSE;
 377     mainEnv->CallVoidMethod(obj, jClipboardContentChanged);
 378     CHECK_JNI_EXCEPTION(mainEnv)
 379 }
 380 
 381 extern "C" {
 382 
 383 /*
 384  * Class:     com_sun_glass_ui_gtk_GtkSystemClipboard
 385  * Method:    init
 386  * Signature: ()V
 387  */
 388 JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_init
 389   (JNIEnv *env, jobject obj)
 390 {
 391     if (jclipboard) {
 392         ERROR0("GtkSystemClipboard already initiated");
 393     }
 394 
 395     jclipboard = env->NewGlobalRef(obj);
 396     owner_change_handler_id = g_signal_connect(G_OBJECT(get_clipboard()),
 397             "owner-change", G_CALLBACK(clipboard_owner_changed_callback), jclipboard);
 398 }
 399 
 400 /*
 401  * Class:     com_sun_glass_ui_gtk_GtkSystemClipboard
 402  * Method:    dispose
 403  * Signature: ()V
 404  */
 405 JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_dispose
 406   (JNIEnv *env, jobject obj)
 407 {
 408     (void)obj;
 409 
 410     g_signal_handler_disconnect(G_OBJECT(get_clipboard()), owner_change_handler_id);
 411     env->DeleteGlobalRef(jclipboard);
 412 
 413     owner_change_handler_id = 0;
 414     jclipboard = NULL;
 415 }
 416 
 417 /*
 418  * Class:     com_sun_glass_ui_gtk_GtkSystemClipboard
 419  * Method:    isOwner
 420  * Signature: ()Z
 421  */
 422 JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_isOwner
 423   (JNIEnv *env, jobject obj)
 424 {
 425     (void)env;
 426     (void)obj;
 427 
 428     return is_clipboard_owner ? JNI_TRUE : JNI_FALSE;
 429 }
 430 
 431 /*
 432  * Class:     com_sun_glass_ui_gtk_GtkSystemClipboard
 433  * Method:    pushToSystem
 434  * Signature: (Ljava/util/HashMap;I)V
 435  */
 436 JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_pushToSystem
 437   (JNIEnv * env, jobject obj, jobject data, jint supported)
 438 {
 439     (void)obj;
 440     (void)supported;
 441 
 442     GtkTargetEntry* targets = NULL;
 443     gint ntargets;
 444     data = env->NewGlobalRef(data);
 445     init_atoms();
 446     data_to_targets(env, data, &targets, &ntargets);
 447     CHECK_JNI_EXCEPTION(env)
 448     if (targets) {
 449         gtk_clipboard_set_with_data(get_clipboard(), targets, ntargets, set_data_func, clear_data_func, data);
 450         gtk_target_table_free(targets, ntargets);
 451     } else {
 452         // targets == NULL means that we want to clear clipboard.
 453         // Passing NULL as targets parameter to gtk_clipboard_set_with_data will produce Gtk-CRITICAL assertion
 454         // but passing 0 as n_targets parameter allows to set empty list of available mime types
 455         GtkTargetEntry dummy_targets = {(gchar*) "MIME_DUMMY_TARGET", 0, 0};
 456         gtk_clipboard_set_with_data(get_clipboard(), &dummy_targets, 0, set_data_func, clear_data_func, data);
 457     }
 458 
 459     is_clipboard_updated_by_glass = TRUE;
 460 }
 461 
 462 /*
 463  * Class:     com_sun_glass_ui_gtk_GtkSystemClipboard
 464  * Method:    pushTargetActionToSystem
 465  * Signature: (I)V
 466  */
 467 JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_pushTargetActionToSystem
 468   (JNIEnv * env, jobject obj, jint action)
 469 {
 470     //Not used for clipboard. DnD only
 471     (void)env;
 472     (void)obj;
 473     (void)action;
 474 }
 475 
 476 /*
 477  * Class:     com_sun_glass_ui_gtk_GtkSystemClipboard
 478  * Method:    popFromSystem
 479  * Signature: (Ljava/lang/String;)Ljava/lang/Object;
 480  */
 481 JNIEXPORT jobject JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_popFromSystem
 482   (JNIEnv * env, jobject obj, jstring mime)
 483 {
 484     (void)env;
 485     (void)obj;
 486 
 487     const char* cmime = env->GetStringUTFChars(mime, NULL);
 488     jobject result;
 489 
 490     init_atoms();
 491     if (g_strcmp0(cmime, "text/plain") == 0) {
 492         result = get_data_text(env);
 493     } else if (g_strcmp0(cmime, "text/uri-list") == 0) {
 494         result = get_data_uri_list(env, FALSE);
 495     } else if (g_str_has_prefix(cmime, "text/")) {
 496         result = get_data_raw(env, cmime, TRUE);
 497     } else if (g_strcmp0(cmime, "application/x-java-file-list") == 0) {
 498         result = get_data_uri_list(env, TRUE);
 499     } else if (g_strcmp0(cmime, "application/x-java-rawimage") == 0 ) {
 500         result = get_data_image(env);
 501     } else {
 502         result = get_data_raw(env, cmime, FALSE);
 503     }
 504     LOG_EXCEPTION(env)
 505     env->ReleaseStringUTFChars(mime, cmime);
 506 
 507     return result;
 508 }
 509 
 510 /*
 511  * Class:     com_sun_glass_ui_gtk_GtkSystemClipboard
 512  * Method:    supportedSourceActionsFromSystem
 513  * Signature: ()I
 514  */
 515 JNIEXPORT jint JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_supportedSourceActionsFromSystem
 516   (JNIEnv *env, jobject obj)
 517 {
 518     //Not used for clipboard. DnD only
 519     (void)env;
 520     (void)obj;
 521     return 0;
 522 }
 523 
 524 /*
 525  * Class:     com_sun_glass_ui_gtk_GtkSystemClipboard
 526  * Method:    mimesFromSystem
 527  * Signature: ()[Ljava/lang/String;
 528  */
 529 JNIEXPORT jobjectArray JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_mimesFromSystem
 530   (JNIEnv * env, jobject obj)
 531 {
 532     (void)obj;
 533 
 534     GdkAtom *targets;
 535     gint ntargets;
 536     gint i;
 537     GdkAtom *convertible;
 538     GdkAtom *convertible_ptr;
 539     gchar *name;
 540     jobjectArray result;
 541     jstring tmpString;
 542 
 543     init_atoms();
 544 
 545     gtk_clipboard_wait_for_targets(get_clipboard(), &targets, &ntargets);
 546 
 547     convertible = (GdkAtom*) glass_try_malloc0_n(ntargets * 2, sizeof(GdkAtom)); //theoretically, the number can double
 548     if (!convertible) {
 549         if (ntargets > 0) {
 550             glass_throw_oom(env, "Failed to allocate mimes");
 551         }
 552         g_free(targets);
 553         return NULL;
 554     }
 555 
 556     convertible_ptr = convertible;
 557 
 558     bool uri_list_added = false;
 559     bool text_added = false;
 560     bool image_added = false;
 561 
 562     for (i = 0; i < ntargets; ++i) {
 563         //handle text targets
 564         //if (targets[i] == TEXT_TARGET || targets[i] == STRING_TARGET || targets[i] == UTF8_STRING_TARGET) {
 565 
 566         if (gtk_targets_include_text(targets + i, 1) && !text_added) {
 567             *(convertible_ptr++) = MIME_TEXT_PLAIN_TARGET;
 568             text_added = true;
 569         } else if (gtk_targets_include_image(targets + i, 1, TRUE) && !image_added) {
 570             *(convertible_ptr++) = MIME_JAVA_IMAGE;
 571             image_added = true;
 572         }
 573         //TODO text/x-moz-url ? RT-17802
 574 
 575         if (targets[i] == MIME_TEXT_URI_LIST_TARGET) {
 576             if (uri_list_added) {
 577                 continue;
 578             }
 579 
 580             gchar** uris = gtk_clipboard_wait_for_uris(get_clipboard());
 581             if (uris) {
 582                 guint size = g_strv_length(uris);
 583                 guint files_cnt = get_files_count(uris);
 584                 if (files_cnt) {
 585                     *(convertible_ptr++) = MIME_FILES_TARGET;
 586                 }
 587                 if (size - files_cnt) {
 588                     *(convertible_ptr++) = MIME_TEXT_URI_LIST_TARGET;
 589                 }
 590                 g_strfreev(uris);
 591             }
 592             uri_list_added = true;
 593         } else {
 594             *(convertible_ptr++) = targets[i];
 595         }
 596     }
 597 
 598     result = env->NewObjectArray(convertible_ptr - convertible, jStringCls, NULL);
 599     EXCEPTION_OCCURED(env);
 600     for (i = 0; convertible + i < convertible_ptr; ++i) {
 601         name = gdk_atom_name(convertible[i]);
 602         tmpString = env->NewStringUTF(name);
 603         EXCEPTION_OCCURED(env);
 604         env->SetObjectArrayElement(result, (jsize)i, tmpString);
 605         EXCEPTION_OCCURED(env);
 606         g_free(name);
 607     }
 608 
 609     g_free(targets);
 610     g_free(convertible);
 611     return result;
 612 }
 613 
 614 } // extern "C" {