Bug 311340 - "Clipboard data is lost on exit (Should implement the freedesktop.org specification for clipboard management)" [r=karlt]
authorMartin Stránský <stransky@redhat.com>
Wed, 25 Nov 2009 22:08:09 -0600
changeset 35242 270a70535807c45277659d18d60e1d744b51ebd3
parent 35241 d2c981b11f92bc8039959094ff9637e3940c673a
child 35243 f49658bb165584c1247c297d62e729c1cc92bab3
push id10518
push userreed@reedloden.com
push dateThu, 26 Nov 2009 04:08:23 +0000
treeherdermozilla-central@270a70535807 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskarlt
bugs311340
milestone1.9.3a1pre
Bug 311340 - "Clipboard data is lost on exit (Should implement the freedesktop.org specification for clipboard management)" [r=karlt]
widget/src/gtk2/nsClipboard.cpp
widget/src/gtk2/nsClipboard.h
--- a/widget/src/gtk2/nsClipboard.cpp
+++ b/widget/src/gtk2/nsClipboard.cpp
@@ -41,45 +41,45 @@
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "nsXPIDLString.h"
 #include "nsPrimitiveHelpers.h"
 #include "nsICharsetConverterManager.h"
 #include "nsIServiceManager.h"
 #include "nsImageToPixbuf.h"
 #include "nsStringStream.h"
+#include "nsIObserverService.h"
 
 #include "imgIContainer.h"
 
 #include <gtk/gtk.h>
 
 // For manipulation of the X event queue
 #include <X11/Xlib.h>
 #include <gdk/gdkx.h>
 #include <sys/time.h>
 #include <sys/types.h>
 #include <unistd.h>
 
 #ifdef POLL_WITH_XCONNECTIONNUMBER
 #include <poll.h>
 #endif
 
-// Callback when someone asks us for the selection
+// Callback when someone asks us for the data
 void
-invisible_selection_get_cb (GtkWidget          *aWidget,
-                            GtkSelectionData   *aSelectionData,
-                            guint               aTime,
-                            guint               aInfo,
-                            nsClipboard        *aClipboard);
+clipboard_get_cb(GtkClipboard *aGtkClipboard,
+                 GtkSelectionData *aSelectionData,
+                 guint info,
+                 gpointer user_data);
 
-gboolean
-selection_clear_event_cb   (GtkWidget          *aWidget,
-                            GdkEventSelection  *aEvent,
-                            nsClipboard        *aClipboard);
-
+// Callback when someone asks us to clear a clipboard
+void
+clipboard_clear_cb(GtkClipboard *aGtkClipboard,
+                   gpointer user_data);
+                   
 static void
 ConvertHTMLtoUCS2          (guchar             *data,
                             PRInt32             dataLength,
                             PRUnichar         **unicodeData,
                             PRInt32            &outUnicodeLen);
 
 static void
 GetHTMLCharset             (guchar * data, PRInt32 dataLength, nsCString& str);
@@ -116,43 +116,56 @@ clipboard_contents_received(GtkClipboard
                             gpointer          data);
 
 static void
 clipboard_text_received(GtkClipboard *clipboard,
                         const gchar  *text,
                         gpointer      data);
 
 nsClipboard::nsClipboard()
