Skip to content
This repository was archived by the owner on Sep 2, 2021. It is now read-only.
86 changes: 83 additions & 3 deletions appshell/cefclient_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@

#define CLOSING_PROP L"CLOSING"

#define FIRST_INSTANCE_MUTEX_NAME L"FIRST_INSTANCE_MUTEX"
Copy link
Contributor

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"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

#define ID_WM_COPYDATA_SENDOPENFILECOMMAND 1001
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be WM_USER + 1001

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Expand All @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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};

Expand Down Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The 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"

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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,
Expand All @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should just check if (hMutex != NULL)

Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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);
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Good call.

::SetForegroundWindow(hFirstInstanceWnd);
if (::IsIconic(hFirstInstanceWnd))
::ShowWindow(hFirstInstanceWnd, SW_RESTORE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not indented?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
Expand All @@ -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();
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down
25 changes: 25 additions & 0 deletions installer/win/Brackets.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,29 @@ xmlns:fire="http://schemas.microsoft.com/wix/FirewallExtension">
</Component>
</DirectoryRef>

<Component Id="FileAssociations" Guid="{D0195E8D-0881-42B6-9B4F-DA84D9396506}" Directory="INSTALLDIR" KeyPath="yes">
<!-- Capabilities keys for Vista/7 "Set Program Access and Defaults" -->
<RegistryValue Root="HKLM" Key="SOFTWARE\$(var.RegistryRoot)\Capabilities" Name="ApplicationIcon" Value="[INSTALLDIR]$(var.ExeName).exe,0" Type="string" />
<RegistryValue Root="HKLM" Key="SOFTWARE\$(var.RegistryRoot)\Capabilities" Name="ApplicationName" Value="!(loc.ProductName) $(var.ProductVersionName)" Type="string" />
<RegistryValue Root="HKLM" Key="SOFTWARE\RegisteredApplications" Name="!(loc.ProductName) $(var.ProductVersionName)" Value="SOFTWARE\$(var.RegistryRoot)\Capabilities" Type="string" />

<!-- File associations -->
<?define SupportedFiletypes=txt;groovy;ini;properties;css;scss;html;htm;shtm;shtml;xhtml;cfm;cfm1;cfc;dhtml;xht;tpl;twig;hbs;handlebars;kit;jsp;aspx;ejs;js;jsx;json;svg;xml;wxs;wxl;wsdl;rss;atom;rdf;xslt;xul;xbl;mathml;config;php;php3;php4;php5;phtm;phtml;ctp;c;h;i;cc;cp;cpp;c++;cxx;hh;hpp;hxx;h++;ii;cs;cshtml;asax;ashx;java;scala;sbt;coffee;cson;cf;clj;pl;pm;rb;ru;gemspec;rake;py;pyw;wsgi;sass;less;lua;sql;diff;patch;md;markdown;yaml;yml;hx;sh?>

<?foreach filetype in $(var.SupportedFiletypes)?>
<!-- associate program with file type -->
<RegistryValue Root="HKLM" Key="SOFTWARE\$(var.RegistryRoot)\Capabilities\FileAssociations" Value="!(loc.ProductName) $(var.ProductVersionName) FileExt" Name=".$(var.filetype)" Type="string" />

<!-- associate each supported filetype with application -->
<RegistryValue Root="HKCR" Key=".$(var.filetype)" Value="text" Name="PerceivedType" Type="string" />
<RegistryValue Root="HKCR" Key=".$(var.filetype)\OpenWithProgids" Value="" Name="!(loc.ProductName) $(var.ProductVersionName) FileExt" Type="string" />
<?endforeach?>

<!-- create ProgId entry -->
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\!(loc.ProductName) $(var.ProductVersionName) FileExt\shell\open\command" Value="&quot;[INSTALLDIR]$(var.ExeName).exe&quot; &quot;%1&quot;" Type="string" />
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\Applications\$(var.ExeName).exe\shell\open" Name="FriendlyAppName" Value="!(loc.ProductName) $(var.ProductVersionName)" Type="string" />

</Component>
<!-- Start Menu Shortcuts-->
<UIRef Id="WixUI_MyInstallDir" />
<UIRef Id="WixUI_ErrorProgressText" />
Expand Down Expand Up @@ -78,6 +101,8 @@ xmlns:fire="http://schemas.microsoft.com/wix/FirewallExtension">
<ComponentGroupRef Id='BRACKETSHARVESTMANAGER'/>

<ComponentRef Id='StartMenuShortcut' />

<ComponentRef Id='FileAssociations' />
</Feature>
</Product>
</Wix>
Expand Down
2 changes: 1 addition & 1 deletion installer/win/brackets-win-install-build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ default="build.mul">
<property name="product.version.number" value="0.${product.sprint.number}"/>
<property name="product.version.name" value="Sprint ${product.sprint.number}"/>
<property name="product.manufacturer" value="brackets.io"/>
<property name="product.registry.root" value="${product.shortname}"/>
<property name="product.registry.root" value="${product.shortname} ${product.version.name}"/>


<!-- Installer properties -->
Expand Down