Back to index

lightning-sunbird  0.9+nobinonly
nsClipboard.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* vim:expandtab:shiftwidth=4:tabstop=4:
00003  */
00004 /* ***** BEGIN LICENSE BLOCK *****
00005  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00006  *
00007  * The contents of this file are subject to the Mozilla Public License Version
00008  * 1.1 (the "License"); you may not use this file except in compliance with
00009  * the License. You may obtain a copy of the License at
00010  * http://www.mozilla.org/MPL/
00011  *
00012  * Software distributed under the License is distributed on an "AS IS" basis,
00013  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00014  * for the specific language governing rights and limitations under the
00015  * License.
00016  *
00017  * The Original Code is mozilla.org code.
00018  *
00019  * The Initial Developer of the Original Code is Christopher Blizzard
00020  * <blizzard@mozilla.org>.  Portions created by the Initial Developer
00021  * are Copyright (C) 2001 the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nsClipboard.h"
00040 #include "nsSupportsPrimitives.h"
00041 #include "nsString.h"
00042 #include "nsReadableUtils.h"
00043 #include "nsXPIDLString.h"
00044 #include "nsPrimitiveHelpers.h"
00045 #include "nsICharsetConverterManager.h"
00046 #include "nsIServiceManager.h"
00047 
00048 #include <gtk/gtkclipboard.h>
00049 #include <gtk/gtkinvisible.h>
00050 
00051 // For manipulation of the X event queue
00052 #include <X11/Xlib.h>
00053 #include <gdk/gdkx.h>
00054 #include <gtk/gtkmain.h>
00055 #include <sys/time.h>
00056 #include <sys/types.h>
00057 #include <unistd.h>
00058 
00059 #ifdef POLL_WITH_XCONNECTIONNUMBER
00060 #include <poll.h>
00061 #endif
00062 
00063 // Callback when someone asks us for the selection
00064 void
00065 invisible_selection_get_cb (GtkWidget          *aWidget,
00066                             GtkSelectionData   *aSelectionData,
00067                             guint               aTime,
00068                             guint               aInfo,
00069                             nsClipboard        *aClipboard);
00070 
00071 gboolean
00072 selection_clear_event_cb   (GtkWidget          *aWidget,
00073                             GdkEventSelection  *aEvent,
00074                             nsClipboard        *aClipboard);
00075 
00076 static void
00077 ConvertHTMLtoUCS2          (guchar             *data,
00078                             PRInt32             dataLength,
00079                             PRUnichar         **unicodeData,
00080                             PRInt32            &outUnicodeLen);
00081 
00082 static void
00083 GetHTMLCharset             (guchar * data, PRInt32 dataLength, nsCString& str);
00084 
00085 
00086 // Our own versions of gtk_clipboard_wait_for_contents and
00087 // gtk_clipboard_wait_for_text, which don't run the event loop while
00088 // waiting for the data.  This prevents a lot of problems related to
00089 // dispatching events at unexpected times.
00090 
00091 static GtkSelectionData *
00092 wait_for_contents          (GtkClipboard *clipboard, GdkAtom target);
00093 
00094 static gchar *
00095 wait_for_text              (GtkClipboard *clipboard);
00096 
00097 static Bool
00098 checkEventProc(Display *display, XEvent *event, XPointer arg);
00099 
00100 struct retrieval_context
00101 {
00102     PRBool   completed;
00103     void    *data;
00104 
00105     retrieval_context() : completed(PR_FALSE), data(nsnull) { }
00106 };
00107 
00108 static void
00109 wait_for_retrieval(GtkClipboard *clipboard, retrieval_context *transferData);
00110 
00111 static void
00112 clipboard_contents_received(GtkClipboard     *clipboard,
00113                             GtkSelectionData *selection_data,
00114                             gpointer          data);
00115 
00116 static void
00117 clipboard_text_received(GtkClipboard *clipboard,
00118                         const gchar  *text,
00119                         gpointer      data);
00120 
00121 nsClipboard::nsClipboard()
00122 {
00123     mWidget = nsnull;
00124 }
00125 
00126 nsClipboard::~nsClipboard()
00127 {
00128     if (mWidget)
00129         gtk_widget_destroy(mWidget);
00130 }
00131 
00132 NS_IMPL_ISUPPORTS1(nsClipboard, nsIClipboard)
00133 
00134 nsresult
00135 nsClipboard::Init(void)
00136 {
00137     mWidget = gtk_invisible_new();
00138     if (!mWidget)
00139         return NS_ERROR_FAILURE;
00140 
00141     g_signal_connect(G_OBJECT(mWidget), "selection_get",
00142                      G_CALLBACK(invisible_selection_get_cb), this);
00143 
00144     g_signal_connect(G_OBJECT(mWidget), "selection_clear_event",
00145                      G_CALLBACK(selection_clear_event_cb), this);
00146 
00147     // XXX make sure to set up the selection_clear event
00148 
00149     return NS_OK;
00150 }
00151 
00152 NS_IMETHODIMP
00153 nsClipboard::SetData(nsITransferable *aTransferable,
00154                      nsIClipboardOwner *aOwner, PRInt32 aWhichClipboard)
00155 {
00156     // See if we can short cut
00157     if ((aWhichClipboard == kGlobalClipboard &&
00158          aTransferable == mGlobalTransferable.get() &&
00159          aOwner == mGlobalOwner.get()) ||
00160         (aWhichClipboard == kSelectionClipboard &&
00161          aTransferable == mSelectionTransferable.get() &&
00162          aOwner == mSelectionOwner.get())) {
00163         return NS_OK;
00164     }
00165 
00166     // Clear out the clipboard in order to set the new data
00167     EmptyClipboard(aWhichClipboard);
00168 
00169     if (aWhichClipboard == kSelectionClipboard) {
00170         mSelectionOwner = aOwner;
00171         mSelectionTransferable = aTransferable;
00172     }
00173     else {
00174         mGlobalOwner = aOwner;
00175         mGlobalTransferable = aTransferable;
00176     }
00177 
00178     // Which selection are we about to claim, CLIPBOARD or PRIMARY?
00179     GdkAtom selectionAtom = GetSelectionAtom(aWhichClipboard);
00180 
00181     // Make ourselves the owner.  If we fail to, return.
00182     if (!gtk_selection_owner_set(mWidget, selectionAtom, GDK_CURRENT_TIME))
00183         return NS_ERROR_FAILURE;
00184 
00185     // Clear the old selection target list.
00186     gtk_selection_clear_targets(mWidget, selectionAtom);
00187 
00188     // Get the types of supported flavors
00189     nsresult rv;
00190     nsCOMPtr<nsISupportsArray> flavors;
00191 
00192     rv = aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavors));
00193     if (!flavors || NS_FAILED(rv))
00194         return NS_ERROR_FAILURE;
00195 
00196     // Add all the flavors to this widget's supported type.
00197     PRUint32 count;
00198     flavors->Count(&count);
00199     for (PRUint32 i=0; i < count; i++) {
00200         nsCOMPtr<nsISupports> tastesLike;
00201         flavors->GetElementAt(i, getter_AddRefs(tastesLike));
00202         nsCOMPtr<nsISupportsCString> flavor = do_QueryInterface(tastesLike);
00203 
00204         if (flavor) {
00205             nsXPIDLCString flavorStr;
00206             flavor->ToString(getter_Copies(flavorStr));
00207 
00208             // special case text/unicode since we can handle all of
00209             // the string types
00210             if (!strcmp(flavorStr, kUnicodeMime)) {
00211                 AddTarget(gdk_atom_intern("UTF8_STRING", FALSE),
00212                           selectionAtom);
00213                 AddTarget(gdk_atom_intern("COMPOUND_TEXT", FALSE),
00214                           selectionAtom);
00215                 AddTarget(gdk_atom_intern("TEXT", FALSE), selectionAtom);
00216                 AddTarget(GDK_SELECTION_TYPE_STRING, selectionAtom);
00217                 // next loop iteration
00218                 continue;
00219             }
00220 
00221             // Add this to our list of valid targets
00222             GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
00223             AddTarget(atom, selectionAtom);
00224         }
00225     }
00226 
00227     return NS_OK;
00228 }
00229 
00230 NS_IMETHODIMP
00231 nsClipboard::GetData(nsITransferable *aTransferable, PRInt32 aWhichClipboard)
00232 {
00233     if (!aTransferable)
00234         return NS_ERROR_FAILURE;
00235 
00236     GtkClipboard *clipboard;
00237     clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
00238 
00239     guchar        *data = NULL;
00240     gint           length = 0;
00241     PRBool         foundData = PR_FALSE;
00242     nsCAutoString  foundFlavor;
00243 
00244     // Get a list of flavors this transferable can import
00245     nsCOMPtr<nsISupportsArray> flavors;
00246     nsresult rv;
00247     rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavors));
00248     if (!flavors || NS_FAILED(rv))
00249         return NS_ERROR_FAILURE;
00250 
00251     PRUint32 count;
00252     flavors->Count(&count);
00253     for (PRUint32 i=0; i < count; i++) {
00254         nsCOMPtr<nsISupports> genericFlavor;
00255         flavors->GetElementAt(i, getter_AddRefs(genericFlavor));
00256 
00257         nsCOMPtr<nsISupportsCString> currentFlavor;
00258         currentFlavor = do_QueryInterface(genericFlavor);
00259 
00260         if (currentFlavor) {
00261             nsXPIDLCString flavorStr;
00262             currentFlavor->ToString(getter_Copies(flavorStr));
00263 
00264             // Special case text/unicode since we can convert any
00265             // string into text/unicode
00266             if (!strcmp(flavorStr, kUnicodeMime)) {
00267                 gchar* new_text = wait_for_text(clipboard);
00268                 if (new_text) {
00269                     // Convert utf-8 into our unicode format.
00270                     NS_ConvertUTF8toUCS2 ucs2string(new_text);
00271                     data = (guchar *)ToNewUnicode(ucs2string);
00272                     length = ucs2string.Length() * 2;
00273                     g_free(new_text);
00274                     foundData = PR_TRUE;
00275                     foundFlavor = kUnicodeMime;
00276                     break;
00277                 }
00278                 // If the type was text/unicode and we couldn't get
00279                 // text off the clipboard, run the next loop
00280                 // iteration.
00281                 continue;
00282             }
00283 
00284             // Get the atom for this type and try to request it off
00285             // the clipboard.
00286             GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
00287             GtkSelectionData *selectionData;
00288             selectionData = wait_for_contents(clipboard, atom);
00289             if (selectionData) {
00290                 length = selectionData->length;
00291                 // Special case text/html since we can convert into UCS2
00292                 if (!strcmp(flavorStr, kHTMLMime)) {
00293                     PRUnichar* htmlBody= nsnull;
00294                     PRInt32 htmlBodyLen = 0;
00295                     // Convert text/html into our unicode format
00296                     ConvertHTMLtoUCS2((guchar *)selectionData->data, length,
00297                                       &htmlBody, htmlBodyLen);
00298                     if (!htmlBodyLen)
00299                         break;
00300                     data = (guchar *)htmlBody;
00301                     length = htmlBodyLen * 2;
00302                 } else {
00303                     data = (guchar *)nsMemory::Alloc(length);
00304                     if (!data)
00305                         break;
00306                     memcpy(data, selectionData->data, length);
00307                 }
00308                 foundData = PR_TRUE;
00309                 foundFlavor = flavorStr;
00310                 break;
00311             }
00312         }
00313     }
00314 
00315     if (foundData) {
00316         nsCOMPtr<nsISupports> wrapper;
00317         nsPrimitiveHelpers::CreatePrimitiveForData(foundFlavor.get(),
00318                                                    data, length,
00319                                                    getter_AddRefs(wrapper));
00320         aTransferable->SetTransferData(foundFlavor.get(),
00321                                        wrapper, length);
00322     }
00323 
00324     if (data)
00325         nsMemory::Free(data);
00326 
00327     return NS_OK;
00328 }
00329 
00330 NS_IMETHODIMP
00331 nsClipboard::EmptyClipboard(PRInt32 aWhichClipboard)
00332 {
00333     if (aWhichClipboard == kSelectionClipboard) {
00334         if (mSelectionOwner) {
00335             mSelectionOwner->LosingOwnership(mSelectionTransferable);
00336             mSelectionOwner = nsnull;
00337         }
00338         mSelectionTransferable = nsnull;
00339     }
00340     else {
00341         if (mGlobalOwner) {
00342             mGlobalOwner->LosingOwnership(mGlobalTransferable);
00343             mGlobalOwner = nsnull;
00344         }
00345         mGlobalTransferable = nsnull;
00346     }
00347 
00348     return NS_OK;
00349 }
00350 
00351 NS_IMETHODIMP
00352 nsClipboard::HasDataMatchingFlavors(nsISupportsArray *aFlavorList,
00353                                     PRInt32 aWhichClipboard, PRBool *_retval)
00354 {
00355     *_retval = PR_FALSE;
00356 
00357     PRUint32 length = 0;
00358     aFlavorList->Count(&length);
00359     if (!length)
00360         return NS_OK;
00361 
00362     GtkSelectionData *selection_data =
00363         GetTargets(GetSelectionAtom(aWhichClipboard));
00364     if (!selection_data)
00365         return NS_OK;
00366 
00367     gint n_targets = 0;
00368     GdkAtom *targets = NULL;
00369 
00370     if (!gtk_selection_data_get_targets(selection_data, 
00371                                         &targets, &n_targets) ||
00372         !n_targets)
00373         return NS_OK;
00374 
00375     // Walk through the provided types and try to match it to a
00376     // provided type.
00377     for (PRUint32 i = 0; i < length && !*_retval; i++) {
00378         nsCOMPtr<nsISupports> genericFlavor;
00379         aFlavorList->GetElementAt(i, getter_AddRefs(genericFlavor));
00380         nsCOMPtr<nsISupportsCString> flavorWrapper;
00381         flavorWrapper = do_QueryInterface(genericFlavor);
00382 
00383         if (flavorWrapper) {
00384             nsXPIDLCString myStr;
00385             flavorWrapper->ToString(getter_Copies(myStr));
00386 
00387             // We special case text/unicode here.
00388             if (!strcmp(myStr, kUnicodeMime) && 
00389                 gtk_selection_data_targets_include_text(selection_data)) {
00390                 *_retval = PR_TRUE;
00391                 break;
00392             }
00393 
00394             for (PRInt32 j = 0; j < n_targets; j++) {
00395                 gchar *atom_name = gdk_atom_name(targets[j]);
00396                 if (!strcmp(atom_name, (const char *)myStr))
00397                     *_retval = PR_TRUE;
00398 
00399                 g_free(atom_name);
00400 
00401                 if (*_retval)
00402                     break;
00403             }
00404         }
00405     }
00406     gtk_selection_data_free(selection_data);
00407     g_free(targets);
00408 
00409     return NS_OK;
00410 }
00411 
00412 NS_IMETHODIMP
00413 nsClipboard::SupportsSelectionClipboard(PRBool *_retval)
00414 {
00415     *_retval = PR_TRUE; // yeah, unix supports the selection clipboard
00416     return NS_OK;
00417 }
00418 
00419 /* static */
00420 GdkAtom
00421 nsClipboard::GetSelectionAtom(PRInt32 aWhichClipboard)
00422 {
00423     if (aWhichClipboard == kGlobalClipboard)
00424         return GDK_SELECTION_CLIPBOARD;
00425 
00426     return GDK_SELECTION_PRIMARY;
00427 }
00428 
00429 /* static */
00430 GtkSelectionData *
00431 nsClipboard::GetTargets(GdkAtom aWhichClipboard)
00432 {
00433     GtkClipboard *clipboard = gtk_clipboard_get(aWhichClipboard);
00434     return wait_for_contents(clipboard, gdk_atom_intern("TARGETS", FALSE));
00435 }
00436 
00437 nsITransferable *
00438 nsClipboard::GetTransferable(PRInt32 aWhichClipboard)
00439 {
00440     nsITransferable *retval;
00441 
00442     if (aWhichClipboard == kSelectionClipboard)
00443         retval = mSelectionTransferable.get();
00444     else
00445         retval = mGlobalTransferable.get();
00446         
00447     return retval;
00448 }
00449 
00450 void
00451 nsClipboard::AddTarget(GdkAtom aName, GdkAtom aClipboard)
00452 {
00453     gtk_selection_add_target(mWidget, aClipboard, aName, 0);
00454 }
00455 
00456 void
00457 nsClipboard::SelectionGetEvent (GtkWidget        *aWidget,
00458                                 GtkSelectionData *aSelectionData,
00459                                 guint             aTime)
00460 {
00461     // Someone has asked us to hand them something.  The first thing
00462     // that we want to do is see if that something includes text.  If
00463     // it does, try to give it text/unicode after converting it to
00464     // utf-8.
00465 
00466     PRInt32 whichClipboard;
00467 
00468     // which clipboard?
00469     if (aSelectionData->selection == GDK_SELECTION_PRIMARY)
00470         whichClipboard = kSelectionClipboard;
00471     else if (aSelectionData->selection == GDK_SELECTION_CLIPBOARD)
00472         whichClipboard = kGlobalClipboard;
00473     else
00474         return; // THAT AINT NO CLIPBOARD I EVER HEARD OF
00475 
00476     nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
00477     
00478     nsresult rv;
00479     nsCOMPtr<nsISupports> item;
00480     PRUint32 len;
00481 
00482     // Check to see if the selection data includes any of the string
00483     // types that we support.
00484     if (aSelectionData->target == gdk_atom_intern ("STRING", FALSE) ||
00485         aSelectionData->target == gdk_atom_intern ("TEXT", FALSE) ||
00486         aSelectionData->target == gdk_atom_intern ("COMPOUND_TEXT", FALSE) ||
00487         aSelectionData->target == gdk_atom_intern ("UTF8_STRING", FALSE)) {
00488         // Try to convert our internal type into a text string.  Get
00489         // the transferable for this clipboard and try to get the
00490         // text/unicode type for it.
00491         rv = trans->GetTransferData("text/unicode", getter_AddRefs(item),
00492                                     &len);
00493         if (!item || NS_FAILED(rv))
00494             return;
00495         
00496         nsCOMPtr<nsISupportsString> wideString;
00497         wideString = do_QueryInterface(item);
00498         if (!wideString)
00499             return;
00500 
00501         nsAutoString ucs2string;
00502         wideString->GetData(ucs2string);
00503         char *utf8string = ToNewUTF8String(ucs2string);
00504         if (!utf8string)
00505             return;
00506         
00507         gtk_selection_data_set_text (aSelectionData, utf8string,
00508                                      strlen(utf8string));
00509 
00510         nsMemory::Free(utf8string);
00511         return;
00512     }
00513 
00514     // Try to match up the selection data target to something our
00515     // transferable provides.
00516     gchar *target_name = gdk_atom_name(aSelectionData->target);
00517     if (!target_name)
00518         return;
00519 
00520     rv = trans->GetTransferData(target_name, getter_AddRefs(item), &len);
00521     // nothing found?
00522     if (!item || NS_FAILED(rv)) {
00523         g_free(target_name);
00524         return;
00525     }
00526 
00527     void *primitive_data = nsnull;
00528     nsPrimitiveHelpers::CreateDataFromPrimitive(target_name, item,
00529                                                 &primitive_data, len);
00530 
00531     if (primitive_data) {
00532         // Check to see if the selection data is text/html
00533         if (aSelectionData->target == gdk_atom_intern (kHTMLMime, FALSE)) {
00534             /*
00535              * "text/html" can be encoded UCS2. It is recommended that
00536              * documents transmitted as UCS2 always begin with a ZERO-WIDTH
00537              * NON-BREAKING SPACE character (hexadecimal FEFF, also called
00538              * Byte Order Mark (BOM)). Adding BOM can help other app to
00539              * detect mozilla use UCS2 encoding when copy-paste.
00540              */
00541             guchar *buffer = (guchar *)
00542                     nsMemory::Alloc((len * sizeof(guchar)) + sizeof(PRUnichar));
00543             if (!buffer)
00544                 return;
00545             PRUnichar prefix = 0xFEFF;
00546             memcpy(buffer, &prefix, sizeof(prefix));
00547             memcpy(buffer + sizeof(prefix), primitive_data, len);
00548             nsMemory::Free((guchar *)primitive_data);
00549             primitive_data = (guchar *)buffer;
00550             len += sizeof(prefix);
00551         }
00552   
00553         gtk_selection_data_set(aSelectionData, aSelectionData->target,
00554                                8, /* 8 bits in a unit */
00555                                (const guchar *)primitive_data, len);
00556         nsMemory::Free(primitive_data);
00557     }
00558 
00559     g_free(target_name);
00560                            
00561 }
00562 
00563 void
00564 nsClipboard::SelectionClearEvent (GtkWidget         *aWidget,
00565                                   GdkEventSelection *aEvent)
00566 {
00567     PRInt32 whichClipboard;
00568 
00569     // which clipboard?
00570     if (aEvent->selection == GDK_SELECTION_PRIMARY)
00571         whichClipboard = kSelectionClipboard;
00572     else if (aEvent->selection == GDK_SELECTION_CLIPBOARD)
00573         whichClipboard = kGlobalClipboard;
00574     else
00575         return; // THAT AINT NO CLIPBOARD I EVER HEARD OF
00576 
00577     EmptyClipboard(whichClipboard);
00578 }
00579 
00580 void
00581 invisible_selection_get_cb (GtkWidget          *aWidget,
00582                             GtkSelectionData   *aSelectionData,
00583                             guint               aTime,
00584                             guint               aInfo,
00585                             nsClipboard        *aClipboard)
00586 {
00587     aClipboard->SelectionGetEvent(aWidget, aSelectionData, aTime);
00588 }
00589 
00590 gboolean
00591 selection_clear_event_cb   (GtkWidget          *aWidget,
00592                             GdkEventSelection  *aEvent,
00593                             nsClipboard        *aClipboard)
00594 {
00595     aClipboard->SelectionClearEvent(aWidget, aEvent);
00596     return TRUE;
00597 }
00598 
00599 /*
00600  * when copy-paste, mozilla wants data encoded using UCS2,
00601  * other app such as StarOffice use "text/html"(RFC2854).
00602  * This function convert data(got from GTK clipboard)
00603  * to data mozilla wanted.
00604  *
00605  * data from GTK clipboard can be 3 forms:
00606  *  1. From current mozilla
00607  *     "text/html", charset = utf-16
00608  *  2. From old version mozilla or mozilla-based app
00609  *     content("body" only), charset = utf-16
00610  *  3. From other app who use "text/html" when copy-paste
00611  *     "text/html", has "charset" info
00612  *
00613  * data      : got from GTK clipboard
00614  * dataLength: got from GTK clipboard
00615  * body      : pass to Mozilla
00616  * bodyLength: pass to Mozilla
00617  */
00618 void ConvertHTMLtoUCS2(guchar * data, PRInt32 dataLength,
00619                        PRUnichar** unicodeData, PRInt32& outUnicodeLen)
00620 {
00621     nsCAutoString charset;
00622     GetHTMLCharset(data, dataLength, charset);// get charset of HTML
00623     if (charset.EqualsLiteral("UTF-16")) {//current mozilla
00624         outUnicodeLen = (dataLength / 2) - 1;
00625         *unicodeData = NS_REINTERPRET_CAST(PRUnichar*,
00626                        nsMemory::Alloc((outUnicodeLen + sizeof('\0')) *
00627                        sizeof(PRUnichar)));
00628         if (unicodeData) {
00629             memcpy(*unicodeData, data + sizeof(PRUnichar),
00630                    outUnicodeLen * sizeof(PRUnichar));
00631             (*unicodeData)[outUnicodeLen] = '\0';
00632         }
00633     } else if (charset.EqualsLiteral("UNKNOWN")) {
00634         outUnicodeLen = 0;
00635         return;
00636     } else {
00637         // app which use "text/html" to copy&paste
00638         nsCOMPtr<nsIUnicodeDecoder> decoder;
00639         nsresult rv;
00640         // get the decoder
00641         nsCOMPtr<nsICharsetConverterManager> ccm =
00642             do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
00643         if (NS_FAILED(rv)) {
00644 #ifdef DEBUG_CLIPBOARD
00645             g_print("        can't get CHARSET CONVERTER MANAGER service\n");
00646 #endif
00647             outUnicodeLen = 0;
00648             return;
00649         }
00650         rv = ccm->GetUnicodeDecoder(charset.get(), getter_AddRefs(decoder));
00651         if (NS_FAILED(rv)) {
00652 #ifdef DEBUG_CLIPBOARD
00653             g_print("        get unicode decoder error\n");
00654 #endif
00655             outUnicodeLen = 0;
00656             return;
00657         }
00658         // converting
00659         decoder->GetMaxLength((const char *)data, dataLength, &outUnicodeLen);
00660         // |outUnicodeLen| is number of chars
00661         if (outUnicodeLen) {
00662             *unicodeData = NS_REINTERPRET_CAST(PRUnichar*,
00663                            nsMemory::Alloc((outUnicodeLen + sizeof('\0')) *
00664                            sizeof(PRUnichar)));
00665             if (unicodeData) {
00666                 PRInt32 numberTmp = dataLength;
00667                 decoder->Convert((const char *)data, &numberTmp,
00668                                  *unicodeData, &outUnicodeLen);
00669 #ifdef DEBUG_CLIPBOARD
00670                 if (numberTmp != dataLength)
00671                     printf("didn't consume all the bytes\n");
00672 #endif
00673                 // null terminate. Convert() doesn't do it for us
00674                 (*unicodeData)[outUnicodeLen] = '\0';
00675             }
00676         } // if valid length
00677     }
00678 }
00679 
00680 /*
00681  * get "charset" information from clipboard data
00682  * return value can be:
00683  *  1. "UTF-16":      mozilla or "text/html" with "charset=utf-16"
00684  *  2. "UNKNOWN":     mozilla can't detect what encode it use
00685  *  3. other:         "text/html" with other charset than utf-16
00686  */
00687 void GetHTMLCharset(guchar * data, PRInt32 dataLength, nsCString& str)
00688 {
00689     // if detect "FFFE" or "FEFF", assume UTF-16
00690     PRUnichar* beginChar =  (PRUnichar*)data;
00691     if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) {
00692         str.AssignLiteral("UTF-16");
00693         return;
00694     }
00695     // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
00696     nsDependentCString htmlStr =
00697         nsDependentCString((const char *)data, dataLength);
00698     nsACString::const_iterator start, end;
00699     htmlStr.BeginReading(start);
00700     htmlStr.EndReading(end);
00701     nsACString::const_iterator valueStart(start), valueEnd(start);
00702 
00703     if (CaseInsensitiveFindInReadable(
00704         NS_LITERAL_CSTRING("CONTENT=\"text/html;"),
00705         start, end)) {
00706         start = end;
00707         htmlStr.EndReading(end);
00708 
00709         if (CaseInsensitiveFindInReadable(
00710             NS_LITERAL_CSTRING("charset="),
00711             start, end)) {
00712             valueStart = end;
00713             start = end;
00714             htmlStr.EndReading(end);
00715           
00716             if (FindCharInReadable('"', start, end))
00717                 valueEnd = start;
00718         }
00719     }
00720     // find "charset" in HTML
00721     if (valueStart != valueEnd) {
00722         str = Substring(valueStart, valueEnd);
00723         ToUpperCase(str);
00724 #ifdef DEBUG_CLIPBOARD
00725         printf("Charset of HTML = %s\n", charsetUpperStr.get());
00726 #endif
00727         return;
00728     }
00729     str.AssignLiteral("UNKNOWN");
00730 }
00731 
00732 static void
00733 DispatchSelectionNotifyEvent(GtkWidget *widget, XEvent *xevent)
00734 {
00735     GdkEvent event;
00736     event.selection.type = GDK_SELECTION_NOTIFY;
00737     event.selection.window = widget->window;
00738     event.selection.selection = gdk_x11_xatom_to_atom(xevent->xselection.selection);
00739     event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target);
00740     event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property);
00741     event.selection.time = xevent->xselection.time;
00742 
00743     gtk_widget_event(widget, &event);
00744 }
00745 
00746 static void
00747 DispatchPropertyNotifyEvent(GtkWidget *widget, XEvent *xevent)
00748 {
00749     if (((GdkWindowObject *) widget->window)->event_mask & GDK_PROPERTY_CHANGE_MASK) {
00750         GdkEvent event;
00751         event.property.type = GDK_PROPERTY_NOTIFY;
00752         event.property.window = widget->window;
00753         event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom);
00754         event.property.time = xevent->xproperty.time;
00755         event.property.state = xevent->xproperty.state;
00756 
00757         gtk_widget_event(widget, &event);
00758     }
00759 }
00760 
00761 struct checkEventContext
00762 {
00763     GtkWidget *cbWidget;
00764     Atom       selAtom;
00765 };
00766 
00767 static Bool
00768 checkEventProc(Display *display, XEvent *event, XPointer arg)
00769 {
00770     checkEventContext *context = (checkEventContext *) arg;
00771 
00772     if (event->xany.type == SelectionNotify ||
00773         (event->xany.type == PropertyNotify &&
00774          event->xproperty.atom == context->selAtom)) {
00775 
00776         GdkWindow *cbWindow = gdk_window_lookup(event->xany.window);
00777         if (cbWindow) {
00778             GtkWidget *cbWidget = NULL;
00779             gdk_window_get_user_data(cbWindow, (gpointer *)&cbWidget);
00780             if (cbWidget && GTK_IS_WIDGET(cbWidget)) {
00781                 context->cbWidget = cbWidget;
00782                 return True;
00783             }
00784         }
00785     }
00786 
00787     return False;
00788 }
00789 
00790 // Idle timeout for receiving selection and property notify events (microsec)
00791 static const int kClipboardTimeout = 500000;
00792 
00793 static void
00794 wait_for_retrieval(GtkClipboard *clipboard, retrieval_context *r_context)
00795 {
00796     if (r_context->completed)  // the request completed synchronously
00797         return;
00798 
00799     Display *xDisplay = GDK_DISPLAY();
00800     checkEventContext context;
00801     context.cbWidget = NULL;
00802     context.selAtom = gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION",
00803                                                             FALSE));
00804 
00805     // Send X events which are relevant to the ongoing selection retrieval
00806     // to the clipboard widget.  Wait until either the operation completes, or
00807     // we hit our timeout.  All other X events remain queued.
00808 
00809     int select_result;
00810 
00811 #ifdef POLL_WITH_XCONNECTIONNUMBER
00812     struct pollfd fds[1];
00813     fds[0].fd = XConnectionNumber(xDisplay);
00814     fds[0].events = POLLIN;
00815 #else
00816     int cnumber = ConnectionNumber(xDisplay);
00817     fd_set select_set;
00818     FD_ZERO(&select_set);
00819     FD_SET(cnumber, &select_set);
00820     ++cnumber;
00821     struct timeval tv;
00822 #endif
00823 
00824     do {
00825         XEvent xevent;
00826 
00827         while (XCheckIfEvent(xDisplay, &xevent, checkEventProc,
00828                              (XPointer) &context)) {
00829 
00830             if (xevent.xany.type == SelectionNotify)
00831                 DispatchSelectionNotifyEvent(context.cbWidget, &xevent);
00832             else
00833                 DispatchPropertyNotifyEvent(context.cbWidget, &xevent);
00834 
00835             if (r_context->completed)
00836                 return;
00837         }
00838 
00839 #ifdef POLL_WITH_XCONNECTIONNUMBER
00840         select_result = poll(fds, 1, kClipboardTimeout / 1000);
00841 #else
00842         tv.tv_sec = 0;
00843         tv.tv_usec = kClipboardTimeout;
00844         select_result = select(cnumber, &select_set, NULL, NULL, &tv);
00845 #endif
00846     } while (select_result == 1);
00847 
00848 #ifdef DEBUG_CLIPBOARD
00849     printf("exceeded clipboard timeout\n");
00850 #endif
00851 }
00852 
00853 static void
00854 clipboard_contents_received(GtkClipboard     *clipboard,
00855                             GtkSelectionData *selection_data,
00856                             gpointer          data)
00857 {
00858     retrieval_context *context = NS_STATIC_CAST(retrieval_context *, data);
00859     context->completed = PR_TRUE;
00860 
00861     if (selection_data->length >= 0)
00862         context->data = gtk_selection_data_copy(selection_data);
00863 }
00864 
00865 
00866 static GtkSelectionData *
00867 wait_for_contents(GtkClipboard *clipboard, GdkAtom target)
00868 {
00869     retrieval_context context;
00870     gtk_clipboard_request_contents(clipboard, target,
00871                                    clipboard_contents_received,
00872                                    &context);
00873 
00874     wait_for_retrieval(clipboard, &context);
00875     return NS_STATIC_CAST(GtkSelectionData *, context.data);
00876 }
00877 
00878 static void
00879 clipboard_text_received(GtkClipboard *clipboard,
00880                         const gchar  *text,
00881                         gpointer      data)
00882 {
00883     retrieval_context *context = NS_STATIC_CAST(retrieval_context *, data);
00884     context->completed = PR_TRUE;
00885     context->data = g_strdup(text);
00886 }
00887 
00888 static gchar *
00889 wait_for_text(GtkClipboard *clipboard)
00890 {
00891     retrieval_context context;
00892     gtk_clipboard_request_text(clipboard, clipboard_text_received, &context);
00893 
00894     wait_for_retrieval(clipboard, &context);
00895     return NS_STATIC_CAST(gchar *, context.data);
00896 }