browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

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

mercurial