/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape 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/NPL/ * * 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 Communicator client code. * * The Initial Developer of the Original Code is Netscape Communications * Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): * Bill Law law@netscape.com */ #include "nsNativeAppSupportBase.h" #include "nsNativeAppSupportWin.h" #include "nsString.h" #include "nsICmdLineService.h" #include "nsCOMPtr.h" #include "nsIComponentManager.h" #include "nsIServiceManager.h" #include "nsIAppShellService.h" #include "nsAppShellCIDs.h" #include "nsIDOMWindow.h" #include #include #include #include #include #include // Define this macro to 1 to get DDE debugging output. #define MOZ_DEBUG_DDE 0 #ifdef DEBUG_law #undef MOZ_DEBUG_DDE #define MOZ_DEBUG_DDE 1 #endif class nsSplashScreenWin : public nsISplashScreen { public: nsSplashScreenWin(); ~nsSplashScreenWin(); NS_IMETHOD Show(); NS_IMETHOD Hide(); // nsISupports methods NS_IMETHOD_(nsrefcnt) AddRef() { mRefCnt++; return mRefCnt; } NS_IMETHOD_(nsrefcnt) Release() { --mRefCnt; if ( !mRefCnt ) { delete this; return 0; } return mRefCnt; } NS_IMETHOD QueryInterface( const nsIID &iid, void**p ) { nsresult rv = NS_OK; if ( p ) { *p = 0; if ( iid.Equals( NS_GET_IID( nsISplashScreen ) ) ) { nsISplashScreen *result = this; *p = result; NS_ADDREF( result ); } else if ( iid.Equals( NS_GET_IID( nsISupports ) ) ) { nsISupports *result = NS_STATIC_CAST( nsISupports*, this ); *p = result; NS_ADDREF( result ); } else { rv = NS_NOINTERFACE; } } else { rv = NS_ERROR_NULL_POINTER; } return rv; } void SetDialog( HWND dlg ); void LoadBitmap(); static nsSplashScreenWin* GetPointer( HWND dlg ); static BOOL CALLBACK DialogProc( HWND dlg, UINT msg, WPARAM wp, LPARAM lp ); static BOOL CALLBACK PhantomDialogProc( HWND dlg, UINT msg, WPARAM wp, LPARAM lp ); static DWORD WINAPI ThreadProc( LPVOID ); HWND mDlg; HBITMAP mBitmap; nsrefcnt mRefCnt; }; // class nsSplashScreenWin // Simple Win32 mutex wrapper. struct Mutex { Mutex( const char *name ) : mName( name ), mHandle( 0 ), mState( -1 ) { mHandle = CreateMutex( 0, FALSE, mName.GetBuffer() ); #if MOZ_DEBUG_DDE printf( "CreateMutex error = 0x%08X\n", (int)GetLastError() ); #endif } ~Mutex() { if ( mHandle ) { // Make sure we release it if we own it. Unlock(); BOOL rc = CloseHandle( mHandle ); #if MOZ_DEBUG_DDE if ( !rc ) { printf( "CloseHandle error = 0x%08X\n", (int)GetLastError() ); } #endif } } BOOL Lock( DWORD timeout ) { if ( mHandle ) { #if MOZ_DEBUG_DDE printf( "Waiting (%d msec) for DDE mutex...\n", (int)timeout ); #endif mState = WaitForSingleObject( mHandle, timeout ); #if MOZ_DEBUG_DDE printf( "...wait complete, result = 0x%08X\n", (int)mState ); #endif return mState == WAIT_OBJECT_0; } else { return FALSE; } } void Unlock() { if ( mHandle && mState == WAIT_OBJECT_0 ) { #if MOZ_DEBUG_DDE printf( "Releasing DDE mutex\n" ); #endif ReleaseMutex( mHandle ); mState = -1; } } private: nsCString mName; HANDLE mHandle; DWORD mState; }; /* DDE Notes * * This section describes the Win32 DDE service implementation for * Mozilla. DDE is used on Win32 platforms to communicate between * separate instances of mozilla.exe (or other Mozilla-based * executables), or, between the Win32 desktop shell and Mozilla. * * The first instance of Mozilla will become the "server" and * subsequent executables (and the shell) will use DDE to send * requests to that process. The requests are DDE "execute" requests * that pass the command line arguments. * * Mozilla registers the DDE application "Mozilla" and currently * supports only the "WWW_OpenURL" topic. This should be reasonably * compatible with applications that interfaced with Netscape * Communicator (and its predecessors?). Note that even that topic * may not be supported in a compatible fashion as the command-line * options for Mozilla are different than for Communiator. * * It is imperative that at most one instance of Mozilla execute in * "server mode" at any one time. The "native app support" in Mozilla * on Win32 ensures that only the server process performs XPCOM * initialization (that is not required for subsequent client processes * to communicate with the server process). * * To guarantee that only one server starts up, a Win32 "mutex" is used * to ensure only one process executes the server-detection code. That * code consists of initializing DDE and doing a DdeConnect to Mozilla's * application/topic. If that connection succeeds, then a server process * must be running already. * * Otherwise, no server has started. In that case, the current process * calls DdeNameService to register that application/topic. Only at that * point does the mutex get released. * * There are a couple of subtleties that one should be aware of: * * 1. It is imperative that DdeInitialize be called only after the mutex * lock has been obtained. The reason is that at shutdown, DDE * notifications go out to all initialized DDE processes. Thus, if * the mutex is owned by a terminating intance of Mozilla, then * calling DdeInitialize and then WaitForSingleObject will cause the * DdeUninitialize from the terminating process to "hang" until the * process waiting for the mutex times out (and can then service the * notification that the DDE server is terminating). So, don't mess * with the sequence of things in the startup/shutdown logic. * * 2. All mutex requests are made with a reasonably long timeout value and * are designed to "fail safe" (i.e., a timeout is treated as failure). * * 3. An attempt has been made to minimize the degree to which the main * Mozilla application logic needs to be aware of the DDE mechanisms * implemented herein. As a result, this module surfaces a very * large-grained interface, consisting of simple start/stop methods. * As a consequence, details of certain scenarios can be "lost." * Particularly, incoming DDE requests can arrive after this module * initiates the DDE server, but before Mozilla is initialized to the * point where those requests can be serviced (e.g., open a browser * window to a particular URL). Since the client process sends the * request early on, it may not be prepared to respond to that error. * Thus, such situations may fail silently. The design goal is that * they fail harmlessly. Refinements on this point will be made as * details emerge (and time permits). */ class nsNativeAppSupportWin : public nsNativeAppSupportBase { public: // Overrides of base implementation. NS_IMETHOD Start( PRBool *aResult ); NS_IMETHOD Stop( PRBool *aResult ); NS_IMETHOD Quit(); // Utility function to handle a Win32-specific command line // option: "-console", which dynamically creates a Windows // console. static void CheckConsole(); private: static HDDEDATA CALLBACK HandleDDENotification( UINT uType, UINT uFmt, HCONV hconv, HSZ hsz1, HSZ hsz2, HDDEDATA hdata, ULONG dwData1, ULONG dwData2 ); static void HandleRequest( LPBYTE request ); static nsresult GetCmdLineArgs( LPBYTE request, nsICmdLineService **aResult ); static nsresult OpenWindow( const char *urlstr, const char *args ); static int mConversations; static HSZ mApplication, mTopic; static DWORD mInstance; }; // nsNativeAppSupportWin nsSplashScreenWin::nsSplashScreenWin() : mDlg( 0 ), mBitmap( 0 ), mRefCnt( 0 ) { } nsSplashScreenWin::~nsSplashScreenWin() { #if MOZ_DEBUG_DDE printf( "splash screen dtor called\n" ); #endif // Make sure dialog is gone. Hide(); } NS_IMETHODIMP nsSplashScreenWin::Show() { /* * A hack for http://bugzilla.mozilla.org/show_bug.cgi?id=26581 * * Windows NT seems to think that the first thread to show a window must * therefore be our main UI thread. It then seems to want to put the first * window that shows up on other threads in our application behind the main * windows of other applications. Since we want to show our splash screen * on a 'non-main' thread, our browser windows start showing up later on a * thread that Windows thinks is not our main UI thread. So, (I believe) it * pushes the first one of our browser windows to the back until the user * pulls it forward. That is not what we want! * * This hack attempts to trick Windows by very briefly showing an * offscreen window on the main thread. This happens before the splash * screen thread creates its window (which it sets as topmost). So when our * browser windows start showing up on the main thread Windows will see that * they are on the "first thread with a window" and it will (hopefully!) not * push them to the back. */ DialogBox( GetModuleHandle( 0 ), MAKEINTRESOURCE( IDD_SPLASH ), HWND_DESKTOP, (DLGPROC)PhantomDialogProc ); // Spawn new thread to display real splash screen. DWORD threadID = 0; HANDLE handle = CreateThread( 0, 0, (LPTHREAD_START_ROUTINE)ThreadProc, this, 0, &threadID ); CloseHandle(handle); return NS_OK; } NS_IMETHODIMP nsSplashScreenWin::Hide() { if ( mDlg ) { // Dismiss the dialog. EndDialog( mDlg, 0 ); // Release custom bitmap (if there is one). if ( mBitmap ) { BOOL ok = DeleteObject( mBitmap ); } mBitmap = 0; mDlg = 0; } return NS_OK; } BOOL CALLBACK nsSplashScreenWin::PhantomDialogProc( HWND dlg, UINT msg, WPARAM wp, LPARAM lp ) { if ( msg == WM_INITDIALOG ) { // Show window for an instant to make this the active thread. ShowWindow( dlg, SW_SHOW ); EndDialog( dlg, 0 ); return 1; } return 0; } void nsSplashScreenWin::LoadBitmap() { // Check for '.bmp" in same directory as executable. char fileName[ _MAX_PATH ]; int fileNameLen = ::GetModuleFileName( NULL, fileName, sizeof fileName ); if ( fileNameLen >= 3 ) { fileName[ fileNameLen - 3 ] = 0; strcat( fileName, "bmp" ); // Try to load bitmap from that file. HBITMAP bitmap = (HBITMAP)::LoadImage( NULL, fileName, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE ); if ( bitmap ) { HWND bitmapControl = GetDlgItem( mDlg, IDB_SPLASH ); if ( bitmapControl ) { HBITMAP old = (HBITMAP)SendMessage( bitmapControl, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)bitmap ); // Remember bitmap so we can delete it later. mBitmap = bitmap; // Delete old bitmap. if ( old ) { BOOL ok = DeleteObject( old ); } } else { // Delete bitmap since it isn't going to be used. DeleteObject( bitmap ); } } } } BOOL CALLBACK nsSplashScreenWin::DialogProc( HWND dlg, UINT msg, WPARAM wp, LPARAM lp ) { if ( msg == WM_INITDIALOG ) { // Store dialog handle. nsSplashScreenWin *splashScreen = (nsSplashScreenWin*)lp; if ( lp ) { splashScreen->SetDialog( dlg ); // Try to load customized bitmap. splashScreen->LoadBitmap(); } /* Size and center the splash screen correctly. The flags in the * dialog template do not do the right thing if the user's * machine is using large fonts. */ HWND bitmapControl = GetDlgItem( dlg, IDB_SPLASH ); if ( bitmapControl ) { HBITMAP hbitmap = (HBITMAP)SendMessage( bitmapControl, STM_GETIMAGE, IMAGE_BITMAP, 0 ); if ( hbitmap ) { BITMAP bitmap; if ( GetObject( hbitmap, sizeof bitmap, &bitmap ) ) { SetWindowPos( dlg, NULL, GetSystemMetrics(SM_CXSCREEN)/2 - bitmap.bmWidth/2, GetSystemMetrics(SM_CYSCREEN)/2 - bitmap.bmHeight/2, bitmap.bmWidth, bitmap.bmHeight, SWP_NOZORDER ); ShowWindow( dlg, SW_SHOW ); } } } return 1; } return 0; } void nsSplashScreenWin::SetDialog( HWND dlg ) { // Save dialog handle. mDlg = dlg; // Store this pointer in the dialog. SetWindowLong( mDlg, DWL_USER, (LONG)(void*)this ); } nsSplashScreenWin *nsSplashScreenWin::GetPointer( HWND dlg ) { // Get result from dialog user data. LONG data = GetWindowLong( dlg, DWL_USER ); return (nsSplashScreenWin*)(void*)data; } DWORD WINAPI nsSplashScreenWin::ThreadProc( LPVOID splashScreen ) { DialogBoxParam( GetModuleHandle( 0 ), MAKEINTRESOURCE( IDD_SPLASH ), HWND_DESKTOP, (DLGPROC)DialogProc, (LPARAM)splashScreen ); return 0; } void nsNativeAppSupportWin::CheckConsole() { for ( int i = 1; i < __argc; i++ ) { if ( strcmp( "-console", __argv[i] ) == 0 || strcmp( "/console", __argv[i] ) == 0 ) { // Users wants to make sure we have a console. // Try to allocate one. BOOL rc = ::AllocConsole(); if ( rc ) { // Console allocated. Fix it up so that output works in // all cases. See http://support.microsoft.com/support/kb/articles/q105/3/05.asp. // stdout int hCrt = ::_open_osfhandle( (long)GetStdHandle( STD_OUTPUT_HANDLE ), _O_TEXT ); if ( hCrt != -1 ) { FILE *hf = ::_fdopen( hCrt, "w" ); if ( hf ) { *stdout = *hf; ::fprintf( stdout, "stdout directed to dynamic console\n" ); } } // stderr hCrt = ::_open_osfhandle( (long)::GetStdHandle( STD_ERROR_HANDLE ), _O_TEXT ); if ( hCrt != -1 ) { FILE *hf = ::_fdopen( hCrt, "w" ); if ( hf ) { *stderr = *hf; ::fprintf( stderr, "stderr directed to dynamic console\n" ); } } // stdin? /* Don't bother for now. hCrt = ::_open_osfhandle( (long)::GetStdHandle( STD_INPUT_HANDLE ), _O_TEXT ); if ( hCrt != -1 ) { FILE *hf = ::_fdopen( hCrt, "r" ); if ( hf ) { *stdin = *hf; } } */ } else { // Failed. Probably because there already is one. // There's little we can do, in any case. } // Don't bother doing this more than once. break; } } return; } // Create and return an instance of class nsNativeAppSupportWin. nsresult NS_CreateNativeAppSupport( nsINativeAppSupport **aResult ) { if ( aResult ) { *aResult = new nsNativeAppSupportWin; if ( *aResult ) { NS_ADDREF( *aResult ); } else { return NS_ERROR_OUT_OF_MEMORY; } } else { return NS_ERROR_NULL_POINTER; } // Check for dynamic console creation request. nsNativeAppSupportWin::CheckConsole(); return NS_OK; } // Create instance of Windows splash screen object. nsresult NS_CreateSplashScreen( nsISplashScreen **aResult ) { if ( aResult ) { *aResult = 0; for ( int i = 1; i < __argc; i++ ) { if ( strcmp( "-quiet", __argv[i] ) == 0 || strcmp( "/quiet", __argv[i] ) == 0 ) { // No splash screen, please. return NS_OK; } } *aResult = new nsSplashScreenWin; if ( *aResult ) { NS_ADDREF( *aResult ); } else { return NS_ERROR_OUT_OF_MEMORY; } } else { return NS_ERROR_NULL_POINTER; } return NS_OK; } // Constants #define MOZ_DDE_APPLICATION "Mozilla" #define MOZ_DDE_TOPIC "WWW_OpenURL" #define MOZ_DDE_MUTEX_NAME "MozillaDDEMutex" #define MOZ_DDE_START_TIMEOUT 30000 #define MOZ_DDE_STOP_TIMEOUT 15000 #define MOZ_DDE_EXEC_TIMEOUT 15000 // Static member definitions. int nsNativeAppSupportWin::mConversations = 0; HSZ nsNativeAppSupportWin::mApplication = 0; HSZ nsNativeAppSupportWin::mTopic = 0; DWORD nsNativeAppSupportWin::mInstance = 0; // Try to initiate DDE conversation. If that succeeds, pass // request to server process. Otherwise, register application // and topic (i.e., become server process). NS_IMETHODIMP nsNativeAppSupportWin::Start( PRBool *aResult ) { NS_ENSURE_ARG( aResult ); NS_ENSURE_TRUE( mInstance == 0, NS_ERROR_NOT_INITIALIZED ); nsresult rv = NS_ERROR_FAILURE; *aResult = PR_FALSE; // Grab mutex before doing DdeInitialize! This is // important (see comment above). Mutex ddeLock = Mutex( MOZ_DDE_MUTEX_NAME ); if ( ddeLock.Lock( MOZ_DDE_START_TIMEOUT ) ) { // Initialize DDE. UINT rc = DdeInitialize( &mInstance, nsNativeAppSupportWin::HandleDDENotification, APPCLASS_STANDARD, 0 ); if ( rc == DMLERR_NO_ERROR ) { mApplication = DdeCreateStringHandle( mInstance, MOZ_DDE_APPLICATION, CP_WINANSI ); mTopic = DdeCreateStringHandle( mInstance, MOZ_DDE_TOPIC, CP_WINANSI ); if ( mApplication && mTopic ) { // Everything OK so far, try to connect to previusly // started Mozilla. HCONV hconv = DdeConnect( mInstance, mApplication, mTopic, 0 ); if ( hconv ) { // We're the client... // Get command line to pass to server. LPTSTR cmd = GetCommandLine(); #if MOZ_DEBUG_DDE printf( "Acting as DDE client, cmd=%s\n", cmd ); #endif rc = (UINT)DdeClientTransaction( (LPBYTE)cmd, strlen( cmd ) + 1, hconv, 0, 0, XTYP_EXECUTE, MOZ_DDE_EXEC_TIMEOUT, 0 ); if ( rc ) { // Inform caller that request was issued. rv = NS_OK; } else { // Something went wrong. Not much we can do, though... #if MOZ_DEBUG_DDE printf( "DdeClientTransaction failed, error = 0x%08X\n", (int)DdeGetLastError( mInstance ) ); #endif } } else { // We're going to be the server... #if MOZ_DEBUG_DDE printf( "Setting up DDE server...\n" ); #endif // Next step is to register a DDE service. rc = (UINT)DdeNameService( mInstance, mApplication, 0, DNS_REGISTER ); if ( rc ) { #if MOZ_DEBUG_DDE printf( "...DDE server started\n" ); #endif // Tell app to do its thing. *aResult = PR_TRUE; rv = NS_OK; } else { #if MOZ_DEBUG_DDE printf( "DdeNameService failed, error = 0x%08X\n", (int)DdeGetLastError( mInstance ) ); #endif } } } else { #if MOZ_DEBUG_DDE printf( "DdeCreateStringHandle failed, error = 0x%08X\n", (int)DdeGetLastError( mInstance ) ); #endif } } else { #if MOZ_DEBUG_DDE printf( "DdeInitialize failed, error = 0x%08X\n", (int)rc ); #endif } // Release mutex. ddeLock.Unlock(); } // Clean up. The only case in which we need to preserve DDE stuff // is if we're going to be acting as server. if ( !*aResult ) { Quit(); } return rv; } // If no DDE conversations are pending, terminate DDE. NS_IMETHODIMP nsNativeAppSupportWin::Stop( PRBool *aResult ) { NS_ENSURE_ARG( aResult ); NS_ENSURE_TRUE( mInstance, NS_ERROR_NOT_INITIALIZED ); nsresult rv = NS_OK; *aResult = PR_TRUE; Mutex ddeLock( MOZ_DDE_MUTEX_NAME ); if ( ddeLock.Lock( MOZ_DDE_STOP_TIMEOUT ) ) { if ( mConversations == 0 ) { this->Quit(); } else { *aResult = PR_FALSE; } ddeLock.Unlock(); } return rv; } // Terminate DDE regardless. NS_IMETHODIMP nsNativeAppSupportWin::Quit() { if ( mInstance ) { // Clean up strings. if ( mApplication ) { DdeFreeStringHandle( mInstance, mApplication ); mApplication = 0; } if ( mTopic ) { DdeFreeStringHandle( mInstance, mTopic ); mTopic = 0; } DdeUninitialize( mInstance ); mInstance = 0; } return NS_OK; } PRBool NS_CanRun() { return PR_TRUE; } #if MOZ_DEBUG_DDE // Macro to generate case statement for a given XTYP value. #define XTYP_CASE(t) \ case t: result = #t; break static nsCString uTypeDesc( UINT uType ) { nsCString result; switch ( uType ) { XTYP_CASE(XTYP_ADVSTART); XTYP_CASE(XTYP_CONNECT); XTYP_CASE(XTYP_ADVREQ); XTYP_CASE(XTYP_REQUEST); XTYP_CASE(XTYP_WILDCONNECT); XTYP_CASE(XTYP_ADVDATA); XTYP_CASE(XTYP_EXECUTE); XTYP_CASE(XTYP_POKE); XTYP_CASE(XTYP_ADVSTOP); XTYP_CASE(XTYP_CONNECT_CONFIRM); XTYP_CASE(XTYP_DISCONNECT); XTYP_CASE(XTYP_ERROR); XTYP_CASE(XTYP_MONITOR); XTYP_CASE(XTYP_REGISTER); XTYP_CASE(XTYP_XACT_COMPLETE); XTYP_CASE(XTYP_UNREGISTER); default: result = "XTYP_?????"; } return result; } static nsCString hszValue( DWORD instance, HSZ hsz ) { // Extract string from HSZ. nsCString result = "["; DWORD len = DdeQueryString( instance, hsz, NULL, NULL, CP_WINANSI ); if ( len ) { char buffer[ 256 ]; DdeQueryString( instance, hsz, buffer, sizeof buffer, CP_WINANSI ); result += buffer; } result += "]"; return result; } #else // These are purely a safety measure to avoid the infamous "won't // build non-debug" type Tinderbox flames. static nsCString uTypeDesc( UINT ) { return nsCString( "?" ); } static nsCString hszValue( DWORD, HSZ ) { return nsCString( "?" ); } #endif HDDEDATA CALLBACK nsNativeAppSupportWin::HandleDDENotification( UINT uType, // transaction type UINT uFmt, // clipboard data format HCONV hconv, // handle to the conversation HSZ hsz1, // handle to a string HSZ hsz2, // handle to a string HDDEDATA hdata, // handle to a global memory object ULONG dwData1, // transaction-specific data ULONG dwData2 ) { // transaction-specific data #if MOZ_DEBUG_DDE printf( "DDE: uType =%s\n", uTypeDesc( uType ).GetBuffer() ); printf( " uFmt =%u\n", (unsigned)uFmt ); printf( " hconv =%08x\n", (int)hconv ); printf( " hsz1 =%08x:%s\n", (int)hsz1, hszValue( mInstance, hsz1 ).GetBuffer() ); printf( " hsz2 =%08x:%s\n", (int)hsz2, hszValue( mInstance, hsz2 ).GetBuffer() ); printf( " hdata =%08x\n", (int)hdata ); printf( " dwData1=%08x\n", (int)dwData1 ); printf( " dwData2=%08x\n", (int)dwData2 ); #endif HDDEDATA result = 0; if ( uType & XCLASS_BOOL ) { switch ( uType ) { case XTYP_CONNECT: case XTYP_CONNECT_CONFIRM: // Make sure its for our service/topic. if ( DdeCmpStringHandles( hsz1, mTopic ) == 0 && DdeCmpStringHandles( hsz2, mApplication ) == 0 ) { // We support this connection. result = (HDDEDATA)1; } } } else if ( uType & XCLASS_DATA ) { } else if ( uType & XCLASS_FLAGS ) { if ( uType == XTYP_EXECUTE ) { // Prove that we received the request. DWORD bytes; LPBYTE request = DdeAccessData( hdata, &bytes ); #if MOZ_DEBUG_DDE printf( "Handling dde request: [%s]...\n", (char*)request ); #endif HandleRequest( request ); result = (HDDEDATA)DDE_FACK; } else { result = (HDDEDATA)DDE_FNOTPROCESSED; } } else if ( uType & XCLASS_NOTIFICATION ) { } return result; } // Handle DDE request. The argument is the command line received by the // DDE client process. We convert that string to an nsICmdLineService // object via GetCmdLineArgs. Then, we look for certain well-known cmd // arguments. This replicates code elsewhere, to some extent, // unfortunately (if you can fix that, please do). void nsNativeAppSupportWin::HandleRequest( LPBYTE request ) { // Parse command line. nsCOMPtr args; nsresult rv = GetCmdLineArgs( request, getter_AddRefs( args ) ); if ( NS_SUCCEEDED( rv ) ) { char *arg; if ( NS_SUCCEEDED( args->GetURLToLoad( &arg ) ) && arg ) { // Launch browser. #if MOZ_DEBUG_DDE printf( "Launching browser on url [%s]...\n", arg ); #endif (void)OpenWindow( "chrome://navigator/content/", arg ); } else if ( NS_SUCCEEDED( args->GetCmdLineValue( "-chrome", &arg ) ) && arg ) { // Launch chrome. #if MOZ_DEBUG_DDE printf( "Launching chrome url [%s]...\n", arg ); #endif (void)OpenWindow( arg, "" ); } else if ( NS_SUCCEEDED( args->GetCmdLineValue( "-edit", &arg ) ) && arg ) { // Launch composer. #if MOZ_DEBUG_DDE printf( "Launching editor on url [%s]...\n", arg ); #endif (void)OpenWindow( "chrome://editor/content/", arg ); } else if ( NS_SUCCEEDED( args->GetCmdLineValue( "-mail", &arg ) ) && arg ) { // Launch composer. #if MOZ_DEBUG_DDE printf( "Launching mail...\n" ); #endif (void)OpenWindow( "chrome://messenger/content/", "" ); } else { #if MOZ_DEBUG_DDE printf( "Unknown request [%s]\n", (char*) request ); #endif } } } // Parse command line args according to MS spec // (see "Parsing C++ Command-Line Arguments" at // http://msdn.microsoft.com/library/devprods/vs6/visualc/vclang/_pluslang_parsing_c.2b2b_.command.2d.line_arguments.htm). nsresult nsNativeAppSupportWin::GetCmdLineArgs( LPBYTE request, nsICmdLineService **aResult ) { nsresult rv = NS_OK; int justCounting = 1; char **argv = 0; // Flags, etc. int init = 1; int between, quoted, bSlashCount; int argc; char *p; nsCString arg; // We loop if we've not finished the second pass through. while ( 1 ) { // Initialize if required. if ( init ) { p = (char*)request; between = 1; argc = quoted = bSlashCount = 0; init = 0; } if ( between ) { // We are traversing whitespace between args. // Check for start of next arg. if ( *p != 0 && !isspace( *p ) ) { // Start of another arg. between = 0; arg = ""; switch ( *p ) { case '\\': // Count the backslash. bSlashCount = 1; break; case '"': // Remember we're inside quotes. quoted = 1; break; default: // Add character to arg. arg += *p; break; } } else { // Another space between args, ignore it. } } else { // We are processing the contents of an argument. // Check for whitespace or end. if ( *p == 0 || ( !quoted && isspace( *p ) ) ) { // Process pending backslashes (interpret them // literally since they're not followed by a "). while( bSlashCount ) { arg += '\\'; bSlashCount--; } // End current arg. if ( !justCounting ) { argv[argc] = new char[ arg.Length() + 1 ]; strcpy( argv[argc], arg.GetBuffer() ); } argc++; // We're now between args. between = 1; } else { // Still inside argument, process the character. switch ( *p ) { case '"': // First, digest preceding backslashes (if any). while ( bSlashCount > 1 ) { // Put one backsplash in arg for each pair. arg += '\\'; bSlashCount -= 2; } if ( bSlashCount ) { // Quote is literal. arg += '"'; bSlashCount = 0; } else { // Quote starts or ends a quoted section. if ( quoted ) { // Check for special case of consecutive double // quotes inside a quoted section. if ( *(p+1) == '"' ) { // This implies a literal double-quote. Fake that // out by causing next double-quote to look as // if it was preceded by a backslash. bSlashCount = 1; } else { quoted = 0; } } else { quoted = 1; } } break; case '\\': // Add to count. bSlashCount++; break; default: // Accept any preceding backslashes literally. while ( bSlashCount ) { arg += '\\'; bSlashCount--; } // Just add next char to the current arg. arg += *p; break; } } } // Check for end of input. if ( *p ) { // Go to next character. p++; } else { // If on first pass, go on to second. if ( justCounting ) { // Allocate argv array. argv = new char*[ argc ]; // Start second pass justCounting = 0; init = 1; } else { // Quit. break; } } } // OK, now create nsICmdLineService object from argc/argv. static NS_DEFINE_CID( kCmdLineServiceCID, NS_COMMANDLINE_SERVICE_CID ); rv = nsComponentManager::CreateInstance( kCmdLineServiceCID, 0, NS_GET_IID( nsICmdLineService ), (void**)aResult ); if ( NS_FAILED( rv ) || NS_FAILED( ( rv = (*aResult)->Initialize( argc, argv ) ) ) ) { #if MOZ_DEBUG_DDE printf( "Error creating command line service = 0x%08X\n", (int)rv ); #endif } // Cleanup. while ( argc ) { delete [] argv[ --argc ]; } delete [] argv; return rv; } nsresult nsNativeAppSupportWin::OpenWindow( const char*urlstr, const char *args ) { nsresult rv; static NS_DEFINE_CID( kAppShellServiceCID, NS_APPSHELL_SERVICE_CID ); NS_WITH_SERVICE(nsIAppShellService, appShellService, kAppShellServiceCID, &rv) if ( NS_SUCCEEDED( rv ) ) { nsCOMPtr hiddenWindow; JSContext *jsContext; rv = appShellService->GetHiddenWindowAndJSContext( getter_AddRefs( hiddenWindow ), &jsContext ); if ( NS_SUCCEEDED( rv ) ) { void *stackPtr; jsval *argv = JS_PushArguments( jsContext, &stackPtr, "ssss", urlstr, "_blank", "chrome,dialog=no,all", args ); if( argv ) { nsCOMPtr newWindow; rv = hiddenWindow->OpenDialog( jsContext, argv, 4, getter_AddRefs( newWindow ) ); JS_PopArguments( jsContext, stackPtr ); } } else { #ifdef MOZ_DEBUG_DDE printf( "GetHiddenWindowAndJSContext failed, rv=0x%08X\n", (int)rv ); #endif } } return rv; }