michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: michael@0: #include "CEHHelper.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #ifdef SHOW_CONSOLE michael@0: #define DEBUG_DELAY_SHUTDOWN 1 michael@0: #endif michael@0: michael@0: // Heartbeat timer duration used while waiting for an incoming request. michael@0: #define HEARTBEAT_MSEC 250 michael@0: // Total number of heartbeats we wait before giving up and shutting down. michael@0: #define REQUEST_WAIT_TIMEOUT 30 michael@0: // Pulled from desktop browser's shell michael@0: #define APP_REG_NAME L"Firefox" michael@0: michael@0: const WCHAR* kFirefoxExe = L"firefox.exe"; michael@0: static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL"; michael@0: static const WCHAR* kMetroRestartCmdLine = L"--metro-restart"; michael@0: static const WCHAR* kMetroUpdateCmdLine = L"--metro-update"; michael@0: static const WCHAR* kDesktopRestartCmdLine = L"--desktop-restart"; michael@0: static const WCHAR* kNsisLaunchCmdLine = L"--launchmetro"; michael@0: static const WCHAR* kExplorerLaunchCmdLine = L"-Embedding"; michael@0: michael@0: static bool GetDefaultBrowserPath(CStringW& aPathBuffer); michael@0: michael@0: /* michael@0: * Retrieve our module dir path. michael@0: * michael@0: * @aPathBuffer Buffer to fill michael@0: */ michael@0: static bool GetModulePath(CStringW& aPathBuffer) michael@0: { michael@0: WCHAR buffer[MAX_PATH]; michael@0: memset(buffer, 0, sizeof(buffer)); michael@0: michael@0: if (!GetModuleFileName(nullptr, buffer, MAX_PATH)) { michael@0: Log(L"GetModuleFileName failed."); michael@0: return false; michael@0: } michael@0: michael@0: WCHAR* slash = wcsrchr(buffer, '\\'); michael@0: if (!slash) michael@0: return false; michael@0: *slash = '\0'; michael@0: michael@0: aPathBuffer = buffer; michael@0: return true; michael@0: } michael@0: michael@0: michael@0: template void SafeRelease(T **ppT) michael@0: { michael@0: if (*ppT) { michael@0: (*ppT)->Release(); michael@0: *ppT = nullptr; michael@0: } michael@0: } michael@0: michael@0: template HRESULT SetInterface(T **ppT, IUnknown *punk) michael@0: { michael@0: SafeRelease(ppT); michael@0: return punk ? punk->QueryInterface(ppT) : E_NOINTERFACE; michael@0: } michael@0: michael@0: class __declspec(uuid("5100FEC1-212B-4BF5-9BF8-3E650FD794A3")) michael@0: CExecuteCommandVerb : public IExecuteCommand, michael@0: public IObjectWithSelection, michael@0: public IInitializeCommand, michael@0: public IObjectWithSite, michael@0: public IExecuteCommandApplicationHostEnvironment michael@0: { michael@0: public: michael@0: michael@0: CExecuteCommandVerb() : michael@0: mRef(0), michael@0: mShellItemArray(nullptr), michael@0: mUnkSite(nullptr), michael@0: mTargetIsFileSystemLink(false), michael@0: mTargetIsDefaultBrowser(false), michael@0: mTargetIsBrowser(false), michael@0: mRequestType(DEFAULT_LAUNCH), michael@0: mRequestMet(false), michael@0: mDelayedLaunchType(NONE), michael@0: mVerb(L"open") michael@0: { michael@0: } michael@0: michael@0: ~CExecuteCommandVerb() michael@0: { michael@0: } michael@0: michael@0: bool RequestMet() { return mRequestMet; } michael@0: void SetRequestMet(); michael@0: long RefCount() { return mRef; } michael@0: void HeartBeat(); michael@0: michael@0: // IUnknown michael@0: IFACEMETHODIMP QueryInterface(REFIID aRefID, void **aInt) michael@0: { michael@0: static const QITAB qit[] = { michael@0: QITABENT(CExecuteCommandVerb, IExecuteCommand), michael@0: QITABENT(CExecuteCommandVerb, IObjectWithSelection), michael@0: QITABENT(CExecuteCommandVerb, IInitializeCommand), michael@0: QITABENT(CExecuteCommandVerb, IObjectWithSite), michael@0: QITABENT(CExecuteCommandVerb, IExecuteCommandApplicationHostEnvironment), michael@0: { 0 }, michael@0: }; michael@0: return QISearch(this, qit, aRefID, aInt); michael@0: } michael@0: michael@0: IFACEMETHODIMP_(ULONG) AddRef() michael@0: { michael@0: return InterlockedIncrement(&mRef); michael@0: } michael@0: michael@0: IFACEMETHODIMP_(ULONG) Release() michael@0: { michael@0: long cRef = InterlockedDecrement(&mRef); michael@0: if (!cRef) { michael@0: delete this; michael@0: } michael@0: return cRef; michael@0: } michael@0: michael@0: // IExecuteCommand michael@0: IFACEMETHODIMP SetKeyState(DWORD aKeyState) michael@0: { michael@0: mKeyState = aKeyState; michael@0: return S_OK; michael@0: } michael@0: michael@0: IFACEMETHODIMP SetParameters(PCWSTR aParameters) michael@0: { michael@0: Log(L"SetParameters: '%s'", aParameters); michael@0: michael@0: if (!_wcsicmp(aParameters, kMetroRestartCmdLine)) { michael@0: mRequestType = METRO_RESTART; michael@0: } else if (_wcsicmp(aParameters, kMetroUpdateCmdLine) == 0) { michael@0: mRequestType = METRO_UPDATE; michael@0: } else if (_wcsicmp(aParameters, kDesktopRestartCmdLine) == 0) { michael@0: mRequestType = DESKTOP_RESTART; michael@0: } else { michael@0: mParameters = aParameters; michael@0: } michael@0: return S_OK; michael@0: } michael@0: michael@0: IFACEMETHODIMP SetPosition(POINT aPoint) michael@0: { return S_OK; } michael@0: michael@0: IFACEMETHODIMP SetShowWindow(int aShowFlag) michael@0: { return S_OK; } michael@0: michael@0: IFACEMETHODIMP SetNoShowUI(BOOL aNoUI) michael@0: { return S_OK; } michael@0: michael@0: IFACEMETHODIMP SetDirectory(PCWSTR aDirPath) michael@0: { return S_OK; } michael@0: michael@0: IFACEMETHODIMP Execute(); michael@0: michael@0: // IObjectWithSelection michael@0: IFACEMETHODIMP SetSelection(IShellItemArray *aArray) michael@0: { michael@0: if (!aArray) { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: SetInterface(&mShellItemArray, aArray); michael@0: michael@0: DWORD count = 0; michael@0: aArray->GetCount(&count); michael@0: if (!count) { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: #ifdef SHOW_CONSOLE michael@0: Log(L"SetSelection param count: %d", count); michael@0: for (DWORD idx = 0; idx < count; idx++) { michael@0: IShellItem* item = nullptr; michael@0: if (SUCCEEDED(aArray->GetItemAt(idx, &item))) { michael@0: LPWSTR str = nullptr; michael@0: if (FAILED(item->GetDisplayName(SIGDN_FILESYSPATH, &str))) { michael@0: if (FAILED(item->GetDisplayName(SIGDN_URL, &str))) { michael@0: Log(L"Failed to get a shell item array item."); michael@0: item->Release(); michael@0: continue; michael@0: } michael@0: } michael@0: item->Release(); michael@0: Log(L"SetSelection param: '%s'", str); michael@0: CoTaskMemFree(str); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: IShellItem* item = nullptr; michael@0: if (FAILED(aArray->GetItemAt(0, &item))) { michael@0: return E_FAIL; michael@0: } michael@0: michael@0: bool isFileSystem = false; michael@0: if (!SetTargetPath(item) || !mTarget.GetLength()) { michael@0: Log(L"SetTargetPath failed."); michael@0: return E_FAIL; michael@0: } michael@0: item->Release(); michael@0: michael@0: Log(L"SetSelection target: %s", mTarget); michael@0: return S_OK; michael@0: } michael@0: michael@0: IFACEMETHODIMP GetSelection(REFIID aRefID, void **aInt) michael@0: { michael@0: *aInt = nullptr; michael@0: return mShellItemArray ? mShellItemArray->QueryInterface(aRefID, aInt) : E_FAIL; michael@0: } michael@0: michael@0: // IInitializeCommand michael@0: IFACEMETHODIMP Initialize(PCWSTR aVerb, IPropertyBag* aPropBag) michael@0: { michael@0: if (!aVerb) michael@0: return E_FAIL; michael@0: // 'open', 'edit', etc. Based on our registry settings michael@0: Log(L"Initialize(%s)", aVerb); michael@0: mVerb = aVerb; michael@0: return S_OK; michael@0: } michael@0: michael@0: // IObjectWithSite michael@0: IFACEMETHODIMP SetSite(IUnknown *aUnkSite) michael@0: { michael@0: SetInterface(&mUnkSite, aUnkSite); michael@0: return S_OK; michael@0: } michael@0: michael@0: IFACEMETHODIMP GetSite(REFIID aRefID, void **aInt) michael@0: { michael@0: *aInt = nullptr; michael@0: return mUnkSite ? mUnkSite->QueryInterface(aRefID, aInt) : E_FAIL; michael@0: } michael@0: michael@0: // IExecuteCommandApplicationHostEnvironment michael@0: IFACEMETHODIMP GetValue(AHE_TYPE *aLaunchType) michael@0: { michael@0: Log(L"IExecuteCommandApplicationHostEnvironment::GetValue()"); michael@0: *aLaunchType = GetLaunchType(); michael@0: return S_OK; michael@0: } michael@0: michael@0: /** michael@0: * Choose the appropriate launch type based on the user's previously chosen michael@0: * host environment, along with system constraints. michael@0: * michael@0: * AHE_DESKTOP = 0, AHE_IMMERSIVE = 1 michael@0: */ michael@0: AHE_TYPE GetLaunchType() michael@0: { michael@0: AHE_TYPE ahe = GetLastAHE(); michael@0: Log(L"Previous AHE: %d", ahe); michael@0: michael@0: // Default launch settings from GetLastAHE() can be overriden by michael@0: // custom parameter values we receive. michael@0: if (mRequestType == DESKTOP_RESTART) { michael@0: Log(L"Restarting in desktop host environment."); michael@0: return AHE_DESKTOP; michael@0: } else if (mRequestType == METRO_RESTART) { michael@0: Log(L"Restarting in metro host environment."); michael@0: ahe = AHE_IMMERSIVE; michael@0: } else if (mRequestType == METRO_UPDATE) { michael@0: // Shouldn't happen from GetValue above, but might from other calls. michael@0: ahe = AHE_IMMERSIVE; michael@0: } michael@0: michael@0: if (ahe == AHE_IMMERSIVE) { michael@0: if (!IsDefaultBrowser()) { michael@0: Log(L"returning AHE_DESKTOP because we are not the default browser"); michael@0: return AHE_DESKTOP; michael@0: } michael@0: michael@0: if (!IsDX10Available()) { michael@0: Log(L"returning AHE_DESKTOP because DX10 is not available"); michael@0: return AHE_DESKTOP; michael@0: } michael@0: } michael@0: return ahe; michael@0: } michael@0: michael@0: bool DefaultLaunchIsDesktop() michael@0: { michael@0: return GetLaunchType() == AHE_DESKTOP; michael@0: } michael@0: michael@0: bool DefaultLaunchIsMetro() michael@0: { michael@0: return GetLaunchType() == AHE_IMMERSIVE; michael@0: } michael@0: michael@0: /* michael@0: * Retrieve the target path if it is the default browser michael@0: * or if not default, retreives the target path if it is a firefox browser michael@0: * or if the target is not firefox, relies on a hack to get the michael@0: * 'module dir path\firefox.exe' michael@0: * The reason why it's not good to rely on the CEH path is because there is michael@0: * no guarantee win8 will use the CEH at our expected path. It has an in michael@0: * memory cache even if the registry is updated for the CEH path. michael@0: * michael@0: * @aPathBuffer Buffer to fill michael@0: */ michael@0: bool GetDesktopBrowserPath(CStringW& aPathBuffer) michael@0: { michael@0: // If the target was the default browser itself then return early. Otherwise michael@0: // rely on a hack to check CEH path and calculate it relative to it. michael@0: michael@0: if (mTargetIsDefaultBrowser || mTargetIsBrowser) { michael@0: aPathBuffer = mTarget; michael@0: return true; michael@0: } michael@0: michael@0: if (!GetModulePath(aPathBuffer)) michael@0: return false; michael@0: michael@0: // ceh.exe sits in dist/bin root with the desktop browser. Since this michael@0: // is a firefox only component, this hardcoded filename is ok. michael@0: aPathBuffer.Append(L"\\"); michael@0: aPathBuffer.Append(kFirefoxExe); michael@0: return true; michael@0: } michael@0: michael@0: bool IsDefaultBrowser() michael@0: { michael@0: IApplicationAssociationRegistration* pAAR; michael@0: HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration, michael@0: nullptr, michael@0: CLSCTX_INPROC, michael@0: IID_IApplicationAssociationRegistration, michael@0: (void**)&pAAR); michael@0: if (FAILED(hr)) michael@0: return false; michael@0: michael@0: BOOL res = FALSE; michael@0: hr = pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, michael@0: APP_REG_NAME, michael@0: &res); michael@0: Log(L"QueryAppIsDefaultAll: %d", res); michael@0: if (!res) { michael@0: pAAR->Release(); michael@0: return false; michael@0: } michael@0: // Make sure the Prog ID matches what we have michael@0: LPWSTR registeredApp; michael@0: hr = pAAR->QueryCurrentDefault(L"http", AT_URLPROTOCOL, AL_EFFECTIVE, michael@0: ®isteredApp); michael@0: pAAR->Release(); michael@0: Log(L"QueryCurrentDefault: %X", hr); michael@0: if (FAILED(hr)) michael@0: return false; michael@0: michael@0: Log(L"registeredApp=%s", registeredApp); michael@0: bool result = !wcsicmp(registeredApp, kDefaultMetroBrowserIDPathKey); michael@0: CoTaskMemFree(registeredApp); michael@0: if (!result) michael@0: return false; michael@0: michael@0: // If the registry points another browser's path, michael@0: // activating the Metro browser will fail. So fallback to the desktop. michael@0: CStringW selfPath; michael@0: GetDesktopBrowserPath(selfPath); michael@0: CStringW browserPath; michael@0: GetDefaultBrowserPath(browserPath); michael@0: michael@0: return !selfPath.CompareNoCase(browserPath); michael@0: } michael@0: michael@0: /* michael@0: * Helper for nsis installer when it wants to launch the michael@0: * default metro browser. michael@0: */ michael@0: void CommandLineMetroLaunch() michael@0: { michael@0: mTargetIsDefaultBrowser = true; michael@0: LaunchMetroBrowser(); michael@0: } michael@0: michael@0: private: michael@0: void LaunchDesktopBrowser(); michael@0: bool LaunchMetroBrowser(); michael@0: bool SetTargetPath(IShellItem* aItem); michael@0: bool TestForUpdateLock(); michael@0: michael@0: /* michael@0: * Defines the type of startup request we receive. michael@0: */ michael@0: enum RequestType { michael@0: DEFAULT_LAUNCH, michael@0: DESKTOP_RESTART, michael@0: METRO_RESTART, michael@0: METRO_UPDATE, michael@0: }; michael@0: michael@0: RequestType mRequestType; michael@0: michael@0: /* michael@0: * Defines the type of delayed launch we might do. michael@0: */ michael@0: enum DelayedLaunchType { michael@0: NONE, michael@0: DESKTOP, michael@0: METRO, michael@0: }; michael@0: michael@0: DelayedLaunchType mDelayedLaunchType; michael@0: michael@0: long mRef; michael@0: IShellItemArray *mShellItemArray; michael@0: IUnknown *mUnkSite; michael@0: CStringW mVerb; michael@0: CStringW mTarget; michael@0: CStringW mParameters; michael@0: bool mTargetIsFileSystemLink; michael@0: bool mTargetIsDefaultBrowser; michael@0: bool mTargetIsBrowser; michael@0: DWORD mKeyState; michael@0: bool mRequestMet; michael@0: }; michael@0: michael@0: /* michael@0: * Retrieve the current default browser's path. michael@0: * michael@0: * @aPathBuffer Buffer to fill michael@0: */ michael@0: static bool GetDefaultBrowserPath(CStringW& aPathBuffer) michael@0: { michael@0: WCHAR buffer[MAX_PATH]; michael@0: DWORD length = MAX_PATH; michael@0: michael@0: if (FAILED(AssocQueryStringW(ASSOCF_NOTRUNCATE | ASSOCF_INIT_IGNOREUNKNOWN, michael@0: ASSOCSTR_EXECUTABLE, michael@0: kDefaultMetroBrowserIDPathKey, nullptr, michael@0: buffer, &length))) { michael@0: Log(L"AssocQueryString failed."); michael@0: return false; michael@0: } michael@0: michael@0: // sanity check michael@0: if (lstrcmpiW(PathFindFileNameW(buffer), kFirefoxExe)) michael@0: return false; michael@0: michael@0: aPathBuffer = buffer; michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Retrieve the app model id of the firefox metro browser. michael@0: * michael@0: * @aPathBuffer Buffer to fill michael@0: * @aCharLength Length of buffer to fill in characters michael@0: */ michael@0: template michael@0: static bool GetDefaultBrowserAppModelID(WCHAR (&aIDBuffer)[N]) michael@0: { michael@0: HKEY key; michael@0: if (RegOpenKeyExW(HKEY_CLASSES_ROOT, kDefaultMetroBrowserIDPathKey, michael@0: 0, KEY_READ, &key) != ERROR_SUCCESS) { michael@0: return false; michael@0: } michael@0: DWORD len = sizeof(aIDBuffer); michael@0: memset(aIDBuffer, 0, len); michael@0: if (RegQueryValueExW(key, L"AppUserModelID", nullptr, nullptr, michael@0: (LPBYTE)aIDBuffer, &len) != ERROR_SUCCESS || !len) { michael@0: RegCloseKey(key); michael@0: return false; michael@0: } michael@0: RegCloseKey(key); michael@0: return true; michael@0: } michael@0: michael@0: namespace { michael@0: const FORMATETC kPlainTextFormat = michael@0: {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; michael@0: const FORMATETC kPlainTextWFormat = michael@0: {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; michael@0: } michael@0: michael@0: bool HasPlainText(IDataObject* aDataObj) { michael@0: return SUCCEEDED(aDataObj->QueryGetData((FORMATETC*)&kPlainTextWFormat)) || michael@0: SUCCEEDED(aDataObj->QueryGetData((FORMATETC*)&kPlainTextFormat)); michael@0: } michael@0: michael@0: bool GetPlainText(IDataObject* aDataObj, CStringW& cstrText) michael@0: { michael@0: if (!HasPlainText(aDataObj)) michael@0: return false; michael@0: michael@0: STGMEDIUM store; michael@0: michael@0: // unicode text michael@0: if (SUCCEEDED(aDataObj->GetData((FORMATETC*)&kPlainTextWFormat, &store))) { michael@0: // makes a copy michael@0: cstrText = static_cast(GlobalLock(store.hGlobal)); michael@0: GlobalUnlock(store.hGlobal); michael@0: ReleaseStgMedium(&store); michael@0: return true; michael@0: } michael@0: michael@0: // ascii text michael@0: if (SUCCEEDED(aDataObj->GetData((FORMATETC*)&kPlainTextFormat, &store))) { michael@0: // makes a copy michael@0: cstrText = static_cast(GlobalLock(store.hGlobal)); michael@0: GlobalUnlock(store.hGlobal); michael@0: ReleaseStgMedium(&store); michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * Updates the current target based on the contents of michael@0: * a shell item. michael@0: */ michael@0: bool CExecuteCommandVerb::SetTargetPath(IShellItem* aItem) michael@0: { michael@0: if (!aItem) michael@0: return false; michael@0: michael@0: CString cstrText; michael@0: CComPtr object; michael@0: // Check the underlying data object first to insure we get michael@0: // absolute uri. See chromium bug 157184. michael@0: if (SUCCEEDED(aItem->BindToHandler(nullptr, BHID_DataObject, michael@0: IID_IDataObject, michael@0: reinterpret_cast(&object))) && michael@0: GetPlainText(object, cstrText)) { michael@0: wchar_t scheme[16]; michael@0: URL_COMPONENTS components = {0}; michael@0: components.lpszScheme = scheme; michael@0: components.dwSchemeLength = sizeof(scheme)/sizeof(scheme[0]); michael@0: components.dwStructSize = sizeof(components); michael@0: // note, more advanced use may have issues with paths with spaces. michael@0: if (!InternetCrackUrlW(cstrText, 0, 0, &components)) { michael@0: Log(L"Failed to identify object text '%s'", cstrText); michael@0: return false; michael@0: } michael@0: michael@0: mTargetIsFileSystemLink = (components.nScheme == INTERNET_SCHEME_FILE); michael@0: mTarget = cstrText; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: Log(L"No data object or data object has no text."); michael@0: michael@0: // Use the shell item display name michael@0: LPWSTR str = nullptr; michael@0: mTargetIsFileSystemLink = true; michael@0: if (FAILED(aItem->GetDisplayName(SIGDN_FILESYSPATH, &str))) { michael@0: mTargetIsFileSystemLink = false; michael@0: if (FAILED(aItem->GetDisplayName(SIGDN_URL, &str))) { michael@0: Log(L"Failed to get parameter string."); michael@0: return false; michael@0: } michael@0: } michael@0: mTarget = str; michael@0: CoTaskMemFree(str); michael@0: michael@0: CStringW defaultPath; michael@0: GetDefaultBrowserPath(defaultPath); michael@0: mTargetIsDefaultBrowser = !mTarget.CompareNoCase(defaultPath); michael@0: michael@0: size_t browserEXELen = wcslen(kFirefoxExe); michael@0: mTargetIsBrowser = mTarget.GetLength() >= browserEXELen && michael@0: !mTarget.Right(browserEXELen).CompareNoCase(kFirefoxExe); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Desktop launch - Launch the destop browser to display the current michael@0: * target using shellexecute. michael@0: */ michael@0: void LaunchDesktopBrowserWithParams(CStringW& aBrowserPath, CStringW& aVerb, michael@0: CStringW& aTarget, CStringW& aParameters, michael@0: bool aTargetIsDefaultBrowser, bool aTargetIsBrowser) michael@0: { michael@0: // If a taskbar shortcut, link or local file is clicked, the target will michael@0: // be the browser exe or file. Don't pass in -url for the target if the michael@0: // target is known to be a browser. Otherwise, one instance of Firefox michael@0: // will try to open another instance. michael@0: CStringW params; michael@0: if (!aTargetIsDefaultBrowser && !aTargetIsBrowser && !aTarget.IsEmpty()) { michael@0: // Fallback to the module path if it failed to get the default browser. michael@0: GetDefaultBrowserPath(aBrowserPath); michael@0: params += "-url "; michael@0: params += "\""; michael@0: params += aTarget; michael@0: params += "\""; michael@0: } michael@0: michael@0: // Tack on any extra parameters we received (for example -profilemanager) michael@0: if (!aParameters.IsEmpty()) { michael@0: params += " "; michael@0: params += aParameters; michael@0: } michael@0: michael@0: Log(L"Desktop Launch: verb:'%s' exe:'%s' params:'%s'", aVerb, aBrowserPath, params); michael@0: michael@0: // Relaunch in Desktop mode uses a special URL to trick Windows into michael@0: // switching environments. We shouldn't actually try to open this URL. michael@0: if (!_wcsicmp(aTarget, L"http://-desktop/")) { michael@0: // Ignore any params and just launch on desktop michael@0: params.Empty(); michael@0: } michael@0: michael@0: PROCESS_INFORMATION procInfo; michael@0: STARTUPINFO startInfo; michael@0: memset(&procInfo, 0, sizeof(PROCESS_INFORMATION)); michael@0: memset(&startInfo, 0, sizeof(STARTUPINFO)); michael@0: michael@0: startInfo.cb = sizeof(STARTUPINFO); michael@0: startInfo.dwFlags = STARTF_USESHOWWINDOW; michael@0: startInfo.wShowWindow = SW_SHOWNORMAL; michael@0: michael@0: BOOL result = michael@0: CreateProcessW(aBrowserPath, static_cast(params.GetBuffer()), michael@0: NULL, NULL, FALSE, 0, NULL, NULL, &startInfo, &procInfo); michael@0: if (!result) { michael@0: Log(L"CreateProcess failed! (%d)", GetLastError()); michael@0: return; michael@0: } michael@0: // Hand off foreground/focus rights to the browser we create. If we don't michael@0: // do this the ceh will keep ownership causing desktop firefox to launch michael@0: // deactivated. michael@0: if (!AllowSetForegroundWindow(procInfo.dwProcessId)) { michael@0: Log(L"AllowSetForegroundWindow failed! (%d)", GetLastError()); michael@0: } michael@0: CloseHandle(procInfo.hThread); michael@0: CloseHandle(procInfo.hProcess); michael@0: Log(L"Desktop browser process id: %d", procInfo.dwProcessId); michael@0: } michael@0: michael@0: void michael@0: CExecuteCommandVerb::LaunchDesktopBrowser() michael@0: { michael@0: CStringW browserPath; michael@0: if (!GetDesktopBrowserPath(browserPath)) { michael@0: return; michael@0: } michael@0: michael@0: LaunchDesktopBrowserWithParams(browserPath, mVerb, mTarget, mParameters, michael@0: mTargetIsDefaultBrowser, mTargetIsBrowser); michael@0: } michael@0: michael@0: void michael@0: CExecuteCommandVerb::HeartBeat() michael@0: { michael@0: if (mRequestType == METRO_UPDATE && mDelayedLaunchType == DESKTOP && michael@0: !IsMetroProcessRunning()) { michael@0: mDelayedLaunchType = NONE; michael@0: LaunchDesktopBrowser(); michael@0: SetRequestMet(); michael@0: } michael@0: if (mDelayedLaunchType == METRO && !TestForUpdateLock()) { michael@0: mDelayedLaunchType = NONE; michael@0: LaunchMetroBrowser(); michael@0: SetRequestMet(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: CExecuteCommandVerb::TestForUpdateLock() michael@0: { michael@0: CStringW browserPath; michael@0: if (!GetDefaultBrowserPath(browserPath)) { michael@0: return false; michael@0: } michael@0: michael@0: HANDLE hFile = CreateFileW(browserPath, michael@0: FILE_EXECUTE, FILE_SHARE_READ|FILE_SHARE_WRITE, michael@0: nullptr, OPEN_EXISTING, 0, nullptr); michael@0: if (hFile != INVALID_HANDLE_VALUE) { michael@0: CloseHandle(hFile); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CExecuteCommandVerb::LaunchMetroBrowser() michael@0: { michael@0: HRESULT hr; michael@0: michael@0: CComPtr activateMgr; michael@0: hr = activateMgr.CoCreateInstance(CLSID_ApplicationActivationManager, michael@0: nullptr, CLSCTX_LOCAL_SERVER); michael@0: if (FAILED(hr)) { michael@0: Log(L"CoCreateInstance failed, launching on desktop."); michael@0: return false; michael@0: } michael@0: michael@0: // Hand off focus rights to the out-of-process activation server. This will michael@0: // fail if we don't have the rights to begin with. Log but don't bail. michael@0: hr = CoAllowSetForegroundWindow(activateMgr, nullptr); michael@0: if (FAILED(hr)) { michael@0: Log(L"CoAllowSetForegroundWindow result %X", hr); michael@0: } michael@0: michael@0: WCHAR appModelID[256]; michael@0: if (!GetDefaultBrowserAppModelID(appModelID)) { michael@0: Log(L"GetDefaultBrowserAppModelID failed."); michael@0: return false; michael@0: } michael@0: michael@0: Log(L"Metro Launch: verb:'%s' appid:'%s' params:'%s'", mVerb, appModelID, mTarget); michael@0: michael@0: // shortcuts to the application michael@0: DWORD processID; michael@0: if (mTargetIsDefaultBrowser) { michael@0: hr = activateMgr->ActivateApplication(appModelID, L"", AO_NONE, &processID); michael@0: Log(L"ActivateApplication result %X", hr); michael@0: // files michael@0: } else if (mTargetIsFileSystemLink) { michael@0: hr = activateMgr->ActivateForFile(appModelID, mShellItemArray, mVerb, &processID); michael@0: Log(L"ActivateForFile result %X", hr); michael@0: // protocols michael@0: } else { michael@0: hr = activateMgr->ActivateForProtocol(appModelID, mShellItemArray, &processID); michael@0: Log(L"ActivateForProtocol result %X", hr); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void CExecuteCommandVerb::SetRequestMet() michael@0: { michael@0: SafeRelease(&mShellItemArray); michael@0: SafeRelease(&mUnkSite); michael@0: mRequestMet = true; michael@0: Log(L"Request met, exiting."); michael@0: } michael@0: michael@0: IFACEMETHODIMP CExecuteCommandVerb::Execute() michael@0: { michael@0: Log(L"Execute()"); michael@0: michael@0: if (!mTarget.GetLength()) { michael@0: // We shut down when this flips to true michael@0: SetRequestMet(); michael@0: return E_FAIL; michael@0: } michael@0: michael@0: if (!IsDX10Available()) { michael@0: Log(L"Can't launch in metro due to missing hardware acceleration features."); michael@0: mRequestType = DESKTOP_RESTART; michael@0: } michael@0: michael@0: // Deal with metro restart for an update - launch desktop with a command michael@0: // that tells it to run updater then launch the metro browser. michael@0: if (mRequestType == METRO_UPDATE) { michael@0: // We'll complete this in the heart beat callback from the main msg loop. michael@0: // We do this because the last browser instance makes this call to Execute michael@0: // sync. So we want to make sure it's completely shutdown before we do michael@0: // the update. michael@0: mParameters = kMetroUpdateCmdLine; michael@0: mDelayedLaunchType = DESKTOP; michael@0: return S_OK; michael@0: } michael@0: michael@0: // Launch on the desktop michael@0: if (mRequestType == DESKTOP_RESTART || michael@0: (mRequestType == DEFAULT_LAUNCH && DefaultLaunchIsDesktop())) { michael@0: LaunchDesktopBrowser(); michael@0: SetRequestMet(); michael@0: return S_OK; michael@0: } michael@0: michael@0: // If we have an update in the works, don't try to activate yet, michael@0: // delay until the lock is removed. michael@0: if (TestForUpdateLock()) { michael@0: mDelayedLaunchType = METRO; michael@0: return S_OK; michael@0: } michael@0: michael@0: LaunchMetroBrowser(); michael@0: SetRequestMet(); michael@0: return S_OK; michael@0: } michael@0: michael@0: class ClassFactory : public IClassFactory michael@0: { michael@0: public: michael@0: ClassFactory(IUnknown *punkObject); michael@0: ~ClassFactory(); michael@0: STDMETHODIMP Register(CLSCTX classContent, REGCLS classUse); michael@0: STDMETHODIMP QueryInterface(REFIID riid, void **ppv); michael@0: STDMETHODIMP_(ULONG) AddRef() { return 2; } michael@0: STDMETHODIMP_(ULONG) Release() { return 1; } michael@0: STDMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv); michael@0: STDMETHODIMP LockServer(BOOL); michael@0: private: michael@0: IUnknown* mUnkObject; michael@0: DWORD mRegID; michael@0: }; michael@0: michael@0: ClassFactory::ClassFactory(IUnknown* aUnkObj) : michael@0: mUnkObject(aUnkObj), michael@0: mRegID(0) michael@0: { michael@0: if (mUnkObject) { michael@0: mUnkObject->AddRef(); michael@0: } michael@0: } michael@0: michael@0: ClassFactory::~ClassFactory() michael@0: { michael@0: if (mRegID) { michael@0: CoRevokeClassObject(mRegID); michael@0: } michael@0: mUnkObject->Release(); michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: ClassFactory::Register(CLSCTX aClass, REGCLS aUse) michael@0: { michael@0: return CoRegisterClassObject(__uuidof(CExecuteCommandVerb), michael@0: static_cast(this), michael@0: aClass, aUse, &mRegID); michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: ClassFactory::QueryInterface(REFIID riid, void **ppv) michael@0: { michael@0: IUnknown *punk = nullptr; michael@0: if (riid == IID_IUnknown || riid == IID_IClassFactory) { michael@0: punk = static_cast(this); michael@0: } michael@0: *ppv = punk; michael@0: if (punk) { michael@0: punk->AddRef(); michael@0: return S_OK; michael@0: } else { michael@0: return E_NOINTERFACE; michael@0: } michael@0: } michael@0: michael@0: STDMETHODIMP michael@0: ClassFactory::CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) michael@0: { michael@0: *ppv = nullptr; michael@0: if (punkOuter) michael@0: return CLASS_E_NOAGGREGATION; michael@0: return mUnkObject->QueryInterface(riid, ppv); michael@0: } michael@0: michael@0: LONG gObjRefCnt; michael@0: michael@0: STDMETHODIMP michael@0: ClassFactory::LockServer(BOOL fLock) michael@0: { michael@0: if (fLock) michael@0: InterlockedIncrement(&gObjRefCnt); michael@0: else michael@0: InterlockedDecrement(&gObjRefCnt); michael@0: Log(L"ClassFactory::LockServer() %d", gObjRefCnt); michael@0: return S_OK; michael@0: } michael@0: michael@0: int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR pszCmdLine, int) michael@0: { michael@0: #if defined(SHOW_CONSOLE) michael@0: SetupConsole(); michael@0: #endif michael@0: michael@0: // nsis installer uses this as a helper to launch metro michael@0: if (pszCmdLine && StrStrI(pszCmdLine, kNsisLaunchCmdLine)) michael@0: { michael@0: CoInitialize(nullptr); michael@0: CExecuteCommandVerb *pHandler = new CExecuteCommandVerb(); michael@0: if (!pHandler) michael@0: return E_OUTOFMEMORY; michael@0: pHandler->CommandLineMetroLaunch(); michael@0: delete pHandler; michael@0: CoUninitialize(); michael@0: return 0; michael@0: } michael@0: michael@0: if (!wcslen(pszCmdLine) || StrStrI(pszCmdLine, kExplorerLaunchCmdLine)) michael@0: { michael@0: CoInitialize(nullptr); michael@0: michael@0: CExecuteCommandVerb *pHandler = new CExecuteCommandVerb(); michael@0: if (!pHandler) michael@0: return E_OUTOFMEMORY; michael@0: michael@0: IUnknown* ppi; michael@0: pHandler->QueryInterface(IID_IUnknown, (void**)&ppi); michael@0: if (!ppi) michael@0: return E_FAIL; michael@0: michael@0: ClassFactory classFactory(ppi); michael@0: ppi->Release(); michael@0: ppi = nullptr; michael@0: michael@0: // REGCLS_SINGLEUSE insures we only get used once and then discarded. michael@0: if (FAILED(classFactory.Register(CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE))) michael@0: return -1; michael@0: michael@0: if (!SetTimer(nullptr, 1, HEARTBEAT_MSEC, nullptr)) { michael@0: Log(L"Failed to set timer, can't process request."); michael@0: return -1; michael@0: } michael@0: michael@0: MSG msg; michael@0: long beatCount = 0; michael@0: while (GetMessage(&msg, 0, 0, 0) > 0) { michael@0: if (msg.message == WM_TIMER) { michael@0: pHandler->HeartBeat(); michael@0: if (++beatCount > REQUEST_WAIT_TIMEOUT || michael@0: (pHandler->RequestMet() && pHandler->RefCount() < 2)) { michael@0: break; michael@0: } michael@0: } michael@0: TranslateMessage(&msg); michael@0: DispatchMessage(&msg); michael@0: } michael@0: michael@0: #ifdef DEBUG_DELAY_SHUTDOWN michael@0: Sleep(10000); michael@0: #endif michael@0: CoUninitialize(); michael@0: return 0; michael@0: } michael@0: return 0; michael@0: }