|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 |
|
7 #include "CEHHelper.h" |
|
8 |
|
9 #include <objbase.h> |
|
10 #include <combaseapi.h> |
|
11 #include <atlcore.h> |
|
12 #include <atlstr.h> |
|
13 #include <wininet.h> |
|
14 #include <shlobj.h> |
|
15 #include <shlwapi.h> |
|
16 #include <propkey.h> |
|
17 #include <propvarutil.h> |
|
18 #include <stdio.h> |
|
19 #include <stdlib.h> |
|
20 #include <strsafe.h> |
|
21 #include <io.h> |
|
22 #include <shellapi.h> |
|
23 |
|
24 #ifdef SHOW_CONSOLE |
|
25 #define DEBUG_DELAY_SHUTDOWN 1 |
|
26 #endif |
|
27 |
|
28 // Heartbeat timer duration used while waiting for an incoming request. |
|
29 #define HEARTBEAT_MSEC 250 |
|
30 // Total number of heartbeats we wait before giving up and shutting down. |
|
31 #define REQUEST_WAIT_TIMEOUT 30 |
|
32 // Pulled from desktop browser's shell |
|
33 #define APP_REG_NAME L"Firefox" |
|
34 |
|
35 const WCHAR* kFirefoxExe = L"firefox.exe"; |
|
36 static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL"; |
|
37 static const WCHAR* kMetroRestartCmdLine = L"--metro-restart"; |
|
38 static const WCHAR* kMetroUpdateCmdLine = L"--metro-update"; |
|
39 static const WCHAR* kDesktopRestartCmdLine = L"--desktop-restart"; |
|
40 static const WCHAR* kNsisLaunchCmdLine = L"--launchmetro"; |
|
41 static const WCHAR* kExplorerLaunchCmdLine = L"-Embedding"; |
|
42 |
|
43 static bool GetDefaultBrowserPath(CStringW& aPathBuffer); |
|
44 |
|
45 /* |
|
46 * Retrieve our module dir path. |
|
47 * |
|
48 * @aPathBuffer Buffer to fill |
|
49 */ |
|
50 static bool GetModulePath(CStringW& aPathBuffer) |
|
51 { |
|
52 WCHAR buffer[MAX_PATH]; |
|
53 memset(buffer, 0, sizeof(buffer)); |
|
54 |
|
55 if (!GetModuleFileName(nullptr, buffer, MAX_PATH)) { |
|
56 Log(L"GetModuleFileName failed."); |
|
57 return false; |
|
58 } |
|
59 |
|
60 WCHAR* slash = wcsrchr(buffer, '\\'); |
|
61 if (!slash) |
|
62 return false; |
|
63 *slash = '\0'; |
|
64 |
|
65 aPathBuffer = buffer; |
|
66 return true; |
|
67 } |
|
68 |
|
69 |
|
70 template <class T>void SafeRelease(T **ppT) |
|
71 { |
|
72 if (*ppT) { |
|
73 (*ppT)->Release(); |
|
74 *ppT = nullptr; |
|
75 } |
|
76 } |
|
77 |
|
78 template <class T> HRESULT SetInterface(T **ppT, IUnknown *punk) |
|
79 { |
|
80 SafeRelease(ppT); |
|
81 return punk ? punk->QueryInterface(ppT) : E_NOINTERFACE; |
|
82 } |
|
83 |
|
84 class __declspec(uuid("5100FEC1-212B-4BF5-9BF8-3E650FD794A3")) |
|
85 CExecuteCommandVerb : public IExecuteCommand, |
|
86 public IObjectWithSelection, |
|
87 public IInitializeCommand, |
|
88 public IObjectWithSite, |
|
89 public IExecuteCommandApplicationHostEnvironment |
|
90 { |
|
91 public: |
|
92 |
|
93 CExecuteCommandVerb() : |
|
94 mRef(0), |
|
95 mShellItemArray(nullptr), |
|
96 mUnkSite(nullptr), |
|
97 mTargetIsFileSystemLink(false), |
|
98 mTargetIsDefaultBrowser(false), |
|
99 mTargetIsBrowser(false), |
|
100 mRequestType(DEFAULT_LAUNCH), |
|
101 mRequestMet(false), |
|
102 mDelayedLaunchType(NONE), |
|
103 mVerb(L"open") |
|
104 { |
|
105 } |
|
106 |
|
107 ~CExecuteCommandVerb() |
|
108 { |
|
109 } |
|
110 |
|
111 bool RequestMet() { return mRequestMet; } |
|
112 void SetRequestMet(); |
|
113 long RefCount() { return mRef; } |
|
114 void HeartBeat(); |
|
115 |
|
116 // IUnknown |
|
117 IFACEMETHODIMP QueryInterface(REFIID aRefID, void **aInt) |
|
118 { |
|
119 static const QITAB qit[] = { |
|
120 QITABENT(CExecuteCommandVerb, IExecuteCommand), |
|
121 QITABENT(CExecuteCommandVerb, IObjectWithSelection), |
|
122 QITABENT(CExecuteCommandVerb, IInitializeCommand), |
|
123 QITABENT(CExecuteCommandVerb, IObjectWithSite), |
|
124 QITABENT(CExecuteCommandVerb, IExecuteCommandApplicationHostEnvironment), |
|
125 { 0 }, |
|
126 }; |
|
127 return QISearch(this, qit, aRefID, aInt); |
|
128 } |
|
129 |
|
130 IFACEMETHODIMP_(ULONG) AddRef() |
|
131 { |
|
132 return InterlockedIncrement(&mRef); |
|
133 } |
|
134 |
|
135 IFACEMETHODIMP_(ULONG) Release() |
|
136 { |
|
137 long cRef = InterlockedDecrement(&mRef); |
|
138 if (!cRef) { |
|
139 delete this; |
|
140 } |
|
141 return cRef; |
|
142 } |
|
143 |
|
144 // IExecuteCommand |
|
145 IFACEMETHODIMP SetKeyState(DWORD aKeyState) |
|
146 { |
|
147 mKeyState = aKeyState; |
|
148 return S_OK; |
|
149 } |
|
150 |
|
151 IFACEMETHODIMP SetParameters(PCWSTR aParameters) |
|
152 { |
|
153 Log(L"SetParameters: '%s'", aParameters); |
|
154 |
|
155 if (!_wcsicmp(aParameters, kMetroRestartCmdLine)) { |
|
156 mRequestType = METRO_RESTART; |
|
157 } else if (_wcsicmp(aParameters, kMetroUpdateCmdLine) == 0) { |
|
158 mRequestType = METRO_UPDATE; |
|
159 } else if (_wcsicmp(aParameters, kDesktopRestartCmdLine) == 0) { |
|
160 mRequestType = DESKTOP_RESTART; |
|
161 } else { |
|
162 mParameters = aParameters; |
|
163 } |
|
164 return S_OK; |
|
165 } |
|
166 |
|
167 IFACEMETHODIMP SetPosition(POINT aPoint) |
|
168 { return S_OK; } |
|
169 |
|
170 IFACEMETHODIMP SetShowWindow(int aShowFlag) |
|
171 { return S_OK; } |
|
172 |
|
173 IFACEMETHODIMP SetNoShowUI(BOOL aNoUI) |
|
174 { return S_OK; } |
|
175 |
|
176 IFACEMETHODIMP SetDirectory(PCWSTR aDirPath) |
|
177 { return S_OK; } |
|
178 |
|
179 IFACEMETHODIMP Execute(); |
|
180 |
|
181 // IObjectWithSelection |
|
182 IFACEMETHODIMP SetSelection(IShellItemArray *aArray) |
|
183 { |
|
184 if (!aArray) { |
|
185 return E_FAIL; |
|
186 } |
|
187 |
|
188 SetInterface(&mShellItemArray, aArray); |
|
189 |
|
190 DWORD count = 0; |
|
191 aArray->GetCount(&count); |
|
192 if (!count) { |
|
193 return E_FAIL; |
|
194 } |
|
195 |
|
196 #ifdef SHOW_CONSOLE |
|
197 Log(L"SetSelection param count: %d", count); |
|
198 for (DWORD idx = 0; idx < count; idx++) { |
|
199 IShellItem* item = nullptr; |
|
200 if (SUCCEEDED(aArray->GetItemAt(idx, &item))) { |
|
201 LPWSTR str = nullptr; |
|
202 if (FAILED(item->GetDisplayName(SIGDN_FILESYSPATH, &str))) { |
|
203 if (FAILED(item->GetDisplayName(SIGDN_URL, &str))) { |
|
204 Log(L"Failed to get a shell item array item."); |
|
205 item->Release(); |
|
206 continue; |
|
207 } |
|
208 } |
|
209 item->Release(); |
|
210 Log(L"SetSelection param: '%s'", str); |
|
211 CoTaskMemFree(str); |
|
212 } |
|
213 } |
|
214 #endif |
|
215 |
|
216 IShellItem* item = nullptr; |
|
217 if (FAILED(aArray->GetItemAt(0, &item))) { |
|
218 return E_FAIL; |
|
219 } |
|
220 |
|
221 bool isFileSystem = false; |
|
222 if (!SetTargetPath(item) || !mTarget.GetLength()) { |
|
223 Log(L"SetTargetPath failed."); |
|
224 return E_FAIL; |
|
225 } |
|
226 item->Release(); |
|
227 |
|
228 Log(L"SetSelection target: %s", mTarget); |
|
229 return S_OK; |
|
230 } |
|
231 |
|
232 IFACEMETHODIMP GetSelection(REFIID aRefID, void **aInt) |
|
233 { |
|
234 *aInt = nullptr; |
|
235 return mShellItemArray ? mShellItemArray->QueryInterface(aRefID, aInt) : E_FAIL; |
|
236 } |
|
237 |
|
238 // IInitializeCommand |
|
239 IFACEMETHODIMP Initialize(PCWSTR aVerb, IPropertyBag* aPropBag) |
|
240 { |
|
241 if (!aVerb) |
|
242 return E_FAIL; |
|
243 // 'open', 'edit', etc. Based on our registry settings |
|
244 Log(L"Initialize(%s)", aVerb); |
|
245 mVerb = aVerb; |
|
246 return S_OK; |
|
247 } |
|
248 |
|
249 // IObjectWithSite |
|
250 IFACEMETHODIMP SetSite(IUnknown *aUnkSite) |
|
251 { |
|
252 SetInterface(&mUnkSite, aUnkSite); |
|
253 return S_OK; |
|
254 } |
|
255 |
|
256 IFACEMETHODIMP GetSite(REFIID aRefID, void **aInt) |
|
257 { |
|
258 *aInt = nullptr; |
|
259 return mUnkSite ? mUnkSite->QueryInterface(aRefID, aInt) : E_FAIL; |
|
260 } |
|
261 |
|
262 // IExecuteCommandApplicationHostEnvironment |
|
263 IFACEMETHODIMP GetValue(AHE_TYPE *aLaunchType) |
|
264 { |
|
265 Log(L"IExecuteCommandApplicationHostEnvironment::GetValue()"); |
|
266 *aLaunchType = GetLaunchType(); |
|
267 return S_OK; |
|
268 } |
|
269 |
|
270 /** |
|
271 * Choose the appropriate launch type based on the user's previously chosen |
|
272 * host environment, along with system constraints. |
|
273 * |
|
274 * AHE_DESKTOP = 0, AHE_IMMERSIVE = 1 |
|
275 */ |
|
276 AHE_TYPE GetLaunchType() |
|
277 { |
|
278 AHE_TYPE ahe = GetLastAHE(); |
|
279 Log(L"Previous AHE: %d", ahe); |
|
280 |
|
281 // Default launch settings from GetLastAHE() can be overriden by |
|
282 // custom parameter values we receive. |
|
283 if (mRequestType == DESKTOP_RESTART) { |
|
284 Log(L"Restarting in desktop host environment."); |
|
285 return AHE_DESKTOP; |
|
286 } else if (mRequestType == METRO_RESTART) { |
|
287 Log(L"Restarting in metro host environment."); |
|
288 ahe = AHE_IMMERSIVE; |
|
289 } else if (mRequestType == METRO_UPDATE) { |
|
290 // Shouldn't happen from GetValue above, but might from other calls. |
|
291 ahe = AHE_IMMERSIVE; |
|
292 } |
|
293 |
|
294 if (ahe == AHE_IMMERSIVE) { |
|
295 if (!IsDefaultBrowser()) { |
|
296 Log(L"returning AHE_DESKTOP because we are not the default browser"); |
|
297 return AHE_DESKTOP; |
|
298 } |
|
299 |
|
300 if (!IsDX10Available()) { |
|
301 Log(L"returning AHE_DESKTOP because DX10 is not available"); |
|
302 return AHE_DESKTOP; |
|
303 } |
|
304 } |
|
305 return ahe; |
|
306 } |
|
307 |
|
308 bool DefaultLaunchIsDesktop() |
|
309 { |
|
310 return GetLaunchType() == AHE_DESKTOP; |
|
311 } |
|
312 |
|
313 bool DefaultLaunchIsMetro() |
|
314 { |
|
315 return GetLaunchType() == AHE_IMMERSIVE; |
|
316 } |
|
317 |
|
318 /* |
|
319 * Retrieve the target path if it is the default browser |
|
320 * or if not default, retreives the target path if it is a firefox browser |
|
321 * or if the target is not firefox, relies on a hack to get the |
|
322 * 'module dir path\firefox.exe' |
|
323 * The reason why it's not good to rely on the CEH path is because there is |
|
324 * no guarantee win8 will use the CEH at our expected path. It has an in |
|
325 * memory cache even if the registry is updated for the CEH path. |
|
326 * |
|
327 * @aPathBuffer Buffer to fill |
|
328 */ |
|
329 bool GetDesktopBrowserPath(CStringW& aPathBuffer) |
|
330 { |
|
331 // If the target was the default browser itself then return early. Otherwise |
|
332 // rely on a hack to check CEH path and calculate it relative to it. |
|
333 |
|
334 if (mTargetIsDefaultBrowser || mTargetIsBrowser) { |
|
335 aPathBuffer = mTarget; |
|
336 return true; |
|
337 } |
|
338 |
|
339 if (!GetModulePath(aPathBuffer)) |
|
340 return false; |
|
341 |
|
342 // ceh.exe sits in dist/bin root with the desktop browser. Since this |
|
343 // is a firefox only component, this hardcoded filename is ok. |
|
344 aPathBuffer.Append(L"\\"); |
|
345 aPathBuffer.Append(kFirefoxExe); |
|
346 return true; |
|
347 } |
|
348 |
|
349 bool IsDefaultBrowser() |
|
350 { |
|
351 IApplicationAssociationRegistration* pAAR; |
|
352 HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration, |
|
353 nullptr, |
|
354 CLSCTX_INPROC, |
|
355 IID_IApplicationAssociationRegistration, |
|
356 (void**)&pAAR); |
|
357 if (FAILED(hr)) |
|
358 return false; |
|
359 |
|
360 BOOL res = FALSE; |
|
361 hr = pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, |
|
362 APP_REG_NAME, |
|
363 &res); |
|
364 Log(L"QueryAppIsDefaultAll: %d", res); |
|
365 if (!res) { |
|
366 pAAR->Release(); |
|
367 return false; |
|
368 } |
|
369 // Make sure the Prog ID matches what we have |
|
370 LPWSTR registeredApp; |
|
371 hr = pAAR->QueryCurrentDefault(L"http", AT_URLPROTOCOL, AL_EFFECTIVE, |
|
372 ®isteredApp); |
|
373 pAAR->Release(); |
|
374 Log(L"QueryCurrentDefault: %X", hr); |
|
375 if (FAILED(hr)) |
|
376 return false; |
|
377 |
|
378 Log(L"registeredApp=%s", registeredApp); |
|
379 bool result = !wcsicmp(registeredApp, kDefaultMetroBrowserIDPathKey); |
|
380 CoTaskMemFree(registeredApp); |
|
381 if (!result) |
|
382 return false; |
|
383 |
|
384 // If the registry points another browser's path, |
|
385 // activating the Metro browser will fail. So fallback to the desktop. |
|
386 CStringW selfPath; |
|
387 GetDesktopBrowserPath(selfPath); |
|
388 CStringW browserPath; |
|
389 GetDefaultBrowserPath(browserPath); |
|
390 |
|
391 return !selfPath.CompareNoCase(browserPath); |
|
392 } |
|
393 |
|
394 /* |
|
395 * Helper for nsis installer when it wants to launch the |
|
396 * default metro browser. |
|
397 */ |
|
398 void CommandLineMetroLaunch() |
|
399 { |
|
400 mTargetIsDefaultBrowser = true; |
|
401 LaunchMetroBrowser(); |
|
402 } |
|
403 |
|
404 private: |
|
405 void LaunchDesktopBrowser(); |
|
406 bool LaunchMetroBrowser(); |
|
407 bool SetTargetPath(IShellItem* aItem); |
|
408 bool TestForUpdateLock(); |
|
409 |
|
410 /* |
|
411 * Defines the type of startup request we receive. |
|
412 */ |
|
413 enum RequestType { |
|
414 DEFAULT_LAUNCH, |
|
415 DESKTOP_RESTART, |
|
416 METRO_RESTART, |
|
417 METRO_UPDATE, |
|
418 }; |
|
419 |
|
420 RequestType mRequestType; |
|
421 |
|
422 /* |
|
423 * Defines the type of delayed launch we might do. |
|
424 */ |
|
425 enum DelayedLaunchType { |
|
426 NONE, |
|
427 DESKTOP, |
|
428 METRO, |
|
429 }; |
|
430 |
|
431 DelayedLaunchType mDelayedLaunchType; |
|
432 |
|
433 long mRef; |
|
434 IShellItemArray *mShellItemArray; |
|
435 IUnknown *mUnkSite; |
|
436 CStringW mVerb; |
|
437 CStringW mTarget; |
|
438 CStringW mParameters; |
|
439 bool mTargetIsFileSystemLink; |
|
440 bool mTargetIsDefaultBrowser; |
|
441 bool mTargetIsBrowser; |
|
442 DWORD mKeyState; |
|
443 bool mRequestMet; |
|
444 }; |
|
445 |
|
446 /* |
|
447 * Retrieve the current default browser's path. |
|
448 * |
|
449 * @aPathBuffer Buffer to fill |
|
450 */ |
|
451 static bool GetDefaultBrowserPath(CStringW& aPathBuffer) |
|
452 { |
|
453 WCHAR buffer[MAX_PATH]; |
|
454 DWORD length = MAX_PATH; |
|
455 |
|
456 if (FAILED(AssocQueryStringW(ASSOCF_NOTRUNCATE | ASSOCF_INIT_IGNOREUNKNOWN, |
|
457 ASSOCSTR_EXECUTABLE, |
|
458 kDefaultMetroBrowserIDPathKey, nullptr, |
|
459 buffer, &length))) { |
|
460 Log(L"AssocQueryString failed."); |
|
461 return false; |
|
462 } |
|
463 |
|
464 // sanity check |
|
465 if (lstrcmpiW(PathFindFileNameW(buffer), kFirefoxExe)) |
|
466 return false; |
|
467 |
|
468 aPathBuffer = buffer; |
|
469 return true; |
|
470 } |
|
471 |
|
472 /* |
|
473 * Retrieve the app model id of the firefox metro browser. |
|
474 * |
|
475 * @aPathBuffer Buffer to fill |
|
476 * @aCharLength Length of buffer to fill in characters |
|
477 */ |
|
478 template <size_t N> |
|
479 static bool GetDefaultBrowserAppModelID(WCHAR (&aIDBuffer)[N]) |
|
480 { |
|
481 HKEY key; |
|
482 if (RegOpenKeyExW(HKEY_CLASSES_ROOT, kDefaultMetroBrowserIDPathKey, |
|
483 0, KEY_READ, &key) != ERROR_SUCCESS) { |
|
484 return false; |
|
485 } |
|
486 DWORD len = sizeof(aIDBuffer); |
|
487 memset(aIDBuffer, 0, len); |
|
488 if (RegQueryValueExW(key, L"AppUserModelID", nullptr, nullptr, |
|
489 (LPBYTE)aIDBuffer, &len) != ERROR_SUCCESS || !len) { |
|
490 RegCloseKey(key); |
|
491 return false; |
|
492 } |
|
493 RegCloseKey(key); |
|
494 return true; |
|
495 } |
|
496 |
|
497 namespace { |
|
498 const FORMATETC kPlainTextFormat = |
|
499 {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; |
|
500 const FORMATETC kPlainTextWFormat = |
|
501 {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; |
|
502 } |
|
503 |
|
504 bool HasPlainText(IDataObject* aDataObj) { |
|
505 return SUCCEEDED(aDataObj->QueryGetData((FORMATETC*)&kPlainTextWFormat)) || |
|
506 SUCCEEDED(aDataObj->QueryGetData((FORMATETC*)&kPlainTextFormat)); |
|
507 } |
|
508 |
|
509 bool GetPlainText(IDataObject* aDataObj, CStringW& cstrText) |
|
510 { |
|
511 if (!HasPlainText(aDataObj)) |
|
512 return false; |
|
513 |
|
514 STGMEDIUM store; |
|
515 |
|
516 // unicode text |
|
517 if (SUCCEEDED(aDataObj->GetData((FORMATETC*)&kPlainTextWFormat, &store))) { |
|
518 // makes a copy |
|
519 cstrText = static_cast<LPCWSTR>(GlobalLock(store.hGlobal)); |
|
520 GlobalUnlock(store.hGlobal); |
|
521 ReleaseStgMedium(&store); |
|
522 return true; |
|
523 } |
|
524 |
|
525 // ascii text |
|
526 if (SUCCEEDED(aDataObj->GetData((FORMATETC*)&kPlainTextFormat, &store))) { |
|
527 // makes a copy |
|
528 cstrText = static_cast<char*>(GlobalLock(store.hGlobal)); |
|
529 GlobalUnlock(store.hGlobal); |
|
530 ReleaseStgMedium(&store); |
|
531 return true; |
|
532 } |
|
533 |
|
534 return false; |
|
535 } |
|
536 |
|
537 /* |
|
538 * Updates the current target based on the contents of |
|
539 * a shell item. |
|
540 */ |
|
541 bool CExecuteCommandVerb::SetTargetPath(IShellItem* aItem) |
|
542 { |
|
543 if (!aItem) |
|
544 return false; |
|
545 |
|
546 CString cstrText; |
|
547 CComPtr<IDataObject> object; |
|
548 // Check the underlying data object first to insure we get |
|
549 // absolute uri. See chromium bug 157184. |
|
550 if (SUCCEEDED(aItem->BindToHandler(nullptr, BHID_DataObject, |
|
551 IID_IDataObject, |
|
552 reinterpret_cast<void**>(&object))) && |
|
553 GetPlainText(object, cstrText)) { |
|
554 wchar_t scheme[16]; |
|
555 URL_COMPONENTS components = {0}; |
|
556 components.lpszScheme = scheme; |
|
557 components.dwSchemeLength = sizeof(scheme)/sizeof(scheme[0]); |
|
558 components.dwStructSize = sizeof(components); |
|
559 // note, more advanced use may have issues with paths with spaces. |
|
560 if (!InternetCrackUrlW(cstrText, 0, 0, &components)) { |
|
561 Log(L"Failed to identify object text '%s'", cstrText); |
|
562 return false; |
|
563 } |
|
564 |
|
565 mTargetIsFileSystemLink = (components.nScheme == INTERNET_SCHEME_FILE); |
|
566 mTarget = cstrText; |
|
567 |
|
568 return true; |
|
569 } |
|
570 |
|
571 Log(L"No data object or data object has no text."); |
|
572 |
|
573 // Use the shell item display name |
|
574 LPWSTR str = nullptr; |
|
575 mTargetIsFileSystemLink = true; |
|
576 if (FAILED(aItem->GetDisplayName(SIGDN_FILESYSPATH, &str))) { |
|
577 mTargetIsFileSystemLink = false; |
|
578 if (FAILED(aItem->GetDisplayName(SIGDN_URL, &str))) { |
|
579 Log(L"Failed to get parameter string."); |
|
580 return false; |
|
581 } |
|
582 } |
|
583 mTarget = str; |
|
584 CoTaskMemFree(str); |
|
585 |
|
586 CStringW defaultPath; |
|
587 GetDefaultBrowserPath(defaultPath); |
|
588 mTargetIsDefaultBrowser = !mTarget.CompareNoCase(defaultPath); |
|
589 |
|
590 size_t browserEXELen = wcslen(kFirefoxExe); |
|
591 mTargetIsBrowser = mTarget.GetLength() >= browserEXELen && |
|
592 !mTarget.Right(browserEXELen).CompareNoCase(kFirefoxExe); |
|
593 |
|
594 return true; |
|
595 } |
|
596 |
|
597 /* |
|
598 * Desktop launch - Launch the destop browser to display the current |
|
599 * target using shellexecute. |
|
600 */ |
|
601 void LaunchDesktopBrowserWithParams(CStringW& aBrowserPath, CStringW& aVerb, |
|
602 CStringW& aTarget, CStringW& aParameters, |
|
603 bool aTargetIsDefaultBrowser, bool aTargetIsBrowser) |
|
604 { |
|
605 // If a taskbar shortcut, link or local file is clicked, the target will |
|
606 // be the browser exe or file. Don't pass in -url for the target if the |
|
607 // target is known to be a browser. Otherwise, one instance of Firefox |
|
608 // will try to open another instance. |
|
609 CStringW params; |
|
610 if (!aTargetIsDefaultBrowser && !aTargetIsBrowser && !aTarget.IsEmpty()) { |
|
611 // Fallback to the module path if it failed to get the default browser. |
|
612 GetDefaultBrowserPath(aBrowserPath); |
|
613 params += "-url "; |
|
614 params += "\""; |
|
615 params += aTarget; |
|
616 params += "\""; |
|
617 } |
|
618 |
|
619 // Tack on any extra parameters we received (for example -profilemanager) |
|
620 if (!aParameters.IsEmpty()) { |
|
621 params += " "; |
|
622 params += aParameters; |
|
623 } |
|
624 |
|
625 Log(L"Desktop Launch: verb:'%s' exe:'%s' params:'%s'", aVerb, aBrowserPath, params); |
|
626 |
|
627 // Relaunch in Desktop mode uses a special URL to trick Windows into |
|
628 // switching environments. We shouldn't actually try to open this URL. |
|
629 if (!_wcsicmp(aTarget, L"http://-desktop/")) { |
|
630 // Ignore any params and just launch on desktop |
|
631 params.Empty(); |
|
632 } |
|
633 |
|
634 PROCESS_INFORMATION procInfo; |
|
635 STARTUPINFO startInfo; |
|
636 memset(&procInfo, 0, sizeof(PROCESS_INFORMATION)); |
|
637 memset(&startInfo, 0, sizeof(STARTUPINFO)); |
|
638 |
|
639 startInfo.cb = sizeof(STARTUPINFO); |
|
640 startInfo.dwFlags = STARTF_USESHOWWINDOW; |
|
641 startInfo.wShowWindow = SW_SHOWNORMAL; |
|
642 |
|
643 BOOL result = |
|
644 CreateProcessW(aBrowserPath, static_cast<LPWSTR>(params.GetBuffer()), |
|
645 NULL, NULL, FALSE, 0, NULL, NULL, &startInfo, &procInfo); |
|
646 if (!result) { |
|
647 Log(L"CreateProcess failed! (%d)", GetLastError()); |
|
648 return; |
|
649 } |
|
650 // Hand off foreground/focus rights to the browser we create. If we don't |
|
651 // do this the ceh will keep ownership causing desktop firefox to launch |
|
652 // deactivated. |
|
653 if (!AllowSetForegroundWindow(procInfo.dwProcessId)) { |
|
654 Log(L"AllowSetForegroundWindow failed! (%d)", GetLastError()); |
|
655 } |
|
656 CloseHandle(procInfo.hThread); |
|
657 CloseHandle(procInfo.hProcess); |
|
658 Log(L"Desktop browser process id: %d", procInfo.dwProcessId); |
|
659 } |
|
660 |
|
661 void |
|
662 CExecuteCommandVerb::LaunchDesktopBrowser() |
|
663 { |
|
664 CStringW browserPath; |
|
665 if (!GetDesktopBrowserPath(browserPath)) { |
|
666 return; |
|
667 } |
|
668 |
|
669 LaunchDesktopBrowserWithParams(browserPath, mVerb, mTarget, mParameters, |
|
670 mTargetIsDefaultBrowser, mTargetIsBrowser); |
|
671 } |
|
672 |
|
673 void |
|
674 CExecuteCommandVerb::HeartBeat() |
|
675 { |
|
676 if (mRequestType == METRO_UPDATE && mDelayedLaunchType == DESKTOP && |
|
677 !IsMetroProcessRunning()) { |
|
678 mDelayedLaunchType = NONE; |
|
679 LaunchDesktopBrowser(); |
|
680 SetRequestMet(); |
|
681 } |
|
682 if (mDelayedLaunchType == METRO && !TestForUpdateLock()) { |
|
683 mDelayedLaunchType = NONE; |
|
684 LaunchMetroBrowser(); |
|
685 SetRequestMet(); |
|
686 } |
|
687 } |
|
688 |
|
689 bool |
|
690 CExecuteCommandVerb::TestForUpdateLock() |
|
691 { |
|
692 CStringW browserPath; |
|
693 if (!GetDefaultBrowserPath(browserPath)) { |
|
694 return false; |
|
695 } |
|
696 |
|
697 HANDLE hFile = CreateFileW(browserPath, |
|
698 FILE_EXECUTE, FILE_SHARE_READ|FILE_SHARE_WRITE, |
|
699 nullptr, OPEN_EXISTING, 0, nullptr); |
|
700 if (hFile != INVALID_HANDLE_VALUE) { |
|
701 CloseHandle(hFile); |
|
702 return false; |
|
703 } |
|
704 return true; |
|
705 } |
|
706 |
|
707 bool |
|
708 CExecuteCommandVerb::LaunchMetroBrowser() |
|
709 { |
|
710 HRESULT hr; |
|
711 |
|
712 CComPtr<IApplicationActivationManager> activateMgr; |
|
713 hr = activateMgr.CoCreateInstance(CLSID_ApplicationActivationManager, |
|
714 nullptr, CLSCTX_LOCAL_SERVER); |
|
715 if (FAILED(hr)) { |
|
716 Log(L"CoCreateInstance failed, launching on desktop."); |
|
717 return false; |
|
718 } |
|
719 |
|
720 // Hand off focus rights to the out-of-process activation server. This will |
|
721 // fail if we don't have the rights to begin with. Log but don't bail. |
|
722 hr = CoAllowSetForegroundWindow(activateMgr, nullptr); |
|
723 if (FAILED(hr)) { |
|
724 Log(L"CoAllowSetForegroundWindow result %X", hr); |
|
725 } |
|
726 |
|
727 WCHAR appModelID[256]; |
|
728 if (!GetDefaultBrowserAppModelID(appModelID)) { |
|
729 Log(L"GetDefaultBrowserAppModelID failed."); |
|
730 return false; |
|
731 } |
|
732 |
|
733 Log(L"Metro Launch: verb:'%s' appid:'%s' params:'%s'", mVerb, appModelID, mTarget); |
|
734 |
|
735 // shortcuts to the application |
|
736 DWORD processID; |
|
737 if (mTargetIsDefaultBrowser) { |
|
738 hr = activateMgr->ActivateApplication(appModelID, L"", AO_NONE, &processID); |
|
739 Log(L"ActivateApplication result %X", hr); |
|
740 // files |
|
741 } else if (mTargetIsFileSystemLink) { |
|
742 hr = activateMgr->ActivateForFile(appModelID, mShellItemArray, mVerb, &processID); |
|
743 Log(L"ActivateForFile result %X", hr); |
|
744 // protocols |
|
745 } else { |
|
746 hr = activateMgr->ActivateForProtocol(appModelID, mShellItemArray, &processID); |
|
747 Log(L"ActivateForProtocol result %X", hr); |
|
748 } |
|
749 return true; |
|
750 } |
|
751 |
|
752 void CExecuteCommandVerb::SetRequestMet() |
|
753 { |
|
754 SafeRelease(&mShellItemArray); |
|
755 SafeRelease(&mUnkSite); |
|
756 mRequestMet = true; |
|
757 Log(L"Request met, exiting."); |
|
758 } |
|
759 |
|
760 IFACEMETHODIMP CExecuteCommandVerb::Execute() |
|
761 { |
|
762 Log(L"Execute()"); |
|
763 |
|
764 if (!mTarget.GetLength()) { |
|
765 // We shut down when this flips to true |
|
766 SetRequestMet(); |
|
767 return E_FAIL; |
|
768 } |
|
769 |
|
770 if (!IsDX10Available()) { |
|
771 Log(L"Can't launch in metro due to missing hardware acceleration features."); |
|
772 mRequestType = DESKTOP_RESTART; |
|
773 } |
|
774 |
|
775 // Deal with metro restart for an update - launch desktop with a command |
|
776 // that tells it to run updater then launch the metro browser. |
|
777 if (mRequestType == METRO_UPDATE) { |
|
778 // We'll complete this in the heart beat callback from the main msg loop. |
|
779 // We do this because the last browser instance makes this call to Execute |
|
780 // sync. So we want to make sure it's completely shutdown before we do |
|
781 // the update. |
|
782 mParameters = kMetroUpdateCmdLine; |
|
783 mDelayedLaunchType = DESKTOP; |
|
784 return S_OK; |
|
785 } |
|
786 |
|
787 // Launch on the desktop |
|
788 if (mRequestType == DESKTOP_RESTART || |
|
789 (mRequestType == DEFAULT_LAUNCH && DefaultLaunchIsDesktop())) { |
|
790 LaunchDesktopBrowser(); |
|
791 SetRequestMet(); |
|
792 return S_OK; |
|
793 } |
|
794 |
|
795 // If we have an update in the works, don't try to activate yet, |
|
796 // delay until the lock is removed. |
|
797 if (TestForUpdateLock()) { |
|
798 mDelayedLaunchType = METRO; |
|
799 return S_OK; |
|
800 } |
|
801 |
|
802 LaunchMetroBrowser(); |
|
803 SetRequestMet(); |
|
804 return S_OK; |
|
805 } |
|
806 |
|
807 class ClassFactory : public IClassFactory |
|
808 { |
|
809 public: |
|
810 ClassFactory(IUnknown *punkObject); |
|
811 ~ClassFactory(); |
|
812 STDMETHODIMP Register(CLSCTX classContent, REGCLS classUse); |
|
813 STDMETHODIMP QueryInterface(REFIID riid, void **ppv); |
|
814 STDMETHODIMP_(ULONG) AddRef() { return 2; } |
|
815 STDMETHODIMP_(ULONG) Release() { return 1; } |
|
816 STDMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv); |
|
817 STDMETHODIMP LockServer(BOOL); |
|
818 private: |
|
819 IUnknown* mUnkObject; |
|
820 DWORD mRegID; |
|
821 }; |
|
822 |
|
823 ClassFactory::ClassFactory(IUnknown* aUnkObj) : |
|
824 mUnkObject(aUnkObj), |
|
825 mRegID(0) |
|
826 { |
|
827 if (mUnkObject) { |
|
828 mUnkObject->AddRef(); |
|
829 } |
|
830 } |
|
831 |
|
832 ClassFactory::~ClassFactory() |
|
833 { |
|
834 if (mRegID) { |
|
835 CoRevokeClassObject(mRegID); |
|
836 } |
|
837 mUnkObject->Release(); |
|
838 } |
|
839 |
|
840 STDMETHODIMP |
|
841 ClassFactory::Register(CLSCTX aClass, REGCLS aUse) |
|
842 { |
|
843 return CoRegisterClassObject(__uuidof(CExecuteCommandVerb), |
|
844 static_cast<IClassFactory *>(this), |
|
845 aClass, aUse, &mRegID); |
|
846 } |
|
847 |
|
848 STDMETHODIMP |
|
849 ClassFactory::QueryInterface(REFIID riid, void **ppv) |
|
850 { |
|
851 IUnknown *punk = nullptr; |
|
852 if (riid == IID_IUnknown || riid == IID_IClassFactory) { |
|
853 punk = static_cast<IClassFactory*>(this); |
|
854 } |
|
855 *ppv = punk; |
|
856 if (punk) { |
|
857 punk->AddRef(); |
|
858 return S_OK; |
|
859 } else { |
|
860 return E_NOINTERFACE; |
|
861 } |
|
862 } |
|
863 |
|
864 STDMETHODIMP |
|
865 ClassFactory::CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) |
|
866 { |
|
867 *ppv = nullptr; |
|
868 if (punkOuter) |
|
869 return CLASS_E_NOAGGREGATION; |
|
870 return mUnkObject->QueryInterface(riid, ppv); |
|
871 } |
|
872 |
|
873 LONG gObjRefCnt; |
|
874 |
|
875 STDMETHODIMP |
|
876 ClassFactory::LockServer(BOOL fLock) |
|
877 { |
|
878 if (fLock) |
|
879 InterlockedIncrement(&gObjRefCnt); |
|
880 else |
|
881 InterlockedDecrement(&gObjRefCnt); |
|
882 Log(L"ClassFactory::LockServer() %d", gObjRefCnt); |
|
883 return S_OK; |
|
884 } |
|
885 |
|
886 int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR pszCmdLine, int) |
|
887 { |
|
888 #if defined(SHOW_CONSOLE) |
|
889 SetupConsole(); |
|
890 #endif |
|
891 |
|
892 // nsis installer uses this as a helper to launch metro |
|
893 if (pszCmdLine && StrStrI(pszCmdLine, kNsisLaunchCmdLine)) |
|
894 { |
|
895 CoInitialize(nullptr); |
|
896 CExecuteCommandVerb *pHandler = new CExecuteCommandVerb(); |
|
897 if (!pHandler) |
|
898 return E_OUTOFMEMORY; |
|
899 pHandler->CommandLineMetroLaunch(); |
|
900 delete pHandler; |
|
901 CoUninitialize(); |
|
902 return 0; |
|
903 } |
|
904 |
|
905 if (!wcslen(pszCmdLine) || StrStrI(pszCmdLine, kExplorerLaunchCmdLine)) |
|
906 { |
|
907 CoInitialize(nullptr); |
|
908 |
|
909 CExecuteCommandVerb *pHandler = new CExecuteCommandVerb(); |
|
910 if (!pHandler) |
|
911 return E_OUTOFMEMORY; |
|
912 |
|
913 IUnknown* ppi; |
|
914 pHandler->QueryInterface(IID_IUnknown, (void**)&ppi); |
|
915 if (!ppi) |
|
916 return E_FAIL; |
|
917 |
|
918 ClassFactory classFactory(ppi); |
|
919 ppi->Release(); |
|
920 ppi = nullptr; |
|
921 |
|
922 // REGCLS_SINGLEUSE insures we only get used once and then discarded. |
|
923 if (FAILED(classFactory.Register(CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE))) |
|
924 return -1; |
|
925 |
|
926 if (!SetTimer(nullptr, 1, HEARTBEAT_MSEC, nullptr)) { |
|
927 Log(L"Failed to set timer, can't process request."); |
|
928 return -1; |
|
929 } |
|
930 |
|
931 MSG msg; |
|
932 long beatCount = 0; |
|
933 while (GetMessage(&msg, 0, 0, 0) > 0) { |
|
934 if (msg.message == WM_TIMER) { |
|
935 pHandler->HeartBeat(); |
|
936 if (++beatCount > REQUEST_WAIT_TIMEOUT || |
|
937 (pHandler->RequestMet() && pHandler->RefCount() < 2)) { |
|
938 break; |
|
939 } |
|
940 } |
|
941 TranslateMessage(&msg); |
|
942 DispatchMessage(&msg); |
|
943 } |
|
944 |
|
945 #ifdef DEBUG_DELAY_SHUTDOWN |
|
946 Sleep(10000); |
|
947 #endif |
|
948 CoUninitialize(); |
|
949 return 0; |
|
950 } |
|
951 return 0; |
|
952 } |