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" {