Back to index

lightning-sunbird  0.9+nobinonly
nsCommandLineServiceMac.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator client code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Simon Fraser   <sfraser@netscape.com>
00024  *   Pierre Phaneuf <pp@ludusdesign.com>
00025  *   Mark Mentovai <mark@moxienet.com>
00026  *
00027  * Alternatively, the contents of this file may be used under the terms of
00028  * either the GNU General Public License Version 2 or later (the "GPL"), or
00029  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00030  * in which case the provisions of the GPL or the LGPL are applicable instead
00031  * of those above. If you wish to allow use of your version of this file only
00032  * under the terms of either the GPL or the LGPL, and not to allow others to
00033  * use your version of this file under the terms of the MPL, indicate your
00034  * decision by deleting the provisions above and replace them with the notice
00035  * and other provisions required by the GPL or the LGPL. If you do not delete
00036  * the provisions above, a recipient may use your version of this file under
00037  * the terms of any one of the MPL, the GPL or the LGPL.
00038  *
00039  * ***** END LICENSE BLOCK ***** */
00040 
00041 // Special stuff for the Macintosh implementation of command-line service.
00042 
00043 #include "nsCommandLineServiceMac.h"
00044 
00045 // Mozilla
00046 #include "nsDebug.h"
00047 #include "nsILocalFileMac.h"
00048 #include "nsDebug.h"
00049 #include "nsNetUtil.h"
00050 #include "nsIAppStartup.h"
00051 #include "nsIServiceManager.h"
00052 #include "nsIURL.h"
00053 #include "nsIIOService.h"
00054 #include "nsIURL.h"
00055 #include "nsIServiceManager.h"
00056 #include "nsNetCID.h"
00057 #include "nsIDOMWindow.h"
00058 #include "nsXPCOM.h"
00059 #include "nsISupportsPrimitives.h"
00060 #include "nsIWindowWatcher.h"
00061 #include "jsapi.h"
00062 #include "nsReadableUtils.h"
00063 #include "nsICloseAllWindows.h"
00064 #include "nsIPrefService.h"
00065 #ifdef MOZ_THUNDERBIRD
00066 #include "nsICommandLineRunner.h"
00067 #endif
00068 
00069 #include "nsAEEventHandling.h"
00070 #include "nsXPFEComponentsCID.h"
00071 
00072 // NSPR
00073 #include "prmem.h"
00074 #include "plstr.h"
00075 #include "prenv.h"
00076 
00077 // the static instance
00078 nsMacCommandLine nsMacCommandLine::sMacCommandLine;
00079 
00080 /*
00081  * ReadLine --
00082  *
00083  * Read in a line of text, terminated by CR or LF, from inStream into buf.
00084  * The terminating CR or LF is not included.  The text in buf is terminated
00085  * by a null byte.
00086  * Returns the number of bytes in buf.  If EOF and zero bytes were read, returns -1.
00087  */
00088 
00089 static PRInt32 ReadLine(FILE* inStream, char* buf, PRInt32 bufSize)
00090 {
00091   PRInt32 charsRead = 0;
00092   int c;
00093   
00094   if (bufSize < 2)
00095     return -1;
00096 
00097   while (charsRead < (bufSize-1)) {
00098     c = getc(inStream);
00099     if (c == EOF || c == '\n' || c == '\r')
00100       break;
00101     buf[charsRead++] = c;
00102   }
00103   buf[charsRead] = '\0';
00104   
00105   return (c == EOF && !charsRead) ? -1 : charsRead; 
00106 }
00107 
00108 static PRUint32
00109 ProcessAppleEvents()
00110 {
00111   // Dispatch all of the Apple Events waiting in the event queue.
00112 
00113   PRUint32 processed = 0;
00114 
00115   const EventTypeSpec kAppleEventList[] = {
00116     { kEventClassAppleEvent, kEventAppleEvent },
00117   };
00118 
00119   EventRef carbonEvent;
00120   while (::ReceiveNextEvent(GetEventTypeCount(kAppleEventList),
00121                             kAppleEventList,
00122                             kEventDurationNoWait,
00123                             PR_TRUE,
00124                             &carbonEvent) == noErr) {
00125     EventRecord eventRecord;
00126     ::ConvertEventRefToEventRecord(carbonEvent, &eventRecord);
00127     ::AEProcessAppleEvent(&eventRecord);
00128     ::ReleaseEvent(carbonEvent);
00129     processed++;
00130   }
00131 
00132   return processed;
00133 }
00134 
00135 //----------------------------------------------------------------------------------------
00136 nsMacCommandLine::nsMacCommandLine()
00137 : mArgs(NULL)
00138 , mArgsAllocated(0)
00139 , mArgsUsed(0)
00140 , mStartedUp(PR_FALSE)
00141 //----------------------------------------------------------------------------------------
00142 {
00143 }
00144 
00145 
00146 //----------------------------------------------------------------------------------------
00147 nsMacCommandLine::~nsMacCommandLine()
00148 //----------------------------------------------------------------------------------------
00149 {
00150   ShutdownAEHandlerClasses();
00151   if (mArgs) {
00152     for (PRUint32 i = 0; i < mArgsUsed; i++)
00153       free(mArgs[i]);
00154     free(mArgs);
00155   }
00156 }
00157 
00158 
00159 //----------------------------------------------------------------------------------------
00160 nsresult nsMacCommandLine::Initialize(int& argc, char**& argv)
00161 //----------------------------------------------------------------------------------------
00162 {
00163   mArgs = static_cast<char **>(malloc(kArgsGrowSize * sizeof(char *)));
00164   if (!mArgs)
00165     return NS_ERROR_FAILURE;
00166   mArgs[0] = nsnull;
00167   mArgsAllocated = kArgsGrowSize;
00168   mArgsUsed = 0;
00169   
00170   // Here, we may actually get useful args.
00171   // Copy them first to mArgv.
00172   for (int arg = 0; arg < argc; arg++)
00173     AddToCommandLine(argv[arg]);
00174 
00175   // Set up AppleEvent handling.
00176   OSErr err = CreateAEHandlerClasses(false);
00177   if (err != noErr) return NS_ERROR_FAILURE;
00178 
00179   // Snarf all the odoc and pdoc apple-events.
00180   //
00181   // 1. If they are odoc for 'CMDL' documents, read them into the buffer ready for
00182   //    parsing (concatenating multiple files).
00183   //
00184   // 2. If they are any other kind of document, convert them into -url command-line
00185   //    parameters or -print parameters, with file URLs.
00186 
00187   // Spin a native event loop to allow AE handlers for waiting events to be
00188   // called
00189   ProcessAppleEvents();
00190 
00191   if (GetCurrentKeyModifiers() & optionKey)
00192     AddToCommandLine("-p");
00193 
00194   // we've started up now
00195   mStartedUp = PR_TRUE;
00196   
00197   argc = mArgsUsed;
00198   argv = mArgs;
00199   
00200   return NS_OK;
00201 }
00202 
00203 //----------------------------------------------------------------------------------------
00204 void nsMacCommandLine::SetupCommandLine(int& argc, char**& argv)
00205 //----------------------------------------------------------------------------------------
00206 {
00207   // Initializes the command line from Apple Events and other sources,
00208   // as appropriate for OS X.
00209   //
00210   // IMPORTANT: This must be done before XPCOM shutdown if the app is to
00211   // relaunch (i.e. before the ScopedXPCOMStartup object goes out of scope).
00212   // XPCOM shutdown can cause other things to process native events, and
00213   // native event processing can cause the waiting Apple Events to be
00214   // discarded.
00215 
00216   // Process Apple Events and put them into the arguments.
00217   Initialize(argc, argv);
00218 
00219   Boolean isForeground = PR_FALSE;
00220   ProcessSerialNumber psnSelf, psnFront;
00221 
00222   // If the process will be relaunched, the child should be in the foreground
00223   // if the parent is in the foreground.  This will be communicated in a
00224   // command-line argument to the child.  Adding this argument is harmless
00225   // if not relaunching.
00226   if (::GetCurrentProcess(&psnSelf) == noErr &&
00227       ::GetFrontProcess(&psnFront) == noErr &&
00228       ::SameProcess(&psnSelf, &psnFront, &isForeground) == noErr &&
00229       isForeground) {
00230     // The process is currently in the foreground.  The relaunched
00231     // process should come to the front, too.
00232     AddToCommandLine("-foreground");
00233   }
00234 
00235   argc = mArgsUsed;
00236   argv = mArgs;
00237 }
00238 
00239 //----------------------------------------------------------------------------------------
00240 nsresult nsMacCommandLine::AddToCommandLine(const char* inArgText)
00241 //----------------------------------------------------------------------------------------
00242 {
00243   if (mArgsUsed >= mArgsAllocated - 1) {
00244     // realloc does not free the given pointer if allocation fails.
00245     char **temp = static_cast<char **>(realloc(mArgs, (mArgsAllocated + kArgsGrowSize) * sizeof(char *)));
00246     if (!temp)
00247       return NS_ERROR_OUT_OF_MEMORY;
00248     mArgs = temp;
00249     mArgsAllocated += kArgsGrowSize;
00250   }
00251   char *temp2 = strdup(inArgText);
00252   if (!temp2)
00253     return NS_ERROR_OUT_OF_MEMORY;
00254   mArgs[mArgsUsed++] = temp2;
00255   mArgs[mArgsUsed] = nsnull;
00256   return NS_OK;
00257 }
00258 
00259 
00260 //----------------------------------------------------------------------------------------
00261 nsresult nsMacCommandLine::AddToCommandLine(const char* inOptionString, const FSSpec& inFileSpec)
00262 //----------------------------------------------------------------------------------------
00263 {
00264   // Convert the filespec to a URL.  Avoid using xpcom because this may be
00265   // called before xpcom startup.
00266   FSRef fsRef;
00267   if (::FSpMakeFSRef(&inFileSpec, &fsRef) != noErr)
00268     return NS_ERROR_FAILURE;
00269 
00270   CFURLRef url = ::CFURLCreateFromFSRef(nsnull, &fsRef);
00271   if (!url)
00272     return NS_ERROR_FAILURE;
00273 
00274   CFStringRef string = ::CFURLGetString(url);
00275   if (!string) {
00276     ::CFRelease(url);
00277     return NS_ERROR_FAILURE;
00278   }
00279 
00280   CFIndex length = ::CFStringGetLength(string);
00281   CFIndex bufLen = 0;
00282   ::CFStringGetBytes(string, CFRangeMake(0, length), kCFStringEncodingUTF8,
00283                      0, PR_FALSE, nsnull, 0, &bufLen);
00284 
00285   UInt8 buffer[bufLen + 1];
00286   if (!buffer) {
00287     ::CFRelease(url);
00288     return NS_ERROR_FAILURE;
00289   }
00290 
00291   ::CFStringGetBytes(string, CFRangeMake(0, length), kCFStringEncodingUTF8,
00292                      0, PR_FALSE, buffer, bufLen, nsnull);
00293   buffer[bufLen] = 0;
00294 
00295   ::CFRelease(url);
00296 
00297   AddToCommandLine(inOptionString);
00298   AddToCommandLine((char*)buffer);
00299 
00300    return NS_OK;
00301 }
00302 
00303 //----------------------------------------------------------------------------------------
00304 nsresult nsMacCommandLine::AddToEnvironmentVars(const char* inArgText)
00305 //----------------------------------------------------------------------------------------
00306 {
00307   (void)PR_SetEnv(inArgText);
00308   return NS_OK;
00309 }
00310 
00311 
00312 //----------------------------------------------------------------------------------------
00313 OSErr nsMacCommandLine::HandleOpenOneDoc(const FSSpec& inFileSpec, OSType inFileType)
00314 //----------------------------------------------------------------------------------------
00315 {
00316   nsCOMPtr<nsILocalFileMac> inFile;
00317   nsresult rv = NS_NewLocalFileWithFSSpec(&inFileSpec, PR_TRUE, getter_AddRefs(inFile));
00318   if (NS_FAILED(rv))
00319     return errAEEventNotHandled;
00320 
00321   if (!mStartedUp)
00322   {
00323     // Is it the right type to be a command-line file?
00324     if (inFileType == 'TEXT' || inFileType == 'CMDL')
00325     {
00326       // Can we open the file?
00327       FILE *fp = 0;
00328       rv = inFile->OpenANSIFileDesc("r", &fp);
00329       if (NS_SUCCEEDED(rv))
00330       {
00331         Boolean foundArgs = false;
00332         Boolean foundEnv = false;
00333         char chars[1024];
00334         static const char kCommandLinePrefix[] = "ARGS:";
00335         static const char kEnvVarLinePrefix[] = "ENV:";
00336 
00337         while (ReadLine(fp, chars, sizeof(chars)) != -1)
00338         {       // See if there are any command line or environment var settings
00339           if (PL_strstr(chars, kCommandLinePrefix) == chars)
00340           {
00341             (void)AddToCommandLine(chars + sizeof(kCommandLinePrefix) - 1);
00342             foundArgs = true;
00343           }
00344           else if (PL_strstr(chars, kEnvVarLinePrefix) == chars)
00345           {
00346             (void)AddToEnvironmentVars(chars + sizeof(kEnvVarLinePrefix) - 1);
00347             foundEnv = true;
00348           }
00349         }
00350 
00351         fclose(fp);
00352         // If we found a command line or environment vars we want to return now
00353         // raather than trying to open the file as a URL
00354         if (foundArgs || foundEnv)
00355           return noErr;
00356       }
00357     }
00358     // If it's not a command-line argument, and we are starting up the application,
00359     // add a command-line "-url" argument to the global list. This means that if
00360     // the app is opened with documents on the mac, they'll be handled the same
00361     // way as if they had been typed on the command line in Unix or DOS.
00362     return AddToCommandLine("-url", inFileSpec);
00363   }
00364 
00365   // Final case: we're not just starting up. How do we handle this?
00366   nsCAutoString specBuf;
00367   rv = NS_GetURLSpecFromFile(inFile, specBuf);
00368   if (NS_FAILED(rv))
00369     return errAEEventNotHandled;
00370   
00371 #ifdef MOZ_THUNDERBIRD
00372   nsCOMPtr<nsICommandLineRunner> cmdLine
00373     (do_CreateInstance("@mozilla.org/toolkit/command-line;1"));
00374   if (!cmdLine) {
00375     NS_ERROR("Couldn't create command line!");
00376     return errAEEventNotHandled;
00377   }
00378   nsCOMPtr<nsILocalFile> workingDir;
00379   nsCString filePath;
00380   rv = inFile->GetNativePath(filePath);
00381   if (NS_FAILED(rv))
00382     return errAEEventNotHandled;
00383   char *urlPtr = ToNewCString(filePath);
00384   char **argv = new char *[2];
00385   argv[0] = nsnull;
00386   argv[1] = urlPtr;
00387   rv = cmdLine->Init(2, argv, workingDir, nsICommandLine::STATE_REMOTE_EXPLICIT);
00388   nsMemory::Free(urlPtr);
00389   delete [] argv;
00390   if (NS_FAILED(rv))
00391     return errAEEventNotHandled;
00392   return cmdLine->Run();
00393 #endif // MOZ_THUNDERBIRD  
00394   return OpenURL(specBuf.get());
00395 }
00396 
00397 OSErr nsMacCommandLine::OpenURL(const char* aURL)
00398 {
00399   nsresult rv;
00400   
00401   nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
00402 
00403   nsXPIDLCString browserURL;
00404   if (NS_SUCCEEDED(rv))
00405     rv = prefBranch->GetCharPref("browser.chromeURL", getter_Copies(browserURL));
00406   
00407   if (NS_FAILED(rv)) {
00408     NS_WARNING("browser.chromeURL not supplied! How is the app supposed to know what the main window is?");
00409     browserURL.Assign("chrome://navigator/content/navigator.xul");
00410   }
00411      
00412   rv = OpenWindow(browserURL.get(), NS_ConvertASCIItoUCS2(aURL).get());
00413   if (NS_FAILED(rv))
00414     return errAEEventNotHandled;
00415     
00416   return noErr;
00417 }
00418 
00419 
00420 
00421 //----------------------------------------------------------------------------------------
00422 OSErr nsMacCommandLine::HandlePrintOneDoc(const FSSpec& inFileSpec, OSType fileType)
00423 //----------------------------------------------------------------------------------------
00424 {
00425   // If  we are starting up the application,
00426   // add a command-line "-print" argument to the global list. This means that if
00427   // the app is opened with documents on the mac, they'll be handled the same
00428   // way as if they had been typed on the command line in Unix or DOS.
00429   if (!mStartedUp)
00430     return AddToCommandLine("-print", inFileSpec);
00431   
00432   // Final case: we're not just starting up. How do we handle this?
00433   NS_NOTYETIMPLEMENTED("Write Me");
00434   return errAEEventNotHandled;
00435 }
00436 
00437 
00438 
00439 //----------------------------------------------------------------------------------------
00440 nsresult nsMacCommandLine::OpenWindow(const char *chrome, const PRUnichar *url)
00441 //----------------------------------------------------------------------------------------
00442 {
00443   nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
00444   nsCOMPtr<nsISupportsString> urlWrapper(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
00445   if (!wwatch || !urlWrapper)
00446     return NS_ERROR_FAILURE;
00447 
00448   urlWrapper->SetData(nsDependentString(url));
00449 
00450   nsCOMPtr<nsIDOMWindow> newWindow;
00451   nsresult rv;
00452   rv = wwatch->OpenWindow(0, chrome, "_blank",
00453                "chrome,dialog=no,all", urlWrapper,
00454                getter_AddRefs(newWindow));
00455 
00456   return rv;
00457 }
00458 
00459 //----------------------------------------------------------------------------------------
00460 OSErr nsMacCommandLine::DispatchURLToNewBrowser(const char* url)
00461 //----------------------------------------------------------------------------------------
00462 {
00463   OSErr err = errAEEventNotHandled;
00464   if (mStartedUp)
00465     return OpenURL(url);
00466   else {
00467     err = AddToCommandLine("-url");
00468     if (err == noErr)
00469       err = AddToCommandLine(url);
00470   }
00471   
00472   return err;
00473 }
00474 
00475 //----------------------------------------------------------------------------------------
00476 OSErr nsMacCommandLine::Quit(TAskSave askSave)
00477 //----------------------------------------------------------------------------------------
00478 {
00479   nsresult rv;
00480   
00481   nsCOMPtr<nsICloseAllWindows> closer =
00482            do_CreateInstance("@mozilla.org/appshell/closeallwindows;1", &rv);
00483   if (NS_FAILED(rv))
00484     return errAEEventNotHandled;
00485 
00486   PRBool doQuit;
00487   rv = closer->CloseAll(askSave != eSaveNo, &doQuit);
00488   if (NS_FAILED(rv))
00489     return errAEEventNotHandled;
00490   if (!doQuit)
00491     return userCanceledErr;
00492 
00493   nsCOMPtr<nsIAppStartup> appStartup =
00494            do_GetService(NS_APPSTARTUP_CONTRACTID, &rv);
00495   if (NS_FAILED(rv))
00496     return errAEEventNotHandled;
00497 
00498   appStartup->Quit(nsIAppStartup::eAttemptQuit);
00499   return noErr;
00500 }
00501 
00502 #pragma mark -
00503 
00504 //----------------------------------------------------------------------------------------
00505 void SetupMacCommandLine(int& argc, char**& argv)
00506 //----------------------------------------------------------------------------------------
00507 {
00508   nsMacCommandLine& cmdLine = nsMacCommandLine::GetMacCommandLine();
00509   return cmdLine.SetupCommandLine(argc, argv);
00510 }