-
Notifications
You must be signed in to change notification settings - Fork 604
Implement Windows Explorer "Open with..." option (no .msi) #299
Changes from 4 commits
841768e
54e67d7
578e7ba
e0432ca
6986083
203b3f3
21dadf6
45ec711
3dc1a0f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,9 @@ | |
|
|
||
| #define CLOSING_PROP L"CLOSING" | ||
|
|
||
| #define FIRST_INSTANCE_MUTEX_NAME L"FIRST_INSTANCE_MUTEX" | ||
| #define ID_WM_COPYDATA_SENDOPENFILECOMMAND 1001 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be WM_USER + 1001
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically, that ID wasn't really a private windows message, so it doesn't have to be in the WM_USER range. It's just an integer ID value specific to how an app wants to use the WM_COPYDATA message. Still, it doesn't matter either way to me, so I've made the requested change. |
||
|
|
||
| // Global Variables: | ||
| DWORD g_appStartupTime; | ||
| HINSTANCE hInst; // current instance | ||
|
|
@@ -44,6 +47,7 @@ std::wstring gFilesToOpen; // Filenames passed as arguments to app | |
| TCHAR szTitle[MAX_LOADSTRING]; // The title bar text | ||
| TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name | ||
| char szWorkingDir[MAX_PATH]; // The current working directory | ||
| static const TCHAR szMutexName[] = APP_NAME FIRST_INSTANCE_MUTEX_NAME; // proper name of first instance mutex | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any reason not to just pass the constant around? I don't think we need to init a TCHAR buffer to it. This should be wchar_t buffer anyway since the string is a L"" type string. Alternately, you could change the string to a _T(); But then you would need to use the _T version of CreateMutex. I'd say just pass the #define value into CreateMutex
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typically, declaring a string as a static const helps direct the compiler in optimizing how the string literal is stored. In the past, #define literals might be duplicated more than once in the compiled binary. As a result, using a static const would help save on, at the very least, file size. To your TCHAR point, that data type will be compiled as WCHAR since we're compiling with the UNICODE flags. Still, since this string is only used twice, it doesn't really matter much either way it's written, so I'm happy making the requested change. |
||
|
|
||
| TCHAR szInitialUrl[MAX_PATH] = {0}; | ||
|
|
||
|
|
@@ -142,6 +146,28 @@ std::wstring GetFilenamesFromCommandLine() { | |
| return result; | ||
| } | ||
|
|
||
| // EnumWindowsProc callback function | ||
| // - searches for an already running Brackets application window | ||
| BOOL CALLBACK FindFirstBracketsInstance(HWND hwnd, LPARAM lParam) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this is not FindFirstBracketsInstance if there are more than one open it is maybe FindSuitableBracketsInstance since some brackets instances may not qualify?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call. Done. |
||
| { | ||
| 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)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you should also check to see if there is a Modal Dialog open and not try to send it a message in that case. It would be great to check for a brackets modal dialog as well but I think we should at least check that a File Open / Save As, etc... dialog isn't open
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can do that by enumerating child windows and looking for a window with the class "#32770"
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added the check for the modal dialog by checking if the main window is disabled. As you noted, this won't catch the JS "modal" dialogs, like the About box. However, as those aren't really modal, the selected file still gets opened anyway. Consequently, I didn't bother iterating the child windows to watch for these. It still seems to work as expected. However, if you're really bothered by this, I can add it.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bchintx yep, i thought about suggesting the disabled window check but wasn't sure if that was the only case. I think i t's fine to error on that side and it is much easier to test for a disabled window rather than enumerating the child windows. |
||
| // found it! 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 +193,39 @@ 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, szMutexName); | ||
| if (hMutex == NULL) { | ||
| // first instance of this app, so create the mutex and continue execution of this instance. | ||
| hMutex = ::CreateMutex(NULL, FALSE, szMutexName); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should just check if (hMutex != NULL)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then we execute this block which returns 0 if it brings the app into the foreground and sends a WM_COPYDATA message
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. Another good call. |
||
| } else if (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(FindFirstBracketsInstance, (LPARAM)&hFirstInstanceWnd); | ||
| ASSERT(hFirstInstanceWnd != NULL); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like we should handle this case by bailing and just creating a new instance.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and by bailing I mean that this code should be if (hFirstInstancnceWnd != NULL) { bring to front, send wm_copydata, return 0 } so we can fall through, create the mutex etc if it fails.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. Good call. |
||
| ::SetForegroundWindow(hFirstInstanceWnd); | ||
| if (::IsIconic(hFirstInstanceWnd)) | ||
| ::ShowWindow(hFirstInstanceWnd, SW_RESTORE); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this not indented?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. I guess the VS2010 editor tabs came out differently. Re-indented using Brackets editor. |
||
|
|
||
| // 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; | ||
| } | ||
|
|
||
| CefSettings settings; | ||
|
|
||
| // Populate the settings based on command line arguments. | ||
|
|
@@ -180,9 +239,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<CefCommandLine> cmdLine = AppGetCommandLine(); | ||
|
|
@@ -272,6 +329,10 @@ int APIENTRY wWinMain(HINSTANCE hInstance, | |
| // Shut down CEF. | ||
| CefShutdown(); | ||
|
|
||
| // release the first instance mutex | ||
| if (hMutex != NULL) | ||
| ReleaseMutex(hMutex); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
|
|
@@ -867,6 +928,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<CefBrowser> 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); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer that this had a name like "Brackets.Shell.Instance"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.