Skip to content
This repository was archived by the owner on Sep 2, 2021. It is now read-only.

Commit 04c8c4d

Browse files
committed
Merge pull request #299 from adobe/bchin/open-with-brackets-from-windows-explorer-no-msi
Implement Windows Explorer "Open with..." option (no .msi)
2 parents 914c17a + 42f25c6 commit 04c8c4d

File tree

3 files changed

+116
-4
lines changed

3 files changed

+116
-4
lines changed

appshell/cefclient_win.cpp

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535

3636
#define CLOSING_PROP L"CLOSING"
3737

38+
#define FIRST_INSTANCE_MUTEX_NAME (APP_NAME L".Shell.Instance")
39+
#define ID_WM_COPYDATA_SENDOPENFILECOMMAND (WM_USER+1001)
40+
3841
// Global Variables:
3942
DWORD g_appStartupTime;
4043
HINSTANCE hInst; // current instance
@@ -142,6 +145,32 @@ std::wstring GetFilenamesFromCommandLine() {
142145
return result;
143146
}
144147

148+
// EnumWindowsProc callback function
149+
// - searches for an already running Brackets application window
150+
BOOL CALLBACK FindSuitableBracketsInstance(HWND hwnd, LPARAM lParam)
151+
{
152+
ASSERT(lParam != NULL); // must be passed an HWND pointer to return, if found
153+
154+
// check for the Brackets application window by class name and title
155+
WCHAR cName[MAX_PATH+1] = {0}, cTitle[MAX_PATH+1] = {0};
156+
::GetClassName(hwnd, cName, MAX_PATH);
157+
::GetWindowText(hwnd, cTitle, MAX_PATH);
158+
if ((wcscmp(cName, szWindowClass) == 0) && (wcsstr(cTitle, APP_NAME) != 0)) {
159+
// found an already running instance of Brackets. Now, check that that window
160+
// isn't currently disabled (eg. modal dialog). If it is keep searching.
161+
if ((::GetWindowLong(hwnd, GWL_STYLE) & WS_DISABLED) == 0) {
162+
//return the window handle and stop searching
163+
*(HWND*)lParam = hwnd;
164+
return FALSE;
165+
}
166+
}
167+
168+
return TRUE; // otherwise, continue searching
169+
}
170+
171+
// forward declaration; implemented in appshell_extensions_win.cpp
172+
void ConvertToUnixPath(ExtensionString& filename);
173+
145174
// Program entry point function.
146175
int APIENTRY wWinMain(HINSTANCE hInstance,
147176
HINSTANCE hPrevInstance,
@@ -167,6 +196,43 @@ int APIENTRY wWinMain(HINSTANCE hInstance,
167196
// Parse command line arguments. The passed in values are ignored on Windows.
168197
AppInitCommandLine(0, NULL);
169198

199+
// Initialize global strings
200+
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
201+
LoadString(hInstance, IDC_CEFCLIENT, szWindowClass, MAX_LOADSTRING);
202+
203+
// Determine if we should use an already running instance of Brackets.
204+
HANDLE hMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, FIRST_INSTANCE_MUTEX_NAME);
205+
if ((hMutex != NULL) && AppGetCommandLine()->HasArguments() && (lpCmdLine != NULL)) {
206+
// for subsequent instances, re-use an already running instance if we're being called to
207+
// open an existing file on the command-line (eg. Open With.. from Windows Explorer)
208+
HWND hFirstInstanceWnd = NULL;
209+
::EnumWindows(FindSuitableBracketsInstance, (LPARAM)&hFirstInstanceWnd);
210+
if (hFirstInstanceWnd != NULL) {
211+
::SetForegroundWindow(hFirstInstanceWnd);
212+
if (::IsIconic(hFirstInstanceWnd))
213+
::ShowWindow(hFirstInstanceWnd, SW_RESTORE);
214+
215+
// message the other Brackets instance to actually open the given filename
216+
std::wstring wstrFilename = lpCmdLine;
217+
ConvertToUnixPath(wstrFilename);
218+
// note: WM_COPYDATA will manage passing the string across process space
219+
COPYDATASTRUCT data;
220+
data.dwData = ID_WM_COPYDATA_SENDOPENFILECOMMAND;
221+
data.cbData = (wstrFilename.length() + 1) * sizeof(WCHAR);
222+
data.lpData = (LPVOID)wstrFilename.c_str();
223+
::SendMessage(hFirstInstanceWnd, WM_COPYDATA, (WPARAM)(HWND)hFirstInstanceWnd, (LPARAM)(LPVOID)&data);
224+
225+
// exit this instance
226+
return 0;
227+
}
228+
// otherwise, fall thru and launch a new instance
229+
}
230+
231+
if (hMutex == NULL) {
232+
// first instance of this app, so create the mutex and continue execution of this instance.
233+
hMutex = ::CreateMutex(NULL, FALSE, FIRST_INSTANCE_MUTEX_NAME);
234+
}
235+
170236
CefSettings settings;
171237

172238
// Populate the settings based on command line arguments.
@@ -180,9 +246,7 @@ int APIENTRY wWinMain(HINSTANCE hInstance,
180246
// Initialize CEF.
181247
CefInitialize(main_args, settings, app.get());
182248

183-
// Initialize global strings
184-
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
185-
LoadString(hInstance, IDC_CEFCLIENT, szWindowClass, MAX_LOADSTRING);
249+
// Register window class
186250
MyRegisterClass(hInstance, *(app->GetCurrentLanguage().GetStruct()));
187251

188252
CefRefPtr<CefCommandLine> cmdLine = AppGetCommandLine();
@@ -272,6 +336,10 @@ int APIENTRY wWinMain(HINSTANCE hInstance,
272336
// Shut down CEF.
273337
CefShutdown();
274338

339+
// release the first instance mutex
340+
if (hMutex != NULL)
341+
ReleaseMutex(hMutex);
342+
275343
return result;
276344
}
277345

@@ -867,6 +935,25 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
867935
PostQuitMessage(0);
868936
return 0;
869937

938+
case WM_COPYDATA:
939+
// handle the interprocess communication request from another Brackets running instance
940+
if (lParam != NULL) {
941+
PCOPYDATASTRUCT data = (PCOPYDATASTRUCT)lParam;
942+
if ((data->dwData == ID_WM_COPYDATA_SENDOPENFILECOMMAND) && (data->cbData > 0)) {
943+
// another Brackets instance requests that we open the given filename
944+
std::wstring wstrFilename = (LPCWSTR)data->lpData;
945+
// Windows Explorer might enclose the filename in double-quotes. We need to strip these off.
946+
if ((wstrFilename.front() == '\"') && wstrFilename.back() == '\"')
947+
wstrFilename = wstrFilename.substr(1, wstrFilename.length() - 2);
948+
ASSERT(g_handler != NULL);
949+
CefRefPtr<CefBrowser> browser = g_handler->GetBrowser();
950+
// call into Javascript code to handle the open file command
951+
ASSERT(browser != NULL);
952+
g_handler->SendOpenFileCommand(browser, CefString(wstrFilename.c_str()));
953+
}
954+
}
955+
break;
956+
870957
case WM_INITMENUPOPUP:
871958
// Notify before popping up
872959
g_handler->SendJSCommand(g_handler->GetBrowser(), APP_BEFORE_MENUPOPUP);

installer/win/Brackets.wxs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,29 @@ xmlns:fire="http://schemas.microsoft.com/wix/FirewallExtension">
4242
</Component>
4343
</DirectoryRef>
4444

45+
<Component Id="FileAssociations" Guid="{D0195E8D-0881-42B6-9B4F-DA84D9396506}" Directory="INSTALLDIR" KeyPath="yes">
46+
<!-- Capabilities keys for Vista/7 "Set Program Access and Defaults" -->
47+
<RegistryValue Root="HKLM" Key="SOFTWARE\$(var.RegistryRoot)\Capabilities" Name="ApplicationIcon" Value="[INSTALLDIR]$(var.ExeName).exe,0" Type="string" />
48+
<RegistryValue Root="HKLM" Key="SOFTWARE\$(var.RegistryRoot)\Capabilities" Name="ApplicationName" Value="!(loc.ProductName) $(var.ProductVersionName)" Type="string" />
49+
<RegistryValue Root="HKLM" Key="SOFTWARE\RegisteredApplications" Name="!(loc.ProductName) $(var.ProductVersionName)" Value="SOFTWARE\$(var.RegistryRoot)\Capabilities" Type="string" />
50+
51+
<!-- File associations -->
52+
<?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?>
53+
54+
<?foreach filetype in $(var.SupportedFiletypes)?>
55+
<!-- associate program with file type -->
56+
<RegistryValue Root="HKLM" Key="SOFTWARE\$(var.RegistryRoot)\Capabilities\FileAssociations" Value="!(loc.ProductName) $(var.ProductVersionName) FileExt" Name=".$(var.filetype)" Type="string" />
57+
58+
<!-- associate each supported filetype with application -->
59+
<RegistryValue Root="HKCR" Key=".$(var.filetype)" Value="text" Name="PerceivedType" Type="string" />
60+
<RegistryValue Root="HKCR" Key=".$(var.filetype)\OpenWithProgids" Value="" Name="!(loc.ProductName) $(var.ProductVersionName) FileExt" Type="string" />
61+
<?endforeach?>
62+
63+
<!-- create ProgId entry -->
64+
<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" />
65+
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\Applications\$(var.ExeName).exe\shell\open" Name="FriendlyAppName" Value="!(loc.ProductName) $(var.ProductVersionName)" Type="string" />
66+
67+
</Component>
4568
<!-- Start Menu Shortcuts-->
4669
<UIRef Id="WixUI_MyInstallDir" />
4770
<UIRef Id="WixUI_ErrorProgressText" />
@@ -78,6 +101,8 @@ xmlns:fire="http://schemas.microsoft.com/wix/FirewallExtension">
78101
<ComponentGroupRef Id='BRACKETSHARVESTMANAGER'/>
79102

80103
<ComponentRef Id='StartMenuShortcut' />
104+
105+
<ComponentRef Id='FileAssociations' />
81106
</Feature>
82107
</Product>
83108
</Wix>

installer/win/brackets-win-install-build.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ default="build.mul">
1616
<property name="product.version.number" value="0.${product.sprint.number}"/>
1717
<property name="product.version.name" value="Sprint ${product.sprint.number}"/>
1818
<property name="product.manufacturer" value="brackets.io"/>
19-
<property name="product.registry.root" value="${product.shortname}"/>
19+
<property name="product.registry.root" value="${product.shortname} ${product.version.name}"/>
2020

2121

2222
<!-- Installer properties -->

0 commit comments

Comments
 (0)