unit shlcom; {$IFDEF FPC} {$PACKRECORDS 4} {$MODE Delphi} {$ENDIF} interface uses Windows, m_api, shlipc, shlicons; {$DEFINE COM_STRUCTS} {$DEFINE SHLCOM} {$INCLUDE shlc.inc} {$UNDEF SHLCOM} {$UNDEF COM_STRUCTS} function DllGetClassObject(const CLSID: TCLSID; const IID: TIID; var Obj): HResult; stdcall; function DllCanUnloadNow: HResult; stdcall; procedure InvokeThreadServer; procedure CheckRegisterServer; procedure CheckUnregisterServer; function RemoveCOMRegistryEntries: HResult; function ExtractIcon(hInst: THandle; pszExe: PChar; nIndex: Integer): HICON; stdcall; external 'shell32.dll' name 'ExtractIconA'; implementation var dllpublic: record FactoryCount: Integer; ObjectCount: Integer; end; VistaOrLater:Boolean; {$DEFINE COMAPI} {$INCLUDE shlc.inc} {$UNDEF COMAPI} const IPC_PACKET_SIZE = $1000 * 32; // IPC_PACKET_NAME = 'm.mi.miranda.ipc'; // prior to 1.0.6.6 // IPC_PACKET_NAME = 'mi.miranda.IPCServer'; // prior to 2.0.0.9 IPC_PACKET_NAME = 'm.mi.miranda.ipc.server'; const { Flags returned by IContextMenu*:QueryContextMenu() } CMF_NORMAL = $00000000; CMF_DEFAULTONLY = $00000001; CMF_VERBSONLY = $00000002; CMF_EXPLORE = $00000004; CMF_NOVERBS = $00000008; CMF_CANRENAME = $00000010; CMF_NODEFAULT = $00000020; CMF_INCLUDESTATIC = $00000040; CMF_RESERVED = $FFFF0000; { view specific } { IContextMenu*:GetCommandString() uType flags } GCS_VERBA = $00000000; // canonical verb GCS_HELPTEXTA = $00000001; // help text (for status bar) GCS_VALIDATEA = $00000002; // validate command exists GCS_VERBW = $00000004; // canonical verb (unicode) GC_HELPTEXTW = $00000005; // help text (unicode version) GCS_VALIDATEW = $00000006; // validate command exists (unicode) GCS_UNICODE = $00000004; // for bit testing - Unicode string GCS_VERB = GCS_VERBA; // GCS_HELPTEXT = GCS_HELPTEXTA; GCS_VALIDATE = GCS_VALIDATEA; type { this structure is returned by InvokeCommand() } PCMInvokeCommandInfo = ^TCMInvokeCommandInfo; TCMInvokeCommandInfo = packed record cbSize: DWORD; fMask: DWORD; hwnd: hwnd; lpVerb: PChar; { maybe index, type cast as Integer } lpParams: PChar; lpDir: PChar; nShow: Integer; dwHotkey: DWORD; HICON: THandle; end; { completely stolen from modules.c: 'NameHashFunction' modified slightly } function StrHash(const szStr: PChar): DWORD;// cdecl; begin result:=mir_hash(szStr,strlen(szStr)); { asm // esi content has to be preserved with basm push esi xor edx,edx xor eax,eax mov esi,szStr mov al,[esi] xor cl,cl @@lph_top: // only 4 of 9 instructions in here don't use AL, so optimal pipe use is impossible xor edx,eax inc esi xor eax,eax and cl,31 mov al,[esi] add cl,5 test al,al rol eax,cl // rol is u-pipe only, but pairable // rol doesn't touch z-flag jnz @@lph_top // 5 clock tick loop. not bad. xor eax,edx pop esi } end; function CreateProcessUID(const pid: Cardinal): string; var pidrep: string[16]; begin str(pid, pidrep); Result := Concat('mim.shlext.', pidrep, '$'); end; function CreateUID: string; var pidrep, tidrep: string[16]; begin str(GetCurrentProcessId(), pidrep); str(GetCurrentThreadId(), tidrep); Result := Concat('mim.shlext.caller', pidrep, '$', tidrep); end; // FPC doesn't support array[0..n] of Char extended syntax with Str() function wsprintf(lpOut, lpFmt: PChar; ArgInt: Integer): Integer; cdecl; external 'user32.dll' name 'wsprintfA'; procedure str(i: Integer; S: PChar); begin i := wsprintf(S, '%d', i); if i > 2 then PChar(S)[i] := #0; end; { IShlCom } type PLResult = ^LResult; // bare minimum interface of IDataObject, since GetData() is only required. PVTable_IDataObject = ^TVTable_IDataObject; TVTable_IDataObject = record { IUnknown } QueryInterface: Pointer; AddRef: function(Self: Pointer): Cardinal; stdcall; Release: function(Self: Pointer): Cardinal; stdcall; { IDataObject } GetData: function(Self:Pointer; var formatetcIn:TFormatEtc; var medium:TStgMedium): HResult; stdcall; GetDataHere: Pointer; QueryGetData: Pointer; GetCanonicalFormatEtc: Pointer; SetData: Pointer; EnumFormatEtc: Pointer; DAdvise: Pointer; DUnadvise: Pointer; EnumDAdvise: Pointer; end; PDataObject_Interface = ^TDataObject_Interface; TDataObject_Interface = record ptrVTable: PVTable_IDataObject; end; { TShlComRec inherits from different interfaces with different function tables all "compiler magic" is lost in this case, but it's pretty easy to return a different function table for each interface, IContextMenu is returned as IContextMenu'3' since it inherits from '2' and '1' } PVTable_IShellExtInit = ^TVTable_IShellExtInit; TVTable_IShellExtInit = record { IUnknown } QueryInterface: Pointer; AddRef: Pointer; Release: Pointer; { IShellExtInit } Initialise: Pointer; end; PShlComRec = ^TShlComRec; PShellExtInit_Interface = ^TShellExtInit_Interface; TShellExtInit_Interface = record { pointer to function table } ptrVTable: PVTable_IShellExtInit; { instance data } ptrInstance: PShlComRec; { function table itself } vTable: TVTable_IShellExtInit; end; PVTable_IContextMenu3 = ^TVTable_IContextMenu3; TVTable_IContextMenu3 = record { IUnknown } QueryInterface: Pointer; AddRef: Pointer; Release: Pointer; { IContextMenu } QueryContextMenu: Pointer; InvokeCommand: Pointer; GetCommandString: Pointer; { IContextMenu2 } HandleMenuMsg: Pointer; { IContextMenu3 } HandleMenuMsg2: Pointer; end; PContextMenu3_Interface = ^TContextMenu3_Interface; TContextMenu3_Interface = record ptrVTable: PVTable_IContextMenu3; ptrInstance: PShlComRec; vTable: TVTable_IContextMenu3; end; PCommon_Interface = ^TCommon_Interface; TCommon_Interface = record ptrVTable: Pointer; ptrInstance: PShlComRec; end; TShlComRec = record ShellExtInit_Interface: TShellExtInit_Interface; ContextMenu3_Interface: TContextMenu3_Interface; { fields } RefCount: LongInt; // this is owned by the shell after items are added 'n' is used to // grab menu information directly via id rather than array indexin' hRootMenu: THandle; idCmdFirst: Integer; // most of the memory allocated is on this heap object so HeapDestroy() // can do most of the cleanup, extremely lazy I know. hDllHeap: THandle; // This is a submenu that recently used contacts are inserted into // the contact is inserted twice, once in its normal list (or group) and here // Note: These variables are global data, but refered to locally by each instance // Do not rely on these variables outside the process enumeration. hRecentMenu: THandle; RecentCount: Cardinal; // number of added items // array of all the protocol icons, for every running instance! ProtoIcons: ^TSlotProtoIconsArray; ProtoIconsCount: Cardinal; // maybe null, taken from IShellExtInit_Initalise() and AddRef()'d // only used if a Miranda instance is actually running and a user // is selected pDataObject: PDataObject_Interface; // DC is used for font metrics and saves on creating and destroying lots of DC handles // during WM_MEASUREITEM hMemDC: HDC; end; { this is passed to the enumeration callback so it can process PID's with main windows by the class name MIRANDANAME loaded with the plugin and use the IPC stuff between enumerations -- } PEnumData = ^TEnumData; TEnumData = record Self: PShlComRec; // autodetected, don't hard code since shells that don't support it // won't send WM_MEASUREITETM/WM_DRAWITEM at all. bOwnerDrawSupported: LongBool; // as per user setting (maybe of multiple Mirandas) bShouldOwnerDraw: LongBool; idCmdFirst: Integer; ipch: PHeaderIPC; // OpenEvent()'d handle to give each IPC server an object to set signalled hWaitFor: THandle; pid: DWORD; // sub-unique value used to make work object name end; procedure FreeGroupTreeAndEmptyGroups(hParentMenu: THandle; pp, p: PGroupNode); var q: PGroupNode; begin while p <> nil do begin q := p^.Right; if p^.Left <> nil then begin FreeGroupTreeAndEmptyGroups(p^.Left^.hMenu, p, p^.Left); end; // if if p^.dwItems = 0 then begin if pp <> nil then begin DeleteMenu(pp^.hMenu, p^.hMenuGroupID, MF_BYCOMMAND) end else begin DeleteMenu(hParentMenu, p^.hMenuGroupID, MF_BYCOMMAND); end; // if end else begin // make sure this node's parent know's it exists if pp <> nil then inc(pp^.dwItems); end; Dispose(p); p := q; end; end; procedure DecideMenuItemInfo(pct: PSlotIPC; pg: PGroupNode; var mii: TMenuItemInfo; lParam: PEnumData); var psd: PMenuDrawInfo; hDllHeap: THandle; c: Cardinal; pp: ^TSlotProtoIconsArray; begin mii.wID := lParam^.idCmdFirst; inc(lParam^.idCmdFirst); // get the heap object hDllHeap := lParam^.Self^.hDllHeap; psd := HeapAlloc(hDllHeap, 0, sizeof(TMenuDrawInfo)); if pct <> nil then begin psd^.cch := pct^.cbStrSection - 1; // no null; psd^.szText := HeapAlloc(hDllHeap, 0, pct^.cbStrSection); lstrcpya(psd^.szText, PChar(uint_ptr(pct) + sizeof(TSlotIPC))); psd^.hContact := pct^.hContact; psd^.fTypes := [dtContact]; // find the protocol icon array to use and which status c := lParam^.Self^.ProtoIconsCount; pp := lParam^.Self^.ProtoIcons; psd^.hStatusIcon := 0; while c > 0 do begin dec(c); if (pp[c].hProto = pct^.hProto) and (pp[c].pid = lParam^.pid) then begin psd^.hStatusIcon := pp[c].hIcons[pct^.Status - ID_STATUS_OFFLINE]; psd^.hStatusBitmap := pp[c].hBitmaps[pct^.Status - ID_STATUS_OFFLINE]; break; end; end; // while psd^.pid := lParam^.pid; end else if pg <> nil then begin // 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]; end; // if psd^.wID := mii.wID; psd^.szProfile := nil; // store mii.dwItemData := uint_ptr(psd); if ((lParam^.bOwnerDrawSupported) and (lParam^.bShouldOwnerDraw)) then begin mii.fType := MFT_OWNERDRAW; Pointer(mii.dwTypeData) := psd; end else begin // normal menu mii.fType := MFT_STRING; if pct <> nil then begin uint_ptr(mii.dwTypeData) := uint_ptr(pct) + sizeof(TSlotIPC); end else begin mii.dwTypeData := pg^.szGroup; end; { For Vista + let the system draw the theme and icons, pct = contact associated data } if VistaOrLater and (pct <> nil) and (psd <> nil) then begin mii.fMask := MIIM_BITMAP or MIIM_FTYPE or MIIM_ID or MIIM_DATA or MIIM_STRING; // BuildSkinIcons() built an array of bitmaps which we can use here mii.hBmpItem := psd^.hStatusBitmap; end; end; // if end; // must be called after DecideMenuItemInfo() procedure BuildMRU(pct: PSlotIPC; var mii: TMenuItemInfo; lParam: PEnumData); begin if pct^.MRU > 0 then begin inc(lParam^.Self^.RecentCount); // lParam^.Self == pointer to object data InsertMenuitem(lParam^.Self^.hRecentMenu, $FFFFFFFF, True, mii); end; end; procedure BuildContactTree(group: PGroupNode; lParam: PEnumData); label grouploop; var pct: PSlotIPC; pg, px: PGroupNode; str: TStrTokRec; sz: PChar; Hash: Cardinal; Depth: Cardinal; mii: TMenuItemInfo; begin // set up the menu item mii.cbSize := sizeof(TMenuItemInfo); mii.fMask := MIIM_ID or MIIM_TYPE or MIIM_DATA; // set up the scanner str.szSet := ['\']; str.bSetTerminator := False; // go thru all the contacts pct := lParam^.ipch^.ContactsBegin; while (pct <> nil) and (pct^.cbSize = sizeof(TSlotIPC)) and (pct^.fType = REQUEST_CONTACTS) do begin if pct^.hGroup <> 0 then begin // at the end of the slot header is the contact's display name // and 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 str.szStr := PChar(uint_ptr(pct) + sizeof(TSlotIPC) + uint_ptr(pct^.cbStrSection) + 1); sz := StrTok(str); // restore the root pg := group; Depth := 0; while sz <> nil do begin Hash := StrHash(sz); // find this node within while pg <> nil do begin // does this node have the right hash and the right depth? if (Hash = pg^.Hash) and (Depth = pg^.Depth) then 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 px := pg^.Left; if px <> nil then begin // keep searching this level while px <> nil do begin if (Hash = px^.Hash) and (Depth = px^.Depth) then begin // found the node we're looking for at the next level to pg, px is now pq for next time pg := px; goto grouploop; end; // if px := px^.Right; end; // if end; // if pg := pg^.Right; end; // while grouploop: inc(Depth); // process next token sz := StrTok(str); end; // while // tokenisation finished, if pg <> nil then the group is found if pg <> nil then begin DecideMenuItemInfo(pct, nil, mii, lParam); BuildMRU(pct, mii, lParam); InsertMenuitem(pg^.hMenu, $FFFFFFFF, True, mii); inc(pg^.dwItems); end; end; // if pct := pct^.Next; end; // while end; procedure BuildMenuGroupTree(p: PGroupNode; lParam: PEnumData; hLastMenu: hMenu); var mii: TMenuItemInfo; begin mii.cbSize := sizeof(TMenuItemInfo); mii.fMask := MIIM_ID or MIIM_DATA or MIIM_TYPE or MIIM_SUBMENU; // go thru each group and create a menu for it adding submenus too. while p <> nil do begin mii.hSubMenu := CreatePopupMenu(); if p^.Left <> nil then BuildMenuGroupTree(p^.Left, lParam, mii.hSubMenu); p^.hMenu := mii.hSubMenu; DecideMenuItemInfo(nil, p, mii, lParam); InsertMenuitem(hLastMenu, $FFFFFFFF, True, mii); p := p^.Right; end; // while end; { this callback is triggered by the menu code and IPC is already taking place, just the transfer type+data needs to be setup } function ClearMRUIPC(pipch: PHeaderIPC; // IPC header info, already mapped hWorkThreadEvent: THandle; // event object being waited on on miranda thread hAckEvent: THandle; // ack event object that has been created psd: PMenuDrawInfo // command/draw info ): Integer; stdcall; begin Result := S_OK; ipcPrepareRequests(IPC_PACKET_SIZE, pipch, REQUEST_CLEARMRU); ipcSendRequest(hWorkThreadEvent, hAckEvent, pipch, 100); end; procedure RemoveCheckmarkSpace(hMenu: hMenu); const MIM_STYLE = $00000010; MNS_CHECKORBMP = $4000000; type TMENUINFO = record cbSize: DWORD; fMask: DWORD; dwStyle: DWORD; cyMax: LongInt; hbrBack: THandle; dwContextHelpID: DWORD; dwMenuData: Pointer; end; var SetMenuInfo: function(hMenu: hMenu; var mi: TMENUINFO): Boolean; stdcall; mi: TMENUINFO; begin if not VistaOrLater then Exit; SetMenuInfo := GetProcAddress(GetModuleHandle('user32'), 'SetMenuInfo'); if @SetMenuInfo = nil then Exit; mi.cbSize := sizeof(mi); mi.fMask := MIM_STYLE; mi.dwStyle := MNS_CHECKORBMP; SetMenuInfo(hMenu, mi); end; procedure BuildMenus(lParam: PEnumData); {$DEFINE SHL_IDC} {$DEFINE SHL_KEYS} {$INCLUDE shlc.inc} {$UNDEF SHL_KEYS} {$UNDEF SHL_IDC} var hBaseMenu: hMenu; hGroupMenu: hMenu; pg: PSlotIPC; mii: TMenuItemInfo; j: TGroupNodeList; p, q: PGroupNode; Depth, Hash: Cardinal; Token: PChar; tk: TStrTokRec; hDllHeap: THandle; psd: PMenuDrawInfo; c: Cardinal; pp: ^TSlotProtoIconsArray; begin ZeroMemory(@mii, sizeof(mii)); hDllHeap := lParam^.Self^.hDllHeap; hBaseMenu := lParam^.Self^.hRootMenu; // build an in memory tree of the groups pg := lParam^.ipch^.GroupsBegin; tk.szSet := ['\']; tk.bSetTerminator := False; j.First := nil; j.Last := nil; while pg <> nil do begin if (pg^.cbSize <> sizeof(TSlotIPC)) or (pg^.fType <> REQUEST_GROUPS) then break; Depth := 0; p := j.First; // start at root again // get the group uint_ptr(tk.szStr) := (uint_ptr(pg) + sizeof(TSlotIPC)); // find each word between \ and create sub groups if needed. Token := StrTok(tk); while Token <> nil do begin Hash := StrHash(Token); // if the (sub)group doesn't exist, create it. q := FindGroupNode(p, Hash, Depth); if q = nil then begin 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 := HeapAlloc(hDllHeap, 0, q^.cchGroup + 1); lstrcpya(q^.szGroup, Token); q^.dwItems := 0; end; p := q; inc(Depth); Token := StrTok(tk); end; // while pg := pg^.Next; end; // while // 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 <> nil then begin BuildMenuGroupTree(j.First, lParam, hGroupMenu); // add contacts that have a group somewhere BuildContactTree(j.First, lParam); end; // mii.cbSize := sizeof(TMenuItemInfo); mii.fMask := MIIM_ID or MIIM_TYPE or MIIM_DATA; // add all the contacts that have no group (which maybe all of them) pg := lParam^.ipch^.ContactsBegin; while pg <> nil do begin if (pg^.cbSize <> sizeof(TSlotIPC)) or (pg^.fType <> REQUEST_CONTACTS) then break; if pg^.hGroup = 0 then begin DecideMenuItemInfo(pg, nil, mii, lParam); BuildMRU(pg, mii, lParam); InsertMenuitem(hGroupMenu, $FFFFFFFF, True, mii); end; // if pg := pg^.Next; end; // while // 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 then begin // insert seperator and 'clear list' menu mii.fType := MFT_SEPARATOR; mii.fMask := MIIM_TYPE; InsertMenuitem(lParam^.Self^.hRecentMenu, $FFFFFFFF, True, mii); // insert 'clear MRU' item and setup callback mii.fMask := MIIM_TYPE or MIIM_ID or MIIM_DATA; mii.wID := lParam^.idCmdFirst; inc(lParam^.idCmdFirst); mii.fType := MFT_STRING; mii.dwTypeData := lParam^.ipch^.ClearEntries; // "Clear entries" // allocate menu substructure psd := 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; Pointer(mii.dwItemData) := psd; InsertMenuitem(lParam^.Self^.hRecentMenu, $FFFFFFFF, True, mii); // insert MRU submenu into group menu (with) ownerdraw support as needed psd := 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 := strlen(lParam^.ipch^.MRUMenuName); psd^.szText := HeapAlloc(hDllHeap, 0, psd^.cch + 1); lstrcpyn(psd^.szText, lParam^.ipch^.MRUMenuName, sizeof(lParam^.ipch^.MRUMenuName) - 1); pointer(mii.dwItemData) := psd; if (lParam^.bOwnerDrawSupported) and (lParam^.bShouldOwnerDraw) then begin mii.fType := MFT_OWNERDRAW; Pointer(mii.dwTypeData) := psd; end else begin mii.dwTypeData := lParam^.ipch^.MRUMenuName; // 'Recent'; end; mii.wID := lParam^.idCmdFirst; inc(lParam^.idCmdFirst); mii.fMask := MIIM_TYPE or MIIM_SUBMENU or MIIM_DATA or MIIM_ID; mii.hSubMenu := lParam^.Self^.hRecentMenu; InsertMenuitem(hGroupMenu, 0, True, mii); end else begin // no items were attached to the MRU, delete the MRU menu DestroyMenu(lParam^.Self^.hRecentMenu); lParam^.Self^.hRecentMenu := 0; end; // allocate display info/memory for "Miranda" string mii.cbSize := sizeof(TMenuItemInfo); mii.fMask := MIIM_ID or MIIM_DATA or MIIM_TYPE or MIIM_SUBMENU; if VistaOrLater then begin mii.fMask := MIIM_ID or MIIM_DATA or MIIM_FTYPE or MIIM_SUBMENU or MIIM_STRING or MIIM_BITMAP; end; mii.hSubMenu := hGroupMenu; // by default, the menu will have space for icons and checkmarks (on Vista+) and we don't need this RemoveCheckmarkSpace(hGroupMenu); psd := HeapAlloc(hDllHeap, 0, sizeof(TMenuDrawInfo)); psd^.cch := strlen(lParam^.ipch^.MirandaName); psd^.szText := HeapAlloc(hDllHeap, 0, psd^.cch + 1); lstrcpyn(psd^.szText, lParam^.ipch^.MirandaName, sizeof(lParam^.ipch^.MirandaName) - 1); // there may not be a profile name pg := lParam^.ipch^.DataPtr; psd^.szProfile := nil; if ((pg <> nil) and (pg^.Status = STATUS_PROFILENAME)) then begin psd^.szProfile := HeapAlloc(hDllHeap, 0, pg^.cbStrSection); lstrcpya(psd^.szProfile, PChar(uint_ptr(pg) + sizeof(TSlotIPC))); end; // if // owner draw menus need ID's mii.wID := lParam^.idCmdFirst; inc(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 do begin dec(c); if (pp[c].pid = lParam^.pid) and (pp[c].hProto = 0) then begin // either of these can be 0 psd^.hStatusIcon := pp[c].hIcons[0]; mii.hBmpItem := pp[c].hBitmaps[0]; break; end; // if end; // while pointer(mii.dwItemData) := psd; if ((lParam^.bOwnerDrawSupported) and (lParam^.bShouldOwnerDraw)) then begin mii.fType := MFT_OWNERDRAW; Pointer(mii.dwTypeData) := psd; end else begin mii.fType := MFT_STRING; mii.dwTypeData := lParam^.ipch^.MirandaName; mii.cch := sizeof(lParam^.ipch^.MirandaName) - 1; end; // add it all InsertMenuitem(hBaseMenu, 0, True, mii); // free the group tree FreeGroupTreeAndEmptyGroups(hGroupMenu, nil, j.First); end; procedure BuildSkinIcons(lParam: PEnumData); var pct: PSlotIPC; p, d: PSlotProtoIcons; Self: PShlComRec; j: Cardinal; imageFactory: PImageFactory_Interface; begin pct := lParam^.ipch^.NewIconsBegin; Self := lParam^.Self; while (pct <> nil) do begin if (pct^.cbSize <> sizeof(TSlotIPC)) or (pct^.fType <> REQUEST_NEWICONS) then break; uint_ptr(p) := uint_ptr(pct) + sizeof(TSlotIPC); ReAllocMem(Self^.ProtoIcons, (Self^.ProtoIconsCount + 1) * sizeof(TSlotProtoIcons)); d := @Self^.ProtoIcons[Self^.ProtoIconsCount]; CopyMemory(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. } imageFactory := nil; for j := 0 to 9 do begin if imageFactory = nil then imageFactory := ARGB_GetWorker(); if VistaOrLater then begin d^.hBitmaps[j] := ARGB_BitmapFromIcon(imageFactory, Self^.hMemDC, p^.hIcons[j]); d^.hIcons[j] := 0; end else begin d^.hBitmaps[j] := 0; d^.hIcons[j] := CopyIcon(p^.hIcons[j]); end; end; if imageFactory <> nil then begin imageFactory^.ptrVTable^.Release(imageFactory); imageFactory := nil; end; inc(Self^.ProtoIconsCount); pct := pct^.Next; end; end; function ProcessRequest(hwnd: hwnd; lParam: PEnumData): BOOL; stdcall; var pid: Integer; hMirandaWorkEvent: THandle; replyBits: Integer; szBuf: array [0 .. MAX_PATH] of Char; begin Result := True; pid := 0; GetWindowThreadProcessId(hwnd, @pid); If pid <> 0 then begin // 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 := OpenEvent(EVENT_ALL_ACCESS, False, PChar(CreateProcessUID(pid))); if (hMirandaWorkEvent <> 0) then begin GetClassName(hwnd, szBuf, sizeof(szBuf)); if lstrcmp(szBuf, MirandaName) <> 0 then begin // opened but not valid. CloseHandle(hMirandaWorkEvent); Exit; end; // if end; // if { If the event object exists, then a shlext.dll running in the instance must of created it. } If hMirandaWorkEvent <> 0 then begin { prep the request } ipcPrepareRequests(IPC_PACKET_SIZE, lParam^.ipch, REQUEST_ICONS or REQUEST_GROUPS or REQUEST_CONTACTS or REQUEST_NEWICONS); // slots will be in the order of icon data, groups then 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, then don't bother showing this instance of Miranda } if (replyBits <> REPLY_FAIL) and (lParam^.ipch^.ContactsBegin <> nil) then begin // 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 and HIPC_NOICONS) = 0; // process the icons BuildSkinIcons(lParam); // process other replies BuildMenus(lParam); end; { close the work object } CloseHandle(hMirandaWorkEvent); end; // if end; // if end; function TShlComRec_QueryInterface(Self: PCommon_Interface; const IID: TIID; var Obj): HResult; stdcall; begin Pointer(Obj) := nil; { IShellExtInit is given when the TShlRec is created } if IsEqualIID(IID, IID_IContextMenu) or IsEqualIID(IID, IID_IContextMenu2) or IsEqualIID(IID, IID_IContextMenu3) then begin with Self^.ptrInstance^ do begin Pointer(Obj) := @ContextMenu3_Interface; inc(RefCount); end; { with } Result := S_OK; end else begin // under XP, it may ask for IShellExtInit again, this fixes the -double- click to see menus issue // which was really just the object not being created if IsEqualIID(IID, IID_IShellExtInit) then begin with Self^.ptrInstance^ do begin Pointer(Obj) := @ShellExtInit_Interface; inc(RefCount); end; // if Result := S_OK; end else begin Result := CLASS_E_CLASSNOTAVAILABLE; end; // if end; // if end; function TShlComRec_AddRef(Self: PCommon_Interface): LongInt; stdcall; begin with Self^.ptrInstance^ do begin inc(RefCount); Result := RefCount; end; { with } end; function TShlComRec_Release(Self: PCommon_Interface): LongInt; stdcall; var j, c: Cardinal; begin with Self^.ptrInstance^ do begin dec(RefCount); Result := RefCount; If RefCount = 0 then begin // time to go byebye. with Self^.ptrInstance^ do begin // Note MRU menu is associated with a window (indirectly) so windows will free it. // free icons! if ProtoIcons <> nil then begin c := ProtoIconsCount; while c > 0 do begin dec(c); for j := 0 to 9 do begin with ProtoIcons[c] do begin if hIcons[j] <> 0 then DestroyIcon(hIcons[j]); if hBitmaps[j] <> 0 then DeleteObject(hBitmaps[j]); end; end; end; FreeMem(ProtoIcons); ProtoIcons := nil; end; // if // free IDataObject reference if pointer exists if pDataObject <> nil then begin pDataObject^.ptrVTable^.Release(pDataObject); end; // if pDataObject := nil; // free the heap and any memory allocated on it HeapDestroy(hDllHeap); // destroy the DC if hMemDC <> 0 then DeleteDC(hMemDC); end; // with // free the instance (class record) created Dispose(Self^.ptrInstance); dec(dllpublic.ObjectCount); end; { if } end; { with } end; function TShlComRec_Initialise(Self: PContextMenu3_Interface; pidLFolder: Pointer; DObj: PDataObject_Interface; hKeyProdID: HKEY): HResult; stdcall; begin // 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. with Self^.ptrInstance^ do begin if DObj <> nil then begin Result := S_OK; // if an instance already exists, free it. if pDataObject <> nil then pDataObject^.ptrVTable^.Release(pDataObject); // store the new one and AddRef() it pDataObject := DObj; pDataObject^.ptrVTable^.AddRef(pDataObject); end else begin Result := E_INVALIDARG; end; // if end; // if end; function MAKE_HRESULT(Severity, Facility, Code: Integer): HResult; {$IFDEF FPC} inline; {$ENDIF} begin Result := (Severity shl 31) or (Facility shl 16) or Code; end; function TShlComRec_QueryContextMenu(Self: PContextMenu3_Interface; Menu: hMenu; indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult; stdcall; type TDllVersionInfo = record cbSize: DWORD; dwMajorVersion: DWORD; dwMinorVersion: DWORD; dwBuildNumber: DWORD; dwPlatformID: DWORD; end; TDllGetVersionProc = function(var dv: TDllVersionInfo): HResult; stdcall; var hShellInst: THandle; bMF_OWNERDRAW: Boolean; DllGetVersionProc: TDllGetVersionProc; dvi: TDllVersionInfo; ed: TEnumData; hMap: THandle; pipch: PHeaderIPC; begin Result := 0; if ((LOWORD(uFlags) and CMF_VERBSONLY) <> CMF_VERBSONLY) and ((LOWORD(uFlags) and CMF_DEFAULTONLY) <> CMF_DEFAULTONLY) then begin bMF_OWNERDRAW := False; // get the shell version hShellInst := LoadLibrary('shell32.dll'); if hShellInst <> 0 then begin DllGetVersionProc := GetProcAddress(hShellInst, 'DllGetVersion'); if @DllGetVersionProc <> nil then begin dvi.cbSize := sizeof(TDllVersionInfo); if DllGetVersionProc(dvi) >= 0 then begin // it's at least 4.00 bMF_OWNERDRAW := (dvi.dwMajorVersion > 4) or (dvi.dwMinorVersion >= 71); end; // if end; // if FreeLibrary(hShellInst); end; // if // if we're using Vista (or later), then the ownerdraw code will be disabled, because the system draws the icons. if VistaOrLater then bMF_OWNERDRAW := False; hMap := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, IPC_PACKET_SIZE, IPC_PACKET_NAME); If (hMap <> 0) and (GetLastError <> ERROR_ALREADY_EXISTS) then begin { map the memory to this address space } pipch := MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0); If pipch <> nil then begin { let the callback have instance vars } ed.Self := Self^.ptrInstance; // not used 'ere ed.Self^.hRootMenu := Menu; // store the first ID to offset with index for InvokeCommand() Self^.ptrInstance^.idCmdFirst := idCmdFirst; // store the starting index to offset Result := idCmdFirst; 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() } lstrcpya(@pipch^.SignalEventName, PChar(CreateUID())); { create the wait wait-for-wait object } ed.hWaitFor := CreateEvent(nil, False, False, pipch^.SignalEventName); If ed.hWaitFor <> 0 then begin { enumerate all the top level windows to find all loaded MIRANDANAME classes -- } EnumWindows(@ProcessRequest, lParam(@ed)); { close the wait-for-reply object } CloseHandle(ed.hWaitFor); end; { unmap the memory from this address space } UnmapViewOfFile(pipch); end; { if } { close the mapping } CloseHandle(hMap); // use the MSDN recommended way, thou there ain't much difference Result := MAKE_HRESULT(0, 0, (ed.idCmdFirst - Result) + 1); end else begin // the mapping file already exists, which is not good! end; end else begin // same as giving a SEVERITY_SUCCESS, FACILITY_NULL, since that // just clears the higher bits, which is done anyway Result := MAKE_HRESULT(0, 0, 1); end; // if end; function TShlComRec_GetCommandString(Self: PContextMenu3_Interface; idCmd, uType: UINT; pwReserved: PUINT; pszName: PChar; cchMax: UINT): HResult; stdcall; begin Result := E_NOTIMPL; end; function ipcGetFiles(pipch: PHeaderIPC; pDataObject: PDataObject_Interface; const hContact: THandle): Integer; type TDragQueryFile = function(hDrop: THandle; fileIndex: Integer; FileName: PChar; cbSize: Integer): Integer; stdcall; var fet: TFormatEtc; stgm: TStgMedium; pct: PSlotIPC; iFile: Cardinal; iFileMax: Cardinal; hShell: THandle; DragQueryFile: TDragQueryFile; cbSize: Integer; hDrop: THandle; begin Result := E_INVALIDARG; hShell := LoadLibrary('shell32.dll'); if hShell <> 0 then begin DragQueryFile := GetProcAddress(hShell, 'DragQueryFileA'); if @DragQueryFile <> nil then begin fet.cfFormat := CF_HDROP; fet.ptd := nil; fet.dwAspect := DVASPECT_CONTENT; fet.lindex := -1; fet.tymed := TYMED_HGLOBAL; Result := pDataObject^.ptrVTable^.GetData(pDataObject, fet, stgm); if Result = S_OK then begin // FIX, actually lock the global object and get a pointer Pointer(hDrop) := GlobalLock(stgm.hGlobal); if hDrop <> 0 then begin // get the maximum number of files iFileMax := DragQueryFile(stgm.hGlobal, $FFFFFFFF, nil, 0); iFile := 0; while iFile < iFileMax do begin // get the size of the file path cbSize := DragQueryFile(stgm.hGlobal, iFile, nil, 0); // get the buffer pct := ipcAlloc(pipch, cbSize + 1); // including null term // allocated? if pct = nil then break; // store the hContact pct^.hContact := hContact; // copy it to the buffer DragQueryFile(stgm.hGlobal, iFile, PChar(uint_ptr(pct) + sizeof(TSlotIPC)), pct^.cbStrSection); // next file inc(iFile); end; // while // store the number of files pipch^.Slots := iFile; GlobalUnlock(stgm.hGlobal); end; // if hDrop check // release the mediumn the lock may of failed ReleaseStgMedium(stgm); end; // if end; // if // free the dll FreeLibrary(hShell); end; // if end; function RequestTransfer(Self: PShlComRec; idxCmd: Integer): Integer; var hMap: THandle; pipch: PHeaderIPC; mii: TMenuItemInfo; hTransfer: THandle; psd: PMenuDrawInfo; hReply: THandle; replyBits: Integer; begin Result := E_INVALIDARG; // get the contact information mii.cbSize := sizeof(TMenuItemInfo); mii.fMask := MIIM_ID or MIIM_DATA; if GetMenuItemInfo(Self^.hRootMenu, Self^.idCmdFirst + idxCmd, False, mii) then begin // get the pointer uint_ptr(psd) := mii.dwItemData; // the ID stored in the item pointer and the ID for the menu must match if (psd = nil) or (psd^.wID <> mii.wID) then begin // MessageBox(0,'ptr assocated with menu is NULL','',MB_OK); Exit; end; // if end else begin // MessageBox(0,'GetMenuItemInfo failed?','',MB_OK); // couldn't get the info, can't start the transfer Result := E_INVALIDARG; Exit; end; // if // is there an IDataObject instance? if Self^.pDataObject <> nil then begin // OpenEvent() the work object to see if the instance is still around hTransfer := OpenEvent(EVENT_ALL_ACCESS, False, PChar(CreateProcessUID(psd^.pid))); if hTransfer <> 0 then begin // map the ipc file again hMap := CreateFileMapping(INVALID_HANDLE_VALUE,nil,PAGE_READWRITE,0,IPC_PACKET_SIZE,IPC_PACKET_NAME); if (hMap <> 0) and (GetLastError <> ERROR_ALREADY_EXISTS) then begin // map it to process pipch := MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0); if pipch <> nil then begin // create the name of the object to be signalled by the ST lstrcpya(pipch^.SignalEventName, PChar(CreateUID())); // create it hReply := CreateEvent(nil, False, False, pipch^.SignalEventName); if hReply <> 0 then begin if dtCommand in psd^.fTypes then begin if Assigned(psd^.MenuCommandCallback) then Result := psd^.MenuCommandCallback(pipch, hTransfer, hReply, psd); end else begin // 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 then begin // 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 replyBits := ipcSendRequest(hTransfer, hReply, pipch, 200); if replyBits <> REPLY_FAIL then begin // they got the files! Result := S_OK; end; // if end; end; // close the work object name CloseHandle(hReply); end; // if // unmap it from this process UnmapViewOfFile(pipch); end; // if // close the map CloseHandle(hMap); end; // if // close the handle to the ST object name CloseHandle(hTransfer); end; // if end // if; end; function TShlComRec_InvokeCommand(Self: PContextMenu3_Interface; var lpici: TCMInvokeCommandInfo): HResult; stdcall; begin Result := RequestTransfer(Self^.ptrInstance, LOWORD(uint_ptr(lpici.lpVerb))); end; function TShlComRec_HandleMenuMsgs(Self: PContextMenu3_Interface; uMsg: UINT; wParam: wParam; lParam: lParam; pResult: PLResult): HResult; const WM_DRAWITEM = $002B; WM_MEASUREITEM = $002C; var dwi: PDrawItemStruct; msi: PMeasureItemStruct; psd: PMenuDrawInfo; ncm: TNonClientMetrics; hOldFont: THandle; hFont: THandle; tS: TSize; dx: Integer; hBr: HBRUSH; icorc: TRect; hMemDC: HDC; begin pResult^ := Integer(True); if (uMsg = WM_DRAWITEM) and (wParam = 0) then begin // either a main sub menu, a group menu or a contact dwi := PDrawItemStruct(lParam); uint_ptr(psd) := dwi^.itemData; // don't fill SetBkMode(dwi^.HDC, TRANSPARENT); // where to draw the icon? icorc.Left := 0; // center it with dwi^ do icorc.Top := rcItem.Top + ((rcItem.Bottom - rcItem.Top) div 2) - (16 div 2); icorc.Right := icorc.Left + 16; icorc.Bottom := icorc.Top + 16; // draw for groups if (dtGroup in psd^.fTypes) or (dtEntry in psd^.fTypes) then begin hBr := GetSysColorBrush(COLOR_MENU); FillRect(dwi^.HDC, dwi^.rcItem, hBr); DeleteObject(hBr); // if (ODS_SELECTED and dwi^.itemState = ODS_SELECTED) then begin // 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)); end; // if // draw icon with dwi^, icorc do begin if (ODS_SELECTED and dwi^.itemState) = ODS_SELECTED then begin hBr := GetSysColorBrush(COLOR_HIGHLIGHT); end else begin hBr := GetSysColorBrush(COLOR_MENU); end; // if DrawIconEx(HDC, Left + 1, Top, psd^.hStatusIcon, 16, 16, // width, height 0, // step hBr, // brush DI_NORMAL); DeleteObject(hBr); end; // with // draw the text with dwi^ do begin inc(rcItem.Left, ((rcItem.Bottom - rcItem.Top) - 2)); DrawText(HDC, psd^.szText, psd^.cch, rcItem, DT_NOCLIP or DT_NOPREFIX or DT_SINGLELINE or DT_VCENTER); // draw the name of the database text if it's there if psd^.szProfile <> nil then begin GetTextExtentPoint32(dwi^.HDC, psd^.szText, psd^.cch, tS); inc(rcItem.Left, tS.cx + 8); SetTextColor(HDC, GetSysColor(COLOR_GRAYTEXT)); DrawText(HDC, psd^.szProfile, lstrlena(psd^.szProfile), rcItem, DT_NOCLIP or DT_NOPREFIX or DT_SINGLELINE or DT_VCENTER); end; // if end; // with end else begin // it's a contact! hBr := GetSysColorBrush(COLOR_MENU); FillRect(dwi^.HDC, dwi^.rcItem, hBr); DeleteObject(hBr); if ODS_SELECTED and dwi^.itemState = ODS_SELECTED then begin hBr := GetSysColorBrush(COLOR_HIGHLIGHT); FillRect(dwi^.HDC, dwi^.rcItem, hBr); DeleteObject(hBr); SetTextColor(dwi^.HDC, GetSysColor(COLOR_HIGHLIGHTTEXT)); end; // draw icon with dwi^, icorc do begin if (ODS_SELECTED and dwi^.itemState) = ODS_SELECTED then begin hBr := GetSysColorBrush(COLOR_HIGHLIGHT); end else begin hBr := GetSysColorBrush(COLOR_MENU); end; // if DrawIconEx(HDC, Left + 2, Top, psd^.hStatusIcon, 16, 16, // width, height 0, // step hBr, // brush DI_NORMAL); DeleteObject(hBr); end; // with // draw the text with dwi^ do begin inc(rcItem.Left, (rcItem.Bottom - rcItem.Top) + 1); DrawText(HDC, psd^.szText, psd^.cch, rcItem, DT_NOCLIP or DT_NOPREFIX or DT_SINGLELINE or DT_VCENTER); end; // with end; // if end else if (uMsg = WM_MEASUREITEM) then begin // don't check if it's really a menu msi := PMeasureItemStruct(lParam); uint_ptr(psd) := msi^.itemData; ncm.cbSize := sizeof(TNonClientMetrics); SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, @ncm, 0); // create the font used in menus, this font should be cached somewhere really {$IFDEF FPC} hFont := CreateFontIndirect(@ncm.lfMenuFont); {$ELSE} hFont := CreateFontIndirect(ncm.lfMenuFont); {$ENDIF} hMemDC := Self^.ptrInstance^.hMemDC; // select in the font hOldFont := SelectObject(hMemDC, hFont); // default to an icon dx := 16; // get the size 'n' account for the icon GetTextExtentPoint32(hMemDC, psd^.szText, psd^.cch, tS); inc(dx, tS.cx); // main menu item? if psd^.szProfile <> nil then begin GetTextExtentPoint32(hMemDC, psd^.szProfile, lstrlena(psd^.szProfile), tS); inc(dx, tS.cx); end; // store it msi^.itemWidth := dx + Integer(ncm.iMenuWidth); msi^.itemHeight := Integer(ncm.iMenuHeight) + 2; if tS.cy > msi^.itemHeight then inc(msi^.itemHeight, tS.cy - msi^.itemHeight); // clean up SelectObject(hMemDC, hOldFont); DeleteObject(hFont); end; Result := S_OK; end; function TShlComRec_HandleMenuMsg(Self: PContextMenu3_Interface; uMsg: UINT; wParam: wParam; lParam: lParam): HResult; stdcall; var Dummy: HResult; begin Result := TShlComRec_HandleMenuMsgs(Self, uMsg, wParam, lParam, @Dummy); end; function TShlComRec_HandleMenuMsg2(Self: PContextMenu3_Interface; uMsg: UINT; wParam: wParam; lParam: lParam; PLResult: Pointer { ^LResult } ): HResult; stdcall; var Dummy: HResult; begin // this will be null if a return value isn't needed. if PLResult = nil then PLResult := @Dummy; Result := TShlComRec_HandleMenuMsgs(Self, uMsg, wParam, lParam, PLResult); end; function TShlComRec_Create: PShlComRec; var DC: HDC; begin New(Result); { build all the function tables for interfaces } with Result^.ShellExtInit_Interface do begin { this is only owned by us... } ptrVTable := @vTable; { IUnknown } vTable.QueryInterface := @TShlComRec_QueryInterface; vTable.AddRef := @TShlComRec_AddRef; vTable.Release := @TShlComRec_Release; { IShellExtInit } vTable.Initialise := @TShlComRec_Initialise; { instance of a TShlComRec } ptrInstance := Result; end; with Result^.ContextMenu3_Interface do begin ptrVTable := @vTable; { IUnknown } vTable.QueryInterface := @TShlComRec_QueryInterface; vTable.AddRef := @TShlComRec_AddRef; vTable.Release := @TShlComRec_Release; { IContextMenu } vTable.QueryContextMenu := @TShlComRec_QueryContextMenu; vTable.InvokeCommand := @TShlComRec_InvokeCommand; vTable.GetCommandString := @TShlComRec_GetCommandString; { IContextMenu2 } vTable.HandleMenuMsg := @TShlComRec_HandleMenuMsg; { IContextMenu3 } vTable.HandleMenuMsg2 := @TShlComRec_HandleMenuMsg2; { instance data } ptrInstance := Result; end; { initalise variables } Result^.RefCount := 1; Result^.hDllHeap := HeapCreate(0, 0, 0); Result^.hRootMenu := 0; Result^.hRecentMenu := 0; Result^.RecentCount := 0; Result^.idCmdFirst := 0; Result^.pDataObject := nil; Result^.ProtoIcons := nil; Result^.ProtoIconsCount := 0; // create an inmemory DC DC := GetDC(0); Result^.hMemDC := CreateCompatibleDC(DC); ReleaseDC(0, DC); { keep count on the number of objects } inc(dllpublic.ObjectCount); end; { IClassFactory } type PVTable_IClassFactory = ^TVTable_IClassFactory; TVTable_IClassFactory = record { IUnknown } QueryInterface: Pointer; AddRef: Pointer; Release: Pointer; { IClassFactory } CreateInstance: Pointer; LockServer: Pointer; end; PClassFactoryRec = ^TClassFactoryRec; TClassFactoryRec = record ptrVTable: PVTable_IClassFactory; vTable: TVTable_IClassFactory; { fields } RefCount: LongInt; end; function TClassFactoryRec_QueryInterface(Self: PClassFactoryRec; const IID: TIID; var Obj): HResult; stdcall; begin Pointer(Obj) := nil; Result := E_NOTIMPL; end; function TClassFactoryRec_AddRef(Self: PClassFactoryRec): LongInt; stdcall; begin inc(Self^.RefCount); Result := Self^.RefCount; end; function TClassFactoryRec_Release(Self: PClassFactoryRec): LongInt; stdcall; begin dec(Self^.RefCount); Result := Self^.RefCount; if Result = 0 then begin Dispose(Self); dec(dllpublic.FactoryCount); end; { if } end; function TClassFactoryRec_CreateInstance(Self: PClassFactoryRec; unkOuter: Pointer; const IID: TIID; var Obj): HResult; stdcall; var ShlComRec: PShlComRec; begin Pointer(Obj) := nil; Result := CLASS_E_NOAGGREGATION; if unkOuter = nil then begin { Before Vista, the system queried for a IShell interface then queried for a context menu, Vista now queries for a context menu (or a shell menu) then QI()'s the other interface } if IsEqualIID(IID, IID_IContextMenu) then begin Result := S_OK; ShlComRec := TShlComRec_Create; Pointer(Obj) := @ShlComRec^.ContextMenu3_Interface; end; if IsEqualIID(IID, IID_IShellExtInit) then begin Result := S_OK; ShlComRec := TShlComRec_Create; Pointer(Obj) := @ShlComRec^.ShellExtInit_Interface; end; // if end; // if end; function TClassFactoryRec_LockServer(Self: PClassFactoryRec; fLock: BOOL): HResult; stdcall; begin Result := E_NOTIMPL; end; function TClassFactoryRec_Create: PClassFactoryRec; begin New(Result); Result^.ptrVTable := @Result^.vTable; { IUnknown } Result^.vTable.QueryInterface := @TClassFactoryRec_QueryInterface; Result^.vTable.AddRef := @TClassFactoryRec_AddRef; Result^.vTable.Release := @TClassFactoryRec_Release; { IClassFactory } Result^.vTable.CreateInstance := @TClassFactoryRec_CreateInstance; Result^.vTable.LockServer := @TClassFactoryRec_LockServer; { inital the variables } Result^.RefCount := 1; { count the number of factories } inc(dllpublic.FactoryCount); end; // // IPC part // type PFileList = ^TFileList; TFileList = array [0 .. 0] of PChar; PAddArgList = ^TAddArgList; TAddArgList = record szFile: PChar; // file being processed cch: Cardinal; // it's length (with space for NULL char) count: Cardinal; // number we have so far files: PFileList; hContact: THandle; hEvent: THandle; end; function AddToList(var args: TAddArgList): LongBool; var attr: Cardinal; p: Pointer; hFind: THandle; fd: TWIN32FINDDATA; szBuf: array [0 .. MAX_PATH] of Char; szThis: PChar; cchThis: Cardinal; begin Result := False; attr := GetFileAttributes(args.szFile); if (attr <> $FFFFFFFF) and ((attr and FILE_ATTRIBUTE_HIDDEN) = 0) then begin if args.count mod 10 = 5 then begin if CallService(MS_SYSTEM_TERMINATED, 0, 0) <> 0 then begin Result := True; Exit; end; // if end; if attr and FILE_ATTRIBUTE_DIRECTORY <> 0 then begin // add the directory lstrcpya(szBuf, args.szFile); ReAllocMem(args.files, (args.count + 1) * sizeof(PChar)); GetMem(p, strlen(szBuf) + 1); lstrcpya(p, szBuf); args.files^[args.count] := p; inc(args.count); // tack on ending search token lstrcata(szBuf, '\*'); hFind := FindFirstFile(szBuf, fd); while True do begin if fd.cFileName[0] <> '.' then begin lstrcpya(szBuf, args.szFile); lstrcata(szBuf, '\'); lstrcata(szBuf, fd.cFileName); // keep a copy of the current thing being processed szThis := args.szFile; args.szFile := szBuf; cchThis := args.cch; args.cch := strlen(szBuf) + 1; // recurse Result := AddToList(args); // restore args.szFile := szThis; args.cch := cchThis; if Result then break; end; // if if not FindNextFile(hFind, fd) then break; end; // while FindClose(hFind); end else begin // add the file ReAllocMem(args.files, (args.count + 1) * sizeof(PChar)); GetMem(p, args.cch); lstrcpya(p, args.szFile); args.files^[args.count] := p; inc(args.count); end; // if end; end; procedure MainThreadIssueTransfer(p: PAddArgList); stdcall; {$DEFINE SHL_IDC} {$DEFINE SHL_KEYS} {$INCLUDE shlc.inc} {$UNDEF SHL_KEYS} {$UNDEF SHL_IDC} begin DBWriteContactSettingByte(p^.hContact, SHLExt_Name, SHLExt_MRU, 1); CallService(MS_FILE_SENDSPECIFICFILES, p^.hContact, lParam(p^.files)); SetEvent(p^.hEvent); end; function IssueTransferThread(pipch: PHeaderIPC): Cardinal; stdcall; var szBuf: array [0 .. MAX_PATH] of Char; pct: PSlotIPC; args: TAddArgList; bQuit: LongBool; j, c: Cardinal; p: Pointer; hMainThread: THandle; begin result:=0; Thread_Push(0,nil); hMainThread := THandle(pipch^.Param); GetCurrentDirectory(sizeof(szBuf), szBuf); args.count := 0; args.files := nil; pct := pipch^.DataPtr; bQuit := False; while pct <> nil do begin if (pct^.cbSize <> sizeof(TSlotIPC)) then break; args.szFile := PChar(uint_ptr(pct) + sizeof(TSlotIPC)); args.hContact := pct^.hContact; args.cch := pct^.cbStrSection + 1; bQuit := AddToList(args); if bQuit then break; pct := pct^.Next; end; // while if args.files <> nil then begin ReAllocMem(args.files, (args.count + 1) * sizeof(PChar)); args.files^[args.count] := nil; inc(args.count); if (not bQuit) then begin args.hEvent := CreateEvent(nil, True, False, nil); QueueUserAPC(@MainThreadIssueTransfer, hMainThread, uint_ptr(@args)); while True do begin if WaitForSingleObjectEx(args.hEvent, INFINITE, True) <> WAIT_IO_COMPLETION then break; end; CloseHandle(args.hEvent); end; // if c := args.count - 1; for j := 0 to c do begin p := args.files^[j]; if p <> nil then FreeMem(p); end; FreeMem(args.files); end; SetCurrentDirectory(szBuf); FreeMem(pipch); CloseHandle(hMainThread); Thread_Pop(); ExitThread(0); end; type PSlotInfo = ^TSlotInfo; TSlotInfo = record hContact: THandle; hProto: Cardinal; dwStatus: Integer; // will be aligned anyway end; TSlotArray = array [0 .. $FFFFFF] of TSlotInfo; PSlotArray = ^TSlotArray; function SortContact(var Item1, Item2: TSlotInfo): Integer; stdcall; begin Result := CallService(MS_CLIST_CONTACTSCOMPARE, Item1.hContact, Item2.hContact); end; // from FP FCL procedure QuickSort(FList: PSlotArray; L, R: LongInt); var i, j: LongInt; p, q: TSlotInfo; begin repeat i := L; j := R; p := FList^[(L + R) div 2]; repeat while SortContact(p, FList^[i]) > 0 do inc(i); while SortContact(p, FList^[j]) < 0 do dec(j); if i <= j then begin q := FList^[i]; FList^[i] := FList^[j]; FList^[j] := q; inc(i); dec(j); end; // if until i > j; if L < j then QuickSort(FList, L, j); L := i; until i >= R; end; {$DEFINE SHL_KEYS} {$INCLUDE shlc.inc} {$UNDEF SHL_KEYS} procedure ipcGetSkinIcons(ipch: PHeaderIPC); var protoCount: Integer; pp: ^PPROTOCOLDESCRIPTOR; spi: TSlotProtoIcons; j: Cardinal; pct: PSlotIPC; szTmp: array [0 .. 63] of Char; dwCaps: Cardinal; begin if (CallService(MS_PROTO_ENUMPROTOCOLS, wParam(@protoCount), lParam(@pp)) = 0) and (protoCount <> 0) then begin spi.pid := GetCurrentProcessId(); while protoCount > 0 do begin lstrcpya(szTmp, pp^.szName); lstrcata(szTmp, PS_GETCAPS); dwCaps := CallService(szTmp, PFLAGNUM_1, 0); if (dwCaps and PF1_FILESEND) <> 0 then begin pct := ipcAlloc(ipch, sizeof(TSlotProtoIcons)); if pct <> nil then begin // capture all the icons! spi.hProto := StrHash(pp^.szName); for j := 0 to 9 do begin spi.hIcons[j] := LoadSkinnedProtoIcon(pp^.szName, ID_STATUS_OFFLINE + j); end; // for pct^.fType := REQUEST_NEWICONS; CopyMemory(Pointer(uint_ptr(pct) + sizeof(TSlotIPC)), @spi, sizeof(TSlotProtoIcons)); if ipch^.NewIconsBegin = nil then ipch^.NewIconsBegin := pct; end; // if end; // if inc(pp); dec(protoCount); end; // while end; // if // add Miranda icon pct := ipcAlloc(ipch, sizeof(TSlotProtoIcons)); if pct <> nil then begin ZeroMemory(@spi.hIcons, sizeof(spi.hIcons)); spi.hProto := 0; // no protocol spi.hIcons[0] := LoadSkinnedIcon(SKINICON_OTHER_MIRANDA); pct^.fType := REQUEST_NEWICONS; CopyMemory(Pointer(uint_ptr(pct) + sizeof(TSlotIPC)), @spi, sizeof(TSlotProtoIcons)); if ipch^.NewIconsBegin = nil then ipch^.NewIconsBegin := pct; end; // if end; function ipcGetSortedContacts(ipch: PHeaderIPC; pSlot: pint; bGroupMode: Boolean): Boolean; var dwContacts: Cardinal; pContacts: PSlotArray; hContact: THandle; i: Integer; dwOnline: Cardinal; szProto: PChar; dwStatus: Integer; pct: PSlotIPC; szContact: PChar; dbv: TDBVariant; bHideOffline: Boolean; szTmp: array [0 .. 63] of Char; dwCaps: Cardinal; szSlot: PChar; n, rc, cch: Cardinal; begin Result := False; // hide offliners? bHideOffline := DBGetContactSettingByte(0, 'CList', 'HideOffline', 0) = 1; // do they wanna hide the offline people anyway? if DBGetContactSettingByte(0, SHLExt_Name, SHLExt_ShowNoOffline, 0) = 1 then begin // hide offline people bHideOffline := True; end; // get the number of contacts dwContacts := CallService(MS_DB_CONTACT_GETCOUNT, 0, 0); if dwContacts = 0 then Exit; // get the contacts in the array to be sorted by status, trim out anyone // who doesn't wanna be seen. GetMem(pContacts, (dwContacts + 2) * sizeof(TSlotInfo)); i := 0; dwOnline := 0; hContact := CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); while (hContact <> 0) do begin if i >= dwContacts then break; (* do they have a running protocol? *) uint_ptr(szProto) := CallService(MS_PROTO_GETCONTACTBASEPROTO, hContact, 0); if szProto <> nil then begin (* does it support file sends? *) lstrcpya(szTmp, szProto); lstrcata(szTmp, PS_GETCAPS); dwCaps := CallService(szTmp, PFLAGNUM_1, 0); if (dwCaps and PF1_FILESEND) = 0 then begin hContact := CallService(MS_DB_CONTACT_FINDNEXT, hContact, 0); continue; end; dwStatus := DBGetContactSettingWord(hContact, szProto, 'Status', ID_STATUS_OFFLINE); if dwStatus <> ID_STATUS_OFFLINE then inc(dwOnline) else if bHideOffline then begin hContact := CallService(MS_DB_CONTACT_FINDNEXT, hContact, 0); continue; end; // if // is HIT on? if BST_UNCHECKED = DBGetContactSettingByte(0, SHLExt_Name, SHLExt_UseHITContacts, BST_UNCHECKED) then begin // don't show people who are "Hidden" "NotOnList" or Ignored if (DBGetContactSettingByte(hContact, 'CList', 'Hidden', 0) = 1) or (DBGetContactSettingByte(hContact, 'CList', 'NotOnList', 0) = 1) or (CallService(MS_IGNORE_ISIGNORED, hContact, IGNOREEVENT_MESSAGE or IGNOREEVENT_URL or IGNOREEVENT_FILE) <> 0) then begin hContact := CallService(MS_DB_CONTACT_FINDNEXT, hContact, 0); continue; end; // if end; // if // is HIT2 off? if BST_UNCHECKED = DBGetContactSettingByte(0, SHLExt_Name, SHLExt_UseHIT2Contacts, BST_UNCHECKED) then begin if DBGetContactSettingWord(hContact, szProto, 'ApparentMode', 0) = ID_STATUS_OFFLINE then begin hContact := CallService(MS_DB_CONTACT_FINDNEXT, hContact, 0); continue; end; // if end; // if // store pContacts^[i].hContact := hContact; pContacts^[i].dwStatus := dwStatus; pContacts^[i].hProto := StrHash(szProto); inc(i); end else begin // contact has no protocol! end; // if hContact := CallService(MS_DB_CONTACT_FINDNEXT, hContact, 0); end; // while // if no one is online and the CList isn't showing offliners, quit if (dwOnline = 0) and (bHideOffline) then begin FreeMem(pContacts); Exit; end; // if dwContacts := i; i := 0; // sort the array QuickSort(pContacts, 0, dwContacts - 1); // create an IPC slot for each contact and store display name, etc while i < dwContacts do begin uint_ptr(szContact) := CallService(MS_CLIST_GETCONTACTDISPLAYNAME,pContacts^[i].hContact, 0); if (szContact <> nil) then begin n := 0; rc := 1; if bGroupMode then begin rc := DBGetContactSetting(pContacts^[i].hContact, 'CList', 'Group', @dbv); if rc = 0 then begin n := lstrlena(dbv.szVal.a) + 1; end; end; // if cch := lstrlena(szContact) + 1; pct := ipcAlloc(ipch, cch + 1 + n); if pct = nil then begin DBFreeVariant(@dbv); break; end; // lie about the actual size of the TSlotIPC pct^.cbStrSection := cch; szSlot := PChar(uint_ptr(pct) + sizeof(TSlotIPC)); lstrcpya(szSlot, szContact); pct^.fType := REQUEST_CONTACTS; pct^.hContact := pContacts^[i].hContact; pct^.Status := pContacts^[i].dwStatus; pct^.hProto := pContacts^[i].hProto; pct^.MRU := DBGetContactSettingByte(pct^.hContact, SHLExt_Name, SHLExt_MRU, 0); if ipch^.ContactsBegin = nil then ipch^.ContactsBegin := pct; inc(szSlot, cch + 1); if rc = 0 then begin pct^.hGroup := StrHash(dbv.szVal.a); lstrcpya(szSlot, dbv.szVal.a); DBFreeVariant(@dbv); end else begin pct^.hGroup := 0; szSlot^ := #0; end; inc(pSlot^); end; // if inc(i); end; // while FreeMem(pContacts); // Result := True; end; // worker thread to clear MRU, called by the IPC bridge function ClearMRUThread(notused: Pointer): Cardinal; stdcall; {$DEFINE SHL_IDC} {$DEFINE SHL_KEYS} {$INCLUDE shlc.inc} {$UNDEF SHL_KEYS} {$UNDEF SHL_IDC} var hContact: THandle; begin result:=0; Thread_Push(0,nil); begin hContact := CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); while hContact <> 0 do begin if DBGetContactSettingByte(hContact, SHLExt_Name, SHLExt_MRU, 0) > 0 then begin DBWriteContactSettingByte(hContact, SHLExt_Name, SHLExt_MRU, 0); end; hContact := CallService(MS_DB_CONTACT_FINDNEXT, hContact, 0); end; end; Thread_Pop(); ExitThread(0); end; // this function is called from an APC into the main thread procedure ipcService(dwParam: DWORD); stdcall; label Reply; var hMap: THandle; pMMT: PHeaderIPC; hSignal: THandle; pct: PSlotIPC; szBuf: PChar; iSlot: Integer; szGroupStr: array [0 .. 31] of Char; dbv: TDBVariant; bits: pint; bGroupMode: Boolean; tid: Cardinal; cloned: PHeaderIPC; szMiranda: PChar; begin { try to open the file mapping object the caller must make sure no other running instance is using this file } hMap := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, IPC_PACKET_NAME); If hMap <> 0 then begin { map the file to this process } pMMT := MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0); { if it fails the caller should of had some timeout in wait } if (pMMT <> nil) and (pMMT^.cbSize = sizeof(THeaderIPC)) and (pMMT^.dwVersion = PLUGIN_MAKE_VERSION(2, 0, 1, 2)) then begin // toggle the right bits bits := @pMMT^.fRequests; // jump right to a worker thread for file processing? if (bits^ and REQUEST_XFRFILES) = REQUEST_XFRFILES then begin GetMem(cloned, IPC_PACKET_SIZE); // translate from client space to cloned heap memory pMMT^.pServerBaseAddress := pMMT^.pClientBaseAddress; pMMT^.pClientBaseAddress := cloned; CopyMemory(cloned, pMMT, IPC_PACKET_SIZE); ipcFixupAddresses(True, cloned); DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), @cloned^.Param, THREAD_SET_CONTEXT, False, 0); CloseHandle(CreateThread(nil, 0, @IssueTransferThread, cloned, 0, tid)); goto Reply; end; // the request was to clear the MRU entries, we have no return data if (bits^ and REQUEST_CLEARMRU) = REQUEST_CLEARMRU then begin CloseHandle(CreateThread(nil, 0, @ClearMRUThread, nil, 0, tid)); goto Reply; end; // the IPC header may have pointers that need to be translated // in either case the supplied data area pointers has to be // translated to this address space. // the server base address is always removed to get an offset // to which the client base is added, this is what ipcFixupAddresses() does pMMT^.pServerBaseAddress := pMMT^.pClientBaseAddress; pMMT^.pClientBaseAddress := pMMT; // translate to the server space map ipcFixupAddresses(True, pMMT); // store the address map offset so the caller can retranslate pMMT^.pServerBaseAddress := pMMT; // return some options to the client if DBGetContactSettingByte(0, SHLExt_Name, SHLExt_ShowNoIcons, 0) <> 0 then begin pMMT^.dwFlags := HIPC_NOICONS; end; // see if we have a custom string for 'Miranda' szMiranda := Translate('Miranda'); lstrcpyn(pMMT^.MirandaName, szMiranda, sizeof(pMMT^.MirandaName) - 1); // for the MRU menu szBuf := Translate('Recently'); lstrcpyn(pMMT^.MRUMenuName, szBuf, sizeof(pMMT^.MRUMenuName) - 1); // and a custom string for "clear entries" szBuf := Translate('Clear entries'); lstrcpyn(pMMT^.ClearEntries, szBuf, sizeof(pMMT^.ClearEntries) - 1); // if the group mode is on, check if they want the CList setting bGroupMode := BST_CHECKED = DBGetContactSettingByte(0, SHLExt_Name, SHLExt_UseGroups, BST_UNCHECKED); if bGroupMode and (BST_CHECKED = DBGetContactSettingByte(0, SHLExt_Name, SHLExt_UseCListSetting, BST_UNCHECKED)) then begin bGroupMode := 1 = DBGetContactSettingByte(0, 'CList', 'UseGroups', 0); end; iSlot := 0; // return profile if set if BST_UNCHECKED = DBGetContactSettingByte(0, SHLExt_Name, SHLExt_ShowNoProfile, BST_UNCHECKED) then begin pct := ipcAlloc(pMMT, 50); if pct <> nil then begin // will actually return with .dat if there's space for it, not what the docs say pct^.Status := STATUS_PROFILENAME; CallService(MS_DB_GETPROFILENAME, 49, uint_ptr(pct) + sizeof(TSlotIPC)); end; // if end; // if if (bits^ and REQUEST_NEWICONS) = REQUEST_NEWICONS then begin ipcGetSkinIcons(pMMT); end; if (bits^ and REQUEST_GROUPS = REQUEST_GROUPS) then begin // return contact's grouping if it's present while bGroupMode do begin str(iSlot, szGroupStr); if DBGetContactSetting(0, 'CListGroups', szGroupStr, @dbv) <> 0 then break; pct := ipcAlloc(pMMT, lstrlena(dbv.szVal.a + 1) + 1); // first byte has flags, need null term if pct <> nil then begin if pMMT^.GroupsBegin = nil then pMMT^.GroupsBegin := pct; pct^.fType := REQUEST_GROUPS; pct^.hContact := 0; uint_ptr(szBuf) := uint_ptr(pct) + sizeof(TSlotIPC); // get the end of the slot lstrcpya(szBuf, dbv.szVal.a + 1); pct^.hGroup := 0; DBFreeVariant(@dbv); // free the string end else begin // outta space DBFreeVariant(@dbv); break; end; // if inc(iSlot); end; { while } // if there was no space left, it'll end on null if pct = nil then bits^ := (bits^ or GROUPS_NOTIMPL) and not REQUEST_GROUPS; end; { if: group request } // SHOULD check slot space. if (bits^ and REQUEST_CONTACTS = REQUEST_CONTACTS) then begin if not ipcGetSortedContacts(pMMT, @iSlot, bGroupMode) then begin // fail if there were no contacts AT ALL bits^ := (bits^ or CONTACTS_NOTIMPL) and not REQUEST_CONTACTS; end; // if end; // if:contact request // store the number of slots allocated pMMT^.Slots := iSlot; Reply: { get the handle the caller wants to be signalled on } hSignal := OpenEvent(EVENT_ALL_ACCESS, False, pMMT^.SignalEventName); { did it open? } If hSignal <> 0 then begin { signal and close } SetEvent(hSignal); CloseHandle(hSignal); end; { unmap the shared memory from this process } UnmapViewOfFile(pMMT); end; { close the map file } CloseHandle(hMap); end; { if } // end; function ThreadServer(hMainThread: Pointer): Cardinal; {$IFDEF FPC} stdcall; {$ENDIF} var hEvent: THandle; begin result:=0; Thread_Push(0,nil); hEvent := CreateEvent(nil, False, False, PChar(CreateProcessUID(GetCurrentProcessId()))); while True do begin Result := WaitForSingleObjectEx(hEvent, INFINITE, True); if Result = WAIT_OBJECT_0 then begin QueueUserAPC(@ipcService, THandle(hMainThread), 0); end; // if if CallService(MS_SYSTEM_TERMINATED, 0, 0) = 1 then break; end; // while CloseHandle(hEvent); CloseHandle(THandle(hMainThread)); Thread_Pop(); ExitThread(0); end; procedure InvokeThreadServer; var {$IFDEF FPC} tid: LongWord; {$ELSE} tid: Cardinal; {$ENDIF} var hMainThread: THandle; begin hMainThread := 0; DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), @hMainThread, THREAD_SET_CONTEXT, False, 0); if hMainThread <> 0 then begin {$IFDEF FPC} CloseHandle(CreateThread(nil, 0, @ThreadServer, Pointer(hMainThread), 0, tid)); {$ELSE} CloseHandle(BeginThread(nil, 0, @ThreadServer, Pointer(hMainThread), 0, tid)); {$ENDIF} end; // if end; { exported functions } function DllGetClassObject(const CLSID: TCLSID; const IID: TIID; var Obj): HResult; stdcall; begin Pointer(Obj) := nil; Result := CLASS_E_CLASSNOTAVAILABLE; if (IsEqualCLSID(CLSID, CLSID_ISHLCOM)) and (IsEqualIID(IID, IID_IClassFactory)) and (FindWindow(MirandaName, nil) <> 0) then begin Pointer(Obj) := TClassFactoryRec_Create; Result := S_OK; end; // if end; function DllCanUnloadNow: HResult; begin if ((dllpublic.FactoryCount = 0) and (dllpublic.ObjectCount = 0)) then begin Result := S_OK; end else begin Result := S_FALSE; end; // if end; { helper functions } type PSHELLEXECUTEINFO = ^TSHELLEXECUTEINFO; TSHELLEXECUTEINFO = record cbSize: DWORD; fMask: LongInt; hwnd: THandle; lpVerb: PChar; lpFile: PChar; lpParameters: PChar; lpDirectory: PChar; nShow: Integer; hInstApp: THandle; lpIDLIst: Pointer; lpClass: PChar; HKEY: THandle; dwHotkey: DWORD; HICON: THandle; // is union hProcess: THandle; end; function ShellExecuteEx(var se: TSHELLEXECUTEINFO): Boolean; stdcall; external 'shell32.dll' name 'ShellExecuteExA'; function wsprintfs(lpOut, lpFmt: PChar; args: PChar): Integer; cdecl; external 'user32.dll' name 'wsprintfA'; function RemoveCOMRegistryEntries: HResult; var hRootKey: HKEY; begin if RegOpenKeyEx(HKEY_CLASSES_ROOT, 'miranda.shlext', 0, KEY_READ, hRootKey) = ERROR_SUCCESS then begin (* need to delete the subkey before the parent key is deleted under NT/2000/XP *) RegDeleteKey(hRootKey, 'CLSID'); (* close the key *) RegCloseKey(hRootKey); (* delete it *) if RegDeleteKey(HKEY_CLASSES_ROOT, 'miranda.shlext') <> ERROR_SUCCESS then begin MessageBox(0, 'Unable to delete registry key for "shlext COM", this key may already be deleted or you may need admin rights.', 'Problem', MB_ICONERROR); end; // if end; // if if RegOpenKeyEx(HKEY_CLASSES_ROOT, '\*\shellex\ContextMenuHandlers', 0, KEY_ALL_ACCESS, hRootKey) = ERROR_SUCCESS then begin if RegDeleteKey(hRootKey, 'miranda.shlext') <> ERROR_SUCCESS then begin MessageBox(0, 'Unable to delete registry key for "File context menu handlers", this key may already be deleted or you may need admin rights.', 'Problem', MB_ICONERROR); end; // if RegCloseKey(hRootKey); end; // if if RegOpenKeyEx(HKEY_CLASSES_ROOT, 'Directory\shellex\ContextMenuHandlers', 0, KEY_ALL_ACCESS, hRootKey) = ERROR_SUCCESS then begin if RegDeleteKey(hRootKey, 'miranda.shlext') <> ERROR_SUCCESS then begin MessageBox(0, 'Unable to delete registry key for "Directory context menu handlers", this key may already be deleted or you may need admin rights.', 'Problem', MB_ICONERROR); end; // if RegCloseKey(hRootKey); end; // if if ERROR_SUCCESS = RegOpenKeyEx(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved', 0, KEY_ALL_ACCESS, hRootKey) then begin if RegDeleteValue(hRootKey, '{72013A26-A94C-11d6-8540-A5E62932711D}') <> ERROR_SUCCESS then begin MessageBox(0, 'Unable to delete registry entry for "Approved context menu handlers", this key may already be deleted or you may need admin rights.', 'Problem', MB_ICONERROR); end; // if RegCloseKey(hRootKey); end; // if Result := S_OK; end; { called by the options code to remove COM entries, and before that, get permission, if required. } procedure CheckUnregisterServer; var sei: TSHELLEXECUTEINFO; szBuf: array [0 .. MAX_PATH * 2] of Char; szFileName: array [0 .. MAX_PATH] of Char; begin if not VistaOrLater then begin RemoveCOMRegistryEntries(); Exit; end; // launches regsvr to remove the dll under admin. GetModuleFileName(System.hInstance, szFileName, sizeof(szFileName)); wsprintfs(szBuf, '/s /u "%s"', szFileName); ZeroMemory(@sei, sizeof(sei)); sei.cbSize := sizeof(sei); sei.lpVerb := 'runas'; sei.lpFile := 'regsvr32'; sei.lpParameters := szBuf; ShellExecuteEx(sei); Sleep(1000); RemoveCOMRegistryEntries(); end; { Wow, I can't believe there isn't a direct API for this - 'runas' will invoke the UAC and ask for permission before installing the shell extension. note the filepath arg has to be quoted } procedure CheckRegisterServer; var hRegKey: HKEY; sei: TSHELLEXECUTEINFO; szBuf: array [0 .. MAX_PATH * 2] of Char; szFileName: array [0 .. MAX_PATH] of Char; begin if ERROR_SUCCESS = RegOpenKeyEx(HKEY_CLASSES_ROOT, 'miranda.shlext', 0, KEY_READ, hRegKey) then begin RegCloseKey(hRegKey); end else begin if VistaOrLater then begin MessageBox(0, 'Shell context menus requires your permission to register with Windows Explorer (one time only).', 'Miranda IM - Shell context menus (shlext.dll)', MB_OK or MB_ICONINFORMATION); // /s = silent GetModuleFileName(System.hInstance, szFileName, sizeof(szFileName)); wsprintfs(szBuf, '/s "%s"', szFileName); ZeroMemory(@sei, sizeof(sei)); sei.cbSize := sizeof(sei); sei.lpVerb := 'runas'; sei.lpFile := 'regsvr32'; sei.lpParameters := szBuf; ShellExecuteEx(sei); end; end; end; initialization begin FillChar(dllpublic, sizeof(dllpublic), 0); IsMultiThread := True; VistaOrLater := GetProcAddress(GetModuleHandle('kernel32'), 'GetProductInfo') <> nil; end; end.