gecko-dev/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp

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,
&registeredApp);
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;
}