From 8257d9c829e65f5102632b1f448713730e2c8ddb Mon Sep 17 00:00:00 2001 From: George Hazan Date: Fri, 30 Aug 2013 19:03:13 +0000 Subject: missing files git-svn-id: http://svn.miranda-ng.org/main/trunk@5890 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- plugins/ShellExt/src/shlcom.h | 1 + plugins/ShellExt/src/shlext.cpp | 933 ++++++++++++++++++++++++++++++++++++ plugins/ShellExt/src/shlfactory.cpp | 72 +++ 3 files changed, 1006 insertions(+) create mode 100644 plugins/ShellExt/src/shlext.cpp create mode 100644 plugins/ShellExt/src/shlfactory.cpp (limited to 'plugins/ShellExt/src') 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; +} -- cgit v1.2.3