Subject: implement startup-notification
From: Robert O'Callahan <[email-masked]>

Index: configure.in
================================================================================
--- config/autoconf.mk.in
+++ config/autoconf.mk.in
@@ -223,6 +223,10 @@
 MOZ_GNOMEUI_CFLAGS = @MOZ_GNOMEUI_CFLAGS@
 MOZ_GNOMEUI_LIBS = @MOZ_GNOMEUI_LIBS@
 
+MOZ_ENABLE_STARTUP_NOTIFICATION = @MOZ_ENABLE_STARTUP_NOTIFICATION@
+MOZ_STARTUP_NOTIFICATION_CFLAGS = @MOZ_STARTUP_NOTIFICATION_CFLAGS@
+MOZ_STARTUP_NOTIFICATION_LIBS = @MOZ_STARTUP_NOTIFICATION_LIBS@
+
 MOZ_GNOMEVFS_CFLAGS = @MOZ_GNOMEVFS_CFLAGS@
 MOZ_GNOMEVFS_LIBS = @MOZ_GNOMEVFS_LIBS@
 
--- configure.in
+++ configure.in
@@ -125,6 +125,7 @@
 GNOMEUI_VERSION=2.2.0
 GCONF_VERSION=1.2.1
 LIBGNOME_VERSION=2.0
+STARTUP_NOTIFICATION_VERSION=0.8
 
 dnl Set various checks
 dnl ========================================================
@@ -4088,6 +4089,42 @@
 
 AC_SUBST(MOZ_DEFAULT_TOOLKIT)
 
+dnl ========================================================
+dnl = startup-notification support module
+dnl ========================================================
+
+if test "$MOZ_ENABLE_GTK2"
+then
+    MOZ_ENABLE_STARTUP_NOTIFICATION=
+
+    MOZ_ARG_ENABLE_BOOL(startup-notification,
+    [  --enable-startup-notification       Enable startup-notification support (default: disabled) ],
+        MOZ_ENABLE_STARTUP_NOTIFICATION=force,
+        MOZ_ENABLE_STARTUP_NOTIFICATION=)
+
+    if test "$MOZ_ENABLE_STARTUP_NOTIFICATION"
+    then
+        PKG_CHECK_MODULES(MOZ_STARTUP_NOTIFICATION,
+                          libstartup-notification-1.0 >= $STARTUP_NOTIFICATION_VERSION,
+        [MOZ_ENABLE_STARTUP_NOTIFICATION=1], [
+            if test "$MOZ_ENABLE_STARTUP_NOTIFICATION" = "force"
+            then
+                AC_MSG_ERROR([* * * Could not find startup-notification >= $STARTUP_NOTIFICATION_VERSION])
+            fi
+            MOZ_ENABLE_STARTUP_NOTIFICATION=
+        ])
+    fi
+
+    if test "$MOZ_ENABLE_STARTUP_NOTIFICATION"; then
+        AC_DEFINE(MOZ_ENABLE_STARTUP_NOTIFICATION)
+    fi
+
+    TK_LIBS="$TK_LIBS $MOZ_STARTUP_NOTIFICATION_LIBS"
+fi
+AC_SUBST(MOZ_ENABLE_STARTUP_NOTIFICATION)
+AC_SUBST(MOZ_STARTUP_NOTIFICATION_CFLAGS)
+AC_SUBST(MOZ_STARTUP_NOTIFICATION_LIBS)
+
 AC_SUBST(GTK_CONFIG)
 AC_SUBST(TK_CFLAGS)
 AC_SUBST(TK_LIBS)
--- toolkit/components/remote/Makefile.in
+++ toolkit/components/remote/Makefile.in
@@ -56,8 +56,9 @@
 	string \
 	appcomps \
 	toolkitcomps \
-	appcomps \
+	appshell \
 	xulapp \
+	layout \
 	widget \
 	gfx \
 	dom \
--- toolkit/components/remote/nsGTKRemoteService.cpp
+++ toolkit/components/remote/nsGTKRemoteService.cpp
@@ -50,7 +50,7 @@
 
 #include "nsIBaseWindow.h"
 #include "nsIDocShell.h"
-#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
 #include "nsIGenericFactory.h"
 #include "nsILocalFile.h"
 #include "nsIObserverService.h"
@@ -58,6 +58,8 @@
 #include "nsIServiceManager.h"
 #include "nsIWeakReference.h"
 #include "nsIWidget.h"
+#include "nsIAppShellService.h"
+#include "nsAppShellCID.h"
 
 #include "nsCOMPtr.h"
 #include "nsString.h"
@@ -72,6 +74,10 @@
 #include "nsISuiteRemoteService.h"
 #endif
 
+#ifdef MOZ_WIDGET_GTK2
+#include "nsGTKToolkit.h"
+#endif
+
 #define MOZILLA_VERSION_PROP   "_MOZILLA_VERSION"
 #define MOZILLA_LOCK_PROP      "_MOZILLA_LOCK"
 #define MOZILLA_COMMAND_PROP   "_MOZILLA_COMMAND"
@@ -155,20 +161,44 @@
   return PL_DHASH_NEXT;
 }
 
