widget/gtk/nsClipboard.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
     2 /* vim:expandtab:shiftwidth=4:tabstop=4:
     3  */
     4 /* This Source Code Form is subject to the terms of the Mozilla Public
     5  * License, v. 2.0. If a copy of the MPL was not distributed with this
     6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     8 #include "mozilla/ArrayUtils.h"
    10 #include "nsClipboard.h"
    11 #include "nsSupportsPrimitives.h"
    12 #include "nsString.h"
    13 #include "nsReadableUtils.h"
    14 #include "nsXPIDLString.h"
    15 #include "nsPrimitiveHelpers.h"
    16 #include "nsICharsetConverterManager.h"
    17 #include "nsIServiceManager.h"
    18 #include "nsImageToPixbuf.h"
    19 #include "nsStringStream.h"
    20 #include "nsIObserverService.h"
    21 #include "mozilla/Services.h"
    22 #include "mozilla/RefPtr.h"
    23 #include "mozilla/TimeStamp.h"
    25 #include "imgIContainer.h"
    27 #include <gtk/gtk.h>
    29 // For manipulation of the X event queue
    30 #include <X11/Xlib.h>
    31 #include <gdk/gdkx.h>
    32 #include <sys/time.h>
    33 #include <sys/types.h>
    34 #include <errno.h>
    35 #include <unistd.h>
    37 using namespace mozilla;
    39 // Callback when someone asks us for the data
    40 void
    41 clipboard_get_cb(GtkClipboard *aGtkClipboard,
    42                  GtkSelectionData *aSelectionData,
    43                  guint info,
    44                  gpointer user_data);
    46 // Callback when someone asks us to clear a clipboard
    47 void
    48 clipboard_clear_cb(GtkClipboard *aGtkClipboard,
    49                    gpointer user_data);
    51 static void
    52 ConvertHTMLtoUCS2          (guchar             *data,
    53                             int32_t             dataLength,
    54                             char16_t         **unicodeData,
    55                             int32_t            &outUnicodeLen);
    57 static void
    58 GetHTMLCharset             (guchar * data, int32_t dataLength, nsCString& str);
    61 // Our own versions of gtk_clipboard_wait_for_contents and
    62 // gtk_clipboard_wait_for_text, which don't run the event loop while
    63 // waiting for the data.  This prevents a lot of problems related to
    64 // dispatching events at unexpected times.
    66 static GtkSelectionData *
    67 wait_for_contents          (GtkClipboard *clipboard, GdkAtom target);
    69 static gchar *
    70 wait_for_text              (GtkClipboard *clipboard);
    72 nsClipboard::nsClipboard()
    73 {
    74 }
    76 nsClipboard::~nsClipboard()
    77 {
    78     // We have to clear clipboard before gdk_display_close() call.
    79     // See bug 531580 for details.
    80     if (mGlobalTransferable) {
    81         gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
    82     }
    83     if (mSelectionTransferable) {
    84         gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
    85     }
    86 }
    88 NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
    90 nsresult
    91 nsClipboard::Init(void)
    92 {
    93     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
    94     if (!os)
    95       return NS_ERROR_FAILURE;
    97     os->AddObserver(this, "quit-application", false);
    99     return NS_OK;
   100 }
   102 NS_IMETHODIMP
   103 nsClipboard::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
   104 {
   105     if (strcmp(aTopic, "quit-application") == 0) {
   106         // application is going to quit, save clipboard content
   107         Store();
   108     }
   109     return NS_OK;
   110 }
   112 nsresult
   113 nsClipboard::Store(void)
   114 {
   115     // Ask the clipboard manager to store the current clipboard content
   116     if (mGlobalTransferable) {
   117         GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
   118         gtk_clipboard_store(clipboard);
   119     }
   120     return NS_OK;
   121 }
   123 NS_IMETHODIMP
   124 nsClipboard::SetData(nsITransferable *aTransferable,
   125                      nsIClipboardOwner *aOwner, int32_t aWhichClipboard)
   126 {
   127     // See if we can short cut
   128     if ((aWhichClipboard == kGlobalClipboard &&
   129          aTransferable == mGlobalTransferable.get() &&
   130          aOwner == mGlobalOwner.get()) ||
   131         (aWhichClipboard == kSelectionClipboard &&
   132          aTransferable == mSelectionTransferable.get() &&
   133          aOwner == mSelectionOwner.get())) {
   134         return NS_OK;
   135     }
   137     nsresult rv;
   138     if (!mPrivacyHandler) {
   139         rv = NS_NewClipboardPrivacyHandler(getter_AddRefs(mPrivacyHandler));
   140         NS_ENSURE_SUCCESS(rv, rv);
   141     }
   142     rv = mPrivacyHandler->PrepareDataForClipboard(aTransferable);
   143     NS_ENSURE_SUCCESS(rv, rv);
   145     // Clear out the clipboard in order to set the new data
   146     EmptyClipboard(aWhichClipboard);
   148     // List of suported targets
   149     GtkTargetList *list = gtk_target_list_new(nullptr, 0);
   151     // Get the types of supported flavors
   152     nsCOMPtr<nsISupportsArray> flavors;
   154     rv = aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavors));
   155     if (!flavors || NS_FAILED(rv))
   156         return NS_ERROR_FAILURE;
   158     // Add all the flavors to this widget's supported type.
   159     bool imagesAdded = false;
   160     uint32_t count;
   161     flavors->Count(&count);
   162     for (uint32_t i=0; i < count; i++) {
   163         nsCOMPtr<nsISupports> tastesLike;
   164         flavors->GetElementAt(i, getter_AddRefs(tastesLike));
   165         nsCOMPtr<nsISupportsCString> flavor = do_QueryInterface(tastesLike);
   167         if (flavor) {
   168             nsXPIDLCString flavorStr;
   169             flavor->ToString(getter_Copies(flavorStr));
   171             // special case text/unicode since we can handle all of
   172             // the string types
   173             if (!strcmp(flavorStr, kUnicodeMime)) {
   174                 gtk_target_list_add(list, gdk_atom_intern("UTF8_STRING", FALSE), 0, 0);
   175                 gtk_target_list_add(list, gdk_atom_intern("COMPOUND_TEXT", FALSE), 0, 0);
   176                 gtk_target_list_add(list, gdk_atom_intern("TEXT", FALSE), 0, 0);
   177                 gtk_target_list_add(list, GDK_SELECTION_TYPE_STRING, 0, 0);
   178                 continue;
   179             }
   181             if (flavorStr.EqualsLiteral(kNativeImageMime) ||
   182                 flavorStr.EqualsLiteral(kPNGImageMime) ||
   183                 flavorStr.EqualsLiteral(kJPEGImageMime) ||
   184                 flavorStr.EqualsLiteral(kJPGImageMime) ||
   185                 flavorStr.EqualsLiteral(kGIFImageMime)) {
   186                 // don't bother adding image targets twice
   187                 if (!imagesAdded) {
   188                     // accept any writable image type
   189                     gtk_target_list_add_image_targets(list, 0, TRUE);
   190                     imagesAdded = true;
   191                 }
   192                 continue;
   193             }
   195             // Add this to our list of valid targets
   196             GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
   197             gtk_target_list_add(list, atom, 0, 0);
   198         }
   199     }
   201     // Get GTK clipboard (CLIPBOARD or PRIMARY)
   202     GtkClipboard *gtkClipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
   204     gint numTargets;
   205     GtkTargetEntry *gtkTargets = gtk_target_table_new_from_list(list, &numTargets);
   207     // Set getcallback and request to store data after an application exit
   208     if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets, 
   209                                     clipboard_get_cb, clipboard_clear_cb, this))
   210     {
   211         // We managed to set-up the clipboard so update internal state
   212         // We have to set it now because gtk_clipboard_set_with_data() calls clipboard_clear_cb()
   213         // which reset our internal state 
   214         if (aWhichClipboard == kSelectionClipboard) {
   215             mSelectionOwner = aOwner;
   216             mSelectionTransferable = aTransferable;
   217         }
   218         else {
   219             mGlobalOwner = aOwner;
   220             mGlobalTransferable = aTransferable;
   221             gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
   222         }
   224         rv = NS_OK;
   225     }
   226     else {  
   227         rv = NS_ERROR_FAILURE;
   228     }
   230     gtk_target_table_free(gtkTargets, numTargets);
   231     gtk_target_list_unref(list);
   233     return rv;
   234 }
   236 NS_IMETHODIMP
   237 nsClipboard::GetData(nsITransferable *aTransferable, int32_t aWhichClipboard)
   238 {
   239     if (!aTransferable)
   240         return NS_ERROR_FAILURE;
   242     GtkClipboard *clipboard;
   243     clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
   245     guchar        *data = nullptr;
   246     gint           length = 0;
   247     bool           foundData = false;
   248     nsAutoCString  foundFlavor;
   250     // Get a list of flavors this transferable can import
   251     nsCOMPtr<nsISupportsArray> flavors;
   252     nsresult rv;
   253     rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavors));
   254     if (!flavors || NS_FAILED(rv))
   255         return NS_ERROR_FAILURE;
   257     uint32_t count;
   258     flavors->Count(&count);
   259     for (uint32_t i=0; i < count; i++) {
   260         nsCOMPtr<nsISupports> genericFlavor;
   261         flavors->GetElementAt(i, getter_AddRefs(genericFlavor));
   263         nsCOMPtr<nsISupportsCString> currentFlavor;
   264         currentFlavor = do_QueryInterface(genericFlavor);
   266         if (currentFlavor) {
   267             nsXPIDLCString flavorStr;
   268             currentFlavor->ToString(getter_Copies(flavorStr));
   270             // Special case text/unicode since we can convert any
   271             // string into text/unicode
   272             if (!strcmp(flavorStr, kUnicodeMime)) {
   273                 gchar* new_text = wait_for_text(clipboard);
   274                 if (new_text) {
   275                     // Convert utf-8 into our unicode format.
   276                     NS_ConvertUTF8toUTF16 ucs2string(new_text);
   277                     data = (guchar *)ToNewUnicode(ucs2string);
   278                     length = ucs2string.Length() * 2;
   279                     g_free(new_text);
   280                     foundData = true;
   281                     foundFlavor = kUnicodeMime;
   282                     break;
   283                 }
   284                 // If the type was text/unicode and we couldn't get
   285                 // text off the clipboard, run the next loop
   286                 // iteration.
   287                 continue;
   288             }
   290             // For images, we must wrap the data in an nsIInputStream then return instead of break,
   291             // because that code below won't help us.
   292             if (!strcmp(flavorStr, kJPEGImageMime) ||
   293                 !strcmp(flavorStr, kJPGImageMime) ||
   294                 !strcmp(flavorStr, kPNGImageMime) ||
   295                 !strcmp(flavorStr, kGIFImageMime)) {
   296                 // Emulate support for image/jpg
   297                 if (!strcmp(flavorStr, kJPGImageMime)) {
   298                     flavorStr.Assign(kJPEGImageMime);
   299                 }
   301                 GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
   303                 GtkSelectionData *selectionData = wait_for_contents(clipboard, atom);
   304                 if (!selectionData)
   305                     continue;
   307                 nsCOMPtr<nsIInputStream> byteStream;
   308                 NS_NewByteInputStream(getter_AddRefs(byteStream), 
   309                                       (const char*)gtk_selection_data_get_data(selectionData),
   310                                       gtk_selection_data_get_length(selectionData), 
   311                                       NS_ASSIGNMENT_COPY);
   312                 aTransferable->SetTransferData(flavorStr, byteStream, sizeof(nsIInputStream*));
   313                 gtk_selection_data_free(selectionData);
   314                 return NS_OK;
   315             }
   317             // Get the atom for this type and try to request it off
   318             // the clipboard.
   319             GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
   320             GtkSelectionData *selectionData;
   321             selectionData = wait_for_contents(clipboard, atom);
   322             if (selectionData) {
   323                 const guchar *clipboardData = gtk_selection_data_get_data(selectionData);
   324                 length = gtk_selection_data_get_length(selectionData);
   325                 // Special case text/html since we can convert into UCS2
   326                 if (!strcmp(flavorStr, kHTMLMime)) {
   327                     char16_t* htmlBody= nullptr;
   328                     int32_t htmlBodyLen = 0;
   329                     // Convert text/html into our unicode format
   330                     ConvertHTMLtoUCS2(const_cast<guchar*>(clipboardData), length,
   331                                       &htmlBody, htmlBodyLen);
   332                     // Try next data format?
   333                     if (!htmlBodyLen)
   334                         continue;
   335                     data = (guchar *)htmlBody;
   336                     length = htmlBodyLen * 2;
   337                 } else {
   338                     data = (guchar *)nsMemory::Alloc(length);
   339                     if (!data)
   340                         break;
   341                     memcpy(data, clipboardData, length);
   342                 }
   343                 gtk_selection_data_free(selectionData);
   344                 foundData = true;
   345                 foundFlavor = flavorStr;
   346                 break;
   347             }
   348         }
   349     }
   351     if (foundData) {
   352         nsCOMPtr<nsISupports> wrapper;
   353         nsPrimitiveHelpers::CreatePrimitiveForData(foundFlavor.get(),
   354                                                    data, length,
   355                                                    getter_AddRefs(wrapper));
   356         aTransferable->SetTransferData(foundFlavor.get(),
   357                                        wrapper, length);
   358     }
   360     if (data)
   361         nsMemory::Free(data);
   363     return NS_OK;
   364 }
   366 NS_IMETHODIMP
   367 nsClipboard::EmptyClipboard(int32_t aWhichClipboard)
   368 {
   369     if (aWhichClipboard == kSelectionClipboard) {
   370         if (mSelectionOwner) {
   371             mSelectionOwner->LosingOwnership(mSelectionTransferable);
   372             mSelectionOwner = nullptr;
   373         }
   374         mSelectionTransferable = nullptr;
   375     }
   376     else {
   377         if (mGlobalOwner) {
   378             mGlobalOwner->LosingOwnership(mGlobalTransferable);
   379             mGlobalOwner = nullptr;
   380         }
   381         mGlobalTransferable = nullptr;
   382     }
   384     return NS_OK;
   385 }
   387 NS_IMETHODIMP
   388 nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
   389                                     int32_t aWhichClipboard, bool *_retval)
   390 {
   391     if (!aFlavorList || !_retval)
   392         return NS_ERROR_NULL_POINTER;
   394     *_retval = false;
   396     GtkSelectionData *selection_data =
   397         GetTargets(GetSelectionAtom(aWhichClipboard));
   398     if (!selection_data)
   399         return NS_OK;
   401     gint n_targets = 0;
   402     GdkAtom *targets = nullptr;
   404     if (!gtk_selection_data_get_targets(selection_data, 
   405                                         &targets, &n_targets) ||
   406         !n_targets)
   407         return NS_OK;
   409     // Walk through the provided types and try to match it to a
   410     // provided type.
   411     for (uint32_t i = 0; i < aLength && !*_retval; i++) {
   412         // We special case text/unicode here.
   413         if (!strcmp(aFlavorList[i], kUnicodeMime) && 
   414             gtk_selection_data_targets_include_text(selection_data)) {
   415             *_retval = true;
   416             break;
   417         }
   419         for (int32_t j = 0; j < n_targets; j++) {
   420             gchar *atom_name = gdk_atom_name(targets[j]);
   421             if (!atom_name)
   422                 continue;
   424             if (!strcmp(atom_name, aFlavorList[i]))
   425                 *_retval = true;
   427             // X clipboard supports image/jpeg, but we want to emulate support
   428             // for image/jpg as well
   429             if (!strcmp(aFlavorList[i], kJPGImageMime) && !strcmp(atom_name, kJPEGImageMime))
   430                 *_retval = true;
   432             g_free(atom_name);
   434             if (*_retval)
   435                 break;
   436         }
   437     }
   438     gtk_selection_data_free(selection_data);
   439     g_free(targets);
   441     return NS_OK;
   442 }
   444 NS_IMETHODIMP
   445 nsClipboard::SupportsSelectionClipboard(bool *_retval)
   446 {
   447     *_retval = true; // yeah, unix supports the selection clipboard
   448     return NS_OK;
   449 }
   451 NS_IMETHODIMP
   452 nsClipboard::SupportsFindClipboard(bool* _retval)
   453 {
   454   *_retval = false;
   455   return NS_OK;
   456 }
   458 /* static */
   459 GdkAtom
   460 nsClipboard::GetSelectionAtom(int32_t aWhichClipboard)
   461 {
   462     if (aWhichClipboard == kGlobalClipboard)
   463         return GDK_SELECTION_CLIPBOARD;
   465     return GDK_SELECTION_PRIMARY;
   466 }
   468 /* static */
   469 GtkSelectionData *
   470 nsClipboard::GetTargets(GdkAtom aWhichClipboard)
   471 {
   472     GtkClipboard *clipboard = gtk_clipboard_get(aWhichClipboard);
   473     return wait_for_contents(clipboard, gdk_atom_intern("TARGETS", FALSE));
   474 }
   476 nsITransferable *
   477 nsClipboard::GetTransferable(int32_t aWhichClipboard)
   478 {
   479     nsITransferable *retval;
   481     if (aWhichClipboard == kSelectionClipboard)
   482         retval = mSelectionTransferable.get();
   483     else
   484         retval = mGlobalTransferable.get();
   486     return retval;
   487 }
   489 void
   490 nsClipboard::SelectionGetEvent(GtkClipboard     *aClipboard,
   491                                GtkSelectionData *aSelectionData)
   492 {
   493     // Someone has asked us to hand them something.  The first thing
   494     // that we want to do is see if that something includes text.  If
   495     // it does, try to give it text/unicode after converting it to
   496     // utf-8.
   498     int32_t whichClipboard;
   500     // which clipboard?
   501     GdkAtom selection = gtk_selection_data_get_selection(aSelectionData);
   502     if (selection == GDK_SELECTION_PRIMARY)
   503         whichClipboard = kSelectionClipboard;
   504     else if (selection == GDK_SELECTION_CLIPBOARD)
   505         whichClipboard = kGlobalClipboard;
   506     else
   507         return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
   509     nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
   510     if (!trans) {
   511       // We have nothing to serve
   512 #ifdef DEBUG_CLIPBOARD
   513       printf("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
   514              whichClipboard == kSelectionClipboard ? "Selection" : "Global");
   515 #endif
   516       return;
   517     }
   519     nsresult rv;
   520     nsCOMPtr<nsISupports> item;
   521     uint32_t len;
   524     GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
   526     // Check to see if the selection data includes any of the string
   527     // types that we support.
   528     if (selectionTarget == gdk_atom_intern ("STRING", FALSE) ||
   529         selectionTarget == gdk_atom_intern ("TEXT", FALSE) ||
   530         selectionTarget == gdk_atom_intern ("COMPOUND_TEXT", FALSE) ||
   531         selectionTarget == gdk_atom_intern ("UTF8_STRING", FALSE)) {
   532         // Try to convert our internal type into a text string.  Get
   533         // the transferable for this clipboard and try to get the
   534         // text/unicode type for it.
   535         rv = trans->GetTransferData("text/unicode", getter_AddRefs(item),
   536                                     &len);
   537         if (!item || NS_FAILED(rv))
   538             return;
   540         nsCOMPtr<nsISupportsString> wideString;
   541         wideString = do_QueryInterface(item);
   542         if (!wideString)
   543             return;
   545         nsAutoString ucs2string;
   546         wideString->GetData(ucs2string);
   547         char *utf8string = ToNewUTF8String(ucs2string);
   548         if (!utf8string)
   549             return;
   551         gtk_selection_data_set_text (aSelectionData, utf8string,
   552                                      strlen(utf8string));
   554         nsMemory::Free(utf8string);
   555         return;
   556     }
   558     // Check to see if the selection data is an image type
   559     if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
   560         // Look through our transfer data for the image
   561         static const char* const imageMimeTypes[] = {
   562             kNativeImageMime, kPNGImageMime, kJPEGImageMime, kJPGImageMime, kGIFImageMime };
   563         nsCOMPtr<nsISupports> item;
   564         uint32_t len;
   565         nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive;
   566         for (uint32_t i = 0; !ptrPrimitive && i < ArrayLength(imageMimeTypes); i++) {
   567             rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(item), &len);
   568             ptrPrimitive = do_QueryInterface(item);
   569         }
   570         if (!ptrPrimitive)
   571             return;
   573         nsCOMPtr<nsISupports> primitiveData;
   574         ptrPrimitive->GetData(getter_AddRefs(primitiveData));
   575         nsCOMPtr<imgIContainer> image(do_QueryInterface(primitiveData));
   576         if (!image) // Not getting an image for an image mime type!?
   577             return;
   579         GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
   580         if (!pixbuf)
   581             return;
   583         gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
   584         g_object_unref(pixbuf);
   585         return;
   586     }
   588     // Try to match up the selection data target to something our
   589     // transferable provides.
   590     gchar *target_name = gdk_atom_name(selectionTarget);
   591     if (!target_name)
   592         return;
   594     rv = trans->GetTransferData(target_name, getter_AddRefs(item), &len);
   595     // nothing found?
   596     if (!item || NS_FAILED(rv)) {
   597         g_free(target_name);
   598         return;
   599     }
   601     void *primitive_data = nullptr;
   602     nsPrimitiveHelpers::CreateDataFromPrimitive(target_name, item,
   603                                                 &primitive_data, len);
   605     if (primitive_data) {
   606         // Check to see if the selection data is text/html
   607         if (selectionTarget == gdk_atom_intern (kHTMLMime, FALSE)) {
   608             /*
   609              * "text/html" can be encoded UCS2. It is recommended that
   610              * documents transmitted as UCS2 always begin with a ZERO-WIDTH
   611              * NON-BREAKING SPACE character (hexadecimal FEFF, also called
   612              * Byte Order Mark (BOM)). Adding BOM can help other app to
   613              * detect mozilla use UCS2 encoding when copy-paste.
   614              */
   615             guchar *buffer = (guchar *)
   616                     nsMemory::Alloc((len * sizeof(guchar)) + sizeof(char16_t));
   617             if (!buffer)
   618                 return;
   619             char16_t prefix = 0xFEFF;
   620             memcpy(buffer, &prefix, sizeof(prefix));
   621             memcpy(buffer + sizeof(prefix), primitive_data, len);
   622             nsMemory::Free((guchar *)primitive_data);
   623             primitive_data = (guchar *)buffer;
   624             len += sizeof(prefix);
   625         }
   627         gtk_selection_data_set(aSelectionData, selectionTarget,
   628                                8, /* 8 bits in a unit */
   629                                (const guchar *)primitive_data, len);
   630         nsMemory::Free(primitive_data);
   631     }
   633     g_free(target_name);
   635 }
   637 void
   638 nsClipboard::SelectionClearEvent(GtkClipboard *aGtkClipboard)
   639 {
   640     int32_t whichClipboard;
   642     // which clipboard?
   643     if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
   644         whichClipboard = kSelectionClipboard;
   645     else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
   646         whichClipboard = kGlobalClipboard;
   647     else
   648         return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
   650     EmptyClipboard(whichClipboard);
   651 }
   653 void
   654 clipboard_get_cb(GtkClipboard *aGtkClipboard,
   655                  GtkSelectionData *aSelectionData,
   656                  guint info,
   657                  gpointer user_data)
   658 {
   659     nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data);
   660     aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
   661 }
   663 void
   664 clipboard_clear_cb(GtkClipboard *aGtkClipboard,
   665                    gpointer user_data)
   666 {
   667     nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data);
   668     aClipboard->SelectionClearEvent(aGtkClipboard);
   669 }
   671 /*
   672  * when copy-paste, mozilla wants data encoded using UCS2,
   673  * other app such as StarOffice use "text/html"(RFC2854).
   674  * This function convert data(got from GTK clipboard)
   675  * to data mozilla wanted.
   676  *
   677  * data from GTK clipboard can be 3 forms:
   678  *  1. From current mozilla
   679  *     "text/html", charset = utf-16
   680  *  2. From old version mozilla or mozilla-based app
   681  *     content("body" only), charset = utf-16
   682  *  3. From other app who use "text/html" when copy-paste
   683  *     "text/html", has "charset" info
   684  *
   685  * data      : got from GTK clipboard
   686  * dataLength: got from GTK clipboard
   687  * body      : pass to Mozilla
   688  * bodyLength: pass to Mozilla
   689  */
   690 void ConvertHTMLtoUCS2(guchar * data, int32_t dataLength,
   691                        char16_t** unicodeData, int32_t& outUnicodeLen)
   692 {
   693     nsAutoCString charset;
   694     GetHTMLCharset(data, dataLength, charset);// get charset of HTML
   695     if (charset.EqualsLiteral("UTF-16")) {//current mozilla
   696         outUnicodeLen = (dataLength / 2) - 1;
   697         *unicodeData = reinterpret_cast<char16_t*>
   698                                        (nsMemory::Alloc((outUnicodeLen + sizeof('\0')) *
   699                        sizeof(char16_t)));
   700         if (*unicodeData) {
   701             memcpy(*unicodeData, data + sizeof(char16_t),
   702                    outUnicodeLen * sizeof(char16_t));
   703             (*unicodeData)[outUnicodeLen] = '\0';
   704         }
   705     } else if (charset.EqualsLiteral("UNKNOWN")) {
   706         outUnicodeLen = 0;
   707         return;
   708     } else {
   709         // app which use "text/html" to copy&paste
   710         nsCOMPtr<nsIUnicodeDecoder> decoder;
   711         nsresult rv;
   712         // get the decoder
   713         nsCOMPtr<nsICharsetConverterManager> ccm =
   714             do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
   715         if (NS_FAILED(rv)) {
   716 #ifdef DEBUG_CLIPBOARD
   717             g_print("        can't get CHARSET CONVERTER MANAGER service\n");
   718 #endif
   719             outUnicodeLen = 0;
   720             return;
   721         }
   722         rv = ccm->GetUnicodeDecoder(charset.get(), getter_AddRefs(decoder));
   723         if (NS_FAILED(rv)) {
   724 #ifdef DEBUG_CLIPBOARD
   725             g_print("        get unicode decoder error\n");
   726 #endif
   727             outUnicodeLen = 0;
   728             return;
   729         }
   730         // converting
   731         decoder->GetMaxLength((const char *)data, dataLength, &outUnicodeLen);
   732         // |outUnicodeLen| is number of chars
   733         if (outUnicodeLen) {
   734             *unicodeData = reinterpret_cast<char16_t*>
   735                                            (nsMemory::Alloc((outUnicodeLen + sizeof('\0')) *
   736                            sizeof(char16_t)));
   737             if (*unicodeData) {
   738                 int32_t numberTmp = dataLength;
   739                 decoder->Convert((const char *)data, &numberTmp,
   740                                  *unicodeData, &outUnicodeLen);
   741 #ifdef DEBUG_CLIPBOARD
   742                 if (numberTmp != dataLength)
   743                     printf("didn't consume all the bytes\n");
   744 #endif
   745                 // null terminate. Convert() doesn't do it for us
   746                 (*unicodeData)[outUnicodeLen] = '\0';
   747             }
   748         } // if valid length
   749     }
   750 }
   752 /*
   753  * get "charset" information from clipboard data
   754  * return value can be:
   755  *  1. "UTF-16":      mozilla or "text/html" with "charset=utf-16"
   756  *  2. "UNKNOWN":     mozilla can't detect what encode it use
   757  *  3. other:         "text/html" with other charset than utf-16
   758  */
   759 void GetHTMLCharset(guchar * data, int32_t dataLength, nsCString& str)
   760 {
   761     // if detect "FFFE" or "FEFF", assume UTF-16
   762     char16_t* beginChar =  (char16_t*)data;
   763     if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) {
   764         str.AssignLiteral("UTF-16");
   765         return;
   766     }
   767     // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
   768     const nsDependentCString htmlStr((const char *)data, dataLength);
   769     nsACString::const_iterator start, end;
   770     htmlStr.BeginReading(start);
   771     htmlStr.EndReading(end);
   772     nsACString::const_iterator valueStart(start), valueEnd(start);
   774     if (CaseInsensitiveFindInReadable(
   775         NS_LITERAL_CSTRING("CONTENT=\"text/html;"),
   776         start, end)) {
   777         start = end;
   778         htmlStr.EndReading(end);
   780         if (CaseInsensitiveFindInReadable(
   781             NS_LITERAL_CSTRING("charset="),
   782             start, end)) {
   783             valueStart = end;
   784             start = end;
   785             htmlStr.EndReading(end);
   787             if (FindCharInReadable('"', start, end))
   788                 valueEnd = start;
   789         }
   790     }
   791     // find "charset" in HTML
   792     if (valueStart != valueEnd) {
   793         str = Substring(valueStart, valueEnd);
   794         ToUpperCase(str);
   795 #ifdef DEBUG_CLIPBOARD
   796         printf("Charset of HTML = %s\n", charsetUpperStr.get());
   797 #endif
   798         return;
   799     }
   800     str.AssignLiteral("UNKNOWN");
   801 }
   803 static void
   804 DispatchSelectionNotifyEvent(GtkWidget *widget, XEvent *xevent)
   805 {
   806     GdkEvent event;
   807     event.selection.type = GDK_SELECTION_NOTIFY;
   808     event.selection.window = gtk_widget_get_window(widget);
   809     event.selection.selection = gdk_x11_xatom_to_atom(xevent->xselection.selection);
   810     event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target);
   811     event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property);
   812     event.selection.time = xevent->xselection.time;
   814     gtk_widget_event(widget, &event);
   815 }
   817 static void
   818 DispatchPropertyNotifyEvent(GtkWidget *widget, XEvent *xevent)
   819 {
   820     GdkWindow *window = gtk_widget_get_window(widget);
   821     if ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK) {
   822         GdkEvent event;
   823         event.property.type = GDK_PROPERTY_NOTIFY;
   824         event.property.window = window;
   825         event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom);
   826         event.property.time = xevent->xproperty.time;
   827         event.property.state = xevent->xproperty.state;
   829         gtk_widget_event(widget, &event);
   830     }
   831 }
   833 struct checkEventContext
   834 {
   835     GtkWidget *cbWidget;
   836     Atom       selAtom;
   837 };
   839 static Bool
   840 checkEventProc(Display *display, XEvent *event, XPointer arg)
   841 {
   842     checkEventContext *context = (checkEventContext *) arg;
   844     if (event->xany.type == SelectionNotify ||
   845         (event->xany.type == PropertyNotify &&
   846          event->xproperty.atom == context->selAtom)) {
   848         GdkWindow *cbWindow = 
   849             gdk_x11_window_lookup_for_display(gdk_x11_lookup_xdisplay(display),
   850                                               event->xany.window);
   851         if (cbWindow) {
   852             GtkWidget *cbWidget = nullptr;
   853             gdk_window_get_user_data(cbWindow, (gpointer *)&cbWidget);
   854             if (cbWidget && GTK_IS_WIDGET(cbWidget)) {
   855                 context->cbWidget = cbWidget;
   856                 return True;
   857             }
   858         }
   859     }
   861     return False;
   862 }
   864 // Idle timeout for receiving selection and property notify events (microsec)
   865 static const int kClipboardTimeout = 500000;
   867 static gchar* CopyRetrievedData(const gchar *aData)
   868 {
   869     return g_strdup(aData);
   870 }
   872 static GtkSelectionData* CopyRetrievedData(GtkSelectionData *aData)
   873 {
   874     // A negative length indicates that retrieving the data failed.
   875     return gtk_selection_data_get_length(aData) >= 0 ?
   876         gtk_selection_data_copy(aData) : nullptr;
   877 }
   879 class RetrievalContext {
   880     ~RetrievalContext()
   881     {
   882         MOZ_ASSERT(!mData, "Wait() wasn't called");
   883     }
   885 public:
   886     NS_INLINE_DECL_REFCOUNTING(RetrievalContext)
   887     enum State { INITIAL, COMPLETED, TIMED_OUT };
   889     RetrievalContext() : mState(INITIAL), mData(nullptr) {}
   891     /**
   892      * Call this when data has been retrieved.
   893      */
   894     template <class T> void Complete(T *aData)
   895     {
   896         if (mState == INITIAL) {
   897             mState = COMPLETED;
   898             mData = CopyRetrievedData(aData);
   899         } else {
   900             // Already timed out
   901             MOZ_ASSERT(mState == TIMED_OUT);
   902         }
   903     }
   905     /**
   906      * Spins X event loop until timing out or being completed. Returns
   907      * null if we time out, otherwise returns the completed data (passing
   908      * ownership to caller).
   909      */
   910     void *Wait();
   912 protected:
   913     State mState;
   914     void* mData;
   915 };
   917 void *
   918 RetrievalContext::Wait()
   919 {
   920     if (mState == COMPLETED) { // the request completed synchronously
   921         void *data = mData;
   922         mData = nullptr;
   923         return data;
   924     }
   926     Display *xDisplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()) ;
   927     checkEventContext context;
   928     context.cbWidget = nullptr;
   929     context.selAtom = gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION",
   930                                                             FALSE));
   932     // Send X events which are relevant to the ongoing selection retrieval
   933     // to the clipboard widget.  Wait until either the operation completes, or
   934     // we hit our timeout.  All other X events remain queued.
   936     int select_result;
   938     int cnumber = ConnectionNumber(xDisplay);
   939     fd_set select_set;
   940     FD_ZERO(&select_set);
   941     FD_SET(cnumber, &select_set);
   942     ++cnumber;
   943     TimeStamp start = TimeStamp::Now();
   945     do {
   946         XEvent xevent;
   948         while (XCheckIfEvent(xDisplay, &xevent, checkEventProc,
   949                              (XPointer) &context)) {
   951             if (xevent.xany.type == SelectionNotify)
   952                 DispatchSelectionNotifyEvent(context.cbWidget, &xevent);
   953             else
   954                 DispatchPropertyNotifyEvent(context.cbWidget, &xevent);
   956             if (mState == COMPLETED) {
   957                 void *data = mData;
   958                 mData = nullptr;
   959                 return data;
   960             }
   961         }
   963         TimeStamp now = TimeStamp::Now();
   964         struct timeval tv;
   965         tv.tv_sec = 0;
   966         tv.tv_usec = std::max<int32_t>(0,
   967             kClipboardTimeout - (now - start).ToMicroseconds());
   968         select_result = select(cnumber, &select_set, nullptr, nullptr, &tv);
   969     } while (select_result == 1 ||
   970              (select_result == -1 && errno == EINTR));
   972 #ifdef DEBUG_CLIPBOARD
   973     printf("exceeded clipboard timeout\n");
   974 #endif
   975     mState = TIMED_OUT;
   976     return nullptr;
   977 }
   979 static void
   980 clipboard_contents_received(GtkClipboard     *clipboard,
   981                             GtkSelectionData *selection_data,
   982                             gpointer          data)
   983 {
   984     RetrievalContext *context = static_cast<RetrievalContext*>(data);
   985     context->Complete(selection_data);
   986     context->Release();
   987 }
   989 static GtkSelectionData *
   990 wait_for_contents(GtkClipboard *clipboard, GdkAtom target)
   991 {
   992     RefPtr<RetrievalContext> context = new RetrievalContext();
   993     // Balanced by Release in clipboard_contents_received
   994     context->AddRef();
   995     gtk_clipboard_request_contents(clipboard, target,
   996                                    clipboard_contents_received,
   997                                    context.get());
   998     return static_cast<GtkSelectionData*>(context->Wait());
   999 }
  1001 static void
  1002 clipboard_text_received(GtkClipboard *clipboard,
  1003                         const gchar  *text,
  1004                         gpointer      data)
  1006     RetrievalContext *context = static_cast<RetrievalContext*>(data);
  1007     context->Complete(text);
  1008     context->Release();
  1011 static gchar *
  1012 wait_for_text(GtkClipboard *clipboard)
  1014     RefPtr<RetrievalContext> context = new RetrievalContext();
  1015     // Balanced by Release in clipboard_text_received
  1016     context->AddRef();
  1017     gtk_clipboard_request_text(clipboard, clipboard_text_received, context.get());
  1018     return static_cast<gchar*>(context->Wait());

mercurial