diff --git a/appshell/cefclient_win.cpp b/appshell/cefclient_win.cpp index 07f2e014f..97441e5ba 100644 --- a/appshell/cefclient_win.cpp +++ b/appshell/cefclient_win.cpp @@ -35,6 +35,9 @@ #define CLOSING_PROP L"CLOSING" +#define FIRST_INSTANCE_MUTEX_NAME (APP_NAME L".Shell.Instance") +#define ID_WM_COPYDATA_SENDOPENFILECOMMAND (WM_USER+1001) + // Global Variables: DWORD g_appStartupTime; HINSTANCE hInst; // current instance @@ -142,6 +145,32 @@ std::wstring GetFilenamesFromCommandLine() { return result; } +// EnumWindowsProc callback function +// - searches for an already running Brackets application window +BOOL CALLBACK FindSuitableBracketsInstance(HWND hwnd, LPARAM lParam) +{ + ASSERT(lParam != NULL); // must be passed an HWND pointer to return, if found + + // check for the Brackets application window by class name and title + WCHAR cName[MAX_PATH+1] = {0}, cTitle[MAX_PATH+1] = {0}; + ::GetClassName(hwnd, cName, MAX_PATH); + ::GetWindowText(hwnd, cTitle, MAX_PATH); + if ((wcscmp(cName, szWindowClass) == 0) && (wcsstr(cTitle, APP_NAME) != 0)) { + // found an already running instance of Brackets. Now, check that that window + // isn't currently disabled (eg. modal dialog). If it is keep searching. + if ((::GetWindowLong(hwnd, GWL_STYLE) & WS_DISABLED) == 0) { + //return the window handle and stop searching + *(HWND*)lParam = hwnd; + return FALSE; + } + } + + return TRUE; // otherwise, continue searching +} + +// forward declaration; implemented in appshell_extensions_win.cpp +void ConvertToUnixPath(ExtensionString& filename); + // Program entry point function. int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, @@ -167,6 +196,43 @@ int APIENTRY wWinMain(HINSTANCE hInstance, // Parse command line arguments. The passed in values are ignored on Windows. AppInitCommandLine(0, NULL); + // Initialize global strings + LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); + LoadString(hInstance, IDC_CEFCLIENT, szWindowClass, MAX_LOADSTRING); + + // Determine if we should use an already running instance of Brackets. + HANDLE hMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, FIRST_INSTANCE_MUTEX_NAME); + if ((hMutex != NULL) && AppGetCommandLine()->HasArguments() && (lpCmdLine != NULL)) { + // for subsequent instances, re-use an already running instance if we're being called to + // open an existing file on the command-line (eg. Open With.. from Windows Explorer) + HWND hFirstInstanceWnd = NULL; + ::EnumWindows(FindSuitableBracketsInstance, (LPARAM)&hFirstInstanceWnd); + if (hFirstInstanceWnd != NULL) { + ::SetForegroundWindow(hFirstInstanceWnd); + if (::IsIconic(hFirstInstanceWnd)) + ::ShowWindow(hFirstInstanceWnd, SW_RESTORE); + + // message the other Brackets instance to actually open the given filename + std::wstring wstrFilename = lpCmdLine; + ConvertToUnixPath(wstrFilename); + // note: WM_COPYDATA will manage passing the string across process space + COPYDATASTRUCT data; + data.dwData = ID_WM_COPYDATA_SENDOPENFILECOMMAND; + data.cbData = (wstrFilename.length() + 1) * sizeof(WCHAR); + data.lpData = (LPVOID)wstrFilename.c_str(); + ::SendMessage(hFirstInstanceWnd, WM_COPYDATA, (WPARAM)(HWND)hFirstInstanceWnd, (LPARAM)(LPVOID)&data); + + // exit this instance + return 0; + } + // otherwise, fall thru and launch a new instance + } + + if (hMutex == NULL) { + // first instance of this app, so create the mutex and continue execution of this instance. + hMutex = ::CreateMutex(NULL, FALSE, FIRST_INSTANCE_MUTEX_NAME); + } + CefSettings settings; // Populate the settings based on command line arguments. @@ -180,9 +246,7 @@ int APIENTRY wWinMain(HINSTANCE hInstance, // Initialize CEF. CefInitialize(main_args, settings, app.get()); - // Initialize global strings - LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); - LoadString(hInstance, IDC_CEFCLIENT, szWindowClass, MAX_LOADSTRING); + // Register window class MyRegisterClass(hInstance, *(app->GetCurrentLanguage().GetStruct())); CefRefPtr cmdLine = AppGetCommandLine(); @@ -272,6 +336,10 @@ int APIENTRY wWinMain(HINSTANCE hInstance, // Shut down CEF. CefShutdown(); + // release the first instance mutex + if (hMutex != NULL) + ReleaseMutex(hMutex); + return result; } @@ -867,6 +935,25 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, PostQuitMessage(0); return 0; + case WM_COPYDATA: + // handle the interprocess communication request from another Brackets running instance + if (lParam != NULL) { + PCOPYDATASTRUCT data = (PCOPYDATASTRUCT)lParam; + if ((data->dwData == ID_WM_COPYDATA_SENDOPENFILECOMMAND) && (data->cbData > 0)) { + // another Brackets instance requests that we open the given filename + std::wstring wstrFilename = (LPCWSTR)data->lpData; + // Windows Explorer might enclose the filename in double-quotes. We need to strip these off. + if ((wstrFilename.front() == '\"') && wstrFilename.back() == '\"') + wstrFilename = wstrFilename.substr(1, wstrFilename.length() - 2); + ASSERT(g_handler != NULL); + CefRefPtr browser = g_handler->GetBrowser(); + // call into Javascript code to handle the open file command + ASSERT(browser != NULL); + g_handler->SendOpenFileCommand(browser, CefString(wstrFilename.c_str())); + } + } + break; + case WM_INITMENUPOPUP: // Notify before popping up g_handler->SendJSCommand(g_handler->GetBrowser(), APP_BEFORE_MENUPOPUP); diff --git a/installer/win/Brackets.wxs b/installer/win/Brackets.wxs index c8ef3ae34..a8dee7a57 100644 --- a/installer/win/Brackets.wxs +++ b/installer/win/Brackets.wxs @@ -42,6 +42,29 @@ xmlns:fire="http://schemas.microsoft.com/wix/FirewallExtension"> + + + + + + + + + + + + + + + + + + + + + + + @@ -78,6 +101,8 @@ xmlns:fire="http://schemas.microsoft.com/wix/FirewallExtension"> + + diff --git a/installer/win/brackets-win-install-build.xml b/installer/win/brackets-win-install-build.xml index a099c8102..ced4ff962 100644 --- a/installer/win/brackets-win-install-build.xml +++ b/installer/win/brackets-win-install-build.xml @@ -16,7 +16,7 @@ default="build.mul"> - +