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