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