summaryrefslogtreecommitdiffstats
path: root/widget/gtk/nsClipboard.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/gtk/nsClipboard.cpp')
-rw-r--r--widget/gtk/nsClipboard.cpp1046
1 files changed, 1046 insertions, 0 deletions
diff --git a/widget/gtk/nsClipboard.cpp b/widget/gtk/nsClipboard.cpp
new file mode 100644
index 000000000..053ae970e
--- /dev/null
+++ b/widget/gtk/nsClipboard.cpp
@@ -0,0 +1,1046 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsArrayUtils.h"
+#include "nsClipboard.h"
+#include "nsSupportsPrimitives.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsXPIDLString.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsIServiceManager.h"
+#include "nsImageToPixbuf.h"
+#include "nsStringStream.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+
+#include "imgIContainer.h"
+
+#include <gtk/gtk.h>
+
+// For manipulation of the X event queue
+#include <X11/Xlib.h>
+#include <gdk/gdkx.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+#include "X11UndefineNone.h"
+
+#include "mozilla/dom/EncodingUtils.h"
+#include "nsIUnicodeDecoder.h"
+
+using mozilla::dom::EncodingUtils;
+using namespace mozilla;
+
+// Callback when someone asks us for the data
+void
+clipboard_get_cb(GtkClipboard *aGtkClipboard,
+ GtkSelectionData *aSelectionData,
+ guint info,
+ gpointer user_data);
+
+// Callback when someone asks us to clear a clipboard
+void
+clipboard_clear_cb(GtkClipboard *aGtkClipboard,
+ gpointer user_data);
+
+static void
+ConvertHTMLtoUCS2 (guchar *data,
+ int32_t dataLength,
+ char16_t **unicodeData,
+ int32_t &outUnicodeLen);
+
+static void
+GetHTMLCharset (guchar * data, int32_t dataLength, nsCString& str);
+
+
+// Our own versions of gtk_clipboard_wait_for_contents and
+// gtk_clipboard_wait_for_text, which don't run the event loop while
+// waiting for the data. This prevents a lot of problems related to
+// dispatching events at unexpected times.
+
+static GtkSelectionData *
+wait_for_contents (GtkClipboard *clipboard, GdkAtom target);
+
+static gchar *
+wait_for_text (GtkClipboard *clipboard);
+
+static GdkFilterReturn
+selection_request_filter (GdkXEvent *gdk_xevent,
+ GdkEvent *event,
+ gpointer data);
+
+nsClipboard::nsClipboard()
+{
+}
+
+nsClipboard::~nsClipboard()
+{
+ // We have to clear clipboard before gdk_display_close() call.
+ // See bug 531580 for details.
+ if (mGlobalTransferable) {
+ gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
+ }
+ if (mSelectionTransferable) {
+ gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
+
+nsresult
+nsClipboard::Init(void)
+{
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (!os)
+ return NS_ERROR_FAILURE;
+
+ os->AddObserver(this, "quit-application", false);
+
+ // A custom event filter to workaround attempting to dereference a null
+ // selection requestor in GTK3 versions before 3.11.3. See bug 1178799.
+#if (MOZ_WIDGET_GTK == 3) && defined(MOZ_X11)
+ if (gtk_check_version(3, 11, 3))
+ gdk_window_add_filter(nullptr, selection_request_filter, nullptr);
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
+{
+ if (strcmp(aTopic, "quit-application") == 0) {
+ // application is going to quit, save clipboard content
+ Store();
+ gdk_window_remove_filter(nullptr, selection_request_filter, nullptr);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsClipboard::Store(void)
+{
+ // Ask the clipboard manager to store the current clipboard content
+ if (mGlobalTransferable) {
+ GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+ gtk_clipboard_store(clipboard);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SetData(nsITransferable *aTransferable,
+ nsIClipboardOwner *aOwner, int32_t aWhichClipboard)
+{
+ // See if we can short cut
+ if ((aWhichClipboard == kGlobalClipboard &&
+ aTransferable == mGlobalTransferable.get() &&
+ aOwner == mGlobalOwner.get()) ||
+ (aWhichClipboard == kSelectionClipboard &&
+ aTransferable == mSelectionTransferable.get() &&
+ aOwner == mSelectionOwner.get())) {
+ return NS_OK;
+ }
+
+ // Clear out the clipboard in order to set the new data
+ EmptyClipboard(aWhichClipboard);
+
+ // List of suported targets
+ GtkTargetList *list = gtk_target_list_new(nullptr, 0);
+
+ // Get the types of supported flavors
+ nsCOMPtr<nsIArray> flavors;
+
+ nsresult rv =
+ aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavors));
+ if (!flavors || NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ // Add all the flavors to this widget's supported type.
+ bool imagesAdded = false;
+ uint32_t count;
+ flavors->GetLength(&count);
+ for (uint32_t i=0; i < count; i++) {
+ nsCOMPtr<nsISupportsCString> flavor = do_QueryElementAt(flavors, i);
+
+ if (flavor) {
+ nsXPIDLCString flavorStr;
+ flavor->ToString(getter_Copies(flavorStr));
+
+ // special case text/unicode since we can handle all of
+ // the string types
+ if (!strcmp(flavorStr, kUnicodeMime)) {
+ gtk_target_list_add(list, gdk_atom_intern("UTF8_STRING", FALSE), 0, 0);
+ gtk_target_list_add(list, gdk_atom_intern("COMPOUND_TEXT", FALSE), 0, 0);
+ gtk_target_list_add(list, gdk_atom_intern("TEXT", FALSE), 0, 0);
+ gtk_target_list_add(list, GDK_SELECTION_TYPE_STRING, 0, 0);
+ continue;
+ }
+
+ if (flavorStr.EqualsLiteral(kNativeImageMime) ||
+ flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kGIFImageMime)) {
+ // don't bother adding image targets twice
+ if (!imagesAdded) {
+ // accept any writable image type
+ gtk_target_list_add_image_targets(list, 0, TRUE);
+ imagesAdded = true;
+ }
+ continue;
+ }
+
+ // Add this to our list of valid targets
+ GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
+ gtk_target_list_add(list, atom, 0, 0);
+ }
+ }
+
+ // Get GTK clipboard (CLIPBOARD or PRIMARY)
+ GtkClipboard *gtkClipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+ gint numTargets;
+ GtkTargetEntry *gtkTargets = gtk_target_table_new_from_list(list, &numTargets);
+
+ // Set getcallback and request to store data after an application exit
+ if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets,
+ clipboard_get_cb, clipboard_clear_cb, this))
+ {
+ // We managed to set-up the clipboard so update internal state
+ // We have to set it now because gtk_clipboard_set_with_data() calls clipboard_clear_cb()
+ // which reset our internal state
+ if (aWhichClipboard == kSelectionClipboard) {
+ mSelectionOwner = aOwner;
+ mSelectionTransferable = aTransferable;
+ }
+ else {
+ mGlobalOwner = aOwner;
+ mGlobalTransferable = aTransferable;
+ gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
+ }
+
+ rv = NS_OK;
+ }
+ else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ gtk_target_table_free(gtkTargets, numTargets);
+ gtk_target_list_unref(list);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsClipboard::GetData(nsITransferable *aTransferable, int32_t aWhichClipboard)
+{
+ if (!aTransferable)
+ return NS_ERROR_FAILURE;
+
+ GtkClipboard *clipboard;
+ clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+ guchar *data = nullptr;
+ gint length = 0;
+ bool foundData = false;
+ nsAutoCString foundFlavor;
+
+ // Get a list of flavors this transferable can import
+ nsCOMPtr<nsIArray> flavors;
+ nsresult rv;
+ rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavors));
+ if (!flavors || NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ uint32_t count;
+ flavors->GetLength(&count);
+ for (uint32_t i=0; i < count; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavors, i);
+
+ if (currentFlavor) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ // Special case text/unicode since we can convert any
+ // string into text/unicode
+ if (!strcmp(flavorStr, kUnicodeMime)) {
+ gchar* new_text = wait_for_text(clipboard);
+ if (new_text) {
+ // Convert utf-8 into our unicode format.
+ NS_ConvertUTF8toUTF16 ucs2string(new_text);
+ data = (guchar *)ToNewUnicode(ucs2string);
+ length = ucs2string.Length() * 2;
+ g_free(new_text);
+ foundData = true;
+ foundFlavor = kUnicodeMime;
+ break;
+ }
+ // If the type was text/unicode and we couldn't get
+ // text off the clipboard, run the next loop
+ // iteration.
+ continue;
+ }
+
+ // For images, we must wrap the data in an nsIInputStream then return instead of break,
+ // because that code below won't help us.
+ if (!strcmp(flavorStr, kJPEGImageMime) ||
+ !strcmp(flavorStr, kJPGImageMime) ||
+ !strcmp(flavorStr, kPNGImageMime) ||
+ !strcmp(flavorStr, kGIFImageMime)) {
+ // Emulate support for image/jpg
+ if (!strcmp(flavorStr, kJPGImageMime)) {
+ flavorStr.Assign(kJPEGImageMime);
+ }
+
+ GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
+
+ GtkSelectionData *selectionData = wait_for_contents(clipboard, atom);
+ if (!selectionData)
+ continue;
+
+ nsCOMPtr<nsIInputStream> byteStream;
+ NS_NewByteInputStream(getter_AddRefs(byteStream),
+ (const char*)gtk_selection_data_get_data(selectionData),
+ gtk_selection_data_get_length(selectionData),
+ NS_ASSIGNMENT_COPY);
+ aTransferable->SetTransferData(flavorStr, byteStream, sizeof(nsIInputStream*));
+ gtk_selection_data_free(selectionData);
+ return NS_OK;
+ }
+
+ // Get the atom for this type and try to request it off
+ // the clipboard.
+ GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
+ GtkSelectionData *selectionData;
+ selectionData = wait_for_contents(clipboard, atom);
+ if (selectionData) {
+ const guchar *clipboardData = gtk_selection_data_get_data(selectionData);
+ length = gtk_selection_data_get_length(selectionData);
+ // Special case text/html since we can convert into UCS2
+ if (!strcmp(flavorStr, kHTMLMime)) {
+ char16_t* htmlBody= nullptr;
+ int32_t htmlBodyLen = 0;
+ // Convert text/html into our unicode format
+ ConvertHTMLtoUCS2(const_cast<guchar*>(clipboardData), length,
+ &htmlBody, htmlBodyLen);
+ // Try next data format?
+ if (!htmlBodyLen)
+ continue;
+ data = (guchar *)htmlBody;
+ length = htmlBodyLen * 2;
+ } else {
+ data = (guchar *)moz_xmalloc(length);
+ if (!data)
+ break;
+ memcpy(data, clipboardData, length);
+ }
+ gtk_selection_data_free(selectionData);
+ foundData = true;
+ foundFlavor = flavorStr;
+ break;
+ }
+ }
+ }
+
+ if (foundData) {
+ nsCOMPtr<nsISupports> wrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(foundFlavor.get(),
+ data, length,
+ getter_AddRefs(wrapper));
+ aTransferable->SetTransferData(foundFlavor.get(),
+ wrapper, length);
+ }
+
+ if (data)
+ free(data);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::EmptyClipboard(int32_t aWhichClipboard)
+{
+ if (aWhichClipboard == kSelectionClipboard) {
+ if (mSelectionOwner) {
+ mSelectionOwner->LosingOwnership(mSelectionTransferable);
+ mSelectionOwner = nullptr;
+ }
+ mSelectionTransferable = nullptr;
+ }
+ else {
+ if (mGlobalOwner) {
+ mGlobalOwner->LosingOwnership(mGlobalTransferable);
+ mGlobalOwner = nullptr;
+ }
+ mGlobalTransferable = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
+ int32_t aWhichClipboard, bool *_retval)
+{
+ if (!aFlavorList || !_retval)
+ return NS_ERROR_NULL_POINTER;
+
+ *_retval = false;
+
+ GtkSelectionData *selection_data =
+ GetTargets(GetSelectionAtom(aWhichClipboard));
+ if (!selection_data)
+ return NS_OK;
+
+ gint n_targets = 0;
+ GdkAtom *targets = nullptr;
+
+ if (!gtk_selection_data_get_targets(selection_data,
+ &targets, &n_targets) ||
+ !n_targets)
+ return NS_OK;
+
+ // Walk through the provided types and try to match it to a
+ // provided type.
+ for (uint32_t i = 0; i < aLength && !*_retval; i++) {
+ // We special case text/unicode here.
+ if (!strcmp(aFlavorList[i], kUnicodeMime) &&
+ gtk_selection_data_targets_include_text(selection_data)) {
+ *_retval = true;
+ break;
+ }
+
+ for (int32_t j = 0; j < n_targets; j++) {
+ gchar *atom_name = gdk_atom_name(targets[j]);
+ if (!atom_name)
+ continue;
+
+ if (!strcmp(atom_name, aFlavorList[i]))
+ *_retval = true;
+
+ // X clipboard supports image/jpeg, but we want to emulate support
+ // for image/jpg as well
+ if (!strcmp(aFlavorList[i], kJPGImageMime) && !strcmp(atom_name, kJPEGImageMime))
+ *_retval = true;
+
+ g_free(atom_name);
+
+ if (*_retval)
+ break;
+ }
+ }
+ gtk_selection_data_free(selection_data);
+ g_free(targets);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsSelectionClipboard(bool *_retval)
+{
+ *_retval = true; // yeah, unix supports the selection clipboard
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsFindClipboard(bool* _retval)
+{
+ *_retval = false;
+ return NS_OK;
+}
+
+/* static */
+GdkAtom
+nsClipboard::GetSelectionAtom(int32_t aWhichClipboard)
+{
+ if (aWhichClipboard == kGlobalClipboard)
+ return GDK_SELECTION_CLIPBOARD;
+
+ return GDK_SELECTION_PRIMARY;
+}
+
+/* static */
+GtkSelectionData *
+nsClipboard::GetTargets(GdkAtom aWhichClipboard)
+{
+ GtkClipboard *clipboard = gtk_clipboard_get(aWhichClipboard);
+ return wait_for_contents(clipboard, gdk_atom_intern("TARGETS", FALSE));
+}
+
+nsITransferable *
+nsClipboard::GetTransferable(int32_t aWhichClipboard)
+{
+ nsITransferable *retval;
+
+ if (aWhichClipboard == kSelectionClipboard)
+ retval = mSelectionTransferable.get();
+ else
+ retval = mGlobalTransferable.get();
+
+ return retval;
+}
+
+void
+nsClipboard::SelectionGetEvent(GtkClipboard *aClipboard,
+ GtkSelectionData *aSelectionData)
+{
+ // Someone has asked us to hand them something. The first thing
+ // that we want to do is see if that something includes text. If
+ // it does, try to give it text/unicode after converting it to
+ // utf-8.
+
+ int32_t whichClipboard;
+
+ // which clipboard?
+ GdkAtom selection = gtk_selection_data_get_selection(aSelectionData);
+ if (selection == GDK_SELECTION_PRIMARY)
+ whichClipboard = kSelectionClipboard;
+ else if (selection == GDK_SELECTION_CLIPBOARD)
+ whichClipboard = kGlobalClipboard;
+ else
+ return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
+
+ nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
+ if (!trans) {
+ // We have nothing to serve
+#ifdef DEBUG_CLIPBOARD
+ printf("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
+ whichClipboard == kSelectionClipboard ? "Selection" : "Global");
+#endif
+ return;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> item;
+ uint32_t len;
+
+ GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
+
+ // Check to see if the selection data includes any of the string
+ // types that we support.
+ if (selectionTarget == gdk_atom_intern ("STRING", FALSE) ||
+ selectionTarget == gdk_atom_intern ("TEXT", FALSE) ||
+ selectionTarget == gdk_atom_intern ("COMPOUND_TEXT", FALSE) ||
+ selectionTarget == gdk_atom_intern ("UTF8_STRING", FALSE)) {
+ // Try to convert our internal type into a text string. Get
+ // the transferable for this clipboard and try to get the
+ // text/unicode type for it.
+ rv = trans->GetTransferData("text/unicode", getter_AddRefs(item),
+ &len);
+ if (!item || NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsISupportsString> wideString;
+ wideString = do_QueryInterface(item);
+ if (!wideString)
+ return;
+
+ nsAutoString ucs2string;
+ wideString->GetData(ucs2string);
+ char *utf8string = ToNewUTF8String(ucs2string);
+ if (!utf8string)
+ return;
+
+ gtk_selection_data_set_text (aSelectionData, utf8string,
+ strlen(utf8string));
+
+ free(utf8string);
+ return;
+ }
+
+ // Check to see if the selection data is an image type
+ if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
+ // Look through our transfer data for the image
+ static const char* const imageMimeTypes[] = {
+ kNativeImageMime, kPNGImageMime, kJPEGImageMime, kJPGImageMime, kGIFImageMime };
+ nsCOMPtr<nsISupports> imageItem;
+ nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive;
+ for (uint32_t i = 0; !ptrPrimitive && i < ArrayLength(imageMimeTypes); i++) {
+ rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem), &len);
+ ptrPrimitive = do_QueryInterface(imageItem);
+ }
+ if (!ptrPrimitive)
+ return;
+
+ nsCOMPtr<nsISupports> primitiveData;
+ ptrPrimitive->GetData(getter_AddRefs(primitiveData));
+ nsCOMPtr<imgIContainer> image(do_QueryInterface(primitiveData));
+ if (!image) // Not getting an image for an image mime type!?
+ return;
+
+ GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
+ if (!pixbuf)
+ return;
+
+ gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
+ g_object_unref(pixbuf);
+ return;
+ }
+
+ // Try to match up the selection data target to something our
+ // transferable provides.
+ gchar *target_name = gdk_atom_name(selectionTarget);
+ if (!target_name)
+ return;
+
+ rv = trans->GetTransferData(target_name, getter_AddRefs(item), &len);
+ // nothing found?
+ if (!item || NS_FAILED(rv)) {
+ g_free(target_name);
+ return;
+ }
+
+ void *primitive_data = nullptr;
+ nsPrimitiveHelpers::CreateDataFromPrimitive(target_name, item,
+ &primitive_data, len);
+
+ if (primitive_data) {
+ // Check to see if the selection data is text/html
+ if (selectionTarget == gdk_atom_intern (kHTMLMime, FALSE)) {
+ /*
+ * "text/html" can be encoded UCS2. It is recommended that
+ * documents transmitted as UCS2 always begin with a ZERO-WIDTH
+ * NON-BREAKING SPACE character (hexadecimal FEFF, also called
+ * Byte Order Mark (BOM)). Adding BOM can help other app to
+ * detect mozilla use UCS2 encoding when copy-paste.
+ */
+ guchar *buffer = (guchar *)
+ moz_xmalloc((len * sizeof(guchar)) + sizeof(char16_t));
+ if (!buffer)
+ return;
+ char16_t prefix = 0xFEFF;
+ memcpy(buffer, &prefix, sizeof(prefix));
+ memcpy(buffer + sizeof(prefix), primitive_data, len);
+ free((guchar *)primitive_data);
+ primitive_data = (guchar *)buffer;
+ len += sizeof(prefix);
+ }
+
+ gtk_selection_data_set(aSelectionData, selectionTarget,
+ 8, /* 8 bits in a unit */
+ (const guchar *)primitive_data, len);
+ free(primitive_data);
+ }
+
+ g_free(target_name);
+
+}
+
+void
+nsClipboard::SelectionClearEvent(GtkClipboard *aGtkClipboard)
+{
+ int32_t whichClipboard;
+
+ // which clipboard?
+ if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
+ whichClipboard = kSelectionClipboard;
+ else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
+ whichClipboard = kGlobalClipboard;
+ else
+ return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
+
+ EmptyClipboard(whichClipboard);
+}
+
+void
+clipboard_get_cb(GtkClipboard *aGtkClipboard,
+ GtkSelectionData *aSelectionData,
+ guint info,
+ gpointer user_data)
+{
+ nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data);
+ aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
+}
+
+void
+clipboard_clear_cb(GtkClipboard *aGtkClipboard,
+ gpointer user_data)
+{
+ nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data);
+ aClipboard->SelectionClearEvent(aGtkClipboard);
+}
+
+/*
+ * when copy-paste, mozilla wants data encoded using UCS2,
+ * other app such as StarOffice use "text/html"(RFC2854).
+ * This function convert data(got from GTK clipboard)
+ * to data mozilla wanted.
+ *
+ * data from GTK clipboard can be 3 forms:
+ * 1. From current mozilla
+ * "text/html", charset = utf-16
+ * 2. From old version mozilla or mozilla-based app
+ * content("body" only), charset = utf-16
+ * 3. From other app who use "text/html" when copy-paste
+ * "text/html", has "charset" info
+ *
+ * data : got from GTK clipboard
+ * dataLength: got from GTK clipboard
+ * body : pass to Mozilla
+ * bodyLength: pass to Mozilla
+ */
+void ConvertHTMLtoUCS2(guchar * data, int32_t dataLength,
+ char16_t** unicodeData, int32_t& outUnicodeLen)
+{
+ nsAutoCString charset;
+ GetHTMLCharset(data, dataLength, charset);// get charset of HTML
+ if (charset.EqualsLiteral("UTF-16")) {//current mozilla
+ outUnicodeLen = (dataLength / 2) - 1;
+ *unicodeData = reinterpret_cast<char16_t*>
+ (moz_xmalloc((outUnicodeLen + sizeof('\0')) *
+ sizeof(char16_t)));
+ if (*unicodeData) {
+ memcpy(*unicodeData, data + sizeof(char16_t),
+ outUnicodeLen * sizeof(char16_t));
+ (*unicodeData)[outUnicodeLen] = '\0';
+ }
+ } else if (charset.EqualsLiteral("UNKNOWN")) {
+ outUnicodeLen = 0;
+ return;
+ } else {
+ // app which use "text/html" to copy&paste
+ nsCOMPtr<nsIUnicodeDecoder> decoder;
+ // get the decoder
+ nsAutoCString encoding;
+ if (!EncodingUtils::FindEncodingForLabelNoReplacement(charset,
+ encoding)) {
+#ifdef DEBUG_CLIPBOARD
+ g_print(" get unicode decoder error\n");
+#endif
+ outUnicodeLen = 0;
+ return;
+ }
+ decoder = EncodingUtils::DecoderForEncoding(encoding);
+ // converting
+ nsresult rv = decoder->GetMaxLength((const char *)data, dataLength,
+ &outUnicodeLen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ outUnicodeLen = 0;
+ return;
+ }
+
+ // |outUnicodeLen| is number of chars
+ if (outUnicodeLen) {
+ *unicodeData = reinterpret_cast<char16_t*>
+ (moz_xmalloc((outUnicodeLen + sizeof('\0')) *
+ sizeof(char16_t)));
+ if (*unicodeData) {
+ int32_t numberTmp = dataLength;
+ decoder->Convert((const char *)data, &numberTmp,
+ *unicodeData, &outUnicodeLen);
+#ifdef DEBUG_CLIPBOARD
+ if (numberTmp != dataLength)
+ printf("didn't consume all the bytes\n");
+#endif
+ // null terminate. Convert() doesn't do it for us
+ (*unicodeData)[outUnicodeLen] = '\0';
+ }
+ } // if valid length
+ }
+}
+
+/*
+ * get "charset" information from clipboard data
+ * return value can be:
+ * 1. "UTF-16": mozilla or "text/html" with "charset=utf-16"
+ * 2. "UNKNOWN": mozilla can't detect what encode it use
+ * 3. other: "text/html" with other charset than utf-16
+ */
+void GetHTMLCharset(guchar * data, int32_t dataLength, nsCString& str)
+{
+ // if detect "FFFE" or "FEFF", assume UTF-16
+ char16_t* beginChar = (char16_t*)data;
+ if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) {
+ str.AssignLiteral("UTF-16");
+ return;
+ }
+ // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
+ const nsDependentCString htmlStr((const char *)data, dataLength);
+ nsACString::const_iterator start, end;
+ htmlStr.BeginReading(start);
+ htmlStr.EndReading(end);
+ nsACString::const_iterator valueStart(start), valueEnd(start);
+
+ if (CaseInsensitiveFindInReadable(
+ NS_LITERAL_CSTRING("CONTENT=\"text/html;"),
+ start, end)) {
+ start = end;
+ htmlStr.EndReading(end);
+
+ if (CaseInsensitiveFindInReadable(
+ NS_LITERAL_CSTRING("charset="),
+ start, end)) {
+ valueStart = end;
+ start = end;
+ htmlStr.EndReading(end);
+
+ if (FindCharInReadable('"', start, end))
+ valueEnd = start;
+ }
+ }
+ // find "charset" in HTML
+ if (valueStart != valueEnd) {
+ str = Substring(valueStart, valueEnd);
+ ToUpperCase(str);
+#ifdef DEBUG_CLIPBOARD
+ printf("Charset of HTML = %s\n", charsetUpperStr.get());
+#endif
+ return;
+ }
+ str.AssignLiteral("UNKNOWN");
+}
+
+static void
+DispatchSelectionNotifyEvent(GtkWidget *widget, XEvent *xevent)
+{
+ GdkEvent event;
+ event.selection.type = GDK_SELECTION_NOTIFY;
+ event.selection.window = gtk_widget_get_window(widget);
+ event.selection.selection = gdk_x11_xatom_to_atom(xevent->xselection.selection);
+ event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target);
+ event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property);
+ event.selection.time = xevent->xselection.time;
+
+ gtk_widget_event(widget, &event);
+}
+
+static void
+DispatchPropertyNotifyEvent(GtkWidget *widget, XEvent *xevent)
+{
+ GdkWindow *window = gtk_widget_get_window(widget);
+ if ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK) {
+ GdkEvent event;
+ event.property.type = GDK_PROPERTY_NOTIFY;
+ event.property.window = window;
+ event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom);
+ event.property.time = xevent->xproperty.time;
+ event.property.state = xevent->xproperty.state;
+
+ gtk_widget_event(widget, &event);
+ }
+}
+
+struct checkEventContext
+{
+ GtkWidget *cbWidget;
+ Atom selAtom;
+};
+
+static Bool
+checkEventProc(Display *display, XEvent *event, XPointer arg)
+{
+ checkEventContext *context = (checkEventContext *) arg;
+
+ if (event->xany.type == SelectionNotify ||
+ (event->xany.type == PropertyNotify &&
+ event->xproperty.atom == context->selAtom)) {
+
+ GdkWindow *cbWindow =
+ gdk_x11_window_lookup_for_display(gdk_x11_lookup_xdisplay(display),
+ event->xany.window);
+ if (cbWindow) {
+ GtkWidget *cbWidget = nullptr;
+ gdk_window_get_user_data(cbWindow, (gpointer *)&cbWidget);
+ if (cbWidget && GTK_IS_WIDGET(cbWidget)) {
+ context->cbWidget = cbWidget;
+ return True;
+ }
+ }
+ }
+
+ return False;
+}
+
+// Idle timeout for receiving selection and property notify events (microsec)
+static const int kClipboardTimeout = 500000;
+
+static gchar* CopyRetrievedData(const gchar *aData)
+{
+ return g_strdup(aData);
+}
+
+static GtkSelectionData* CopyRetrievedData(GtkSelectionData *aData)
+{
+ // A negative length indicates that retrieving the data failed.
+ return gtk_selection_data_get_length(aData) >= 0 ?
+ gtk_selection_data_copy(aData) : nullptr;
+}
+
+class RetrievalContext {
+ ~RetrievalContext()
+ {
+ MOZ_ASSERT(!mData, "Wait() wasn't called");
+ }
+
+public:
+ NS_INLINE_DECL_REFCOUNTING(RetrievalContext)
+ enum State { INITIAL, COMPLETED, TIMED_OUT };
+
+ RetrievalContext() : mState(INITIAL), mData(nullptr) {}
+
+ /**
+ * Call this when data has been retrieved.
+ */
+ template <class T> void Complete(T *aData)
+ {
+ if (mState == INITIAL) {
+ mState = COMPLETED;
+ mData = CopyRetrievedData(aData);
+ } else {
+ // Already timed out
+ MOZ_ASSERT(mState == TIMED_OUT);
+ }
+ }
+
+ /**
+ * Spins X event loop until timing out or being completed. Returns
+ * null if we time out, otherwise returns the completed data (passing
+ * ownership to caller).
+ */
+ void *Wait();
+
+protected:
+ State mState;
+ void* mData;
+};
+
+void *
+RetrievalContext::Wait()
+{
+ if (mState == COMPLETED) { // the request completed synchronously
+ void *data = mData;
+ mData = nullptr;
+ return data;
+ }
+
+ GdkDisplay *gdkDisplay = gdk_display_get_default();
+ if (GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ Display *xDisplay = GDK_DISPLAY_XDISPLAY(gdkDisplay);
+ checkEventContext context;
+ context.cbWidget = nullptr;
+ context.selAtom = gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION",
+ FALSE));
+
+ // Send X events which are relevant to the ongoing selection retrieval
+ // to the clipboard widget. Wait until either the operation completes, or
+ // we hit our timeout. All other X events remain queued.
+
+ int select_result;
+
+ int cnumber = ConnectionNumber(xDisplay);
+ fd_set select_set;
+ FD_ZERO(&select_set);
+ FD_SET(cnumber, &select_set);
+ ++cnumber;
+ TimeStamp start = TimeStamp::Now();
+
+ do {
+ XEvent xevent;
+
+ while (XCheckIfEvent(xDisplay, &xevent, checkEventProc,
+ (XPointer) &context)) {
+
+ if (xevent.xany.type == SelectionNotify)
+ DispatchSelectionNotifyEvent(context.cbWidget, &xevent);
+ else
+ DispatchPropertyNotifyEvent(context.cbWidget, &xevent);
+
+ if (mState == COMPLETED) {
+ void *data = mData;
+ mData = nullptr;
+ return data;
+ }
+ }
+
+ TimeStamp now = TimeStamp::Now();
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = std::max<int32_t>(0,
+ kClipboardTimeout - (now - start).ToMicroseconds());
+ select_result = select(cnumber, &select_set, nullptr, nullptr, &tv);
+ } while (select_result == 1 ||
+ (select_result == -1 && errno == EINTR));
+ }
+#ifdef DEBUG_CLIPBOARD
+ printf("exceeded clipboard timeout\n");
+#endif
+ mState = TIMED_OUT;
+ return nullptr;
+}
+
+static void
+clipboard_contents_received(GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ gpointer data)
+{
+ RetrievalContext *context = static_cast<RetrievalContext*>(data);
+ context->Complete(selection_data);
+ context->Release();
+}
+
+static GtkSelectionData *
+wait_for_contents(GtkClipboard *clipboard, GdkAtom target)
+{
+ RefPtr<RetrievalContext> context = new RetrievalContext();
+ // Balanced by Release in clipboard_contents_received
+ context.get()->AddRef();
+ gtk_clipboard_request_contents(clipboard, target,
+ clipboard_contents_received,
+ context.get());
+ return static_cast<GtkSelectionData*>(context->Wait());
+}
+
+static void
+clipboard_text_received(GtkClipboard *clipboard,
+ const gchar *text,
+ gpointer data)
+{
+ RetrievalContext *context = static_cast<RetrievalContext*>(data);
+ context->Complete(text);
+ context->Release();
+}
+
+static gchar *
+wait_for_text(GtkClipboard *clipboard)
+{
+ RefPtr<RetrievalContext> context = new RetrievalContext();
+ // Balanced by Release in clipboard_text_received
+ context.get()->AddRef();
+ gtk_clipboard_request_text(clipboard, clipboard_text_received, context.get());
+ return static_cast<gchar*>(context->Wait());
+}
+
+static GdkFilterReturn
+selection_request_filter(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data)
+{
+ XEvent *xevent = static_cast<XEvent*>(gdk_xevent);
+ if (xevent->xany.type == SelectionRequest) {
+ if (xevent->xselectionrequest.requestor == X11None)
+ return GDK_FILTER_REMOVE;
+
+ GdkDisplay *display = gdk_x11_lookup_xdisplay(
+ xevent->xselectionrequest.display);
+ if (!display)
+ return GDK_FILTER_REMOVE;
+
+ GdkWindow *window = gdk_x11_window_foreign_new_for_display(display,
+ xevent->xselectionrequest.requestor);
+ if (!window)
+ return GDK_FILTER_REMOVE;
+
+ g_object_unref(window);
+ }
+ return GDK_FILTER_CONTINUE;
+}