diff options
Diffstat (limited to 'plugins/ShellExt/src/shlext.cpp')
-rw-r--r-- | plugins/ShellExt/src/shlext.cpp | 933 |
1 files changed, 933 insertions, 0 deletions
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);
+}
|