Files
tubestation/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp
Ehsan Akhgari 755333d3f2 Backed out 2 changesets (bug 784739) because of Windows build failures
Backed out changeset 06ae36377cfa (bug 784739)
Backed out changeset e56f91d780df (bug 784739)
2013-04-02 23:04:49 -04:00

778 lines
20 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>
#include <wininet.h>
#ifdef SHOW_CONSOLE
#define DEBUG_DELAY_SHUTDOWN 1
#endif
// Heartbeat timer duration used while waiting for an incoming request.
#define HEARTBEAT_MSEC 1000
// 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"
static const WCHAR* kFirefoxExe = L"firefox.exe";
static const WCHAR* kMetroFirefoxExe = L"firefox.exe";
static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL";
static const WCHAR* kDemoMetroBrowserIDPathKey = L"Mozilla.Firefox.URL";
template <class T>void SafeRelease(T **ppT)
{
if (*ppT) {
(*ppT)->Release();
*ppT = NULL;
}
}
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(1),
mShellItemArray(NULL),
mUnkSite(NULL),
mTargetIsFileSystemLink(false),
mIsDesktopRequest(true),
mRequestMet(false)
{
}
bool RequestMet() { return mRequestMet; }
long RefCount() { return mRef; }
// 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);
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 = NULL;
if (SUCCEEDED(aArray->GetItemAt(idx, &item))) {
LPWSTR str = NULL;
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 = NULL;
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 = NULL;
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 = NULL;
return mUnkSite ? mUnkSite->QueryInterface(aRefID, aInt) : E_FAIL;
}
// IExecuteCommandApplicationHostEnvironment
IFACEMETHODIMP GetValue(AHE_TYPE *aLaunchType)
{
Log(L"IExecuteCommandApplicationHostEnvironment::GetValue()");
*aLaunchType = AHE_DESKTOP;
mIsDesktopRequest = true;
if (!mUnkSite) {
Log(L"No mUnkSite.");
return S_OK;
}
HRESULT hr;
IServiceProvider* pSvcProvider = NULL;
hr = mUnkSite->QueryInterface(IID_IServiceProvider, (void**)&pSvcProvider);
if (!pSvcProvider) {
Log(L"Couldn't get IServiceProvider service from explorer. (%X)", hr);
return S_OK;
}
IExecuteCommandHost* pHost = NULL;
// If we can't get this it's a conventional desktop launch
hr = pSvcProvider->QueryService(SID_ExecuteCommandHost,
IID_IExecuteCommandHost, (void**)&pHost);
if (!pHost) {
Log(L"Couldn't get IExecuteCommandHost service from explorer. (%X)", hr);
SafeRelease(&pSvcProvider);
return S_OK;
}
SafeRelease(&pSvcProvider);
EC_HOST_UI_MODE mode;
if (FAILED(pHost->GetUIMode(&mode))) {
Log(L"GetUIMode failed.");
SafeRelease(&pHost);
return S_OK;
}
// 0 - launched from desktop
// 1 - ?
// 2 - launched from tile interface
Log(L"GetUIMode: %d", mode);
if (!IsDefaultBrowser()) {
mode = ECHUIM_DESKTOP;
}
if (mode == ECHUIM_DESKTOP) {
Log(L"returning AHE_DESKTOP");
SafeRelease(&pHost);
return S_OK;
}
SafeRelease(&pHost);
if (!IsDX10Available()) {
Log(L"returning AHE_DESKTOP because DX10 is not available");
*aLaunchType = AHE_DESKTOP;
mIsDesktopRequest = true;
} else {
Log(L"returning AHE_IMMERSIVE");
*aLaunchType = AHE_IMMERSIVE;
mIsDesktopRequest = false;
}
return S_OK;
}
bool IsDefaultBrowser()
{
bool result = false;
IApplicationAssociationRegistration* pAAR;
HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
NULL,
CLSCTX_INPROC,
IID_IApplicationAssociationRegistration,
(void**)&pAAR);
if (SUCCEEDED(hr)) {
BOOL res;
hr = pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE,
APP_REG_NAME,
&res);
Log(L"QueryAppIsDefaultAll: %d", res);
if (!res)
return false;
// Make sure the Prog ID matches what we have
LPWSTR registeredApp;
hr = pAAR->QueryCurrentDefault(L"http", AT_URLPROTOCOL, AL_EFFECTIVE,
&registeredApp);
Log(L"QueryCurrentDefault: %X", hr);
if (SUCCEEDED(hr)) {
Log(L"registeredApp=%s", registeredApp);
result = !wcsicmp(registeredApp, kDefaultMetroBrowserIDPathKey);
if (!result) {
result = !wcsicmp(registeredApp, kDemoMetroBrowserIDPathKey);
}
CoTaskMemFree(registeredApp);
} else {
result = false;
}
pAAR->Release();
return result;
}
return result;
}
private:
~CExecuteCommandVerb()
{
SafeRelease(&mShellItemArray);
SafeRelease(&mUnkSite);
}
void LaunchDesktopBrowser();
bool SetTargetPath(IShellItem* aItem);
bool IsTargetBrowser();
long mRef;
IShellItemArray *mShellItemArray;
IUnknown *mUnkSite;
CStringW mVerb;
CStringW mTarget;
CStringW mParameters;
bool mTargetIsFileSystemLink;
DWORD mKeyState;
bool mIsDesktopRequest;
bool mRequestMet;
};
/*
* 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(NULL, buffer, MAX_PATH)) {
Log(L"GetModuleFileName failed.");
return false;
}
WCHAR* slash = wcsrchr(buffer, '\\');
if (!slash)
return false;
*slash = '\0';
aPathBuffer = buffer;
return true;
}
/*
* Retrieve 'module dir path\firefox.exe'
*
* @aPathBuffer Buffer to fill
*/
static bool GetDesktopBrowserPath(CStringW& aPathBuffer)
{
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;
}
/*
* Retrieve the app model id of the firefox metro browser.
*
* @aPathBuffer Buffer to fill
* @aCharLength Length of buffer to fill in characters
*/
static bool GetDefaultBrowserAppModelID(WCHAR* aIDBuffer,
long aCharLength)
{
if (!aIDBuffer || aCharLength <= 0)
return false;
memset(aIDBuffer, 0, (sizeof(WCHAR)*aCharLength));
HKEY key;
if (RegOpenKeyExW(HKEY_CLASSES_ROOT, kDefaultMetroBrowserIDPathKey,
0, KEY_READ, &key) != ERROR_SUCCESS) {
return false;
}
DWORD len = aCharLength * sizeof(WCHAR);
memset(aIDBuffer, 0, len);
if (RegQueryValueExW(key, L"AppUserModelID", NULL, NULL,
(LPBYTE)aIDBuffer, &len) != ERROR_SUCCESS || !len) {
RegCloseKey(key);
return false;
}
RegCloseKey(key);
return true;
}
/*
* Determines if the current target points directly to a particular
* browser or to a file or url.
*/
bool CExecuteCommandVerb::IsTargetBrowser()
{
if (!mTarget.GetLength() || !mTargetIsFileSystemLink)
return false;
CStringW modulePath;
if (!GetModulePath(modulePath))
return false;
modulePath.MakeLower();
CStringW tmpTarget = mTarget;
tmpTarget.Replace(L"\"", L"");
tmpTarget.MakeLower();
CStringW checkPath;
checkPath = modulePath;
checkPath.Append(L"\\");
checkPath.Append(kFirefoxExe);
if (tmpTarget == checkPath) {
return true;
}
return false;
}
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(NULL, 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 = NULL;
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);
return true;
}
/*
* Desktop launch - Launch the destop browser to display the current
* target using shellexecute.
*/
void CExecuteCommandVerb::LaunchDesktopBrowser()
{
CStringW browserPath;
if (!GetDesktopBrowserPath(browserPath)) {
return;
}
// If a taskbar shortcut, link or local file is clicked, the target will
// be the browser exe or file.
CStringW params;
if (!IsTargetBrowser()) {
params += "-url ";
params += "\"";
params += mTarget;
params += "\"";
}
Log(L"Desktop Launch: verb:%s exe:%s params:%s", mVerb, browserPath, params);
SHELLEXECUTEINFOW seinfo;
memset(&seinfo, 0, sizeof(seinfo));
seinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
seinfo.fMask = NULL;
seinfo.hwnd = NULL;
seinfo.lpVerb = NULL;
seinfo.lpFile = browserPath;
seinfo.lpParameters = params;
seinfo.lpDirectory = NULL;
seinfo.nShow = SW_SHOWNORMAL;
ShellExecuteExW(&seinfo);
}
class AutoSetRequestMet
{
public:
explicit AutoSetRequestMet(bool* aFlag) :
mFlag(aFlag) {}
~AutoSetRequestMet() { if (mFlag) *mFlag = true; }
private:
bool* mFlag;
};
IFACEMETHODIMP CExecuteCommandVerb::Execute()
{
Log(L"Execute()");
// We shut down when this flips to true
AutoSetRequestMet asrm(&mRequestMet);
if (!mTarget.GetLength()) {
return E_FAIL;
}
// Launch on the desktop
if (mIsDesktopRequest) {
LaunchDesktopBrowser();
return S_OK;
}
// Launch into Metro
IApplicationActivationManager* activateMgr = NULL;
DWORD processID;
if (FAILED(CoCreateInstance(CLSID_ApplicationActivationManager, NULL,
CLSCTX_LOCAL_SERVER,
IID_IApplicationActivationManager,
(void**)&activateMgr))) {
Log(L"CoCreateInstance failed, launching on desktop.");
LaunchDesktopBrowser();
return S_OK;
}
HRESULT hr;
WCHAR appModelID[256];
if (!GetDefaultBrowserAppModelID(appModelID, (sizeof(appModelID)/sizeof(WCHAR)))) {
Log(L"GetDefaultBrowserAppModelID failed, launching on desktop.");
activateMgr->Release();
LaunchDesktopBrowser();
return S_OK;
}
// Hand off focus rights to the out-of-process activation server. Without
// this the metro interface won't launch.
hr = CoAllowSetForegroundWindow(activateMgr, NULL);
if (FAILED(hr)) {
Log(L"CoAllowSetForegroundWindow result %X", hr);
activateMgr->Release();
return false;
}
Log(L"Metro Launch: verb:%s appid:%s params:%s", mVerb, appModelID, mTarget);
// shortcuts to the application
if (IsTargetBrowser()) {
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);
}
activateMgr->Release();
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 = NULL;
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 = NULL;
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
//Log(pszCmdLine);
if (!wcslen(pszCmdLine) || StrStrI(pszCmdLine, L"-Embedding"))
{
CoInitialize(NULL);
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 = NULL;
// REGCLS_SINGLEUSE insures we only get used once and then discarded.
if (FAILED(classFactory.Register(CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE)))
return -1;
if (!SetTimer(NULL, 1, HEARTBEAT_MSEC, NULL)) {
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) {
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;
}