/* * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #include "com_sun_glass_ui_gtk_GtkSystemClipboard.h" #include "glass_general.h" #include #include #include static GdkAtom MIME_TEXT_PLAIN_TARGET; static GdkAtom MIME_TEXT_URI_LIST_TARGET; static GdkAtom MIME_JAVA_IMAGE; static GdkAtom MIME_FILES_TARGET; static void init_atoms() { static int initialized = 0; if (!initialized) { MIME_TEXT_PLAIN_TARGET = gdk_atom_intern_static_string("text/plain"); MIME_TEXT_URI_LIST_TARGET = gdk_atom_intern_static_string("text/uri-list"); MIME_JAVA_IMAGE = gdk_atom_intern_static_string("application/x-java-rawimage"); MIME_FILES_TARGET = gdk_atom_intern_static_string("application/x-java-file-list"); initialized = 1; } } static GtkClipboard* clipboard = NULL; static gboolean is_clipboard_owner = FALSE; static gboolean is_clipboard_updated_by_glass = FALSE; static GtkClipboard *get_clipboard() { if (clipboard == NULL) { clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); } return clipboard; } static void add_target_from_jstring(JNIEnv *env, GtkTargetList *list, jstring string) { const char *gstring = env->GetStringUTFChars(string, NULL); if (g_strcmp0(gstring, "text/plain") == 0) { gtk_target_list_add_text_targets(list, 0); } else if (g_strcmp0(gstring, "application/x-java-rawimage") == 0) { gtk_target_list_add_image_targets(list, 0, TRUE); } else if (g_strcmp0(gstring, "application/x-java-file-list") == 0) { gtk_target_list_add(list, MIME_TEXT_URI_LIST_TARGET, 0, 0); } else { gtk_target_list_add(list, gdk_atom_intern(gstring, FALSE), 0, 0); } env->ReleaseStringUTFChars(string, gstring); } static void data_to_targets(JNIEnv *env, jobject data, GtkTargetEntry **targets, gint *ntargets) { jobject keys; jobject keysIterator; jstring next; GtkTargetList *list = gtk_target_list_new(NULL, 0); keys = env->CallObjectMethod(data, jMapKeySet, NULL); CHECK_JNI_EXCEPTION(env) keysIterator = env->CallObjectMethod(keys, jIterableIterator, NULL); CHECK_JNI_EXCEPTION(env) while (env->CallBooleanMethod(keysIterator, jIteratorHasNext) == JNI_TRUE) { next = (jstring) env->CallObjectMethod(keysIterator, jIteratorNext, NULL); add_target_from_jstring(env, list, next); } *targets = gtk_target_table_new_from_list(list, ntargets); gtk_target_list_unref(list); } static void set_text_data(GtkSelectionData *selection_data, jstring data) { const char *text_data = mainEnv->GetStringUTFChars(data, NULL); guint ntext_data = strlen(text_data); gtk_selection_data_set_text(selection_data, text_data, ntext_data); mainEnv->ReleaseStringUTFChars(data, text_data); } static void set_jstring_data(GtkSelectionData *selection_data, GdkAtom target, jstring data) { const char *text_data = mainEnv->GetStringUTFChars(data, NULL); guint ntext_data = strlen(text_data); //XXX is target == type ?? gtk_selection_data_set(selection_data, target, 8, (const guchar *)text_data, ntext_data); mainEnv->ReleaseStringUTFChars(data, text_data); } static void set_bytebuffer_data(GtkSelectionData *selection_data, GdkAtom target, jobject data) { jbyteArray byteArray = (jbyteArray) mainEnv->CallObjectMethod(data, jByteBufferArray); CHECK_JNI_EXCEPTION(mainEnv) jbyte* raw = mainEnv->GetByteArrayElements(byteArray, NULL); jsize nraw = mainEnv->GetArrayLength(byteArray); //XXX is target == type ?? gtk_selection_data_set(selection_data, target, 8, (guchar *)raw, (gint)nraw); mainEnv->ReleaseByteArrayElements(byteArray, raw, JNI_ABORT); } static void set_uri_data(GtkSelectionData *selection_data, jobject data) { const gchar* url = NULL; jstring jurl = NULL; jobjectArray files_array = NULL; gsize files_cnt = 0; jstring typeString; typeString = mainEnv->NewStringUTF("text/uri-list"); if (mainEnv->ExceptionCheck()) return; if (mainEnv->CallBooleanMethod(data, jMapContainsKey, typeString, NULL)) { jurl = (jstring) mainEnv->CallObjectMethod(data, jMapGet, typeString, NULL); CHECK_JNI_EXCEPTION(mainEnv); url = mainEnv->GetStringUTFChars(jurl, NULL); } typeString = mainEnv->NewStringUTF("application/x-java-file-list"); if (mainEnv->ExceptionCheck()) return; if (mainEnv->CallBooleanMethod(data, jMapContainsKey, typeString, NULL)) { files_array = (jobjectArray) mainEnv->CallObjectMethod(data, jMapGet, typeString, NULL); CHECK_JNI_EXCEPTION(mainEnv); if (files_array) { files_cnt = mainEnv->GetArrayLength(files_array); } } if (!url && !files_cnt) { return; } gsize uri_cnt = files_cnt + (url ? 1 : 0); gchar **uris = (gchar**) glass_try_malloc0_n(uri_cnt + 1, // uris must be a NULL-terminated array of strings sizeof(gchar*)); if (!uris) { if (url) { mainEnv->ReleaseStringUTFChars(jurl, url); } glass_throw_oom(mainEnv, "Failed to allocate uri data"); return; } gsize i = 0; if (files_cnt > 0) { for (; i < files_cnt; ++i) { jstring string = (jstring) mainEnv->GetObjectArrayElement(files_array, i); const gchar* file = mainEnv->GetStringUTFChars(string, NULL); uris[i] = g_filename_to_uri(file, NULL, NULL); mainEnv->ReleaseStringUTFChars(string, file); } } if (url) { uris[i] = (gchar*) url; } //http://www.ietf.org/rfc/rfc2483.txt gtk_selection_data_set_uris(selection_data, uris); for (i = 0; i < uri_cnt; ++i) { if (uris[i] != url) { g_free(uris[i]); } } if (url) { mainEnv->ReleaseStringUTFChars(jurl, url); } g_free(uris); } static void set_image_data(GtkSelectionData *selection_data, jobject pixels) { GdkPixbuf *pixbuf = NULL; mainEnv->CallVoidMethod(pixels, jPixelsAttachData, PTR_TO_JLONG(&pixbuf)); if (!EXCEPTION_OCCURED(mainEnv)) { gtk_selection_data_set_pixbuf(selection_data, pixbuf); } g_object_unref(pixbuf); } static void set_data(GdkAtom target, GtkSelectionData *selection_data, jobject data) { gchar *name = gdk_atom_name(target); jstring typeString; jobject result; if (gtk_targets_include_text(&target, 1)) { typeString = mainEnv->NewStringUTF("text/plain"); EXCEPTION_OCCURED(mainEnv); result = mainEnv->CallObjectMethod(data, jMapGet, typeString, NULL); if (!EXCEPTION_OCCURED(mainEnv) && result != NULL) { set_text_data(selection_data, (jstring)result); } } else if (gtk_targets_include_image(&target, 1, TRUE)) { typeString = mainEnv->NewStringUTF("application/x-java-rawimage"); EXCEPTION_OCCURED(mainEnv); result = mainEnv->CallObjectMethod(data, jMapGet, typeString, NULL); if (!EXCEPTION_OCCURED(mainEnv) && result != NULL) { set_image_data(selection_data, result); } } else if (target == MIME_TEXT_URI_LIST_TARGET) { set_uri_data(selection_data, data); } else { typeString = mainEnv->NewStringUTF(name); EXCEPTION_OCCURED(mainEnv); result = mainEnv->CallObjectMethod(data, jMapGet, typeString, NULL); if (!EXCEPTION_OCCURED(mainEnv) && result != NULL) { if (mainEnv->IsInstanceOf(result, jStringCls)) { set_jstring_data(selection_data, target, (jstring)result); } else if (mainEnv->IsInstanceOf(result, jByteBufferCls)) { set_bytebuffer_data(selection_data, target, result); } } } g_free(name); } static void set_data_func(GtkClipboard *clipboard, GtkSelectionData *selection_data, guint info, gpointer user_data) { (void)clipboard; (void)info; jobject data = (jobject) user_data; //HashMap GdkAtom target; target = gtk_selection_data_get_target(selection_data); set_data(target, selection_data, data); CHECK_JNI_EXCEPTION(mainEnv); } static void clear_data_func(GtkClipboard *clipboard, gpointer user_data) { (void)clipboard; jobject data =(jobject) user_data; mainEnv->DeleteGlobalRef(data); } static jobject get_data_text(JNIEnv *env) { gchar *data = gtk_clipboard_wait_for_text(get_clipboard()); if (data == NULL) { return NULL; } jstring jdata = env->NewStringUTF(data); EXCEPTION_OCCURED(env); g_free(data); return jdata; } static jobject get_data_uri_list(JNIEnv *env, gboolean files) { return uris_to_java(env, gtk_clipboard_wait_for_uris(get_clipboard()), files); } static jobject get_data_image(JNIEnv* env) { GdkPixbuf* pixbuf; guchar *data; jbyteArray data_array; jobject buffer, result; int w,h,stride; pixbuf = gtk_clipboard_wait_for_image(get_clipboard()); if (pixbuf == NULL) { return NULL; } if (!gdk_pixbuf_get_has_alpha(pixbuf)) { GdkPixbuf *tmp_buf = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0); g_object_unref(pixbuf); pixbuf = tmp_buf; } w = gdk_pixbuf_get_width(pixbuf); h = gdk_pixbuf_get_height(pixbuf); stride = gdk_pixbuf_get_rowstride(pixbuf); data = gdk_pixbuf_get_pixels(pixbuf); //Actually, we are converting RGBA to BGRA, but that's the same operation data = (guchar*) convert_BGRA_to_RGBA((int*)data, stride, h); data_array = env->NewByteArray(stride*h); EXCEPTION_OCCURED(env); env->SetByteArrayRegion(data_array, 0, stride*h, (jbyte*)data); EXCEPTION_OCCURED(env); buffer = env->CallStaticObjectMethod(jByteBufferCls, jByteBufferWrap, data_array); result = env->NewObject(jGtkPixelsCls, jGtkPixelsInit, w, h, buffer); EXCEPTION_OCCURED(env); g_free(data); g_object_unref(pixbuf); return result; } static jobject get_data_raw(JNIEnv *env, const char* mime, gboolean string_data) { GtkSelectionData *data; const guchar *raw_data; jsize length; jbyteArray array; jobject result = NULL; data = gtk_clipboard_wait_for_contents(get_clipboard(), gdk_atom_intern(mime, FALSE)); if (data != NULL) { raw_data = glass_gtk_selection_data_get_data_with_length(data, &length); if (string_data) { result = env->NewStringUTF((const char*)raw_data); EXCEPTION_OCCURED(env); } else { array = env->NewByteArray(length); EXCEPTION_OCCURED(env); env->SetByteArrayRegion(array, 0, length, (const jbyte*)raw_data); EXCEPTION_OCCURED(env); result = env->CallStaticObjectMethod(jByteBufferCls, jByteBufferWrap, array); } gtk_selection_data_free(data); } return result; } static jobject jclipboard = NULL; static gulong owner_change_handler_id = 0; static void clipboard_owner_changed_callback(GtkClipboard *clipboard, GdkEventOwnerChange *event, jobject obj) { (void)clipboard; (void)event; (void)obj; is_clipboard_owner = is_clipboard_updated_by_glass; is_clipboard_updated_by_glass = FALSE; mainEnv->CallVoidMethod(obj, jClipboardContentChanged); CHECK_JNI_EXCEPTION(mainEnv) } extern "C" { /* * Class: com_sun_glass_ui_gtk_GtkSystemClipboard * Method: init * Signature: ()V */ JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_init (JNIEnv *env, jobject obj) { if (jclipboard) { ERROR0("GtkSystemClipboard already initiated"); } jclipboard = env->NewGlobalRef(obj); owner_change_handler_id = g_signal_connect(G_OBJECT(get_clipboard()), "owner-change", G_CALLBACK(clipboard_owner_changed_callback), jclipboard); } /* * Class: com_sun_glass_ui_gtk_GtkSystemClipboard * Method: dispose * Signature: ()V */ JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_dispose (JNIEnv *env, jobject obj) { (void)obj; g_signal_handler_disconnect(G_OBJECT(get_clipboard()), owner_change_handler_id); env->DeleteGlobalRef(jclipboard); owner_change_handler_id = 0; jclipboard = NULL; } /* * Class: com_sun_glass_ui_gtk_GtkSystemClipboard * Method: isOwner * Signature: ()Z */ JNIEXPORT jboolean JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_isOwner (JNIEnv *env, jobject obj) { (void)env; (void)obj; return is_clipboard_owner ? JNI_TRUE : JNI_FALSE; } /* * Class: com_sun_glass_ui_gtk_GtkSystemClipboard * Method: pushToSystem * Signature: (Ljava/util/HashMap;I)V */ JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_pushToSystem (JNIEnv * env, jobject obj, jobject data, jint supported) { (void)obj; (void)supported; GtkTargetEntry* targets = NULL; gint ntargets; data = env->NewGlobalRef(data); init_atoms(); data_to_targets(env, data, &targets, &ntargets); CHECK_JNI_EXCEPTION(env) if (targets) { gtk_clipboard_set_with_data(get_clipboard(), targets, ntargets, set_data_func, clear_data_func, data); gtk_target_table_free(targets, ntargets); } else { // targets == NULL means that we want to clear clipboard. // Passing NULL as targets parameter to gtk_clipboard_set_with_data will produce Gtk-CRITICAL assertion // but passing 0 as n_targets parameter allows to set empty list of available mime types GtkTargetEntry dummy_targets = {(gchar*) "MIME_DUMMY_TARGET", 0, 0}; gtk_clipboard_set_with_data(get_clipboard(), &dummy_targets, 0, set_data_func, clear_data_func, data); } is_clipboard_updated_by_glass = TRUE; } /* * Class: com_sun_glass_ui_gtk_GtkSystemClipboard * Method: pushTargetActionToSystem * Signature: (I)V */ JNIEXPORT void JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_pushTargetActionToSystem (JNIEnv * env, jobject obj, jint action) { //Not used for clipboard. DnD only (void)env; (void)obj; (void)action; } /* * Class: com_sun_glass_ui_gtk_GtkSystemClipboard * Method: popFromSystem * Signature: (Ljava/lang/String;)Ljava/lang/Object; */ JNIEXPORT jobject JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_popFromSystem (JNIEnv * env, jobject obj, jstring mime) { (void)env; (void)obj; const char* cmime = env->GetStringUTFChars(mime, NULL); jobject result; init_atoms(); if (g_strcmp0(cmime, "text/plain") == 0) { result = get_data_text(env); } else if (g_strcmp0(cmime, "text/uri-list") == 0) { result = get_data_uri_list(env, FALSE); } else if (g_str_has_prefix(cmime, "text/")) { result = get_data_raw(env, cmime, TRUE); } else if (g_strcmp0(cmime, "application/x-java-file-list") == 0) { result = get_data_uri_list(env, TRUE); } else if (g_strcmp0(cmime, "application/x-java-rawimage") == 0 ) { result = get_data_image(env); } else { result = get_data_raw(env, cmime, FALSE); } LOG_EXCEPTION(env) env->ReleaseStringUTFChars(mime, cmime); return result; } /* * Class: com_sun_glass_ui_gtk_GtkSystemClipboard * Method: supportedSourceActionsFromSystem * Signature: ()I */ JNIEXPORT jint JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_supportedSourceActionsFromSystem (JNIEnv *env, jobject obj) { //Not used for clipboard. DnD only (void)env; (void)obj; return 0; } /* * Class: com_sun_glass_ui_gtk_GtkSystemClipboard * Method: mimesFromSystem * Signature: ()[Ljava/lang/String; */ JNIEXPORT jobjectArray JNICALL Java_com_sun_glass_ui_gtk_GtkSystemClipboard_mimesFromSystem (JNIEnv * env, jobject obj) { (void)obj; GdkAtom *targets; gint ntargets; gint i; GdkAtom *convertible; GdkAtom *convertible_ptr; gchar *name; jobjectArray result; jstring tmpString; init_atoms(); gtk_clipboard_wait_for_targets(get_clipboard(), &targets, &ntargets); convertible = (GdkAtom*) glass_try_malloc0_n(ntargets * 2, sizeof(GdkAtom)); //theoretically, the number can double if (!convertible) { if (ntargets > 0) { glass_throw_oom(env, "Failed to allocate mimes"); } g_free(targets); return NULL; } convertible_ptr = convertible; bool uri_list_added = false; bool text_added = false; bool image_added = false; for (i = 0; i < ntargets; ++i) { //handle text targets //if (targets[i] == TEXT_TARGET || targets[i] == STRING_TARGET || targets[i] == UTF8_STRING_TARGET) { if (gtk_targets_include_text(targets + i, 1) && !text_added) { *(convertible_ptr++) = MIME_TEXT_PLAIN_TARGET; text_added = true; } else if (gtk_targets_include_image(targets + i, 1, TRUE) && !image_added) { *(convertible_ptr++) = MIME_JAVA_IMAGE; image_added = true; } //TODO text/x-moz-url ? RT-17802 if (targets[i] == MIME_TEXT_URI_LIST_TARGET) { if (uri_list_added) { continue; } gchar** uris = gtk_clipboard_wait_for_uris(get_clipboard()); if (uris) { guint size = g_strv_length(uris); guint files_cnt = get_files_count(uris); if (files_cnt) { *(convertible_ptr++) = MIME_FILES_TARGET; } if (size - files_cnt) { *(convertible_ptr++) = MIME_TEXT_URI_LIST_TARGET; } g_strfreev(uris); } uri_list_added = true; } else { *(convertible_ptr++) = targets[i]; } } result = env->NewObjectArray(convertible_ptr - convertible, jStringCls, NULL); EXCEPTION_OCCURED(env); for (i = 0; convertible + i < convertible_ptr; ++i) { name = gdk_atom_name(convertible[i]); tmpString = env->NewStringUTF(name); EXCEPTION_OCCURED(env); env->SetObjectArrayElement(result, (jsize)i, tmpString); EXCEPTION_OCCURED(env); g_free(name); } g_free(targets); g_free(convertible); return result; } } // extern "C" {