/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:expandtab:shiftwidth=2:tabstop=8: */ /* ***** 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 * Christopher Blizzard. * Portions created by the Initial Developer are Copyright (C) 2001 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Christopher Blizzard * Benjamin Smedberg * * 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 ***** */ #include "nsGTKRemoteService.h" #include // for XA_STRING #include #include // For some reason GTK+ doesn't include this file // automatically from gtk.h #include #include #include "nsIBaseWindow.h" #include "nsIDocShell.h" #include "nsIDOMWindow.h" #include "nsIGenericFactory.h" #include "nsILocalFile.h" #include "nsIObserverService.h" #include "nsIScriptGlobalObject.h" #include "nsIServiceManager.h" #include "nsIWeakReference.h" #include "nsIWidget.h" #include "nsCOMPtr.h" #include "nsString.h" #include "prprf.h" #include "prenv.h" #include "nsCRT.h" #ifdef MOZ_XUL_APP #include "nsICommandLineRunner.h" #include "nsXULAppAPI.h" #else #include "nsISuiteRemoteService.h" #endif #define MOZILLA_VERSION_PROP "_MOZILLA_VERSION" #define MOZILLA_LOCK_PROP "_MOZILLA_LOCK" #define MOZILLA_COMMAND_PROP "_MOZILLA_COMMAND" #define MOZILLA_RESPONSE_PROP "_MOZILLA_RESPONSE" #define MOZILLA_USER_PROP "_MOZILLA_USER" #define MOZILLA_PROFILE_PROP "_MOZILLA_PROFILE" #define MOZILLA_PROGRAM_PROP "_MOZILLA_PROGRAM" #define MOZILLA_COMMANDLINE_PROP "_MOZILLA_COMMANDLINE" #ifdef IS_BIG_ENDIAN #define TO_LITTLE_ENDIAN32(x) \ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) #else #define TO_LITTLE_ENDIAN32(x) (x) #endif #ifdef MOZ_XUL_APP const unsigned char kRemoteVersion[] = "5.1"; #else const unsigned char kRemoteVersion[] = "5.0"; #endif NS_IMPL_QUERY_INTERFACE2(nsGTKRemoteService, nsIRemoteService, nsIObserver) NS_IMETHODIMP_(nsrefcnt) nsGTKRemoteService::AddRef() { return 1; } NS_IMETHODIMP_(nsrefcnt) nsGTKRemoteService::Release() { return 1; } NS_IMETHODIMP nsGTKRemoteService::Startup(const char* aAppName, const char* aProfileName) { NS_ASSERTION(aAppName, "Don't pass a null appname!"); EnsureAtoms(); if (mServerWindow) return NS_ERROR_ALREADY_INITIALIZED; mAppName = aAppName; ToLowerCase(mAppName); mProfileName = aProfileName; mServerWindow = gtk_invisible_new(); gtk_widget_realize(mServerWindow); HandleCommandsFor(mServerWindow, nsnull); if (!mWindows.IsInitialized()) mWindows.Init(); mWindows.EnumerateRead(StartupHandler, this); nsCOMPtr obs (do_GetService("@mozilla.org/observer-service;1")); if (obs) { obs->AddObserver(this, "xpcom-shutdown", PR_FALSE); obs->AddObserver(this, "quit-application", PR_FALSE); } return NS_OK; } PLDHashOperator nsGTKRemoteService::StartupHandler(const void* aKey, nsIWeakReference* aData, void* aClosure) { GtkWidget* widget = (GtkWidget*) aKey; nsGTKRemoteService* aThis = (nsGTKRemoteService*) aClosure; aThis->HandleCommandsFor(widget, aData); return PL_DHASH_NEXT; } NS_IMETHODIMP nsGTKRemoteService::RegisterWindow(nsIDOMWindow* aWindow) { // get the native window for this instance nsCOMPtr scriptObject (do_QueryInterface(aWindow)); NS_ENSURE_TRUE(scriptObject, NS_ERROR_FAILURE); nsCOMPtr baseWindow (do_QueryInterface(scriptObject->GetDocShell())); NS_ENSURE_TRUE(baseWindow, NS_ERROR_FAILURE); nsCOMPtr mainWidget; baseWindow->GetMainWidget(getter_AddRefs(mainWidget)); NS_ENSURE_TRUE(mainWidget, NS_ERROR_FAILURE); // walk up the widget tree and find the toplevel window in the // hierarchy nsCOMPtr tempWidget (dont_AddRef(mainWidget->GetParent())); while (tempWidget) { tempWidget = dont_AddRef(tempWidget->GetParent()); if (tempWidget) mainWidget = tempWidget; } GtkWidget* widget = (GtkWidget*) mainWidget->GetNativeData(NS_NATIVE_SHELLWIDGET); NS_ENSURE_TRUE(widget, NS_ERROR_FAILURE); nsCOMPtr weak = do_GetWeakReference(aWindow); NS_ENSURE_TRUE(weak, NS_ERROR_FAILURE); if (!mWindows.IsInitialized()) mWindows.Init(); mWindows.Put(widget, weak); // If Startup() has already been called, immediately register this window. if (mServerWindow) { HandleCommandsFor(widget, weak); } return NS_OK; } NS_IMETHODIMP nsGTKRemoteService::Shutdown() { if (!mServerWindow) return NS_ERROR_NOT_INITIALIZED; gtk_widget_destroy(mServerWindow); mServerWindow = nsnull; return NS_OK; } NS_IMETHODIMP nsGTKRemoteService::Observe(nsISupports* aSubject, const char *aTopic, const PRUnichar *aData) { // This can be xpcom-shutdown or quit-application, but it's the same either // way. Shutdown(); return NS_OK; } #define ARRAY_LENGTH(array_) (sizeof(array_)/sizeof(array_[0])) // Minimize the roundtrips to the X server by getting all the atoms at once static char *XAtomNames[] = { MOZILLA_VERSION_PROP, MOZILLA_LOCK_PROP, MOZILLA_COMMAND_PROP, MOZILLA_RESPONSE_PROP, MOZILLA_USER_PROP, MOZILLA_PROFILE_PROP, MOZILLA_PROGRAM_PROP, MOZILLA_COMMANDLINE_PROP }; static Atom XAtoms[ARRAY_LENGTH(XAtomNames)]; void nsGTKRemoteService::EnsureAtoms(void) { if (sMozVersionAtom) return; XInternAtoms(GDK_DISPLAY(), XAtomNames, ARRAY_LENGTH(XAtomNames), False, XAtoms); int i = 0; sMozVersionAtom = XAtoms[i++]; sMozLockAtom = XAtoms[i++]; sMozCommandAtom = XAtoms[i++]; sMozResponseAtom = XAtoms[i++]; sMozUserAtom = XAtoms[i++]; sMozProfileAtom = XAtoms[i++]; sMozProgramAtom = XAtoms[i++]; sMozCommandLineAtom = XAtoms[i++]; } #ifndef MOZ_XUL_APP const char* nsGTKRemoteService::HandleCommand(char* aCommand, nsIDOMWindow* aWindow) { nsresult rv; nsCOMPtr remote (do_GetService("@mozilla.org/browser/xremoteservice;2")); if (!remote) return "509 internal error"; rv = remote->ParseCommand(aCommand, aWindow); if (NS_SUCCEEDED(rv)) return "200 executed command"; if (NS_ERROR_INVALID_ARG == rv) return "500 command not parseable"; if (NS_ERROR_NOT_IMPLEMENTED == rv) return "501 unrecognized command"; return "509 internal error"; } #else //MOZ_XUL_APP const char* nsGTKRemoteService::HandleCommand(char* aCommand, nsIDOMWindow* aWindow) { nsresult rv; nsCOMPtr cmdline (do_CreateInstance("@mozilla.org/toolkit/command-line;1", &rv)); if (NS_FAILED(rv)) return "509 internal error"; // 1) Make sure that it looks remotely valid with parens // 2) Treat ping() immediately and specially nsCAutoString command(aCommand); PRInt32 p1, p2; p1 = command.FindChar('('); p2 = command.FindChar(')'); if (p1 == kNotFound || p2 == kNotFound || p1 == 0 || p2 < p1) { return "500 command not parseable"; } command.Truncate(p1); command.Trim(" ", PR_TRUE, PR_TRUE); ToLowerCase(command); #ifdef DEBUG_bsmedberg printf("Processing xremote command: %s\n", command.get()); #endif if (!command.EqualsLiteral("ping")) { char* argv[3] = {"dummyappname", "-remote", aCommand}; rv = cmdline->Init(3, argv, nsnull, nsICommandLine::STATE_REMOTE_EXPLICIT); if (NS_FAILED(rv)) return "509 internal error"; if (aWindow) cmdline->SetWindowContext(aWindow); rv = cmdline->Run(); if (NS_ERROR_ABORT == rv) return "500 command not parseable"; if (NS_FAILED(rv)) return "509 internal error"; } return "200 executed command"; } const char* nsGTKRemoteService::HandleCommandLine(char* aBuffer, nsIDOMWindow* aWindow) { nsresult rv; nsCOMPtr cmdline (do_CreateInstance("@mozilla.org/toolkit/command-line;1", &rv)); if (NS_FAILED(rv)) return "509 internal error"; // the commandline property is constructed as an array of PRInt32 // followed by a series of null-terminated strings: // // [argc][offsetargv0][offsetargv1...]\0\0argv[1]...\0 // (offset is from the beginning of the buffer) PRInt32 argc = TO_LITTLE_ENDIAN32(*NS_REINTERPRET_CAST(PRInt32*, aBuffer)); char *wd = aBuffer + ((argc + 1) * sizeof(PRInt32)); #ifdef DEBUG_bsmedberg printf("Receiving command line:\n" " wd:\t%s\n" " argc:\t%i\n", wd, argc); #endif nsCOMPtr lf; rv = NS_NewNativeLocalFile(nsDependentCString(wd), PR_TRUE, getter_AddRefs(lf)); if (NS_FAILED(rv)) return "509 internal error"; char **argv = (char**) malloc(sizeof(char*) * argc); if (!argv) return "509 internal error"; PRInt32 *offset = NS_REINTERPRET_CAST(PRInt32*, aBuffer) + 1; for (int i = 0; i < argc; ++i) { argv[i] = aBuffer + TO_LITTLE_ENDIAN32(offset[i]); #ifdef DEBUG_bsmedberg printf(" argv[%i]:\t%s\n", i, argv[i]); #endif } rv = cmdline->Init(argc, argv, lf, nsICommandLine::STATE_REMOTE_AUTO); free (argv); if (NS_FAILED(rv)) { return "509 internal error"; } if (aWindow) cmdline->SetWindowContext(aWindow); rv = cmdline->Run(); if (NS_ERROR_ABORT == rv) return "500 command not parseable"; if (NS_FAILED(rv)) return "509 internal error"; return "200 executed command"; } #endif // MOZ_XUL_APP void nsGTKRemoteService::HandleCommandsFor(GtkWidget* widget, nsIWeakReference* aWindow) { #ifdef MOZ_WIDGET_GTK2 g_signal_connect(G_OBJECT(widget), "property_notify_event", G_CALLBACK(HandlePropertyChange), aWindow); #else // GTK+ gtk_signal_connect(GTK_OBJECT(widget), "property_notify_event", GTK_SIGNAL_FUNC(HandlePropertyChange), aWindow); #endif gtk_widget_add_events(widget, GDK_PROPERTY_CHANGE_MASK); Window window = GDK_WINDOW_XWINDOW(widget->window); // set our version XChangeProperty(GDK_DISPLAY(), window, sMozVersionAtom, XA_STRING, 8, PropModeReplace, kRemoteVersion, sizeof(kRemoteVersion) - 1); // get our username unsigned char *logname; logname = (unsigned char*) PR_GetEnv("LOGNAME"); if (logname) { // set the property on the window if it's available XChangeProperty(GDK_DISPLAY(), window, sMozUserAtom, XA_STRING, 8, PropModeReplace, logname, strlen((char*) logname)); } XChangeProperty(GDK_DISPLAY(), window, sMozProgramAtom, XA_STRING, 8, PropModeReplace, (unsigned char*) mAppName.get(), mAppName.Length()); if (!mProfileName.IsEmpty()) { XChangeProperty(GDK_DISPLAY(), window, sMozProfileAtom, XA_STRING, 8, PropModeReplace, (unsigned char*) mProfileName.get(), mProfileName.Length()); } } #ifdef MOZ_WIDGET_GTK2 #define CMP_GATOM_XATOM(gatom,xatom) (gatom == gdk_x11_xatom_to_atom(xatom)) #else #define CMP_GATOM_XATOM(gatom,xatom) (gatom == xatom) #endif gboolean nsGTKRemoteService::HandlePropertyChange(GtkWidget *aWidget, GdkEventProperty *pevent, nsIWeakReference* aThis) { nsCOMPtr window (do_QueryReferent(aThis)); if (pevent->state == GDK_PROPERTY_NEW_VALUE && CMP_GATOM_XATOM(pevent->atom, sMozCommandAtom)) { // We got a new command atom. int result; Atom actual_type; int actual_format; unsigned long nitems, bytes_after; char *data = 0; result = XGetWindowProperty (GDK_DISPLAY(), GDK_WINDOW_XWINDOW(pevent->window), sMozCommandAtom, 0, /* long_offset */ (65536 / sizeof (long)), /* long_length */ True, /* atomic delete after */ XA_STRING, /* req_type */ &actual_type, /* actual_type return */ &actual_format, /* actual_format_return */ &nitems, /* nitems_return */ &bytes_after, /* bytes_after_return */ (unsigned char **)&data); /* prop_return (we only care about the first ) */ #ifdef DEBUG_bsmedberg printf("Handling command: %s\n", data); #endif // Failed to get property off the window? if (result != Success) return FALSE; // Failed to get the data off the window or it was the wrong type? if (!data || !TO_LITTLE_ENDIAN32(*NS_REINTERPRET_CAST(PRInt32*, data))) return FALSE; // cool, we got the property data. const char *response = HandleCommand(data, window); // put the property onto the window as the response XChangeProperty (GDK_DISPLAY(), GDK_WINDOW_XWINDOW(pevent->window), sMozResponseAtom, XA_STRING, 8, PropModeReplace, (const unsigned char *)response, strlen (response)); XFree(data); return TRUE; } #ifdef MOZ_XUL_APP if (pevent->state == GDK_PROPERTY_NEW_VALUE && CMP_GATOM_XATOM(pevent->atom, sMozCommandLineAtom)) { // We got a new commandline atom. int result; Atom actual_type; int actual_format; unsigned long nitems, bytes_after; char *data = 0; result = XGetWindowProperty (GDK_DISPLAY(), GDK_WINDOW_XWINDOW(pevent->window), sMozCommandLineAtom, 0, /* long_offset */ (65536 / sizeof (long)), /* long_length */ True, /* atomic delete after */ XA_STRING, /* req_type */ &actual_type, /* actual_type return */ &actual_format, /* actual_format_return */ &nitems, /* nitems_return */ &bytes_after, /* bytes_after_return */ (unsigned char **)&data); /* prop_return (we only care about the first ) */ // Failed to get property off the window? if (result != Success) return FALSE; // Failed to get the data off the window or it was the wrong type? if (!data || !TO_LITTLE_ENDIAN32(*NS_REINTERPRET_CAST(PRInt32*, data))) return FALSE; // cool, we got the property data. const char *response = HandleCommandLine(data, window); // put the property onto the window as the response XChangeProperty (GDK_DISPLAY(), GDK_WINDOW_XWINDOW(pevent->window), sMozResponseAtom, XA_STRING, 8, PropModeReplace, (const unsigned char *)response, strlen (response)); XFree(data); return TRUE; } #endif //MOZ_XUL_APP if (pevent->state == GDK_PROPERTY_NEW_VALUE && CMP_GATOM_XATOM(pevent->atom, sMozResponseAtom)) { // client accepted the response. party on wayne. return TRUE; } if (pevent->state == GDK_PROPERTY_NEW_VALUE && CMP_GATOM_XATOM(pevent->atom, sMozLockAtom)) { // someone locked the window return TRUE; } return FALSE; } Atom nsGTKRemoteService::sMozVersionAtom; Atom nsGTKRemoteService::sMozLockAtom; Atom nsGTKRemoteService::sMozCommandAtom; Atom nsGTKRemoteService::sMozResponseAtom; Atom nsGTKRemoteService::sMozUserAtom; Atom nsGTKRemoteService::sMozProfileAtom; Atom nsGTKRemoteService::sMozProgramAtom; Atom nsGTKRemoteService::sMozCommandLineAtom; // {C0773E90-5799-4eff-AD03-3EBCD85624AC} #define NS_REMOTESERVICE_CID \ { 0xc0773e90, 0x5799, 0x4eff, { 0xad, 0x3, 0x3e, 0xbc, 0xd8, 0x56, 0x24, 0xac } } NS_GENERIC_FACTORY_CONSTRUCTOR(nsGTKRemoteService) static const nsModuleComponentInfo components[] = { { "Remote Service", NS_REMOTESERVICE_CID, "@mozilla.org/toolkit/remote-service;1", nsGTKRemoteServiceConstructor } }; NS_IMPL_NSGETMODULE(RemoteServiceModule, components)