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