Implement Windows Explorer "Open with..." option (no .msi) #299
Changes from 8 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".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)) { | ||
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 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" 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. 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 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, APP_NAME 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) { | ||
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. looks like the indentation got messed up here. |
||
::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; | ||
} | ||
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. It looks like maybe you have convert tabs to spaces turned on for C++ I think maybe turn that off and fix the block above and we're good to go. 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. Ok. Lemme redo this again. I had re-indented using Brackets per an earlier comment. Lemme go back to VS2010 to indent. |
||
// 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, APP_NAME 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<CefCommandLine> 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<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.
why no product or project identifier here?
"com.brackets.shell.instance" which I guess would be "com.,adobe.edge.code.shell.instance" for Edge Code. If Brackets and Edge Code are both installed it will get confused.
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.
@JeffryBooher I pre-pend APP_NAME to the string in the spots that I use this. That differentiates the mutex between EC and Brackets when they are both running. Having said that, I can move the pre-pending code directly into the #define to make it more readable. (I was doing this previously when I was using the static const TCHAR variable, but I should've moved it into the #define when I refactored it the last time for you.)