-NS_IMETHODIMP
-nsGTKRemoteService::RegisterWindow(nsIDOMWindow* aWindow)
+static nsIWidget* GetMainWidget(nsIDOMWindow* aWindow)
 {
   // get the native window for this instance
   nsCOMPtr<nsIScriptGlobalObject> scriptObject
     (do_QueryInterface(aWindow));
-  NS_ENSURE_TRUE(scriptObject, NS_ERROR_FAILURE);
+  NS_ENSURE_TRUE(scriptObject, nsnull);
 
   nsCOMPtr<nsIBaseWindow> baseWindow
     (do_QueryInterface(scriptObject->GetDocShell()));
-  NS_ENSURE_TRUE(baseWindow, NS_ERROR_FAILURE);
+  NS_ENSURE_TRUE(baseWindow, nsnull);
 
   nsCOMPtr<nsIWidget> mainWidget;
   baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
+  return mainWidget;
+}
+
+static nsGTKToolkit* GetGTKToolkit()
+{
+  nsCOMPtr<nsIAppShellService> svc = do_GetService(NS_APPSHELLSERVICE_CONTRACTID);
+  if (!svc)
+    return nsnull;
+  nsCOMPtr<nsIDOMWindowInternal> window;
+  svc->GetHiddenDOMWindow(getter_AddRefs(window));
+  if (!window)
+    return nsnull;
+  nsIWidget* widget = GetMainWidget(window);
+  if (!widget)
+     return nsnull;
+  nsIToolkit* toolkit = widget->GetToolkit();
+  if (!toolkit)
+    return nsnull;
+  return NS_STATIC_CAST(nsGTKToolkit*, toolkit);
+}
+
+NS_IMETHODIMP
+nsGTKRemoteService::RegisterWindow(nsIDOMWindow* aWindow)
+{
+  nsIWidget* mainWidget = GetMainWidget(aWindow);
   NS_ENSURE_TRUE(mainWidget, NS_ERROR_FAILURE);
 
   // walk up the widget tree and find the toplevel window in the
@@ -260,7 +290,7 @@
 
 #ifndef MOZ_XUL_APP
 const char*
-nsGTKRemoteService::HandleCommand(char* aCommand, nsIDOMWindow* aWindow)
+nsGTKRemoteService::HandleCommand(char* aCommand, nsIDOMWindow* aWindow, PRUint32 aTimestamp)
 {
   nsresult rv;
 
@@ -283,8 +313,60 @@
 }
 
 #else //MOZ_XUL_APP
+
+// Set desktop startup ID to the passed ID, if there is one, so that any created
+// windows get created with the right window manager metadata, and any windows
+// that get new tabs and are activated also get the right WM metadata.
+// If there is no desktop startup ID, then use the X event's timestamp
+// for _NET_ACTIVE_WINDOW when the window gets focused or shown.
+static void
+SetDesktopStartupIDOrTimestamp(const nsACString& aDesktopStartupID,
+                               PRUint32 aTimestamp) {
+#ifdef MOZ_WIDGET_GTK2
+  nsGTKToolkit* toolkit = GetGTKToolkit();
+  if (!toolkit)
+    return;
+  if (!aDesktopStartupID.IsEmpty()) {
+    toolkit->SetDesktopStartupID(aDesktopStartupID);
+  } else {
+    toolkit->SetFocusTimestamp(aTimestamp);
+  }
+#endif
+}
+
+static PRBool
+FindExtensionParameterInCommand(const char* aParameterName,
+                                const nsACString& aCommand,
+                                char aSeparator,
+                                nsACString* aValue)
+{
+  nsCAutoString searchFor;
+  searchFor.Append(aSeparator);
+  searchFor.Append(aParameterName);
+  searchFor.Append('=');
+
+  nsACString::const_iterator start, end;
+  aCommand.BeginReading(start);
+  aCommand.EndReading(end);
+  if (!FindInReadable(searchFor, start, end))
+    return PR_FALSE;
+
+  nsACString::const_iterator charStart, charEnd;
+  charStart = end;
+  aCommand.EndReading(charEnd);
+  nsACString::const_iterator idStart = charStart, idEnd;
+  if (FindCharInReadable(aSeparator, charStart, charEnd)) {
+    idEnd = charStart;
+  } else {
+    idEnd = charEnd;
+  }
+  *aValue = nsDependentCSubstring(idStart, idEnd);
+  return PR_TRUE;
+}
+
 const char*
-nsGTKRemoteService::HandleCommand(char* aCommand, nsIDOMWindow* aWindow)
+nsGTKRemoteService::HandleCommand(char* aCommand, nsIDOMWindow* aWindow,
+                                  PRUint32 aTimestamp)
 {
   nsresult rv;
 
@@ -314,6 +396,12 @@
 #endif
 
   if (!command.EqualsLiteral("ping")) {
+    nsCAutoString desktopStartupID;
+    nsDependentCString cmd(aCommand);
+    FindExtensionParameterInCommand("DESKTOP_STARTUP_ID",
+                                    cmd, '\n',
+                                    &desktopStartupID);
+
     char* argv[3] = {"dummyappname", "-remote", aCommand};
     rv = cmdline->Init(3, argv, nsnull, nsICommandLine::STATE_REMOTE_EXPLICIT);
     if (NS_FAILED(rv))
@@ -322,6 +410,8 @@
     if (aWindow)
       cmdline->SetWindowContext(aWindow);
 
+    SetDesktopStartupIDOrTimestamp(desktopStartupID, aTimestamp);
+
     rv = cmdline->Run();
     if (NS_ERROR_ABORT == rv)
       return "500 command not parseable";
@@ -333,7 +423,8 @@
 }
 
 const char*
-nsGTKRemoteService::HandleCommandLine(char* aBuffer, nsIDOMWindow* aWindow)
+nsGTKRemoteService::HandleCommandLine(char* aBuffer, nsIDOMWindow* aWindow,
+                                      PRUint32 aTimestamp)
 {
   nsresult rv;
 
@@ -364,6 +455,8 @@
   if (NS_FAILED(rv))
     return "509 internal error";
 
+  nsCAutoString desktopStartupID;
+
   char **argv = (char**) malloc(sizeof(char*) * argc);
   if (!argv) return "509 internal error";
 
@@ -372,6 +465,12 @@
   for (int i = 0; i < argc; ++i) {
     argv[i] = aBuffer + TO_LITTLE_ENDIAN32(offset[i]);
 
+    if (i == 0) {
+      nsDependentCString cmd(argv[0]);
+      FindExtensionParameterInCommand("DESKTOP_STARTUP_ID",
+                                      cmd, ' ',
+                                      &desktopStartupID);
+    }
 #ifdef DEBUG_bsmedberg
     printf("  argv[%i]:\t%s\n", i, argv[i]);
 #endif
@@ -386,7 +485,10 @@
   if (aWindow)
     cmdline->SetWindowContext(aWindow);
 
+  SetDesktopStartupIDOrTimestamp(desktopStartupID, aTimestamp);
+
   rv = cmdline->Run();
+  
   if (NS_ERROR_ABORT == rv)
     return "500 command not parseable";
   
@@ -486,7 +588,7 @@
       return FALSE;
 
     // cool, we got the property data.
-    const char *response = HandleCommand(data, window);
+    const char *response = HandleCommand(data, window, pevent->time);
 
     // put the property onto the window as the response
     XChangeProperty (GDK_DISPLAY(), GDK_WINDOW_XWINDOW(pevent->window),
@@ -531,7 +633,7 @@
       return FALSE;
 
     // cool, we got the property data.
-    const char *response = HandleCommandLine(data, window);
+    const char *response = HandleCommandLine(data, window, pevent->time);
 
     // put the property onto the window as the response
     XChangeProperty (GDK_DISPLAY(), GDK_WINDOW_XWINDOW(pevent->window),
--- toolkit/components/remote/nsGTKRemoteService.h
+++ toolkit/components/remote/nsGTKRemoteService.h
@@ -80,10 +80,12 @@
                                         nsIWeakReference* aData,
                                         void* aClosure);
 
-  static const char* HandleCommand(char* aCommand, nsIDOMWindow* aWindow);
+  static const char* HandleCommand(char* aCommand, nsIDOMWindow* aWindow,
+                                  PRUint32 aTimestamp);
 
 #ifdef MOZ_XUL_APP
-  static const char* HandleCommandLine(char* aBuffer, nsIDOMWindow* aWindow);
+  static const char* HandleCommandLine(char* aBuffer, nsIDOMWindow* aWindow,
+                                      PRUint32 aTimestamp);
 #endif
 
   static gboolean HandlePropertyChange(GtkWidget *widget,
--- toolkit/library/Makefile.in
+++ toolkit/library/Makefile.in
@@ -344,6 +344,10 @@
 EXTRA_DSO_LDOPTS += $(XLDFLAGS) $(XLIBS) $(MOZ_GTK_LDFLAGS) $(MOZ_XFT_LIBS) $(MOZ_GTK2_LIBS) $(XT_LIBS)
 endif
 
+ifdef MOZ_ENABLE_STARTUP_NOTIFICATION
+EXTRA_DSO_LDOPTS += $(MOZ_STARTUP_NOTIFICATION_LIBS)
+endif
+
 ifdef MOZ_ENABLE_XPRINT
 EXTRA_DSO_LDOPTS += $(MOZ_XPRINT_LDFLAGS)
 endif
--- toolkit/xre/Makefile.in
+++ toolkit/xre/Makefile.in
@@ -69,6 +69,7 @@
 	shellservice \
 	string \
 	uriloader \
+	layout \
 	widget \
 	windowwatcher \
 	xpcom \
--- widget/src/gtk2/Makefile.in
+++ widget/src/gtk2/Makefile.in
@@ -97,6 +97,7 @@
 		$(MOZ_COMPONENT_LIBS) \
 		-lgkgfx \
 		-lgtkxtbin \
+		$(MOZ_STARTUP_NOTIFICATION_LIBS) \
 		$(XLDFLAGS) \
 		$(XLIBS) \
 		$(MOZ_GTK2_LIBS)
@@ -107,14 +108,15 @@
 
 EXPORTS		= \
                 nsIGdkPixbufImage.h \
+		nsGTKToolkit.h \
 		mozdrawingarea.h \
 		mozcontainer.h \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
-CFLAGS		+= $(MOZ_GTK2_CFLAGS)
-CXXFLAGS	+= $(MOZ_GTK2_CFLAGS)
+CFLAGS		+= $(MOZ_GTK2_CFLAGS) $(MOZ_STARTUP_NOTIFICATION_CFLAGS)
+CXXFLAGS	+= $(MOZ_GTK2_CFLAGS) $(MOZ_STARTUP_NOTIFICATION_CFLAGS)
 
 DEFINES         += -DUSE_XIM
 
--- widget/src/gtk2/nsGTKToolkit.h
+++ widget/src/gtk2/nsGTKToolkit.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef GTKTOOLKIT_H      
+#define GTKTOOLKIT_H
+
+#include "nsIToolkit.h"
+#include "nsString.h"
+#include <gtk/gtk.h>
+
+/**
+ * Wrapper around the thread running the message pump.
+ * The toolkit abstraction is necessary because the message pump must
+ * execute within the same thread that created the widget under Win32.
+ */ 
+
+class nsGTKToolkit : public nsIToolkit
+{
+public:
+    nsGTKToolkit();
+    virtual ~nsGTKToolkit();
+
+    NS_DECL_ISUPPORTS
+
+    NS_IMETHOD    Init(PRThread *aThread);
+
+    void          CreateSharedGC(void);
+    GdkGC         *GetSharedGC(void);
+    
+    /**
+     * Get/set our value of DESKTOP_STARTUP_ID. When non-empty, this is applied
+     * to the next toplevel window to be shown or focused (and then immediately
+     * cleared).
+     */ 
+    void SetDesktopStartupID(const nsACString& aID) { mDesktopStartupID = aID; }
+    void GetDesktopStartupID(nsACString* aID) { *aID = mDesktopStartupID; }
+
+    /**
+     * Get/set the timestamp value to be used, if non-zero, to focus the
+     * next top-level window to be shown or focused (upon which it is cleared).
+     */
+    void SetFocusTimestamp(PRUint32 aTimestamp) { mFocusTimestamp = aTimestamp; }
+    PRUint32 GetFocusTimestamp() { return mFocusTimestamp; }
+
+private:
+    GdkGC         *mSharedGC;
+    nsCString      mDesktopStartupID;
+    PRUint32       mFocusTimestamp;
+};
+
+#endif  // GTKTOOLKIT_H
--- widget/src/gtk2/nsToolkit.cpp
+++ widget/src/gtk2/nsToolkit.cpp
@@ -38,7 +38,7 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "nscore.h"  // needed for 'nsnull'
-#include "nsToolkit.h"
+#include "nsGTKToolkit.h"
 
 //
 // Static thread local storage index of the Toolkit 
@@ -51,9 +51,10 @@
 // constructor
 //
 //-------------------------------------------------------------------------
-nsToolkit::nsToolkit()
+nsGTKToolkit::nsGTKToolkit()
 {
     mSharedGC = nsnull;
+    mFocusTimestamp = 0;
 }
 
 //-------------------------------------------------------------------------
@@ -61,7 +62,7 @@
 // destructor
 //
 //-------------------------------------------------------------------------
-nsToolkit::~nsToolkit()
+nsGTKToolkit::~nsGTKToolkit()
 {
     if (mSharedGC) {
         gdk_gc_unref(mSharedGC);
@@ -77,9 +78,9 @@
 //
 //-------------------------------------------------------------------------
 
-NS_IMPL_ISUPPORTS1(nsToolkit, nsIToolkit)
+NS_IMPL_ISUPPORTS1(nsGTKToolkit, nsIToolkit)
 
-void nsToolkit::CreateSharedGC(void)
+void nsGTKToolkit::CreateSharedGC(void)
 {
     GdkPixmap *pixmap;
 
@@ -91,7 +92,7 @@
     gdk_pixmap_unref(pixmap);
 }
 
-GdkGC *nsToolkit::GetSharedGC(void)
+GdkGC *nsGTKToolkit::GetSharedGC(void)
 {
     return gdk_gc_ref(mSharedGC);
 }
@@ -100,7 +101,7 @@
 //
 //
 //-------------------------------------------------------------------------
-NS_IMETHODIMP nsToolkit::Init(PRThread *aThread)
+NS_IMETHODIMP nsGTKToolkit::Init(PRThread *aThread)
 {
     CreateSharedGC();
 
@@ -135,7 +136,7 @@
         // Create a new toolkit for this thread...
         //
         if (!toolkit) {
-            toolkit = new nsToolkit();
+            toolkit = new nsGTKToolkit();
 
             if (!toolkit) {
                 rv = NS_ERROR_OUT_OF_MEMORY;
--- widget/src/xremoteclient/XRemoteClient.cpp
+++ widget/src/xremoteclient/XRemoteClient.cpp
@@ -173,6 +173,7 @@
 nsresult
 XRemoteClient::SendCommand (const char *aProgram, const char *aUsername,
                             const char *aProfile, const char *aCommand,
+                            const char* aDesktopStartupID,
                             char **aResponse, PRBool *aWindowFound)
 {
   PR_LOG(sRemoteLm, PR_LOG_DEBUG, ("XRemoteClient::SendCommand"));
@@ -198,7 +199,7 @@
 
     if (NS_SUCCEEDED(rv)) {
       // send our command
-      rv = DoSendCommand(w, aCommand, aResponse, &destroyed);
+      rv = DoSendCommand(w, aCommand, aDesktopStartupID, aResponse, &destroyed);
 
       // if the window was destroyed, don't bother trying to free the
       // lock.
@@ -217,6 +218,7 @@
 XRemoteClient::SendCommandLine (const char *aProgram, const char *aUsername,
                                 const char *aProfile,
                                 PRInt32 argc, char **argv,
+                                const char* aDesktopStartupID,
                                 char **aResponse, PRBool *aWindowFound)
 {
   PR_LOG(sRemoteLm, PR_LOG_DEBUG, ("XRemoteClient::SendCommandLine"));
@@ -242,7 +244,7 @@
 
     if (NS_SUCCEEDED(rv)) {
       // send our command
-      rv = DoSendCommandLine(w, argc, argv, aResponse, &destroyed);
+      rv = DoSendCommandLine(w, argc, argv, aDesktopStartupID, aResponse, &destroyed);
 
       // if the window was destroyed, don't bother trying to free the
       // lock.
@@ -643,6 +645,7 @@
 
 nsresult
 XRemoteClient::DoSendCommand(Window aWindow, const char *aCommand,
+                             const char* aDesktopStartupID,
                              char **aResponse, PRBool *aDestroyed)
 {
   *aDestroyed = PR_FALSE;
@@ -651,9 +654,28 @@
      ("(writing " MOZILLA_COMMAND_PROP " \"%s\" to 0x%x)\n",
       aCommand, (unsigned int) aWindow));
 
+  // We add the DESKTOP_STARTUP_ID setting as an extra line of
+  // the command string. Firefox ignores all lines but the first.
+  static char desktopStartupPrefix[] = "\nDESKTOP_STARTUP_ID=";
+
+  PRInt32 len = strlen(aCommand);
+  if (aDesktopStartupID) {
+    len += sizeof(desktopStartupPrefix) - 1 + strlen(aDesktopStartupID);
+  }
+  char* buffer = (char*)malloc(len + 1);
+  if (!buffer)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  strcpy(buffer, aCommand);
+  if (aDesktopStartupID) {
+    strcat(buffer, desktopStartupPrefix);
+    strcat(buffer, aDesktopStartupID);
+  }
+
   XChangeProperty (mDisplay, aWindow, mMozCommandAtom, XA_STRING, 8,
-           PropModeReplace, (unsigned char *)aCommand,
-           strlen(aCommand));
+           PropModeReplace, (unsigned char *)buffer, len);
+
+  free(buffer);
 
   if (!WaitForResponse(aWindow, aResponse, aDestroyed, mMozCommandAtom))
     return NS_ERROR_FAILURE;
@@ -663,7 +685,7 @@
 
 /* like strcpy, but return the char after the final null */
 static char*
-estrcpy(char* s, char* d)
+estrcpy(const char* s, char* d)
 {
   while (*s)
     *d++ = *s++;
@@ -674,6 +696,7 @@
 
 nsresult
 XRemoteClient::DoSendCommandLine(Window aWindow, PRInt32 argc, char **argv,
+                                 const char* aDesktopStartupID,
                                  char **aResponse, PRBool *aDestroyed)
 {
   int i;
@@ -690,9 +713,16 @@
   // [argc][offsetargv0][offsetargv1...]<workingdir>\0<argv[0]>\0argv[1]...\0
   // (offset is from the beginning of the buffer)
 
+  static char desktopStartupPrefix[] = " DESKTOP_STARTUP_ID=";
+
   PRInt32 argvlen = strlen(cwdbuf);
-  for (i = 0; i < argc; ++i)
-    argvlen += strlen(argv[i]);
+  for (i = 0; i < argc; ++i) {
+    PRInt32 len = strlen(argv[i]);
+    if (i == 0 && aDesktopStartupID) {
+      len += strlen(desktopStartupPrefix) + strlen(aDesktopStartupID);
+    }
+    argvlen += len;
+  }
 
   PRInt32* buffer = (PRInt32*) malloc(argvlen + argc + 1 +
                                       sizeof(PRInt32) * (argc + 1));
@@ -708,6 +738,10 @@
   for (int i = 0; i < argc; ++i) {
     buffer[i + 1] = TO_LITTLE_ENDIAN32(bufend - ((char*) buffer));
     bufend = estrcpy(argv[i], bufend);
+    if (i == 0 && aDesktopStartupID) {
+      bufend = estrcpy(desktopStartupPrefix, bufend - 1);
+      bufend = estrcpy(aDesktopStartupID, bufend - 1);
+    }
   }
 
 #ifdef DEBUG_bsmedberg
--- widget/src/xremoteclient/XRemoteClient.h
+++ widget/src/xremoteclient/XRemoteClient.h
@@ -48,10 +48,12 @@
   virtual nsresult Init();
   virtual nsresult SendCommand(const char *aProgram, const char *aUsername,
                                const char *aProfile, const char *aCommand,
+                               const char* aDesktopStartupID,
                                char **aResponse, PRBool *aSucceeded);
   virtual nsresult SendCommandLine(const char *aProgram, const char *aUsername,
                                    const char *aProfile,
                                    PRInt32 argc, char **argv,
+                                   const char* aDesktopStartupID,
                                    char **aResponse, PRBool *aSucceeded);
   void Shutdown();
 
@@ -67,10 +69,12 @@
                                    PRBool aSupportsCommandLine);
   nsresult       DoSendCommand    (Window aWindow,
                                    const char *aCommand,
+                                   const char* aDesktopStartupID,
                                    char **aResponse,
                                    PRBool *aDestroyed);
   nsresult       DoSendCommandLine(Window aWindow,
                                    PRInt32 argc, char **argv,
+                                   const char* aDesktopStartupID,
                                    char **aResponse,
                                    PRBool *aDestroyed);
   PRBool         WaitForResponse  (Window aWindow, char **aResponse,
--- widget/src/xremoteclient/mozilla-xremote-client.cpp
+++ widget/src/xremoteclient/mozilla-xremote-client.cpp
@@ -40,6 +40,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <plgetopt.h>
+#include <prenv.h>
 #ifdef MOZ_WIDGET_PHOTON
 #include "PhRemoteClient.h"
 #else
@@ -99,7 +100,7 @@
   // send the command - it doesn't get any easier than this
   PRBool success = PR_FALSE;
   char *error = 0;
-  rv = client.SendCommand(browser, username, profile, command,
+  rv = client.SendCommand(browser, username, profile, command, nsnull,
                           &error, &success);
 
   // failed to send command
--- widget/src/xremoteclient/nsRemoteClient.h
+++ widget/src/xremoteclient/nsRemoteClient.h
@@ -85,6 +85,7 @@
    */
   virtual nsresult SendCommand(const char *aProgram, const char *aUsername,
                                const char *aProfile, const char *aCommand,
+                               const char* aDesktopStartupID,
                                char **aResponse, PRBool *aSucceeded) = 0;
 
   /**
@@ -97,6 +98,7 @@
   virtual nsresult SendCommandLine(const char *aProgram, const char *aUsername,
                                    const char *aProfile,
                                    PRInt32 argc, char **argv,
+                                   const char* aDesktopStartupID,
                                    char **aResponse, PRBool *aSucceeded) = 0;
 };
 
Index: widget/src/gtk2/nsWindow.cpp
===================================================================
RCS file: /cvsroot/mozilla/widget/src/gtk2/nsWindow.cpp,v
retrieving revision 1.145.2.12
diff -u -p -6 -r1.145.2.12 nsWindow.cpp
--- widget/src/gtk2/nsWindow.cpp	25 Jan 2007 02:22:26 -0000	1.145.2.12
+++ widget/src/gtk2/nsWindow.cpp	19 Feb 2007 09:39:54 -0000
@@ -37,13 +37,13 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "prlink.h"
 
 #include "nsWindow.h"
-#include "nsToolkit.h"
+#include "nsGTKToolkit.h"
 #include "nsIRenderingContext.h"
 #include "nsIRegion.h"
 #include "nsIRollupListener.h"
 #include "nsIMenuRollup.h"
 #include "nsIDOMNode.h"
 
@@ -55,12 +55,17 @@
 #include "nsGtkCursors.h"
 
 #include <gtk/gtkwindow.h>
 #include <gdk/gdkx.h>
 #include <gdk/gdkkeysyms.h>
 
+#ifdef MOZ_ENABLE_STARTUP_NOTIFICATION
+#define SN_API_NOT_YET_FROZEN
+#include <startup-notification-1.0/libsn/sn.h>
+#endif
+
 #include "gtk2xtbin.h"
 
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 #include "nsIServiceManager.h"
 #include "nsGfxCIID.h"
@@ -657,12 +662,76 @@ nsWindow::SetSizeMode(PRInt32 aMode)
 NS_IMETHODIMP
 nsWindow::Enable(PRBool aState)
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
+typedef void (* SetUserTimeFunc)(GdkWindow* aWindow, guint32 aTimestamp);
+
+static void
+SetUserTimeAndStartupIDForActivatedWindow(GtkWidget* aWindow)
+{
+    nsCOMPtr<nsIToolkit> toolkit;
+    NS_GetCurrentToolkit(getter_AddRefs(toolkit));
+    if (!toolkit)
+        return;
+
+    nsGTKToolkit* GTKToolkit = NS_STATIC_CAST(nsGTKToolkit*,
+        NS_STATIC_CAST(nsIToolkit*, toolkit));
+    nsCAutoString desktopStartupID;
+    GTKToolkit->GetDesktopStartupID(&desktopStartupID);
+    if (desktopStartupID.IsEmpty()) {
+        PRUint32 timestamp = GTKToolkit->GetFocusTimestamp();
+        if (timestamp) {
+            gdk_window_focus(aWindow->window, timestamp);
+            GTKToolkit->SetFocusTimestamp(0);
+        }
+        return;
+    }
+
+#ifdef MOZ_ENABLE_STARTUP_NOTIFICATION
+    GdkDrawable* drawable = GDK_DRAWABLE(aWindow->window);
+    GtkWindow* win = GTK_WINDOW(aWindow);
+    if (!win) {
+        NS_WARNING("Passed in widget was not a GdkWindow!");
+        return;
+    }
+    GdkScreen* screen = gtk_window_get_screen(win);
+    SnDisplay* snd =
+        sn_display_new(gdk_x11_drawable_get_xdisplay(drawable), nsnull, nsnull);
+    if (!snd)
+        return;
+    SnLauncheeContext* ctx =
+        sn_launchee_context_new(snd, gdk_screen_get_number(screen),
+                                desktopStartupID.get());
+    if (!ctx) {
+        sn_display_unref(snd);
+        return;
+    }
+
+    if (sn_launchee_context_get_id_has_timestamp(ctx)) {
+        PRLibrary* gtkLibrary;
+        SetUserTimeFunc setUserTimeFunc = (SetUserTimeFunc)
+            PR_FindFunctionSymbolAndLibrary("gdk_x11_window_set_user_time", &gtkLibrary);
+        if (setUserTimeFunc) {
+            setUserTimeFunc(aWindow->window, sn_launchee_context_get_timestamp(ctx));
+            PR_UnloadLibrary(gtkLibrary);
+        }
+    }
+
+    sn_launchee_context_setup_window(ctx, gdk_x11_drawable_get_xid(drawable));
+
+    sn_launchee_context_complete(ctx);
+
+    sn_launchee_context_unref(ctx);
+    sn_display_unref(snd);
+#endif
+                  
+    GTKToolkit->SetDesktopStartupID(EmptyCString());
+}
+
 NS_IMETHODIMP
 nsWindow::SetFocus(PRBool aRaise)
 {
     // Make sure that our owning widget has focus.  If it doesn't try to
     // grab it.  Note that we don't set our focus flag in this case.
 
@@ -677,12 +746,16 @@ nsWindow::SetFocus(PRBool aRaise)
         return NS_ERROR_FAILURE;
 
     // Raise the window if someone passed in PR_TRUE and the prefs are
     // set properly.
     GtkWidget *toplevelWidget = gtk_widget_get_toplevel(owningWidget);
 
+    if (toplevelWidget && aRaise) {
+        SetUserTimeAndStartupIDForActivatedWindow(toplevelWidget);
+    }
+
     if (gRaiseWindows && aRaise && toplevelWidget &&
         !GTK_WIDGET_HAS_FOCUS(owningWidget) &&
         !GTK_WIDGET_HAS_FOCUS(toplevelWidget)) {
         GtkWidget* top_window = nsnull;
         GetToplevelWidget(&top_window);
         if (top_window && (GTK_WIDGET_VISIBLE(top_window)))
@@ -1164,13 +1237,13 @@ nsWindow::GetNativeData(PRUint32 aDataTy
     case NS_NATIVE_DISPLAY:
         return GDK_DISPLAY();
         break;
 
     case NS_NATIVE_GRAPHIC: {
         NS_ASSERTION(nsnull != mToolkit, "NULL toolkit, unable to get a GC");
-        return (void *)NS_STATIC_CAST(nsToolkit *, mToolkit)->GetSharedGC();
+        return (void *)NS_STATIC_CAST(nsGTKToolkit *, mToolkit)->GetSharedGC();
         break;
     }
 
     case NS_NATIVE_SHELLWIDGET:
         return (void *) mShell;
 
@@ -2799,22 +2872,27 @@ nsWindow::NativeShow (PRBool  aAction)
         // going to need it, because GTK won't let us unset the mask properly
         // later.
         // So, we delay setting the mask until the last moment: when the window
         // is shown.
         // XXX that may or may not be true for GTK+ 2.x
         if (mTransparencyBitmap) {
-          ApplyTransparencyBitmap();
+            ApplyTransparencyBitmap();
         }
 
         // unset our flag now that our window has been shown
         mNeedsShow = PR_FALSE;
 
         if (mIsTopLevel) {
             moz_drawingarea_set_visibility(mDrawingarea, aAction);
             gtk_widget_show(GTK_WIDGET(mContainer));
             gtk_widget_show(mShell);
+
+            // Set up usertime/startupID metadata for the created window.
+            if (mWindowType != eWindowType_invisible) {
+                SetUserTimeAndStartupIDForActivatedWindow(mShell);
+            }
         }
         else if (mContainer) {
             moz_drawingarea_set_visibility(mDrawingarea, TRUE);
             gtk_widget_show(GTK_WIDGET(mContainer));
         }
         else if (mDrawingarea) {
Index: toolkit/xre/nsAppRunner.cpp
===================================================================
RCS file: /cvsroot/mozilla/toolkit/xre/nsAppRunner.cpp,v
retrieving revision 1.113.2.23
diff -u -p -6 -r1.113.2.23 nsAppRunner.cpp
--- toolkit/xre/nsAppRunner.cpp	9 Sep 2007 06:08:37 -0000	1.113.2.23
+++ toolkit/xre/nsAppRunner.cpp	15 Oct 2007 08:01:52 -0000
@@ -96,12 +96,13 @@
 #include "nsIWindowWatcher.h"
 #include "nsIXULAppInfo.h"
 #include "nsIXULRuntime.h"
 #ifdef XP_WIN
 #include "nsIWinAppHelper.h"
 #endif
+#include "nsIScriptGlobalObject.h"
 
 #include "nsCRT.h"
 #include "nsCOMPtr.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsEmbedCID.h"
@@ -112,12 +113,18 @@
 #include "nsXPFEComponentsCID.h"
 
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsXULAppAPI.h"
 #include "nsXREDirProvider.h"
 #include "nsToolkitCompsCID.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMWindowInternal.h"
+#include "nsIBaseWindow.h"
+#include "nsIWidget.h"
+#include "nsIDocShell.h"
+#include "nsAppShellCID.h"
 
 #include "nsINIParser.h"
 
 #include "InstallCleanupDefines.h"
 
 #include <stdlib.h>
@@ -260,12 +267,16 @@ static int    gRestartArgc;
 static char **gRestartArgv;
 
 #if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_GTK2)
 #include <gtk/gtk.h>
 #endif //MOZ_WIDGET_GTK || MOZ_WIDGET_GTK2
 
+#if defined(MOZ_WIDGET_GTK2)
+#include "nsGTKToolkit.h"
+#endif
+
 #if defined(MOZ_WIDGET_QT)
 #include <qapplication.h>
 #endif
 
 // Save the path of the given file to the specified environment variable.
 static void
@@ -1121,13 +1132,13 @@ DumpVersion()
 }
 
 #ifdef MOZ_ENABLE_XREMOTE
 // use int here instead of a PR type since it will be returned
 // from main - just to keep types consistent
 static int
-HandleRemoteArgument(const char* remote)
+HandleRemoteArgument(const char* remote, const char* aDesktopStartupID)
 {
   nsresult rv;
   ArgResult ar;
 
   const char *profile = 0;
   nsCAutoString program(gAppData->name);
@@ -1162,13 +1173,13 @@ HandleRemoteArgument(const char* remote)
     return 1;
   }
 
   nsXPIDLCString response;
   PRBool success = PR_FALSE;
   rv = client.SendCommand(program.get(), username, profile, remote,
-                           getter_Copies(response), &success);
+                          aDesktopStartupID, getter_Copies(response), &success);
   // did the command fail?
   if (NS_FAILED(rv)) {
     PR_fprintf(PR_STDERR, "Error: Failed to send command: %s\n",
                response ? response.get() : "No response included");
     return 1;
   }
@@ -1179,13 +1190,13 @@ HandleRemoteArgument(const char* remote)
   }
 
   return 0;
 }
 
 static RemoteResult
-RemoteCommandLine()
+RemoteCommandLine(const char* aDesktopStartupID)
 {
   nsresult rv;
   ArgResult ar;
 
   nsCAutoString program(gAppData->name);
   ToLowerCase(program);
@@ -1211,13 +1222,13 @@ RemoteCommandLine()
   if (NS_FAILED(rv))
     return REMOTE_NOT_FOUND;
  
   nsXPIDLCString response;
   PRBool success = PR_FALSE;
   rv = client.SendCommandLine(program.get(), username, nsnull,
-                              gArgc, gArgv,
+                              gArgc, gArgv, aDesktopStartupID,
                               getter_Copies(response), &success);
   // did the command fail?
   if (NS_FAILED(rv) || !success)
     return REMOTE_NOT_FOUND;
 
   return REMOTE_FOUND;
@@ -2101,13 +2112,48 @@ public:
 };
 #endif
 
 #ifdef MOZ_WIDGET_GTK2
 #include "prlink.h"
 typedef void (*_g_set_application_name_fn)(const gchar *application_name);
-#endif
+
+static nsIWidget* GetMainWidget(nsIDOMWindow* aWindow)
+{
+  // get the native window for this instance
+  nsCOMPtr<nsIScriptGlobalObject> scriptObject
+    (do_QueryInterface(aWindow));
+  NS_ENSURE_TRUE(scriptObject, nsnull);
+
+  nsCOMPtr<nsIBaseWindow> baseWindow
+    (do_QueryInterface(scriptObject->GetDocShell()));
+  NS_ENSURE_TRUE(baseWindow, nsnull);
+
+  nsCOMPtr<nsIWidget> mainWidget;
+  baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
+  return mainWidget;
+}
+
+static nsGTKToolkit* GetGTKToolkit()
+{
+  nsCOMPtr<nsIAppShellService> svc = do_GetService(NS_APPSHELLSERVICE_CONTRACTID);
+  if (!svc)
+    return nsnull;
+  nsCOMPtr<nsIDOMWindowInternal> window;
+  svc->GetHiddenDOMWindow(getter_AddRefs(window));
+  if (!window)
+    return nsnull;
+  nsIWidget* widget = GetMainWidget(window);
+  if (!widget)
+    return nsnull;
+  nsIToolkit* toolkit = widget->GetToolkit();
+  if (!toolkit)
+    return nsnull;
+  return NS_STATIC_CAST(nsGTKToolkit*, toolkit);
+}
+
+#endif // MOZ_WIDGET_GTK2
 
 int
 XRE_main(int argc, char* argv[], const nsXREAppData* aAppData)
 {
   nsresult rv;
   ArgResult ar;
@@ -2285,12 +2331,25 @@ XRE_main(int argc, char* argv[], const n
 
       chromeReg->CheckForNewChrome();
     }
     return 0;
   }
 
+#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_GTK2) || defined(MOZ_ENABLE_XREMOTE)
+  // Stash DESKTOP_STARTUP_ID becaus gtk_init will clear it.
+#define HAVE_DESKTOP_STARTUP_ID
+  char* desktopStartupID = PR_GetEnv("DESKTOP_STARTUP_ID");
+  if (desktopStartupID) {
+    if (desktopStartupID[0] == 0) {
+      desktopStartupID = nsnull;
+    } else {
+      desktopStartupID = strdup(desktopStartupID);
+    }
+  }
+#endif
+
 #if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_GTK2)
   // setup for private colormap.  Ideally we'd like to do this
   // in nsAppShell::Create, but we need to get in before gtk
   // has been initialized to make sure everything is running
   // consistently.
 #if defined(MOZ_WIDGET_GTK2)
@@ -2313,12 +2372,13 @@ XRE_main(int argc, char* argv[], const n
   if (_g_set_application_name) {
     _g_set_application_name(gAppData->name);
   }
   if (glib2) {
     PR_UnloadLibrary(glib2);
   }
+  gtk_window_set_auto_startup_notification(PR_FALSE);
 #endif
 
   gtk_widget_set_default_visual(gdk_rgb_get_visual());
   gtk_widget_set_default_colormap(gdk_rgb_get_cmap());
 #endif /* MOZ_WIDGET_GTK || MOZ_WIDGET_GTK2 */
 
@@ -2377,18 +2437,18 @@ XRE_main(int argc, char* argv[], const n
   ar = CheckArg("remote", PR_TRUE, &xremotearg);
   if (ar == ARG_BAD) {
     PR_fprintf(PR_STDERR, "Error: -remote requires an argument\n");
     return 1;
   }
   if (ar) {
-    return HandleRemoteArgument(xremotearg);
+    return HandleRemoteArgument(xremotearg, desktopStartupID);
   }
 
   if (!PR_GetEnv("MOZ_NO_REMOTE")) {
     // Try to remote the entire command line. If this fails, start up normally.
-    RemoteResult rr = RemoteCommandLine();
+    RemoteResult rr = RemoteCommandLine(desktopStartupID);
     if (rr == REMOTE_FOUND)
       return 0;
     else if (rr == REMOTE_ARG_BAD)
       return 1;
   }
 #endif
@@ -2597,12 +2657,19 @@ XRE_main(int argc, char* argv[], const n
 
       NS_TIMELINE_ENTER("appStartup->CreateHiddenWindow");
       rv = appStartup->CreateHiddenWindow();
       NS_TIMELINE_LEAVE("appStartup->CreateHiddenWindow");
       NS_ENSURE_SUCCESS(rv, 1);
 
+#if defined(HAVE_DESKTOP_STARTUP_ID) && defined(MOZ_WIDGET_GTK2)
+      nsRefPtr<nsGTKToolkit> toolkit = GetGTKToolkit();
+      if (toolkit && desktopStartupID) {
+        toolkit->SetDesktopStartupID(nsDependentCString(desktopStartupID));
+      }
+#endif
+
       // Extension Compatibility Checking and Startup
       if (gAppData->flags & NS_XRE_ENABLE_EXTENSION_MANAGER) {
         nsCOMPtr<nsIExtensionManager> em(do_GetService("@mozilla.org/extensions/manager;1"));
         NS_ENSURE_TRUE(em, 1);
 
         ar = CheckArg("install-global-extension", PR_TRUE);
@@ -2791,12 +2858,31 @@ XRE_main(int argc, char* argv[], const n
       static char kEnvVar[MAXPATHLEN];
       sprintf(kEnvVar, "XRE_BINARY_PATH=%s", gBinaryPath);
       PR_SetEnv(kEnvVar);
     }
 #endif
 
+#if defined(HAVE_DESKTOP_STARTUP_ID) && defined(MOZ_TOOLKIT_GTK2)
+    nsGTKToolkit* toolkit = GetGTKToolkit();
+    if (toolkit) {
+      nsCAutoString currentDesktopStartupID;
+      toolkit->GetDesktopStartupID(&currentDesktopStartupID);
+      if (!currentDesktopStartupID.IsEmpty()) {
+        nsCAutoString desktopStartupEnv;
+        desktopStartupEnv.AssignLiteral("DESKTOP_STARTUP_ID=");
+        desktopStartupEnv.Append(currentDesktopStartupID);
+        // Leak it with extreme prejudice!
+        PR_SetEnv(ToNewCString(desktopStartupEnv));
+      }
+    }
+#endif
+
     rv = LaunchChild(nativeApp, appInitiatedRestart, upgraded ? -1 : 0);
     return rv == NS_ERROR_LAUNCHED_CHILD_PROCESS ? 0 : 1;
   }
 
+#ifdef HAVE_DESKTOP_STARTUP_ID
+  free(desktopStartupID);
+#endif
+
   return NS_FAILED(rv) ? 1 : 0;
 }
Index: config/system-headers
===================================================================
RCS file: /cvsroot/mozilla/config/system-headers,v
retrieving revision 3.4.4.3
diff -u -r3.4.4.3 system-headers
--- config/system-headers	18 Dec 2006 23:48:58 -0000	3.4.4.3
+++ config/system-headers	14 Nov 2007 09:20:50 -0000
@@ -931,3 +934,11 @@
 #if MOZ_NATIVE_ZLIB==1
 zlib.h
 #endif
+#ifdef MOZ_ENABLE_STARTUP_NOTIFICATION
+libsn/sn.h
+libsn/sn-common.h
+libsn/sn-launchee.h
+libsn/sn-launcher.h
+libsn/sn-monitor.h
+libsn/sn-util.h
+#endif