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