diff options
| -rw-r--r-- | plugins/ShellExt/src/shlcom.h | 1 | ||||
| -rw-r--r-- | plugins/ShellExt/src/shlext.cpp | 933 | ||||
| -rw-r--r-- | plugins/ShellExt/src/shlfactory.cpp | 72 | 
3 files changed, 1006 insertions, 0 deletions
| diff --git a/plugins/ShellExt/src/shlcom.h b/plugins/ShellExt/src/shlcom.h index c4b24ac6fd..c7d88349d0 100644 --- a/plugins/ShellExt/src/shlcom.h +++ b/plugins/ShellExt/src/shlcom.h @@ -120,6 +120,7 @@ struct THeaderIPC  struct TShellExt : public IShellExtInit, public IContextMenu3, public MZeroedObject
  {
  	TShellExt();
 +	~TShellExt();
  	ULONG RefCount;
  	// this is owned by the shell after items are added 'n' is used to
 diff --git a/plugins/ShellExt/src/shlext.cpp b/plugins/ShellExt/src/shlext.cpp new file mode 100644 index 0000000000..ac63a4ce12 --- /dev/null +++ b/plugins/ShellExt/src/shlext.cpp @@ -0,0 +1,933 @@ +#include "stdafx.h"
 +#include "shlcom.h"
 +#include "shlicons.h"
 +
 +static char* CreateUID(char *buf, size_t bufLen)
 +{
 +	sprintf_s(buf, bufLen, "'mim.shlext.caller%d$%d", GetCurrentProcessId(), GetCurrentThreadId());
 +	return buf;
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////////////////
 +
 +TShellExt::TShellExt()
 +{
 +	hDllHeap = HeapCreate(0, 0, 0);
 +	// create an inmemory DC
 +	HDC DC = GetDC(0);
 +	hMemDC = CreateCompatibleDC(DC);
 +	ReleaseDC(0, DC);
 +	// keep count on the number of objects
 +	DllObjectCount++;
 +}
 +
 +TShellExt::~TShellExt()
 +{
 +	// time to go byebye.
 +	// Note MRU menu is associated with a window (indirectly) so windows will free it.
 +	// free icons!
 +	if (ProtoIcons != NULL) {
 +		ULONG c = ProtoIconsCount;
 +		while (c > 0) {
 +			c--;
 +			TSlotProtoIcons *p = &ProtoIcons[c];
 +			for (int j = 0; j < 10; j++) {
 +				if (p->hIcons[j] != 0)
 +					DestroyIcon(p->hIcons[j]);
 +				if (p->hBitmaps[j] != 0)
 +					DeleteObject(p->hBitmaps[j]);
 +			}
 +		}
 +		free(ProtoIcons);
 +		ProtoIcons = NULL;
 +	}
 +	// free IDataObject reference if pointer exists
 +	if (pDataObject != NULL) {
 +		pDataObject->Release();
 +		pDataObject = NULL;
 +	}
 +	// free the heap and any memory allocated on it
 +	HeapDestroy(hDllHeap);
 +	// destroy the DC
 +	if (hMemDC != 0)
 +		DeleteDC(hMemDC);
 +}
 +
 +HRESULT TShellExt::QueryInterface(REFIID riid, void **ppvObject)
 +{
 +	if (ppvObject == NULL)
 +		return E_POINTER;
 +
 +	if (riid == IID_IContextMenu) {
 +		*ppvObject = (IContextMenu*)this;
 +		logA("TShellExt[%p] retrieved as IContextMenu: %d\n", this, RefCount);
 +	}
 +	else if (riid == IID_IContextMenu2) {
 +		*ppvObject = (IContextMenu2*)this;
 +		logA("TShellExt[%p] retrieved as IContextMenu2: %d\n", this, RefCount);
 +	}
 +	else if (riid == IID_IContextMenu3) {
 +		*ppvObject = (IContextMenu3*)this;
 +		logA("TShellExt[%p] retrieved as IContextMenu3: %d\n", this, RefCount);
 +	}
 +	else if (riid == IID_IShellExtInit || riid == IID_IUnknown) {
 +		*ppvObject = (IShellExtInit*)this;
 +		logA("TShellExt[%p] retrieved as IID_IUnknown: %d\n", this, RefCount);
 +	}
 +	else {
 +		*ppvObject = NULL;
 +		#ifdef LOG_ENABLED
 +			RPC_CSTR szGuid;
 +			UuidToStringA(&riid, &szGuid);
 +			logA("TShellExt[%p] failed as {%s}\n", this, szGuid);
 +			RpcStringFreeA(&szGuid);
 +		#endif
 +		return E_NOINTERFACE;
 +	}
 +
 +	AddRef();
 +	return S_OK;
 +}
 +
 +ULONG TShellExt::AddRef()
 +{
 +	RefCount++;
 +	logA("TShellExt[%p] added ref: %d\n", this, RefCount);
 +	return RefCount;
 +}
 +
 +ULONG TShellExt::Release()
 +{
 +	ULONG ret = --RefCount;
 +	if (RefCount == 0) {
 +		// free the instance (class record) created
 +		logA("TShellExt[%p] final release\n", this);
 +		delete this;
 +		DllObjectCount--;
 +	} 
 +	else logA("TShellExt[%p] release ref: %d\n", this, RefCount);
 +
 +	return ret;
 +}
 +
 +HRESULT TShellExt::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
 +{
 +	// DObj is a pointer to an instance of IDataObject which is a pointer itself
 +	// it contains a pointer to a function table containing the function pointer
 +	// address of GetData() - the instance data has to be passed explicitly since
 +	// all compiler magic has gone.
 +	if (pdtobj == NULL)
 +		return E_INVALIDARG;
 +
 +	// if an instance already exists, free it.
 +	if (pDataObject != NULL)
 +		pDataObject->Release();
 +
 +	// store the new one and AddRef() it
 +	pDataObject = pdtobj;
 +	pDataObject->AddRef();
 +	return S_OK;
 +}
 +
 +HRESULT TShellExt::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pReserved, LPSTR pszName, UINT cchMax)
 +{
 +	return E_NOTIMPL;
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////////////////
 +
 +void FreeGroupTreeAndEmptyGroups(HMENU hParentMenu, TGroupNode *pp, TGroupNode *p)
 +{
 +	while (p != NULL) {
 +		TGroupNode *q = p->Right;
 +		if (p->Left != NULL)
 +			FreeGroupTreeAndEmptyGroups(p->Left->hMenu, p, p->Left);
 +
 +		if (p->dwItems == 0) {
 +			if (pp != NULL)
 +				DeleteMenu(pp->hMenu, p->hMenuGroupID, MF_BYCOMMAND);
 +			else
 +				DeleteMenu(hParentMenu, p->hMenuGroupID, MF_BYCOMMAND);
 +		}
 +		else
 +			// make sure this node's parent know's it exists
 +			if (pp != NULL)
 +				pp->dwItems++;
 +
 +		free(p);
 +		p = q;
 +	}
 +}
 +
 +void DecideMenuItemInfo(TSlotIPC *pct, TGroupNode *pg, MENUITEMINFOA &mii, TEnumData *lParam)
 +{
 +	mii.wID = lParam->idCmdFirst;
 +	lParam->idCmdFirst++;
 +	// get the heap object
 +	HANDLE hDllHeap = lParam->Self->hDllHeap;
 +	TMenuDrawInfo *psd = (TMenuDrawInfo*)HeapAlloc(hDllHeap, 0, sizeof(TMenuDrawInfo));
 +	if (pct != NULL) {
 +		psd->cch = pct->cbStrSection - 1; // no null;
 +		psd->szText = (char*)HeapAlloc(hDllHeap, 0, pct->cbStrSection);
 +		lstrcpyA(psd->szText, (char*)pct + sizeof(TSlotIPC));
 +		psd->hContact = pct->hContact;
 +		psd->fTypes = dtContact;
 +		// find the protocol icon array to use && which status
 +		UINT c = lParam->Self->ProtoIconsCount;
 +		TSlotProtoIcons *pp = lParam->Self->ProtoIcons;
 +		psd->hStatusIcon = 0;
 +		while (c > 0) {
 +			c--;
 +			if (pp[c].hProto == pct->hProto && pp[c].pid == lParam->pid) {
 +				psd->hStatusIcon = pp[c].hIcons[pct->Status - ID_STATUS_OFFLINE];
 +				psd->hStatusBitmap = pp[c].hBitmaps[pct->Status - ID_STATUS_OFFLINE];
 +				break;
 +			}
 +		} // while
 +		psd->pid = lParam->pid;
 +	}
 +	else if (pg != NULL) {
 +		// store the given ID
 +		pg->hMenuGroupID = mii.wID;
 +		// steal the pointer from the group node it should be on the heap
 +		psd->cch = pg->cchGroup;
 +		psd->szText = pg->szGroup;
 +		psd->fTypes = dtGroup;
 +	} // if
 +	psd->wID = mii.wID;
 +	psd->szProfile = NULL;
 +	// store
 +	mii.dwItemData = UINT_PTR(psd);
 +
 +	if (lParam->bOwnerDrawSupported && lParam->bShouldOwnerDraw) {
 +		mii.fType = MFT_OWNERDRAW;
 +		mii.dwTypeData = (LPSTR)psd;
 +	}
 +	else {
 +		// normal menu
 +		mii.fType = MFT_STRING;
 +		if (pct != NULL)
 +			mii.dwTypeData = LPSTR(pct) + sizeof(TSlotIPC);
 +		else
 +			mii.dwTypeData = pg->szGroup;
 +
 +		// For Vista + let the system draw the theme && icons, pct = contact associated data
 +		if (VistaOrLater && pct != NULL && psd != NULL) {
 +			mii.fMask = MIIM_BITMAP | MIIM_FTYPE | MIIM_ID | MIIM_DATA | MIIM_STRING;
 +			// BuildSkinIcons() built an array of bitmaps which we can use here
 +			mii.hbmpItem = psd->hStatusBitmap;
 +		}
 +	}
 +}
 +
 +
 +// this callback is triggered by the menu code and IPC is already taking place,
 +// just the transfer type+data needs to be setup
 +
 +int __stdcall ClearMRUIPC(
 +	THeaderIPC *pipch,       // IPC header info, already mapped
 +	HANDLE hWorkThreadEvent, // event object being waited on on miranda thread
 +	HANDLE hAckEvent,        // ack event object that has been created
 +	TMenuDrawInfo *psd)      // command/draw info
 +{
 +	ipcPrepareRequests(IPC_PACKET_SIZE, pipch, REQUEST_CLEARMRU);
 +	ipcSendRequest(hWorkThreadEvent, hAckEvent, pipch, 100);
 +	return S_OK;
 +}
 +
 +void RemoveCheckmarkSpace(HMENU HMENU)
 +{
 +	if (!VistaOrLater)
 +		return;
 +
 +	MENUINFO mi;
 +	mi.cbSize = sizeof(mi);
 +	mi.fMask = MIM_STYLE;
 +	mi.dwStyle = MNS_CHECKORBMP;
 +	SetMenuInfo(HMENU, &mi);
 +}
 +
 +// must be called after DecideMenuItemInfo()
 +void BuildMRU(TSlotIPC *pct, MENUITEMINFOA &mii, TEnumData *lParam)
 +{
 +	if (pct->MRU > 0) {
 +		lParam->Self->RecentCount++;
 +		// lParam->Self == pointer to object data
 +		InsertMenuItemA(lParam->Self->hRecentMenu, 0xFFFFFFFF, true, &mii);
 +	}
 +}
 +
 +void BuildContactTree(TGroupNode *group, TEnumData *lParam)
 +{
 +	// set up the menu item
 +	MENUITEMINFOA mii = { sizeof(mii) };
 +	mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_DATA;
 +
 +	// go thru all the contacts
 +	TSlotIPC *pct = lParam->ipch->ContactsBegin;
 +	while (pct != NULL && pct->cbSize == sizeof(TSlotIPC) && pct->fType == REQUEST_CONTACTS) {
 +		if (pct->hGroup != 0) {
 +			// at the } of the slot header is the contact's display name
 +			// && after a double NULL char there is the group string, which has the full path of the group
 +			// this must be tokenised at '\' and we must walk the in memory group tree til we find our group
 +			// this is faster than the old version since we only ever walk one or at most two levels of the tree
 +			// per tokenised section, and it doesn't matter if two levels use the same group name (which is valid)
 +			// as the tokens processed is equatable to depth of the tree
 +
 +			char *sz = strtok(LPSTR(UINT_PTR(pct) + sizeof(TSlotIPC) + UINT_PTR(pct->cbStrSection) + 1), "\\");
 +			// restore the root
 +			TGroupNode *pg = group;
 +			unsigned Depth = 0;
 +			while (sz != NULL) {
 +				UINT Hash = murmur_hash(sz);
 +				// find this node within
 +				while (pg != NULL) {
 +					// does this node have the right hash and the right depth?
 +					if (Hash == pg->Hash && Depth == pg->Depth) 
 +						break;
 +					// each node may have a left pointer going to a sub tree
 +					// the path syntax doesn't know if a group is a group at the same level
 +					// or a nested one, which means the search node can be anywhere
 +					TGroupNode *px = pg->Left;
 +					if (px != NULL) {
 +						// keep searching this level
 +						while (px != NULL) {
 +							if (Hash == px->Hash && Depth == px->Depth) {
 +								// found the node we're looking for at the next level to pg, px is now pq for next time
 +								pg = px;
 +								goto grouploop;
 +							}
 +							px = px->Right;
 +						}
 +					}
 +					pg = pg->Right;
 +				}
 +grouploop:
 +				Depth++;
 +				// process next token
 +				sz = strtok(NULL, "\\");
 +			}
 +			// tokenisation finished, if pg != NULL  the group is found
 +			if (pg != NULL) {
 +				DecideMenuItemInfo(pct, NULL, mii, lParam);
 +				BuildMRU(pct, mii, lParam);
 +				InsertMenuItemA(pg->hMenu, 0xFFFFFFFF, true, &mii);
 +				pg->dwItems++;
 +			}
 +		} 
 +		pct = pct->Next;
 +	}
 +}
 +
 +static void BuildMenuGroupTree(TGroupNode *p, TEnumData *lParam, HMENU hLastMenu)
 +{
 +	MENUITEMINFOA mii;
 +	mii.cbSize = sizeof(MENUITEMINFO);
 +	mii.fMask = MIIM_ID | MIIM_DATA | MIIM_TYPE | MIIM_SUBMENU;
 +
 +	// go thru each group and create a menu for it adding submenus too.
 +	while (p != NULL) {
 +		mii.hSubMenu = CreatePopupMenu();
 +		if (p->Left != NULL)
 +			BuildMenuGroupTree(p->Left, lParam, mii.hSubMenu);
 +		p->hMenu = mii.hSubMenu;
 +		DecideMenuItemInfo(NULL, p, mii, lParam);
 +		InsertMenuItemA(hLastMenu, 0xFFFFFFFF, true, &mii);
 +		p = p->Right;
 +	}
 +}
 +
 +static void BuildMenus(TEnumData *lParam)
 +{
 +	HMENU hGroupMenu;
 +	LPSTR Token;
 +	TMenuDrawInfo *psd;
 +	UINT c;
 +	TSlotProtoIcons *pp;
 +
 +	MENUITEMINFOA mii = { 0 };
 +	HANDLE hDllHeap = lParam->Self->hDllHeap;
 +	HMENU hBaseMenu = lParam->Self->hRootMenu;
 +
 +	// build an in memory tree of the groups
 +	TGroupNodeList j = { 0, 0 };
 +	TSlotIPC *pg = lParam->ipch->GroupsBegin;
 +	while (pg != NULL) {
 +		if (pg->cbSize != sizeof(TSlotIPC) || pg->fType != REQUEST_GROUPS) 
 +			break;
 +
 +		UINT Depth = 0;
 +		TGroupNode *p = j.First; // start at root again
 +		// get the group
 +		Token = strtok(LPSTR(pg) + sizeof(TSlotIPC), "\\");
 +		while (Token != NULL) {
 +			UINT Hash = murmur_hash(Token);
 +			// if the (sub)group doesn't exist, create it.
 +			TGroupNode *q = FindGroupNode(p, Hash, Depth);
 +			if (q == NULL) {
 +				q = AllocGroupNode(&j, p, Depth);
 +				q->Depth = Depth;
 +				// this is the hash of this group node, but it can be anywhere
 +				// i.e. Foo\Foo this is because each node has a different depth
 +				// trouble is contacts don't come with depths!
 +				q->Hash = Hash;
 +				// don't assume that pg->hGroup's hash is valid for this token
 +				// since it maybe Miranda\Blah\Blah and we have created the first node
 +				// which maybe Miranda, thus giving the wrong hash
 +				// since "Miranda" can be a group of it's own and a full path
 +				q->cchGroup = lstrlenA(Token);
 +				q->szGroup = (LPSTR)HeapAlloc(hDllHeap, 0, q->cchGroup + 1);
 +				lstrcpyA(q->szGroup, Token);
 +				q->dwItems = 0;
 +			}
 +			p = q;
 +			Depth++;
 +			Token = strtok(NULL, "\\");
 +		}
 +		pg = pg->Next;
 +	}
 +
 +	// build the menus inserting into hGroupMenu which will be a submenu of
 +	// the instance menu item. e.g. Miranda -> [Groups ->] contacts
 +	hGroupMenu = CreatePopupMenu();
 +
 +	// allocate MRU menu, this will be associated with the higher up menu
 +	// so doesn't need to be freed (unless theres no MRUs items attached)
 +	// This menu is per process but the handle is stored globally (like a stack)
 +	lParam->Self->hRecentMenu = CreatePopupMenu();
 +	lParam->Self->RecentCount = 0;
 +	// create group menus only if they exist!
 +	if (lParam->ipch->GroupsBegin != NULL) {
 +		BuildMenuGroupTree(j.First, lParam, hGroupMenu);
 +		// add contacts that have a group somewhere
 +		BuildContactTree(j.First, lParam);
 +	}
 +
 +	mii.cbSize = sizeof(MENUITEMINFO);
 +	mii.fMask = MIIM_ID | MIIM_TYPE | MIIM_DATA;
 +	// add all the contacts that have no group (which maybe all of them)
 +	pg = lParam->ipch->ContactsBegin;
 +	while (pg != NULL) {
 +		if (pg->cbSize != sizeof(TSlotIPC) || pg->fType != REQUEST_CONTACTS) 
 +			break;
 +		if (pg->hGroup == 0) {
 +			DecideMenuItemInfo(pg, NULL, mii, lParam);
 +			BuildMRU(pg, mii, lParam);
 +			InsertMenuItemA(hGroupMenu, 0xFFFFFFFF, true, &mii);
 +		} 
 +		pg = pg->Next;
 +	}
 +
 +	// insert MRU menu as a submenu of the contact menu only if
 +	// the MRU list has been created, the menu popup will be deleted by itself
 +	if (lParam->Self->RecentCount > 0) {
 +		// insert seperator and 'clear list' menu
 +		mii.fType = MFT_SEPARATOR;
 +		mii.fMask = MIIM_TYPE;
 +		InsertMenuItemA(lParam->Self->hRecentMenu, 0xFFFFFFFF, true, &mii);
 +
 +		// insert 'clear MRU' item and setup callback
 +		mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_DATA;
 +		mii.wID = lParam->idCmdFirst;
 +		lParam->idCmdFirst++;
 +		mii.fType = MFT_STRING;
 +		mii.dwTypeData = lParam->ipch->ClearEntries; // "Clear entries"
 +		// allocate menu substructure
 +		psd = (TMenuDrawInfo*)HeapAlloc(hDllHeap, 0, sizeof(TMenuDrawInfo));
 +		psd->fTypes = dtCommand;
 +		psd->MenuCommandCallback = &ClearMRUIPC;
 +		psd->wID = mii.wID;
 +		// this is needed because there is a clear list command per each process.
 +		psd->pid = lParam->pid;
 +		mii.dwItemData = (LPARAM)psd;
 +		InsertMenuItemA(lParam->Self->hRecentMenu, 0xFFFFFFFF, true, &mii);
 +
 +		// insert MRU submenu into group menu (with) ownerdraw support as needed
 +		psd = (TMenuDrawInfo*)HeapAlloc(hDllHeap, 0, sizeof(TMenuDrawInfo));
 +		psd->szProfile = "MRU";
 +		psd->fTypes = dtGroup;
 +		// the IPC string pointer wont be around forever, must make a copy
 +		psd->cch = (int)strlen(lParam->ipch->MRUMenuName);
 +		psd->szText = (LPSTR)HeapAlloc(hDllHeap, 0, psd->cch + 1);
 +		lstrcpynA(psd->szText, lParam->ipch->MRUMenuName, sizeof(lParam->ipch->MRUMenuName) - 1);
 +
 +		mii.dwItemData = (LPARAM)psd;
 +		if (lParam->bOwnerDrawSupported && lParam->bShouldOwnerDraw) {
 +			mii.fType = MFT_OWNERDRAW;
 +			mii.dwTypeData = (LPSTR)psd;
 +		}
 +		else mii.dwTypeData = lParam->ipch->MRUMenuName; // 'Recent';
 +
 +		mii.wID = lParam->idCmdFirst;
 +		lParam->idCmdFirst++;
 +		mii.fMask = MIIM_TYPE | MIIM_SUBMENU | MIIM_DATA | MIIM_ID;
 +		mii.hSubMenu = lParam->Self->hRecentMenu;
 +		InsertMenuItemA(hGroupMenu, 0, true, &mii);
 +	}
 +	else {
 +		// no items were attached to the MRU, delete the MRU menu
 +		DestroyMenu(lParam->Self->hRecentMenu);
 +		lParam->Self->hRecentMenu = 0;
 +	}
 +
 +	// allocate display info/memory for "Miranda" string
 +
 +	mii.cbSize = sizeof(MENUITEMINFO);
 +	if (VistaOrLater)
 +		mii.fMask = MIIM_ID | MIIM_DATA | MIIM_FTYPE | MIIM_SUBMENU | MIIM_STRING | MIIM_BITMAP;
 +	else
 +		mii.fMask = MIIM_ID | MIIM_DATA | MIIM_TYPE | MIIM_SUBMENU;
 +
 +	mii.hSubMenu = hGroupMenu;
 +
 +	// by default, the menu will have space for icons and checkmarks (on Vista+) && we don't need this
 +	RemoveCheckmarkSpace(hGroupMenu);
 +
 +	psd = (TMenuDrawInfo*)HeapAlloc(hDllHeap, 0, sizeof(TMenuDrawInfo));
 +	psd->cch = (int)strlen(lParam->ipch->MirandaName);
 +	psd->szText = (LPSTR)HeapAlloc(hDllHeap, 0, psd->cch + 1);
 +	lstrcpynA(psd->szText, lParam->ipch->MirandaName, sizeof(lParam->ipch->MirandaName) - 1);
 +	// there may not be a profile name
 +	pg = lParam->ipch->DataPtr;
 +	psd->szProfile = NULL;
 +	if (pg != NULL && pg->Status == STATUS_PROFILENAME) {
 +		psd->szProfile = (LPSTR)HeapAlloc(hDllHeap, 0, pg->cbStrSection);
 +		lstrcpyA(psd->szProfile, LPSTR(UINT_PTR(pg) + sizeof(TSlotIPC)));
 +	}
 +
 +	// owner draw menus need ID's
 +	mii.wID = lParam->idCmdFirst;
 +	lParam->idCmdFirst++;
 +	psd->fTypes = dtEntry;
 +	psd->wID = mii.wID;
 +	psd->hContact = 0;
 +
 +	// get Miranda's icon or bitmap
 +	c = lParam->Self->ProtoIconsCount;
 +	pp = lParam->Self->ProtoIcons;
 +	while (c > 0) {
 +		c--;
 +		if (pp[c].pid == lParam->pid && pp[c].hProto == 0) {
 +			// either of these can be 0
 +			psd->hStatusIcon = pp[c].hIcons[0];
 +			mii.hbmpItem = pp[c].hBitmaps[0];
 +			break;
 +		}
 +	}
 +	mii.dwItemData = (UINT_PTR)psd;
 +	if (lParam->bOwnerDrawSupported && lParam->bShouldOwnerDraw) {
 +		mii.fType = MFT_OWNERDRAW;
 +		mii.dwTypeData = (LPSTR)psd;
 +	}
 +	else {
 +		mii.fType = MFT_STRING;
 +		mii.dwTypeData = lParam->ipch->MirandaName;
 +		mii.cch = sizeof(lParam->ipch->MirandaName) - 1;
 +	}
 +	// add it all
 +	InsertMenuItemA(hBaseMenu, 0, true, &mii);
 +	// free the group tree
 +	FreeGroupTreeAndEmptyGroups(hGroupMenu, NULL, j.First);
 +}
 +
 +static void BuildSkinIcons(TEnumData *lParam)
 +{
 +	IWICImagingFactory *factory = (VistaOrLater) ? ARGB_GetWorker() : NULL;
 +
 +	TSlotIPC *pct = lParam->ipch->NewIconsBegin;
 +	TShellExt *Self = lParam->Self;
 +	while (pct != NULL) {
 +		if (pct->cbSize != sizeof(TSlotIPC) || pct->fType != REQUEST_NEWICONS) 
 +			break;
 +
 +		TSlotProtoIcons *p = (TSlotProtoIcons*)(PBYTE(pct) + sizeof(TSlotIPC));
 +		Self->ProtoIcons = (TSlotProtoIcons*)realloc(Self->ProtoIcons, (Self->ProtoIconsCount + 1) * sizeof(TSlotProtoIcons));
 +		TSlotProtoIcons *d = &Self->ProtoIcons[Self->ProtoIconsCount];
 +		memmove(d, p, sizeof(TSlotProtoIcons));
 +
 +		// if using Vista (or later), clone all the icons into bitmaps and keep these around,
 +		// if using anything older, just use the default code, the bitmaps (and/or icons) will be freed
 +		// with the shell object.
 +
 +		for (int j = 0; j < 10; j++) {
 +			if (VistaOrLater) {
 +				d->hBitmaps[j] = ARGB_BitmapFromIcon(factory, Self->hMemDC, p->hIcons[j]);
 +				d->hIcons[j] = NULL;				
 +			}
 +			else {
 +				d->hBitmaps[j] = NULL;
 +				d->hIcons[j] = CopyIcon(p->hIcons[j]);
 +			}
 +		}
 +
 +		Self->ProtoIconsCount++;
 +		pct = pct->Next;
 +	}
 +
 +	if (factory)
 +		factory->Release();
 +}
 +
 +BOOL __stdcall ProcessRequest(HWND hwnd, LPARAM param)
 +{
 +	HANDLE hMirandaWorkEvent;
 +	int replyBits;
 +	char szBuf[MAX_PATH];
 +
 +	TEnumData *lParam = (TEnumData*)param;
 +	DWORD pid = 0;
 +	GetWindowThreadProcessId(hwnd, &pid);
 +	if (pid != 0) {
 +		// old system would get a window's pid and the module handle that created it
 +		// and try to OpenEvent() a event object name to it (prefixed with a string)
 +		// this was fine for most Oses (not the best way) but now actually compares
 +		// the class string (a bit slower) but should get rid of those bugs finally.
 +		hMirandaWorkEvent = OpenEventA(EVENT_ALL_ACCESS, false, CreateProcessUID(pid, szBuf, sizeof(szBuf)));
 +		if (hMirandaWorkEvent != 0) {
 +			GetClassNameA(hwnd, szBuf, sizeof(szBuf));
 +			if ( lstrcmpA(szBuf, MIRANDACLASS) != 0) {
 +				// opened but not valid.
 +				logA("ProcessRequest(%d, %p): class %s differs from %s\n", pid, hwnd, szBuf, MIRANDACLASS);
 +				CloseHandle(hMirandaWorkEvent);
 +				return true;
 +			}
 +		}
 +		// if the event object exists,  a shlext.dll running in the instance must of created it.
 +		if (hMirandaWorkEvent != 0) {
 +			logA("ProcessRequest(%d, %p): window found\n", pid, hwnd);
 +			// prep the request
 +			ipcPrepareRequests(IPC_PACKET_SIZE, lParam->ipch, REQUEST_ICONS | REQUEST_GROUPS | REQUEST_CONTACTS | REQUEST_NEWICONS);
 +
 +			// slots will be in the order of icon data, groups  contacts, the first
 +			// slot will contain the profile name
 +			replyBits = ipcSendRequest(hMirandaWorkEvent, lParam->hWaitFor, lParam->ipch, 1000);
 +
 +			// replyBits will be REPLY_FAIL if the wait timed out, or it'll be the request
 +			// bits as sent or a series of *_NOTIMPL bits where the request bit were, if there are no
 +			// contacts to speak of,  don't bother showing this instance of Miranda }
 +			if (replyBits != REPLY_FAIL && lParam->ipch->ContactsBegin != NULL) {
 +				logA("ProcessRequest(%d, %p): IPC succeeded\n", pid, hwnd);
 +				// load the address again, the server side will always overwrite it
 +				lParam->ipch->pClientBaseAddress = lParam->ipch;
 +				// fixup all the pointers to be relative to the memory map
 +				// the base pointer of the client side version of the mapped file
 +				ipcFixupAddresses(false, lParam->ipch);
 +				// store the PID used to create the work event object
 +				// that got replied to -- this is needed since each contact
 +				// on the final menu maybe on a different instance and another OpenEvent() will be needed.
 +				lParam->pid = pid;
 +				// check out the user options from the server
 +				lParam->bShouldOwnerDraw = (lParam->ipch->dwFlags & HIPC_NOICONS) == 0;
 +				// process the icons
 +				BuildSkinIcons(lParam);
 +				// process other replies
 +				BuildMenus(lParam);
 +			}
 +			// close the work object
 +			CloseHandle(hMirandaWorkEvent);
 +		}
 +	}
 +	return true;
 +}
 +
 +struct DllVersionInfo
 +{
 +	DWORD cbSize;
 +	DWORD dwMajorVersion, dwMinorVersion, dwBuildNumber, dwPlatformID;
 +};
 +
 +typedef HRESULT (__stdcall *pfnDllGetVersion)(DllVersionInfo*);
 +
 +HRESULT TShellExt::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT _idCmdFirst, UINT _idCmdLast, UINT uFlags)
 +{
 +	if (((LOWORD(uFlags) & CMF_VERBSONLY) != CMF_VERBSONLY) && ((LOWORD(uFlags) & CMF_DEFAULTONLY) != CMF_DEFAULTONLY)) {
 +		bool bMF_OWNERDRAW = false;
 +		// get the shell version
 +		pfnDllGetVersion DllGetVersionProc = (pfnDllGetVersion)GetProcAddress( GetModuleHandleA("shell32.dll"), "DllGetVersion");
 +		if (DllGetVersionProc != NULL) {
 +			DllVersionInfo dvi;
 +			dvi.cbSize = sizeof(dvi);
 +			if (DllGetVersionProc(&dvi) >= 0) // it's at least 4.00
 +				bMF_OWNERDRAW = (dvi.dwMajorVersion > 4) || (dvi.dwMinorVersion >= 71);
 +		}
 +
 +		// if we're using Vista (or later),  the ownerdraw code will be disabled, because the system draws the icons.
 +		if (VistaOrLater)
 +			bMF_OWNERDRAW = false;
 +
 +		HANDLE hMap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, IPC_PACKET_SIZE, IPC_PACKET_NAME);
 +		if (hMap != 0 && GetLastError() != ERROR_ALREADY_EXISTS) {
 +			TEnumData ed;
 +			// map the memory to this address space
 +			THeaderIPC *pipch = (THeaderIPC*)MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
 +			if (pipch != NULL) {
 +				// let the callback have instance vars
 +				ed.Self = this;
 +				// not used 'ere
 +				hRootMenu = hmenu;
 +				// store the first ID to offset with index for InvokeCommand()
 +				idCmdFirst = _idCmdFirst;
 +				// store the starting index to offset
 +				ed.bOwnerDrawSupported = bMF_OWNERDRAW;
 +				ed.bShouldOwnerDraw = true;
 +				ed.idCmdFirst = idCmdFirst;
 +				ed.ipch = pipch;
 +				// allocate a wait object so the ST can signal us, it can't be anon
 +				// since it has to used by OpenEvent()
 +				CreateUID(pipch->SignalEventName, sizeof(pipch->SignalEventName));
 +				// create the wait wait-for-wait object
 +				ed.hWaitFor = CreateEventA(NULL, false, false, pipch->SignalEventName);
 +				if (ed.hWaitFor != 0) {
 +					// enumerate all the top level windows to find all loaded MIRANDACLASS classes
 +					EnumWindows(&ProcessRequest, LPARAM(&ed));
 +					// close the wait-for-reply object
 +					CloseHandle(ed.hWaitFor);
 +				}
 +				// unmap the memory from this address space
 +				UnmapViewOfFile(pipch);
 +			}
 +			// close the mapping
 +			CloseHandle(hMap);
 +			// use the MSDN recommended way, thou there ain't much difference
 +			return MAKE_HRESULT(0, 0, (ed.idCmdFirst - _idCmdFirst) + 1);
 +		}
 +	}
 +
 +	// same as giving a SEVERITY_SUCCESS, FACILITY_NULL, since that
 +	// just clears the higher bits, which is done anyway
 +	return MAKE_HRESULT(0, 0, 1);
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////////////////
 +
 +HRESULT ipcGetFiles(THeaderIPC *pipch, IDataObject* pDataObject, HANDLE hContact)
 +{
 +	FORMATETC fet;
 +	fet.cfFormat = CF_HDROP;
 +	fet.ptd = NULL;
 +	fet.dwAspect = DVASPECT_CONTENT;
 +	fet.lindex = -1;
 +	fet.tymed = TYMED_HGLOBAL;
 +
 +	STGMEDIUM stgm;
 +	HRESULT hr = pDataObject->GetData(&fet, &stgm);
 +	if (hr == S_OK) {
 +		// FIX, actually lock the global object and get a pointer
 +		HANDLE hDrop = GlobalLock(stgm.hGlobal);
 +		if (hDrop != 0) {
 +			// get the maximum number of files
 +			UINT iFile, iFileMax = DragQueryFileA((HDROP)stgm.hGlobal, -1, NULL, 0);
 +			for (iFile = 0; iFile < iFileMax; iFile++) {
 +				// get the size of the file path
 +				int cbSize = DragQueryFileA((HDROP)stgm.hGlobal, iFile, NULL, 0);
 +				// get the buffer
 +				TSlotIPC *pct = ipcAlloc(pipch, cbSize + 1); // including null term
 +				// allocated?
 +				if (pct == NULL)
 +					break;
 +				// store the hContact
 +				pct->hContact = hContact;
 +				// copy it to the buffer
 +				DragQueryFileA((HDROP)stgm.hGlobal, iFile, LPSTR(pct) + sizeof(TSlotIPC), pct->cbStrSection);
 +			}
 +			// store the number of files
 +			pipch->Slots = iFile;
 +			GlobalUnlock(stgm.hGlobal);
 +		} // if hDrop check
 +		// release the mediumn the lock may of failed
 +		ReleaseStgMedium(&stgm);
 +	}
 +	return hr;
 +}
 +
 +HRESULT RequestTransfer(TShellExt *Self, int idxCmd)
 +{
 +	// get the contact information
 +	MENUITEMINFOA mii;
 +	mii.cbSize = sizeof(MENUITEMINFO);
 +	mii.fMask = MIIM_ID | MIIM_DATA;
 +	if ( !GetMenuItemInfoA(Self->hRootMenu, Self->idCmdFirst + idxCmd, false, &mii))
 +		return E_INVALIDARG;
 +
 +	// get the pointer
 +	TMenuDrawInfo *psd = (TMenuDrawInfo*)mii.dwItemData;
 +	// the ID stored in the item pointer and the ID for the menu must match
 +	if (psd == NULL || psd->wID != mii.wID)
 +		return E_INVALIDARG;
 +
 +	// is there an IDataObject instance?
 +	HRESULT hr = E_INVALIDARG;
 +	if (Self->pDataObject != NULL) {
 +		// OpenEvent() the work object to see if the instance is still around
 +		char szBuf[100];
 +		HANDLE hTransfer = OpenEventA(EVENT_ALL_ACCESS, false, CreateProcessUID(psd->pid, szBuf, sizeof(szBuf)));
 +		if (hTransfer != 0) {
 +			// map the ipc file again
 +			HANDLE hMap = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, IPC_PACKET_SIZE, IPC_PACKET_NAME);
 +			if (hMap != 0 && GetLastError() != ERROR_ALREADY_EXISTS) {
 +				// map it to process
 +				THeaderIPC *pipch = (THeaderIPC*)MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
 +				if (pipch != NULL) {
 +					// create the name of the object to be signalled by the ST
 +					lstrcpyA(pipch->SignalEventName, CreateUID(szBuf, sizeof(szBuf)));
 +					// create it
 +					HANDLE hReply = CreateEventA(NULL, false, false, pipch->SignalEventName);
 +					if (hReply != 0) {
 +						if (psd->fTypes & dtCommand) {
 +							if (psd->MenuCommandCallback) 
 +								hr = psd->MenuCommandCallback(pipch, hTransfer, hReply, psd);
 +						}
 +						else {
 +							// prepare the buffer
 +							ipcPrepareRequests(IPC_PACKET_SIZE, pipch, REQUEST_XFRFILES);
 +							// get all the files into the packet
 +							if (ipcGetFiles(pipch, Self->pDataObject, psd->hContact) == S_OK) {
 +								// need to wait for the ST to open the mapping object
 +								// since if we close it before it's opened it the data it
 +								// has will be undefined
 +								int replyBits = ipcSendRequest(hTransfer, hReply, pipch, 200);
 +								if (replyBits != REPLY_FAIL) // they got the files!
 +									hr = S_OK;
 +							}
 +						}
 +						// close the work object name
 +						CloseHandle(hReply);
 +					}
 +					// unmap it from this process
 +					UnmapViewOfFile(pipch);
 +				}
 +				// close the map
 +				CloseHandle(hMap);
 +			}
 +			// close the handle to the ST object name
 +			CloseHandle(hTransfer);
 +		}
 +	}
 +	return hr;
 +}
 +
 +HRESULT TShellExt::InvokeCommand(CMINVOKECOMMANDINFO *pici)
 +{
 +	return RequestTransfer(this, LOWORD(UINT_PTR(pici->lpVerb)));
 +}
 +
 +/////////////////////////////////////////////////////////////////////////////////////////
 +
 +HRESULT TShellExt::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult)
 +{
 +	LRESULT Dummy;
 +	if (plResult == NULL)
 +		plResult = &Dummy;
 +
 +	SIZE tS;
 +	HBRUSH hBr;
 +
 +	*plResult = true;
 +	if (uMsg == WM_DRAWITEM && wParam == 0) {
 +		// either a main sub menu, a group menu or a contact
 +		DRAWITEMSTRUCT *dwi = (DRAWITEMSTRUCT*)lParam;
 +		TMenuDrawInfo *psd = (TMenuDrawInfo*)dwi->itemData;
 +		// don't fill
 +		SetBkMode(dwi->hDC, TRANSPARENT);
 +		// where to draw the icon?
 +		RECT icorc;
 +		icorc.left = 0;
 +		icorc.top = dwi->rcItem.top + ((dwi->rcItem.bottom - dwi->rcItem.top) / 2) - (16 / 2);
 +		icorc.right = icorc.left + 16;
 +		icorc.bottom = icorc.top + 16;
 +		// draw for groups
 +		if (psd->fTypes & (dtGroup | dtEntry)) {
 +			hBr = GetSysColorBrush(COLOR_MENU);
 +			FillRect(dwi->hDC, &dwi->rcItem, hBr);
 +			DeleteObject(hBr);
 +
 +			if (dwi->itemState & ODS_SELECTED) {
 +				// only do this for entry menu types otherwise a black mask
 +				// is drawn under groups
 +				hBr = GetSysColorBrush(COLOR_HIGHLIGHT);
 +				FillRect(dwi->hDC, &dwi->rcItem, hBr);
 +				DeleteObject(hBr);
 +				SetTextColor(dwi->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
 +			}
 +			// draw icon
 +			if (dwi->itemState & ODS_SELECTED)
 +				hBr = GetSysColorBrush(COLOR_HIGHLIGHT);
 +			else
 +				hBr = GetSysColorBrush(COLOR_MENU);
 +
 +			DrawIconEx(dwi->hDC, icorc.left + 1, icorc.top, psd->hStatusIcon, 16, 16, 0, hBr, DI_NORMAL);
 +			DeleteObject(hBr);
 +
 +			// draw the text
 +			dwi->rcItem.left += dwi->rcItem.bottom - dwi->rcItem.top - 2;
 +			DrawTextA(dwi->hDC, psd->szText, psd->cch, &dwi->rcItem, DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
 +			// draw the name of the database text if it's there
 +			if (psd->szProfile != NULL) {
 +				GetTextExtentPoint32A(dwi->hDC, psd->szText, psd->cch, &tS);
 +				dwi->rcItem.left += tS.cx + 8;
 +				SetTextColor(dwi->hDC, GetSysColor(COLOR_GRAYTEXT));
 +				DrawTextA(dwi->hDC, psd->szProfile, lstrlenA(psd->szProfile), &dwi->rcItem, DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
 +			}
 +		}
 +		else {
 +			// it's a contact!
 +			hBr = GetSysColorBrush(COLOR_MENU);
 +			FillRect(dwi->hDC, &dwi->rcItem, hBr);
 +			DeleteObject(hBr);
 +			if (dwi->itemState & ODS_SELECTED) {
 +				hBr = GetSysColorBrush(COLOR_HIGHLIGHT);
 +				FillRect(dwi->hDC, &dwi->rcItem, hBr);
 +				DeleteObject(hBr);
 +				SetTextColor(dwi->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
 +			}
 +			// draw icon
 +			if (dwi->itemState & ODS_SELECTED)
 +				hBr = GetSysColorBrush(COLOR_HIGHLIGHT);
 +			else
 +				hBr = GetSysColorBrush(COLOR_MENU);
 +
 +			DrawIconEx(dwi->hDC, icorc.left + 2, icorc.top, psd->hStatusIcon, 16, 16, 0, hBr, DI_NORMAL);
 +			DeleteObject(hBr);
 +
 +			// draw the text
 +			dwi->rcItem.left += dwi->rcItem.bottom - dwi->rcItem.top + 1;
 +			DrawTextA(dwi->hDC, psd->szText, psd->cch, &dwi->rcItem, DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
 +		}
 +	}
 +	else if (uMsg == WM_MEASUREITEM) {
 +		// don't check if it's really a menu
 +		MEASUREITEMSTRUCT *msi = (MEASUREITEMSTRUCT*)lParam;
 +		TMenuDrawInfo *psd = (TMenuDrawInfo*)msi->itemData;
 +		NONCLIENTMETRICS ncm;
 +		ncm.cbSize = (VistaOrLater) ? sizeof(ncm) : offsetof(NONCLIENTMETRICS, iPaddedBorderWidth);
 +		SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0);
 +		// create the font used in menus, this font should be cached somewhere really
 +		HFONT hFont = CreateFontIndirect(&ncm.lfMenuFont);
 +		// select in the font
 +		HFONT hOldFont = (HFONT)SelectObject(hMemDC, hFont);
 +		// default to an icon
 +		int dx = 16;
 +		// get the size 'n' account for the icon
 +		GetTextExtentPoint32A(hMemDC, psd->szText, psd->cch, &tS);
 +		dx += tS.cx;
 +		// main menu item?
 +		if (psd->szProfile != NULL) {
 +			GetTextExtentPoint32A(hMemDC, psd->szProfile, lstrlenA(psd->szProfile), &tS);
 +			dx += tS.cx;
 +		}
 +		// store it
 +		msi->itemWidth = dx + ncm.iMenuWidth;
 +		msi->itemHeight = ncm.iMenuHeight + 2;
 +		if (tS.cy > (int)msi->itemHeight) 
 +			msi->itemHeight += tS.cy - msi->itemHeight;
 +		// clean up
 +		SelectObject(hMemDC, hOldFont);
 +		DeleteObject(hFont);
 +	}
 +	return S_OK;
 +}
 +
 +HRESULT TShellExt::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
 +{
 +	return HandleMenuMsg2(uMsg, wParam, lParam, NULL);
 +}
 diff --git a/plugins/ShellExt/src/shlfactory.cpp b/plugins/ShellExt/src/shlfactory.cpp new file mode 100644 index 0000000000..973dbb0aec --- /dev/null +++ b/plugins/ShellExt/src/shlfactory.cpp @@ -0,0 +1,72 @@ +#include "stdafx.h"
 +#include "shlcom.h"
 +
 +/////////////////////////////////////////////////////////////////////////////////////////
 +
 +TClassFactoryRec::TClassFactoryRec() :
 +	RefCount(0)
 +{
 +	DllFactoryCount++;
 +}
 +
 +HRESULT TClassFactoryRec::QueryInterface(REFIID riid, void **ppvObject)
 +{
 +	if (riid == IID_IUnknown)
 +		logA("TClassFactoryRec retrieved as IUnknown: %d\n", RefCount);
 +	else if (riid == IID_IClassFactory)
 +		logA("TClassFactoryRec retrieved as IClassFactory: %d\n", RefCount);
 +	else {
 +		#ifdef LOG_ENABLED
 +			RPC_CSTR szGuid;
 +			UuidToStringA(&riid, &szGuid);
 +			logA("TClassFactoryRec::QueryInterface {%s} failed\n", szGuid);
 +			RpcStringFreeA(&szGuid);
 +		#endif
 +		*ppvObject = NULL;
 +		return E_NOINTERFACE;
 +	}
 +
 +	AddRef();
 +	*ppvObject = this;
 +	return S_OK;
 +}
 +
 +ULONG TClassFactoryRec::AddRef()
 +{
 +	return ++RefCount;
 +}
 +
 +ULONG TClassFactoryRec::Release()
 +{
 +	ULONG result = --RefCount;
 +	if (result == 0) {
 +		logA("TClassFactoryRec released\n");
 +		delete this;
 +		DllFactoryCount--;
 +	}
 +	return result;
 +}
 +
 +HRESULT TClassFactoryRec::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject)
 +{
 +	if (ppvObject == 0)
 +		return E_POINTER;
 +
 +	*ppvObject = NULL;
 +	if (pUnkOuter)
 +		return CLASS_E_NOAGGREGATION;
 +
 +	TShellExt *p = new TShellExt();
 +	if (p == NULL)
 +		return E_OUTOFMEMORY;
 +
 +	HRESULT hr = p->QueryInterface(riid, ppvObject);
 +	if ( FAILED(hr))
 +		delete p;
 +	return hr;
 +}
 +
 +HRESULT TClassFactoryRec::LockServer(BOOL)
 +{
 +	return E_NOTIMPL;
 +}
 | 