-{
-    mWidget = nsnull;
+{    
 }
 
 nsClipboard::~nsClipboard()
 {
-    if (mWidget)
-        gtk_widget_destroy(mWidget);
 }
 
 NS_IMPL_ISUPPORTS1(nsClipboard, nsIClipboard)
 
 nsresult
 nsClipboard::Init(void)
-{
-    mWidget = gtk_invisible_new();
-    if (!mWidget)
-        return NS_ERROR_FAILURE;
+{    
+    nsresult rv;
+    nsCOMPtr<nsIObserverService> os
+      (do_GetService("@mozilla.org/observer-service;1", &rv));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    os->AddObserver(this, "quit-application", PR_FALSE);
+
+    return NS_OK;
+}
 
-    g_signal_connect(G_OBJECT(mWidget), "selection_get",
-                     G_CALLBACK(invisible_selection_get_cb), this);
+NS_IMETHODIMP
+nsClipboard::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData)
+{
+    if (strcmp(aTopic, "quit-application") == 0) {
+        // application is going to quit, save clipboard content
+        Store();
+    }
+    return NS_OK;
+}
 
-    g_signal_connect(G_OBJECT(mWidget), "selection_clear_event",
-                     G_CALLBACK(selection_clear_event_cb), this);
-
-    // XXX make sure to set up the selection_clear event
-
+nsresult
+nsClipboard::Store(void)
+{
+    // Ask the clipboard manager to store the current clipboard content
+    if (mGlobalTransferable) {
+        GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+        gtk_clipboard_store(clipboard);
+    }
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsClipboard::SetData(nsITransferable *aTransferable,
                      nsIClipboardOwner *aOwner, PRInt32 aWhichClipboard)
 {
     // See if we can short cut
@@ -171,93 +184,101 @@ nsClipboard::SetData(nsITransferable *aT
         NS_ENSURE_SUCCESS(rv, rv);
     }
     rv = mPrivacyHandler->PrepareDataForClipboard(aTransferable);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Clear out the clipboard in order to set the new data
     EmptyClipboard(aWhichClipboard);
 
-    if (aWhichClipboard == kSelectionClipboard) {
-        mSelectionOwner = aOwner;
-        mSelectionTransferable = aTransferable;
-    }
-    else {
-        mGlobalOwner = aOwner;
-        mGlobalTransferable = aTransferable;
-    }
-
-    // Which selection are we about to claim, CLIPBOARD or PRIMARY?
-    GdkAtom selectionAtom = GetSelectionAtom(aWhichClipboard);
-
-    // Make ourselves the owner.  If we fail to, return.
-    if (!gtk_selection_owner_set(mWidget, selectionAtom, GDK_CURRENT_TIME))
-        return NS_ERROR_FAILURE;
-
-    // Clear the old selection target list.
-    gtk_selection_clear_targets(mWidget, selectionAtom);
+    // List of suported targets
+    GtkTargetList *list = gtk_target_list_new(NULL, 0);
 
     // Get the types of supported flavors
     nsCOMPtr<nsISupportsArray> flavors;
 
     rv = aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavors));
     if (!flavors || NS_FAILED(rv))
         return NS_ERROR_FAILURE;
 
     // Add all the flavors to this widget's supported type.
-    gint nImageTargets = 0;
+    PRBool imagesAdded = PR_FALSE;
     PRUint32 count;
     flavors->Count(&count);
     for (PRUint32 i=0; i < count; i++) {
         nsCOMPtr<nsISupports> tastesLike;
         flavors->GetElementAt(i, getter_AddRefs(tastesLike));
         nsCOMPtr<nsISupportsCString> flavor = do_QueryInterface(tastesLike);
 
         if (flavor) {
             nsXPIDLCString flavorStr;
             flavor->ToString(getter_Copies(flavorStr));
 
             // special case text/unicode since we can handle all of
             // the string types
             if (!strcmp(flavorStr, kUnicodeMime)) {
-                AddTarget(gdk_atom_intern("UTF8_STRING", FALSE),
-                          selectionAtom);
-                AddTarget(gdk_atom_intern("COMPOUND_TEXT", FALSE),
-                          selectionAtom);
-                AddTarget(gdk_atom_intern("TEXT", FALSE), selectionAtom);
-                AddTarget(GDK_SELECTION_TYPE_STRING, selectionAtom);
-                // next loop iteration
+                gtk_target_list_add(list, gdk_atom_intern("UTF8_STRING", FALSE), 0, 0);
+                gtk_target_list_add(list, gdk_atom_intern("COMPOUND_TEXT", FALSE), 0, 0);
+                gtk_target_list_add(list, gdk_atom_intern("TEXT", FALSE), 0, 0);
+                gtk_target_list_add(list, GDK_SELECTION_TYPE_STRING, 0, 0);
                 continue;
             }
 
             if (flavorStr.EqualsLiteral(kNativeImageMime) ||
                 flavorStr.EqualsLiteral(kPNGImageMime) ||
                 flavorStr.EqualsLiteral(kJPEGImageMime) ||
                 flavorStr.EqualsLiteral(kGIFImageMime)) {
                 // don't bother adding image targets twice
-                if (!nImageTargets) {
+                if (!imagesAdded) {
                     // accept any writable image type
-                    GtkTargetList *list = gtk_target_list_new(NULL, 0);
                     gtk_target_list_add_image_targets(list, 0, TRUE);
-                    GtkTargetEntry *targets = gtk_target_table_new_from_list(list, &nImageTargets);
-                    gtk_selection_add_targets(mWidget, selectionAtom, targets, nImageTargets);
-                    gtk_target_table_free(targets, nImageTargets);
-                    gtk_target_list_unref(list);
+                    imagesAdded = PR_TRUE;
                 }
-                // next loop iteration
                 continue;
             }
 
             // Add this to our list of valid targets
             GdkAtom atom = gdk_atom_intern(flavorStr, FALSE);
-            AddTarget(atom, selectionAtom);
+            gtk_target_list_add(list, atom, 0, 0);
         }
     }
+    
+    // Get GTK clipboard (CLIPBOARD or PRIMARY)
+    GtkClipboard *gtkClipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+  
+    gint numTargets;
+    GtkTargetEntry *gtkTargets = gtk_target_table_new_from_list(list, &numTargets);
+          
+    // Set getcallback and request to store data after an application exit
+    if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets, 
+                                    clipboard_get_cb, clipboard_clear_cb, this))
+    {
+        // We managed to set-up the clipboard so update internal state
+        // We have to set it now because gtk_clipboard_set_with_data() calls clipboard_clear_cb()
+        // which reset our internal state 
+        if (aWhichClipboard == kSelectionClipboard) {
+            mSelectionOwner = aOwner;
+            mSelectionTransferable = aTransferable;
+        }
+        else {
+            mGlobalOwner = aOwner;
+            mGlobalTransferable = aTransferable;
+            gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
+        }
 
-    return NS_OK;
+        rv = NS_OK;
+    }
+    else {  
+        rv = NS_ERROR_FAILURE;
+    }
+
+    gtk_target_table_free(gtkTargets, numTargets);
+    gtk_target_list_unref(list);
+  
+    return rv;
 }
 
 NS_IMETHODIMP
 nsClipboard::GetData(nsITransferable *aTransferable, PRInt32 aWhichClipboard)
 {
     if (!aTransferable)
         return NS_ERROR_FAILURE;
 
@@ -484,25 +505,18 @@ nsClipboard::GetTransferable(PRInt32 aWh
         retval = mSelectionTransferable.get();
     else
         retval = mGlobalTransferable.get();
         
     return retval;
 }
 
 void
-nsClipboard::AddTarget(GdkAtom aName, GdkAtom aClipboard)
-{
-    gtk_selection_add_target(mWidget, aClipboard, aName, 0);
-}
-
-void
-nsClipboard::SelectionGetEvent (GtkWidget        *aWidget,
-                                GtkSelectionData *aSelectionData,
-                                guint             aTime)
+nsClipboard::SelectionGetEvent(GtkClipboard     *aClipboard,
+                               GtkSelectionData *aSelectionData)
 {
     // Someone has asked us to hand them something.  The first thing
     // that we want to do is see if that something includes text.  If
     // it does, try to give it text/unicode after converting it to
     // utf-8.
 
     PRInt32 whichClipboard;
 
@@ -510,17 +524,25 @@ nsClipboard::SelectionGetEvent (GtkWidge
     if (aSelectionData->selection == GDK_SELECTION_PRIMARY)
         whichClipboard = kSelectionClipboard;
     else if (aSelectionData->selection == GDK_SELECTION_CLIPBOARD)
         whichClipboard = kGlobalClipboard;
     else
         return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
 
     nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
-    
+    if (!trans) {
+      // We have nothing to serve
+#ifdef DEBUG_CLIPBOARD
+      printf("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
+             whichClipboard == kSelectionClipboard ? "Selection" : "Global");
+#endif
+      return;
+    }
+
     nsresult rv;
     nsCOMPtr<nsISupports> item;
     PRUint32 len;
 
     // Check to see if the selection data includes any of the string
     // types that we support.
     if (aSelectionData->target == gdk_atom_intern ("STRING", FALSE) ||
         aSelectionData->target == gdk_atom_intern ("TEXT", FALSE) ||
@@ -627,49 +649,47 @@ nsClipboard::SelectionGetEvent (GtkWidge
         nsMemory::Free(primitive_data);
     }
 
     g_free(target_name);
                            
 }
 
 void
-nsClipboard::SelectionClearEvent (GtkWidget         *aWidget,
-                                  GdkEventSelection *aEvent)
+nsClipboard::SelectionClearEvent(GtkClipboard *aGtkClipboard)
 {
     PRInt32 whichClipboard;
 
     // which clipboard?
-    if (aEvent->selection == GDK_SELECTION_PRIMARY)
+    if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
         whichClipboard = kSelectionClipboard;
-    else if (aEvent->selection == GDK_SELECTION_CLIPBOARD)
+    else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
         whichClipboard = kGlobalClipboard;
     else
         return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
 
     EmptyClipboard(whichClipboard);
 }
 
 void
-invisible_selection_get_cb (GtkWidget          *aWidget,
-                            GtkSelectionData   *aSelectionData,
-                            guint               aTime,
-                            guint               aInfo,
-                            nsClipboard        *aClipboard)
+clipboard_get_cb(GtkClipboard *aGtkClipboard,
+                 GtkSelectionData *aSelectionData,
+                 guint info,
+                 gpointer user_data)
 {
-    aClipboard->SelectionGetEvent(aWidget, aSelectionData, aTime);
+    nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data);
+    aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
 }
 
-gboolean
-selection_clear_event_cb   (GtkWidget          *aWidget,
-                            GdkEventSelection  *aEvent,
-                            nsClipboard        *aClipboard)
+void
+clipboard_clear_cb(GtkClipboard *aGtkClipboard,
+                   gpointer user_data)
 {
-    aClipboard->SelectionClearEvent(aWidget, aEvent);
-    return TRUE;
+    nsClipboard *aClipboard = static_cast<nsClipboard *>(user_data);
+    aClipboard->SelectionClearEvent(aGtkClipboard);
 }
 
 /*
  * when copy-paste, mozilla wants data encoded using UCS2,
  * other app such as StarOffice use "text/html"(RFC2854).
  * This function convert data(got from GTK clipboard)
  * to data mozilla wanted.
  *
--- a/widget/src/gtk2/nsClipboard.h
+++ b/widget/src/gtk2/nsClipboard.h
@@ -39,52 +39,49 @@
 #ifndef __nsClipboard_h_
 #define __nsClipboard_h_
 
 #include "nsIClipboard.h"
 #include "nsClipboardPrivacyHandler.h"
 #include "nsAutoPtr.h"
 #include <gtk/gtk.h>
 
-class nsClipboard : public nsIClipboard
+class nsClipboard : public nsIClipboard,
+                    public nsIObserver
 {
 public:
     nsClipboard();
     virtual ~nsClipboard();
     
     NS_DECL_ISUPPORTS
     
     NS_DECL_NSICLIPBOARD
+    NS_DECL_NSIOBSERVER
 
     // Make sure we are initialized, called from the factory
     // constructor
-    nsresult  Init                (void);
-    // Someone requested the selection from the hidden widget
-    void      SelectionGetEvent   (GtkWidget         *aWidget,
-                                   GtkSelectionData  *aSelectionData,
-                                   guint              aTime);
-    void      SelectionClearEvent (GtkWidget         *aWidget,
-                                   GdkEventSelection *aEvent);
+    nsresult  Init              (void);
 
+    // Someone requested the selection
+    void   SelectionGetEvent    (GtkClipboard     *aGtkClipboard,
+                                 GtkSelectionData *aSelectionData);
+    void   SelectionClearEvent  (GtkClipboard     *aGtkClipboard);
 
 private:
     // Utility methods
     static GdkAtom               GetSelectionAtom (PRInt32 aWhichClipboard);
     static GtkSelectionData     *GetTargets       (GdkAtom aWhichClipboard);
 
+    // Save global clipboard content to gtk
+    nsresult                     Store            (void);
+
     // Get our hands on the correct transferable, given a specific
     // clipboard
     nsITransferable             *GetTransferable  (PRInt32 aWhichClipboard);
 
-    // Add a target type to the hidden widget
-    void                         AddTarget        (GdkAtom aName,
-                                                   GdkAtom aClipboard);
-
-    // The hidden widget where we do all of our operations
-    GtkWidget                   *mWidget;
     // Hang on to our owners and transferables so we can transfer data
     // when asked.
     nsCOMPtr<nsIClipboardOwner>  mSelectionOwner;
     nsCOMPtr<nsIClipboardOwner>  mGlobalOwner;
     nsCOMPtr<nsITransferable>    mSelectionTransferable;
     nsCOMPtr<nsITransferable>    mGlobalTransferable;
     nsRefPtr<nsClipboardPrivacyHandler> mPrivacyHandler;