From f7f6e4fed8860b0b66b2635000fe64de58a516c7 Mon Sep 17 00:00:00 2001 From: George Hazan Date: Tue, 20 Aug 2013 21:43:39 +0000 Subject: ShellExt - first 1000 lines translated git-svn-id: http://svn.miranda-ng.org/main/trunk@5766 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- plugins/ShellExt/res/Version.rc | 38 + plugins/ShellExt/shellext_10.vcxproj | 203 +++ plugins/ShellExt/shellext_10.vcxproj.filters | 44 + plugins/ShellExt/shellext_11.vcxproj | 208 +++ plugins/ShellExt/shellext_11.vcxproj.filters | 50 + plugins/ShellExt/src/Version.h | 18 + plugins/ShellExt/src/main.cpp | 111 ++ plugins/ShellExt/src/options.cpp | 121 ++ plugins/ShellExt/src/resource.h | 19 + plugins/ShellExt/src/shlcom.cpp | 2122 ++++++++++++++++++++++++++ plugins/ShellExt/src/shlcom.h | 193 +++ plugins/ShellExt/src/stdafx.cpp | 18 + plugins/ShellExt/src/stdafx.h | 45 + 13 files changed, 3190 insertions(+) create mode 100644 plugins/ShellExt/res/Version.rc create mode 100644 plugins/ShellExt/shellext_10.vcxproj create mode 100644 plugins/ShellExt/shellext_10.vcxproj.filters create mode 100644 plugins/ShellExt/shellext_11.vcxproj create mode 100644 plugins/ShellExt/shellext_11.vcxproj.filters create mode 100644 plugins/ShellExt/src/Version.h create mode 100644 plugins/ShellExt/src/main.cpp create mode 100644 plugins/ShellExt/src/options.cpp create mode 100644 plugins/ShellExt/src/resource.h create mode 100644 plugins/ShellExt/src/shlcom.cpp create mode 100644 plugins/ShellExt/src/shlcom.h create mode 100644 plugins/ShellExt/src/stdafx.cpp create mode 100644 plugins/ShellExt/src/stdafx.h (limited to 'plugins') diff --git a/plugins/ShellExt/res/Version.rc b/plugins/ShellExt/res/Version.rc new file mode 100644 index 0000000000..5bfbab4754 --- /dev/null +++ b/plugins/ShellExt/res/Version.rc @@ -0,0 +1,38 @@ +// Microsoft Visual C++ generated resource script. +// +#ifdef APSTUDIO_INVOKED +#error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + +#include "afxres.h" +#include "..\src\version.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION __FILEVERSION_STRING + PRODUCTVERSION __FILEVERSION_STRING + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x0L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000004b0" + BEGIN + VALUE "FileDescription", __DESCRIPTION + VALUE "InternalName", __PLUGIN_NAME + VALUE "LegalCopyright", __COPYRIGHT + VALUE "OriginalFilename", __FILENAME + VALUE "ProductName", __PLUGIN_NAME + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1200 + END +END diff --git a/plugins/ShellExt/shellext_10.vcxproj b/plugins/ShellExt/shellext_10.vcxproj new file mode 100644 index 0000000000..c7bcaf7f97 --- /dev/null +++ b/plugins/ShellExt/shellext_10.vcxproj @@ -0,0 +1,203 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + ShellExt + {B27B85B5-0EF1-496D-99D7-0702A98A342A} + + + + DynamicLibrary + Unicode + true + + + DynamicLibrary + Unicode + + + DynamicLibrary + Unicode + true + + + DynamicLibrary + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)$(Configuration)\Plugins\ + $(SolutionDir)$(Configuration)\Obj\$(ProjectName)\ + $(SolutionDir)$(Configuration)\Plugins\ + $(SolutionDir)$(Configuration)\Obj\$(ProjectName)\ + $(SolutionDir)$(Configuration)64\Plugins\ + $(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\ + $(SolutionDir)$(Configuration)64\Plugins\ + $(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\ + true + + + + false + Full + Level3 + true + ..\..\include;..\ExternalAPI;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + OnlyExplicitInline + false + Size + stdafx.h + Use + + + NDEBUG;%(PreprocessorDefinitions) + ..\..\include\msapi + + + true + true + Windows + 0x30040000 + $(IntDir)$(TargetName).lib + true + true + false + $(ProfileDir)..\..\bin10\lib + /PDBALTPATH:%_PDB% + + + + + MultiThreadedDebugDLL + false + Disabled + Level3 + true + EditAndContinue + ..\..\include;..\ExternalAPI;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + EnableFastChecks + stdafx.h + Use + + + _DEBUG;%(PreprocessorDefinitions) + ..\..\include\msapi + + + true + true + Windows + 0x30040000 + $(IntDir)$(TargetName).lib + false + $(ProfileDir)..\..\bin10\lib + + + + + false + Full + Level3 + ..\..\include;..\ExternalAPI;%(AdditionalIncludeDirectories) + _DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + Default + OnlyExplicitInline + Size + stdafx.h + Use + + + NDEBUG;%(PreprocessorDefinitions) + ..\..\include\msapi + + + true + true + Windows + 0x30040000 + $(IntDir)$(TargetName).lib + false + $(ProfileDir)..\..\bin10\lib + /PDBALTPATH:%_PDB% + + + + + MultiThreadedDebugDLL + false + Disabled + Level3 + ..\..\include;..\ExternalAPI;%(AdditionalIncludeDirectories) + _DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + EnableFastChecks + stdafx.h + Use + + + _DEBUG;%(PreprocessorDefinitions) + ..\..\include\msapi + + + true + true + Windows + 0x30040000 + $(IntDir)$(TargetName).lib + false + $(ProfileDir)..\..\bin10\lib + + + + + + + + + + + + + + + Create + + + + + + \ No newline at end of file diff --git a/plugins/ShellExt/shellext_10.vcxproj.filters b/plugins/ShellExt/shellext_10.vcxproj.filters new file mode 100644 index 0000000000..6bad33a8fc --- /dev/null +++ b/plugins/ShellExt/shellext_10.vcxproj.filters @@ -0,0 +1,44 @@ + + + + + {08a21636-368d-4feb-8d0d-0a27f237e1a2} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {9e6f20dc-3391-4299-9f74-e91d4f984ad3} + h;hpp;hxx;hm;inl + + + {18fd6180-ebb2-4558-b945-c4dac823f11b} + ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + Resource Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/plugins/ShellExt/shellext_11.vcxproj b/plugins/ShellExt/shellext_11.vcxproj new file mode 100644 index 0000000000..f5f48754e5 --- /dev/null +++ b/plugins/ShellExt/shellext_11.vcxproj @@ -0,0 +1,208 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + ShellExt + {B27B85B5-0EF1-496D-99D7-0702A98A342A} + + + + DynamicLibrary + Unicode + true + v110_xp + + + DynamicLibrary + Unicode + v110_xp + + + DynamicLibrary + Unicode + true + v110_xp + + + DynamicLibrary + Unicode + v110_xp + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)$(Configuration)\Plugins\ + $(SolutionDir)$(Configuration)\Obj\$(ProjectName)\ + $(SolutionDir)$(Configuration)\Plugins\ + $(SolutionDir)$(Configuration)\Obj\$(ProjectName)\ + $(SolutionDir)$(Configuration)64\Plugins\ + $(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\ + $(SolutionDir)$(Configuration)64\Plugins\ + $(SolutionDir)$(Configuration)64\Obj\$(ProjectName)\ + true + + + + false + Full + Level3 + true + ..\..\include;..\ExternalAPI;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + OnlyExplicitInline + false + Size + stdafx.h + Use + + + NDEBUG;%(PreprocessorDefinitions) + ..\..\include\msapi + + + true + true + Windows + 0x30040000 + $(IntDir)$(TargetName).lib + true + true + false + $(ProfileDir)..\..\bin11\lib + + + + + MultiThreadedDebugDLL + false + Disabled + Level3 + true + EditAndContinue + ..\..\include;..\ExternalAPI;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + EnableFastChecks + stdafx.h + Use + + + _DEBUG;%(PreprocessorDefinitions) + ..\..\include\msapi + + + true + true + Windows + 0x30040000 + $(IntDir)$(TargetName).lib + false + $(ProfileDir)..\..\bin11\lib + false + + + + + false + Full + Level3 + ..\..\include;..\ExternalAPI;%(AdditionalIncludeDirectories) + _DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + Default + OnlyExplicitInline + Size + stdafx.h + Use + + + NDEBUG;%(PreprocessorDefinitions) + ..\..\include\msapi + + + true + true + Windows + 0x30040000 + $(IntDir)$(TargetName).lib + false + $(ProfileDir)..\..\bin11\lib + + + + + MultiThreadedDebugDLL + false + Disabled + Level3 + ..\..\include;..\ExternalAPI;%(AdditionalIncludeDirectories) + _DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + EnableFastChecks + stdafx.h + Use + + + _DEBUG;%(PreprocessorDefinitions) + ..\..\include\msapi + + + true + true + Windows + 0x30040000 + $(IntDir)$(TargetName).lib + false + $(ProfileDir)..\..\bin11\lib + + + + + + + + + + + + + + + + + Create + + + + + + \ No newline at end of file diff --git a/plugins/ShellExt/shellext_11.vcxproj.filters b/plugins/ShellExt/shellext_11.vcxproj.filters new file mode 100644 index 0000000000..31875e467f --- /dev/null +++ b/plugins/ShellExt/shellext_11.vcxproj.filters @@ -0,0 +1,50 @@ + + + + + {08a21636-368d-4feb-8d0d-0a27f237e1a2} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {9e6f20dc-3391-4299-9f74-e91d4f984ad3} + h;hpp;hxx;hm;inl + + + {18fd6180-ebb2-4558-b945-c4dac823f11b} + ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/plugins/ShellExt/src/Version.h b/plugins/ShellExt/src/Version.h new file mode 100644 index 0000000000..656c2324b5 --- /dev/null +++ b/plugins/ShellExt/src/Version.h @@ -0,0 +1,18 @@ +#define __MAJOR_VERSION 2 +#define __MINOR_VERSION 2 +#define __RELEASE_NUM 0 +#define __BUILD_NUM 1 + +#define __FILEVERSION_STRING __MAJOR_VERSION,__MINOR_VERSION,__RELEASE_NUM,__BUILD_NUM +#define __FILEVERSION_STRING_DOTS __MAJOR_VERSION.__MINOR_VERSION.__RELEASE_NUM.__BUILD_NUM + +#define __STRINGIFY(x) #x +#define __VERSION_STRING __STRINGIFY(__FILEVERSION_STRING_DOTS) + +#define __PLUGIN_NAME "ShellExt" +#define __FILENAME "ShellExt.dll" +#define __DESCRIPTION "Windows Explorer extension for Miranda NG." +#define __AUTHOR "Sam Kothari, Miranda NG Team" +#define __AUTHOREMAIL "egodust@users.sourceforge.net" +#define __AUTHORWEB "http://miranda-ng.org/p/ShellExt/" +#define __COPYRIGHT "© 2009 Sam Kothari (egoDust)" diff --git a/plugins/ShellExt/src/main.cpp b/plugins/ShellExt/src/main.cpp new file mode 100644 index 0000000000..b2c3473172 --- /dev/null +++ b/plugins/ShellExt/src/main.cpp @@ -0,0 +1,111 @@ +#include "stdafx.h" + +HINSTANCE hInst; +int hLangpack; + +PLUGININFOEX pluginInfoEx = { + sizeof(PLUGININFOEX), + __PLUGIN_NAME, + PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), + __DESCRIPTION, + __AUTHOR, + __AUTHOREMAIL, + __COPYRIGHT, + __AUTHORWEB, + UNICODE_AWARE, + // {7993AB24-1FDA-428C-A89B-BE377A10BE3A} + {0x7993ab24, 0x1fda, 0x428c, {0xa8, 0x9b, 0xbe, 0x37, 0x7a, 0x10, 0xbe, 0x3a}} +}; + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + hInst = hinstDLL; + return TRUE; +} + +extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD mirandaVersion) +{ + return &pluginInfoEx; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +struct HRegKey +{ + HRegKey(HKEY hRoot, const TCHAR *ptszKey) : m_key(NULL) + { RegCreateKeyEx(hRoot, ptszKey, 0, 0, 0, KEY_SET_VALUE | KEY_CREATE_SUB_KEY, 0, &m_key, 0); + } + + ~HRegKey() { if (m_key) RegCloseKey(m_key); } + + operator HKEY() const { return m_key; } + +private: + HKEY m_key; +}; + +char str1[] = "shlext " __VERSION_STRING " - shell context menu support for Miranda NG"; +char str2[] = "{72013A26-A94C-11d6-8540-A5E62932711D}"; +char str3[] = "miranda.shlext"; +char str4[] = "Apartment"; + +TCHAR key1[] = _T("miranda.shlext\\{72013A26-A94C-11d6-8540-A5E62932711D}\\InprocServer32"); + +HRESULT __stdcall DllRegisterServer() +{ + if ( RegSetValueA(HKEY_CLASSES_ROOT, "miranda.shlext", REG_SZ, str1, sizeof(str1)-1)) + return E_FAIL; + if ( RegSetValueA(HKEY_CLASSES_ROOT, "miranda.shlext\\CLSID", REG_SZ, str2, sizeof(str2)-1)) + return E_FAIL; + if ( RegSetValueA(HKEY_CLASSES_ROOT, "miranda.shlext\\{72013A26-A94C-11d6-8540-A5E62932711D}", REG_SZ, str3, sizeof(str3)-1)) + return E_FAIL; + if ( RegSetValueA(HKEY_CLASSES_ROOT, "miranda.shlext\\{72013A26-A94C-11d6-8540-A5E62932711D}\\ProgID", REG_SZ, str3, sizeof(str3)-1)) + return E_FAIL; + + TCHAR tszFileName[MAX_PATH]; + GetModuleFileName(hInst, tszFileName, SIZEOF(tszFileName)); + if ( RegSetValue(HKEY_CLASSES_ROOT, key1, REG_SZ, tszFileName, lstrlen(tszFileName))) + return E_FAIL; + + HRegKey k1(HKEY_CLASSES_ROOT, key1); + if (k1 == NULL) + return E_FAIL; + if ( RegSetValueA(k1, "ThreadingModel", REG_SZ, str4, sizeof(str4))) + return E_FAIL; + + if ( RegSetValueA(HKEY_CLASSES_ROOT, "*\\shellex\\ContextMenuHandlers\\miranda.shlext", REG_SZ, str2, sizeof(str2)-1)) + return E_FAIL; + if ( RegSetValueA(HKEY_CLASSES_ROOT, "Directory\\shellex\\ContextMenuHandlers\\miranda.shlext", REG_SZ, str2, sizeof(str2)-1)) + return E_FAIL; + + HRegKey k2(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved")); + if (k2 == NULL) + return E_FAIL; + if ( RegSetValueA(k2, str2, REG_SZ, str1, sizeof(str1)-1)) + return E_FAIL; + + return S_OK; +} + +HRESULT __stdcall DllUnregisterServer() +{ + return RemoveCOMRegistryEntries(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +extern "C" __declspec(dllexport) int Load(void) +{ + mir_getLP(&pluginInfoEx); + + InvokeThreadServer(); + HookEvent(ME_OPT_INITIALISE, OnOptionsInit); + DllRegisterServer(); + CheckRegisterServer(); + return 0; +} + +extern "C" __declspec(dllexport) int Unload(void) +{ + return 0; +} diff --git a/plugins/ShellExt/src/options.cpp b/plugins/ShellExt/src/options.cpp new file mode 100644 index 0000000000..05f71ab039 --- /dev/null +++ b/plugins/ShellExt/src/options.cpp @@ -0,0 +1,121 @@ +#include "stdafx.h" +#include "resource.h" + +static void AutoSize(HWND hwnd) +{ + HDC hDC = GetDC(hwnd); + HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); + HFONT hOldFont = (HFONT)SelectObject(hDC, hFont); + + TCHAR szBuf[MAX_PATH]; + int i = GetWindowText(hwnd, szBuf, MAX_PATH); + + SIZE tS; + GetTextExtentPoint32(hDC, szBuf, i, &tS); + SelectObject(hDC, hOldFont); + DeleteObject(hFont); + ReleaseDC(hwnd, hDC); + SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, tS.cx + 10, tS.cy, SWP_NOMOVE | SWP_FRAMECHANGED); +} + +//////////////////////////////////////////////////////////////////////////////////////////// + +static TCHAR* COM_OKSTR[2] = { + LPGENT("Problem, registration missing/deleted."), + LPGENT("Successfully created shell registration.") }; +static TCHAR* COM_APPROVEDSTR[2] = { LPGENT("Not Approved"), LPGENT("Approved") }; + +static LRESULT CALLBACK OptDialogProc(HWND hwndDlg, UINT wMsg, WPARAM wParam, LPARAM lParam) +{ + int comReg, iCheck; + TCHAR szBuf[MAX_PATH]; + + switch(wMsg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + comReg = IsCOMRegistered(); + mir_sntprintf(szBuf, SIZEOF(szBuf), _T("%s (%s)"), + COM_OKSTR[ (comReg & COMREG_OK) != 0 ], + COM_APPROVEDSTR[ (comReg & COMREG_APPROVED) != 0 ]); + SetWindowText(GetDlgItem(hwndDlg, IDC_STATUS), szBuf); + // auto size the static windows to fit their text + // they're rendering in a font not selected into the DC. + AutoSize(GetDlgItem(hwndDlg, IDC_CAPMENUS)); + AutoSize(GetDlgItem(hwndDlg, IDC_CAPSTATUS)); + AutoSize(GetDlgItem(hwndDlg, IDC_CAPSHLSTATUS)); + // show all the options + iCheck = db_get_b(0, MODULENAME, SHLExt_UseGroups, BST_UNCHECKED); + CheckDlgButton(hwndDlg, IDC_USEGROUPS, iCheck); + EnableWindow(GetDlgItem(hwndDlg, IDC_CLISTGROUPS), iCheck = BST_CHECKED); + CheckDlgButton(hwndDlg, IDC_CLISTGROUPS, + db_get_b(0, MODULENAME, SHLExt_UseCListSetting, BST_UNCHECKED)); + CheckDlgButton(hwndDlg, IDC_NOPROF, + db_get_b(0, MODULENAME, SHLExt_ShowNoProfile, BST_UNCHECKED)); + CheckDlgButton(hwndDlg, IDC_SHOWFULL, + db_get_b(0, MODULENAME, SHLExt_UseHITContacts, BST_UNCHECKED)); + CheckDlgButton(hwndDlg, IDC_SHOWINVISIBLES, + db_get_b(0, MODULENAME, SHLExt_UseHIT2Contacts, BST_UNCHECKED)); + CheckDlgButton(hwndDlg, IDC_USEOWNERDRAW, + db_get_b(0, MODULENAME, SHLExt_ShowNoIcons, BST_UNCHECKED)); + CheckDlgButton(hwndDlg, IDC_HIDEOFFLINE, + db_get_b(0, MODULENAME, SHLExt_ShowNoOffline, BST_UNCHECKED)); + // give the Remove button a Vista icon + SendMessage(GetDlgItem(hwndDlg, IDC_REMOVE), BCM_SETSHIELD, 0, 1); + return TRUE; + + case WM_NOTIFY: + if (((LPNMHDR)lParam)->code == PSN_APPLY) { + db_set_b(0, MODULENAME, SHLExt_UseGroups, IsDlgButtonChecked(hwndDlg, IDC_USEGROUPS)); + db_set_b(0, MODULENAME, SHLExt_UseCListSetting, IsDlgButtonChecked(hwndDlg, IDC_CLISTGROUPS)); + db_set_b(0, MODULENAME, SHLExt_ShowNoProfile, IsDlgButtonChecked(hwndDlg, IDC_NOPROF)); + db_set_b(0, MODULENAME, SHLExt_UseHITContacts, IsDlgButtonChecked(hwndDlg, IDC_SHOWFULL)); + db_set_b(0, MODULENAME, SHLExt_UseHIT2Contacts, IsDlgButtonChecked(hwndDlg, IDC_SHOWINVISIBLES)); + db_set_b(0, MODULENAME, SHLExt_ShowNoIcons, IsDlgButtonChecked(hwndDlg, IDC_USEOWNERDRAW)); + db_set_b(0, MODULENAME, SHLExt_ShowNoOffline, IsDlgButtonChecked(hwndDlg, IDC_HIDEOFFLINE)); + } + break; + + case WM_COMMAND: + // don't send the changed message if remove is clicked + if ( LOWORD(wParam) != IDC_REMOVE) + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + + switch( LOWORD(wParam)) { + case IDC_USEGROUPS: + EnableWindow(GetDlgItem(hwndDlg, IDC_CLISTGROUPS), BST_CHECKED == IsDlgButtonChecked(hwndDlg, IDC_USEGROUPS)); + break; + case IDC_REMOVE: + if (IDYES == MessageBox(0, + TranslateT("Are you sure? this will remove all the settings stored in your database && all registry entries created for shlext to work with Explorer"), + TranslateT("Disable/Remove shlext"), MB_YESNO | MB_ICONQUESTION)) { + db_unset(0, MODULENAME, SHLExt_UseGroups); + db_unset(0, MODULENAME, SHLExt_UseCListSetting); + db_unset(0, MODULENAME, SHLExt_UseHITContacts); + db_unset(0, MODULENAME, SHLExt_UseHIT2Contacts); + db_unset(0, MODULENAME, SHLExt_ShowNoProfile); + db_unset(0, MODULENAME, SHLExt_ShowNoIcons); + db_unset(0, MODULENAME, SHLExt_ShowNoOffline); + + CheckUnregisterServer(); + SendMessage(hwndDlg, WM_INITDIALOG, 0, 0); + } + } + break; + } + + return 0; +} + +int OnOptionsInit(WPARAM wParam, LPARAM lParam) +{ + OPTIONSDIALOGPAGE opt = { sizeof(opt) }; + opt.flags = ODPF_BOLDGROUPS; + opt.pszGroup = "Plugins"; + opt.position = -1066; + opt.pszTitle = "Shell context menus"; + opt.pszTemplate = MAKEINTRESOURCEA(IDD_SHLOPTS); + opt.hInstance = hInst; + opt.pfnDlgProc = OptDialogProc; + Options_AddPage(wParam, &opt); + return 0; +} diff --git a/plugins/ShellExt/src/resource.h b/plugins/ShellExt/src/resource.h new file mode 100644 index 0000000000..4bf7a5d6d8 --- /dev/null +++ b/plugins/ShellExt/src/resource.h @@ -0,0 +1,19 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by autorun.rc +// + +#define IDD_SHLOPTS 101 + +#define IDC_USEGROUPS 1014 +#define IDC_CLISTGROUPS 1015 +#define IDC_SHOWFULL 1016 +#define IDC_NOPROF 1020 +#define IDC_SHOWINVISIBLES 1021 +#define IDC_HIDEOFFLINE 1022 +#define IDC_STATUS 1023 +#define IDC_CAPMENUS 1025 +#define IDC_CAPSTATUS 1026 +#define IDC_CAPSHLSTATUS 1027 +#define IDC_REMOVE 1028 +#define IDC_USEOWNERDRAW 1029 diff --git a/plugins/ShellExt/src/shlcom.cpp b/plugins/ShellExt/src/shlcom.cpp new file mode 100644 index 0000000000..f2f06f3d0b --- /dev/null +++ b/plugins/ShellExt/src/shlcom.cpp @@ -0,0 +1,2122 @@ +#include "stdafx.h" +#include "shlcom.h" + +struct dllpublic +{ + int FactoryCount, ObjectCount; +}; + +bool VistaOrLater; + +#define IPC_PACKET_SIZE (0x1000 * 32) +#define IPC_PACKET_NAME "m.mi.miranda.ipc.server" + +struct TCMInvokeCommandInfo +{ + int cbSize; + DWORD fMask; + HWND hwnd; + char *lpVerb; // maybe index, type cast as Integer + char *lpParams; + char *lpDir; + int nShow; + DWORD dwHotkey; + HICON hIcon; +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +int IsCOMRegistered() +{ + HKEY hRegKey; + int res = 0; + + // these arent the BEST checks in the world + if ( !RegOpenKeyExA(HKEY_CLASSES_ROOT, "miranda.shlext", 0, KEY_READ, &hRegKey)) { + res += COMREG_OK; + RegCloseKey(hRegKey); + } + + if ( !RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", 0, KEY_READ, &hRegKey)) { + DWORD lpType = REG_SZ; + if ( !RegQueryValueExA(hRegKey, "{72013A26-A94C-11d6-8540-A5E62932711D}", NULL, &lpType, 0, 0)) + res += COMREG_APPROVED; + RegCloseKey(hRegKey); + } + + return res; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static string CreateProcessUID(int pid) +{ + char buf[100]; + mir_snprintf(buf, sizeof(buf), "mim.shlext.%d$", pid); + return buf; +} + +static string CreateUID() +{ + char buf[100]; + mir_snprintf(buf, sizeof(buf), "'mim.shlext.caller%d$%d", GetCurrentProcessId(), GetCurrentThreadId()); + return buf; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +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++; + + mir_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; + } + } // if +} + +// 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 '\' && 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 | at most two levels of the tree + // per tokenised section, && 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 = mir_hashstr(sz); + // find this node within + while (pg != NULL) { + // does this node have the right hash && 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 + // | 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; + } +} + +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; + } +} + +// this callback is triggered by the menu code && 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); +} + +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 = mir_hashstr(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 && we have created the first node + // which maybe Miranda, thus giving the wrong hash + // since "Miranda" can be a group of it's own && 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 && 'clear list' menu + mii.fType = MFT_SEPARATOR; + mii.fMask = MIIM_TYPE; + InsertMenuItemA(lParam->Self->hRecentMenu, 0xFFFFFFFF, true, &mii); + + // insert 'clear MRU' item && 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 && 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 | 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); +} + +void BuildSkinIcons(TEnumData *lParam) +{ + TSlotIPC *pct; + TShlComRec *Self; + UINT j; + IImageFactory *imageFactory; + + pct = lParam->ipch->NewIconsBegin; + 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*)mir_realloc(Self->ProtoIcons, (Self->ProtoIconsCount + 1) * sizeof(TSlotProtoIcons)); + TSlotProtoIcons *d = &Self->ProtoIcons[Self->ProtoIconsCount]; + memmove(d, p, sizeof(TSlotProtoIcons)); + + // if using Vista (| later), clone all the icons into bitmaps && keep these around, + // if using anything older, just use the default code, the bitmaps (&& | icons) will be freed + // with the shell object. + + imageFactory = NULL; + + for (j = 0; j < 10; j++) { + if (imageFactory == NULL) + imageFactory = ARGB_GetWorker(); + if (VistaOrLater) { + d->hBitmaps[j] = ARGB_BitmapFromIcon(imageFactory, Self->hMemDC, p->hIcons[j]); + d->hIcons[j] = 0; + } + else { + d->hBitmaps[j] = 0; + d->hIcons[j] = CopyIcon(p->hIcons[j]); + } + } + + if (imageFactory != NULL) { + imageFactory->ptrVTable->Release(imageFactory); + imageFactory = NULL; + } + + Self->ProtoIconsCount++; + pct = pct->Next; + } +} + +BOOL __stdcall ProcessRequest(HWND hwnd, TEnumData *lParam) +{ + HANDLE hMirandaWorkEvent; + int replyBits; + char szBuf[MAX_PATH]; + + BOOL Result = true; + DWORD pid = 0; + GetWindowThreadProcessId(hwnd, &pid); + if (pid != 0) { + // old system would get a window's pid && the module handle that created it + // && 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).c_str()); + if (hMirandaWorkEvent != 0) { + GetClassNameA(hwnd, szBuf, sizeof(szBuf)); + if ( lstrcmpA(szBuf, MirandaName) != 0) { + // opened but not valid. + CloseHandle(hMirandaWorkEvent); + return Result; + } + } + // if the event object exists, a shlext.dll running in the instance must of created it. + if (hMirandaWorkEvent != 0) { + // 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, | it'll be the request + // bits as sent | 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) { + // 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 && 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); + } + } +} + +function TShlComRec_QueryInterface(Self: PCommon_Interface; const IID: TIID; Obj): HResult; stdcall; +{ + Pointer(Obj) = NULL; + { IShellExtInit is given when the TShlRec is created } + if IsEqualIID(IID, IID_IContextMenu) | IsEqualIID(IID, IID_IContextMenu2) | + IsEqualIID(IID, IID_IContextMenu3) + { + with Self->ptrInstance^ do + { + Pointer(Obj) = @ContextMenu3_Interface; + inc(RefCount); + } { with } + Result = S_OK; + } + else + { + // 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) + { + with Self->ptrInstance^ do + { + Pointer(Obj) = @ShellExtInit_Interface; + inc(RefCount); + } // if + Result = S_OK; + } + else + { + Result = CLASS_E_CLASSNOTAVAILABLE; + } // if + } // if +} + +function TShlComRec_AddRef(Self: PCommon_Interface): LongInt; stdcall; +{ + with Self->ptrInstance^ do + { + inc(RefCount); + Result = RefCount; + } { with } +} + +function TShlComRec_Release(Self: PCommon_Interface): LongInt; stdcall; + + j, c: Cardinal; +{ + with Self->ptrInstance^ do + { + dec(RefCount); + Result = RefCount; + if RefCount = 0 + { + // time to go byebye. + with Self->ptrInstance^ do + { + // Note MRU menu is associated with a window (indirectly) so windows will free it. + // free icons! + if ProtoIcons != NULL + { + c = ProtoIconsCount; + while c > 0 do + { + dec(c); + for j = 0 to 9 do + { + with ProtoIcons[c] do + { + if hIcons[j] != 0 + DestroyIcon(hIcons[j]); + if hBitmaps[j] != 0 + DeleteObject(hBitmaps[j]); + } + } + } + FreeMem(ProtoIcons); + ProtoIcons = NULL; + } // if + // free IDataObject reference if pointer exists + if pDataObject != NULL + { + pDataObject->ptrVTable->Release(pDataObject); + } // if + pDataObject = NULL; + // free the heap && any memory allocated on it + HeapDestroy(hDllHeap); + // destroy the DC + if hMemDC != 0 + DeleteDC(hMemDC); + } // with + // free the instance (class record) created + Dispose(Self->ptrInstance); + dec(dllpublic.ObjectCount); + } { if } + } { with } +} + +function TShlComRec_Initialise(Self: PContextMenu3_Interface; pidLFolder: Pointer; + DObj: PDataObject_Interface; hKeyProdID: HKEY): HResult; stdcall; +{ + // 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 + { + if DObj != NULL + { + Result = S_OK; + // if an instance already exists, free it. + if pDataObject != NULL + pDataObject->ptrVTable->Release(pDataObject); + // store the new one && AddRef() it + pDataObject = DObj; + pDataObject->ptrVTable->AddRef(pDataObject); + } + else + { + Result = E_INVALIDARG; + } // if + } // if +} + +function MAKE_HRESULT(Severity, Facility, Code: Integer): HResult; +{$IFDEF FPC} +inline; +{$ENDIF} +{ + Result = (Severity shl 31) | (Facility shl 16) | Code; +} + +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; + } + + TDllGetVersionProc = function( dv: TDllVersionInfo): HResult; stdcall; + + hShellInst: HANDLE; + bMF_OWNERDRAW: Boolean; + DllGetVersionProc: TDllGetVersionProc; + dvi: TDllVersionInfo; + ed: TEnumData; + hMap: HANDLE; + pipch: THeaderIPC *; +{ + Result = 0; + if ((LOWORD(uFlags) && CMF_VERBSONLY) != CMF_VERBSONLY) && + ((LOWORD(uFlags) && CMF_DEFAULTONLY) != CMF_DEFAULTONLY) + { + bMF_OWNERDRAW = false; + // get the shell version + hShellInst = LoadLibrary('shell32.dll'); + if hShellInst != 0 + { + DllGetVersionProc = GetProcAddress(hShellInst, 'DllGetVersion'); + if @DllGetVersionProc != NULL + { + dvi.cbSize = sizeof(TDllVersionInfo); + if DllGetVersionProc(dvi) >= 0 + { + // it's at least 4.00 + bMF_OWNERDRAW = (dvi.dwMajorVersion > 4) | (dvi.dwMinorVersion >= 71); + } // if + } // if + FreeLibrary(hShellInst); + } // if + + // if we're using Vista (| later), the ownerdraw code will be disabled, because the system draws the icons. + if VistaOrLater + bMF_OWNERDRAW = false; + + hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, IPC_PACKET_SIZE, + IPC_PACKET_NAME); + if (hMap != 0) && (GetLastError != ERROR_ALREADY_EXISTS) + { + { map the memory to this address space } + pipch = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0); + if pipch != NULL + { + { 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, LPSTR(CreateUID())); + { create the wait wait-for-wait object } + ed.hWaitFor = CreateEvent(NULL, false, false, pipch->SignalEventName); + if ed.hWaitFor != 0 + { + { 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); + } + { unmap the memory from this address space } + UnmapViewOfFile(pipch); + } { 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); + } + else + { + // the mapping file already exists, which is not good! + } + } + else + { + // 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); + } // if +} + +function TShlComRec_GetCommandString(Self: PContextMenu3_Interface; idCmd, uType: UINT; + pwReserved: PUINT; pszName: LPSTR; cchMax: UINT): HResult; stdcall; +{ + Result = E_NOTIMPL; +} + +function ipcGetFiles(pipch: THeaderIPC *; pDataObject: PDataObject_Interface; const hContact: HANDLE): Integer; +type + TDragQueryFile = function(hDrop: HANDLE; fileIndex: Integer; FileName: LPSTR; + cbSize: Integer): Integer; stdcall; + + fet: TFormatEtc; + stgm: TStgMedium; + pct: TSlotIPC *; + iFile: Cardinal; + iFileMax: Cardinal; + hShell: HANDLE; + DragQueryFile: TDragQueryFile; + cbSize: Integer; + hDrop: HANDLE; +{ + Result = E_INVALIDARG; + hShell = LoadLibrary('shell32.dll'); + if hShell != 0 + { + DragQueryFile = GetProcAddress(hShell, 'DragQueryFileA'); + if @DragQueryFile != NULL + { + fet.cfFormat = CF_HDROP; + fet.ptd = NULL; + fet.dwAspect = DVASPECT_CONTENT; + fet.lindex = -1; + fet.tymed = TYMED_HGLOBAL; + Result = pDataObject->ptrVTable->GetData(pDataObject, fet, stgm); + if Result = S_OK + { + // FIX, actually lock the global object && get a pointer + Pointer(hDrop) = GlobalLock(stgm.hGlobal); + if hDrop != 0 + { + // get the maximum number of files + iFileMax = DragQueryFile(stgm.hGlobal, 0xFFFFFFFF, NULL, 0); + iFile = 0; + while iFile < iFileMax do + { + // get the size of the file path + cbSize = DragQueryFile(stgm.hGlobal, iFile, NULL, 0); + // get the buffer + pct = ipcAlloc(pipch, cbSize + 1); // including null term + // allocated? + if pct = NULL + break; + // store the hContact + pct->hContact = hContact; + // copy it to the buffer + DragQueryFile(stgm.hGlobal, iFile, LPSTR(UINT_PTR(pct) + sizeof(TSlotIPC)), pct->cbStrSection); + // next file + inc(iFile); + } // while + // store the number of files + pipch->Slots = iFile; + GlobalUnlock(stgm.hGlobal); + } // if hDrop check + // release the mediumn the lock may of failed + ReleaseStgMedium(stgm); + } // if + } // if + // free the dll + FreeLibrary(hShell); + } // if +} + +function RequestTransfer(Self: PShlComRec; idxCmd: Integer): Integer; +{ + hMap: HANDLE; + pipch: THeaderIPC *; + mii: MENUITEMINFO; + hTransfer: HANDLE; + psd: PMenuDrawInfo; + hReply: HANDLE; + replyBits: Integer; + + Result = E_INVALIDARG; + // get the contact information + mii.cbSize = sizeof(MENUITEMINFO); + mii.fMask = MIIM_ID | MIIM_DATA; + if GetMenuItemInfo(Self->hRootMenu, Self->idCmdFirst + idxCmd, false, mii) + { + // get the pointer + UINT_PTR(psd) = mii.dwItemData; + // the ID stored in the item pointer && the ID for the menu must match + if (psd = NULL) | (psd->wID != mii.wID) + { + // MessageBox(0,'ptr assocated with menu is NULL','',MB_OK); + Exit; + } // if + } + else + { + // MessageBox(0,'GetMenuItemInfo failed?','',MB_OK); + // couldn't get the info, can't start the transfer + Result = E_INVALIDARG; + Exit; + } // if + // is there an IDataObject instance? + if Self->pDataObject != NULL + { + // OpenEvent() the work object to see if the instance is still around + hTransfer = OpenEvent(EVENT_ALL_ACCESS, false, LPSTR(CreateProcessUID(psd->pid))); + if hTransfer != 0 + { + // map the ipc file again + hMap = CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,IPC_PACKET_SIZE,IPC_PACKET_NAME); + if (hMap != 0) && (GetLastError != ERROR_ALREADY_EXISTS) + { + // map it to process + pipch = 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, LPSTR(CreateUID())); + // create it + hReply = CreateEvent(NULL, false, false, pipch->SignalEventName); + if hReply != 0 + { + if dtCommand in psd->fTypes + { + if Assigned(psd->MenuCommandCallback) + Result = 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 + replyBits = ipcSendRequest(hTransfer, hReply, pipch, 200); + if replyBits != REPLY_FAIL + { + // they got the files! + Result = S_OK; + } // if + } + + } + // close the work object name + CloseHandle(hReply); + } // if + // unmap it from this process + UnmapViewOfFile(pipch); + } // if + // close the map + CloseHandle(hMap); + } // if + // close the handle to the ST object name + CloseHandle(hTransfer); + } // if + } // if; +} + +function TShlComRec_InvokeCommand(Self: PContextMenu3_Interface; + lpici: TCMInvokeCommandInfo): HResult; stdcall; +{ + Result = RequestTransfer(Self->ptrInstance, LOWORD(UINT_PTR(lpici.lpVerb))); +} + +function TShlComRec_HandleMenuMsgs(Self: PContextMenu3_Interface; uMsg: UINT; wParam: wParam; + lParam: lParam; pResult: PLResult): HResult; +const + WM_DRAWITEM = $002B; + WM_MEASUREITEM = $002C; + + dwi: PDrawItemStruct; + msi: PMeasureItemStruct; + psd: PMenuDrawInfo; + ncm: TNonClientMetrics; + hOldFont: HANDLE; + hFont: HANDLE; + tS: TSize; + dx: Integer; + hBr: HBRUSH; + icorc: TRect; + hMemDC: HDC; +{ + pResult^ = Integer(true); + if (uMsg = WM_DRAWITEM) && (wParam = 0) + { + // either a main sub menu, a group menu | 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) | (dtEntry in psd->fTypes) + { + hBr = GetSysColorBrush(COLOR_MENU); + FillRect(dwi->HDC, dwi->rcItem, hBr); + DeleteObject(hBr); + // + if (ODS_SELECTED && 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)); + } // if + // draw icon + with dwi^, icorc do + { + if (ODS_SELECTED && dwi->itemState) = ODS_SELECTED + { + hBr = GetSysColorBrush(COLOR_HIGHLIGHT); + } + else + { + hBr = GetSysColorBrush(COLOR_MENU); + } // if + DrawIconEx(HDC, Left + 1, Top, psd->hStatusIcon, 16, 16, // width, height + 0, // step + hBr, // brush + DI_NORMAL); + DeleteObject(hBr); + } // with + // draw the text + with dwi^ do + { + inc(rcItem.Left, ((rcItem.Bottom - rcItem.Top) - 2)); + DrawText(HDC, psd->szText, psd->cch, rcItem, DT_NOCLIP | DT_NOPREFIX | + DT_SINGLELINE | DT_VCENTER); + // draw the name of the database text if it's there + if psd->szProfile != NULL + { + 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 | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER); + } // if + } // with + } + else + { + // it's a contact! + hBr = GetSysColorBrush(COLOR_MENU); + FillRect(dwi->HDC, dwi->rcItem, hBr); + DeleteObject(hBr); + if ODS_SELECTED && dwi->itemState = ODS_SELECTED + { + hBr = GetSysColorBrush(COLOR_HIGHLIGHT); + FillRect(dwi->HDC, dwi->rcItem, hBr); + DeleteObject(hBr); + SetTextColor(dwi->HDC, GetSysColor(COLOR_HIGHLIGHTTEXT)); + } + // draw icon + with dwi^, icorc do + { + if (ODS_SELECTED && dwi->itemState) = ODS_SELECTED + { + hBr = GetSysColorBrush(COLOR_HIGHLIGHT); + } + else + { + hBr = GetSysColorBrush(COLOR_MENU); + } // if + DrawIconEx(HDC, Left + 2, Top, psd->hStatusIcon, 16, 16, // width, height + 0, // step + hBr, // brush + DI_NORMAL); + DeleteObject(hBr); + } // with + // draw the text + with dwi^ do + { + inc(rcItem.Left, (rcItem.Bottom - rcItem.Top) + 1); + DrawText(HDC, psd->szText, psd->cch, rcItem, DT_NOCLIP | DT_NOPREFIX | + DT_SINGLELINE | DT_VCENTER); + } // with + } // if + } + else if (uMsg = WM_MEASUREITEM) + { + // 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 != NULL + { + GetTextExtentPoint32(hMemDC, psd->szProfile, lstrlenA(psd->szProfile), tS); + inc(dx, tS.cx); + } + // store it + msi->itemWidth = dx + Integer(ncm.iMenuWidth); + msi->itemHeight = Integer(ncm.iMenuHeight) + 2; + if tS.cy > msi->itemHeight + inc(msi->itemHeight, tS.cy - msi->itemHeight); + // clean up + SelectObject(hMemDC, hOldFont); + DeleteObject(hFont); + } + Result = S_OK; +} + +function TShlComRec_HandleMenuMsg(Self: PContextMenu3_Interface; uMsg: UINT; wParam: wParam; + lParam: lParam): HResult; stdcall; + + Dummy: HResult; +{ + Result = TShlComRec_HandleMenuMsgs(Self, uMsg, wParam, lParam, @Dummy); +} + +function TShlComRec_HandleMenuMsg2(Self: PContextMenu3_Interface; uMsg: UINT; wParam: wParam; + lParam: lParam; PLResult: Pointer { ^LResult } ): HResult; stdcall; + + Dummy: HResult; +{ + // this will be null if a return value isn't needed. + if PLResult = NULL + PLResult = @Dummy; + Result = TShlComRec_HandleMenuMsgs(Self, uMsg, wParam, lParam, PLResult); +} + +function TShlComRec_Create: PShlComRec; + + DC: HDC; +{ + New(Result); + { build all the function tables for interfaces } + with Result->ShellExtInit_Interface do + { + { 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; + } + with Result->ContextMenu3_Interface do + { + 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; + } + { 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 = NULL; + Result->ProtoIcons = NULL; + 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); +} + +{ IClassFactory } + +type + + PVTable_IClassFactory = ^TVTable_IClassFactory; + + TVTable_IClassFactory = record + { IUnknown } + QueryInterface: Pointer; + AddRef: Pointer; + Release: Pointer; + { IClassFactory } + CreateInstance: Pointer; + LockServer: Pointer; + } + + PClassFactoryRec = ^TClassFactoryRec; + + TClassFactoryRec = record + ptrVTable: PVTable_IClassFactory; + vTable: TVTable_IClassFactory; + { fields } + RefCount: LongInt; + } + +function TClassFactoryRec_QueryInterface(Self: PClassFactoryRec; const IID: TIID; Obj): HResult; stdcall; +{ + Pointer(Obj) = NULL; + Result = E_NOTIMPL; +} + +function TClassFactoryRec_AddRef(Self: PClassFactoryRec): LongInt; stdcall; +{ + inc(Self->RefCount); + Result = Self->RefCount; +} + +function TClassFactoryRec_Release(Self: PClassFactoryRec): LongInt; stdcall; +{ + dec(Self->RefCount); + Result = Self->RefCount; + if Result = 0 + { + Dispose(Self); + dec(dllpublic.FactoryCount); + } { if } +} + +function TClassFactoryRec_CreateInstance(Self: PClassFactoryRec; unkOuter: Pointer; + const IID: TIID; Obj): HResult; stdcall; + + ShlComRec: PShlComRec; +{ + Pointer(Obj) = NULL; + Result = CLASS_E_NOAGGREGATION; + if unkOuter = NULL + { + { Before Vista, the system queried for a IShell interface queried for a context menu, Vista now + queries for a context menu (| a shell menu) QI()'s the other interface } + if IsEqualIID(IID, IID_IContextMenu) + { + Result = S_OK; + ShlComRec = TShlComRec_Create; + Pointer(Obj) = @ShlComRec->ContextMenu3_Interface; + } + if IsEqualIID(IID, IID_IShellExtInit) + { + Result = S_OK; + ShlComRec = TShlComRec_Create; + Pointer(Obj) = @ShlComRec->ShellExtInit_Interface; + } // if + } // if +} + +function TClassFactoryRec_LockServer(Self: PClassFactoryRec; fLock: BOOL): HResult; stdcall; +{ + Result = E_NOTIMPL; +} + +function TClassFactoryRec_Create: PClassFactoryRec; +{ + 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); +} + +// +// IPC part +// + +type + PFileList = ^TFileList; + TFileList = array [0 .. 0] of LPSTR; + PAddArgList = ^TAddArgList; + + TAddArgList = record + szFile: LPSTR; // file being processed + cch: Cardinal; // it's length (with space for NULL char) + count: Cardinal; // number we have so far + files: PFileList; + hContact: HANDLE; + hEvent: HANDLE; + } + +function AddToList( args: TAddArgList): LongBool; + + attr: Cardinal; + p: Pointer; + hFind: HANDLE; + fd: TWIN32FINDDATA; + szBuf: array [0 .. MAX_PATH] of Char; + szThis: LPSTR; + cchThis: Cardinal; +{ + Result = false; + attr = GetFileAttributes(args.szFile); + if (attr != 0xFFFFFFFF) && ((attr && FILE_ATTRIBUTE_HIDDEN) = 0) + { + if args.count mod 10 = 5 + { + if CallService(MS_SYSTEM_TERMINATED, 0, 0) != 0 + { + Result = true; + Exit; + } // if + } + if attr && FILE_ATTRIBUTE_DIRECTORY != 0 + { + // add the directory + lstrcpyA(szBuf, args.szFile); + ReAllocMem(args.files, (args.count + 1) * sizeof(LPSTR)); + 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 + { + if fd.cFileName[0] != '.' + { + 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 + break; + } // if + if not FindNextFile(hFind, fd) + break; + } // while + FindClose(hFind); + } + else + { + // add the file + ReAllocMem(args.files, (args.count + 1) * sizeof(LPSTR)); + GetMem(p, args.cch); + lstrcpyA(p, args.szFile); + args.files^[args.count] = p; + inc(args.count); + } // if + } +} + +procedure MainThreadIssueTransfer(p: PAddArgList); stdcall; +{$DEFINE SHL_IDC} +{$DEFINE SHL_KEYS} +{$INCLUDE shlc.inc} +{$UNDEF SHL_KEYS} +{$UNDEF SHL_IDC} +{ + DBWriteContactSettingByte(p->hContact, SHLExt_Name, SHLExt_MRU, 1); + CallService(MS_FILE_SENDSPECIFICFILES, p->hContact, lParam(p->files)); + SetEvent(p->hEvent); +} + +procedure IssueTransferThread(pipch: THeaderIPC *); cdecl; + + szBuf: array [0 .. MAX_PATH] of Char; + pct: TSlotIPC *; + args: TAddArgList; + bQuit: LongBool; + j, c: Cardinal; + p: Pointer; + hMainThread: HANDLE; +{ + hMainThread = HANDLE(pipch->Param); + GetCurrentDirectory(sizeof(szBuf), szBuf); + args.count = 0; + args.files = NULL; + pct = pipch->DataPtr; + bQuit = false; + while pct != NULL do + { + if (pct->cbSize != sizeof(TSlotIPC)) + break; + args.szFile = LPSTR(UINT_PTR(pct) + sizeof(TSlotIPC)); + args.hContact = pct->hContact; + args.cch = pct->cbStrSection + 1; + bQuit = AddToList(args); + if bQuit + break; + pct = pct->Next; + } // while + if args.files != NULL + { + ReAllocMem(args.files, (args.count + 1) * sizeof(LPSTR)); + args.files^[args.count] = NULL; + inc(args.count); + if (not bQuit) + { + args.hEvent = CreateEvent(NULL, true, false, NULL); + QueueUserAPC(@MainThreadIssueTransfer, hMainThread, UINT_PTR(@args)); + while true do + { + if WaitForSingleObjectEx(args.hEvent, INFINITE, true) != WAIT_IO_COMPLETION + break; + } + CloseHandle(args.hEvent); + } // if + c = args.count - 1; + for j = 0 to c do + { + p = args.files^[j]; + if p != NULL + FreeMem(p); + } + FreeMem(args.files); + } + SetCurrentDirectory(szBuf); + FreeMem(pipch); + CloseHandle(hMainThread); +} + +type + + PSlotInfo = ^TSlotInfo; + + TSlotInfo = record + hContact: HANDLE; + hProto: Cardinal; + dwStatus: Integer; // will be aligned anyway + } + + TSlotArray = array [0 .. $FFFFFF] of TSlotInfo; + PSlotArray = ^TSlotArray; + +function SortContact( Item1, Item2: TSlotInfo): Integer; stdcall; +{ + Result = CallService(MS_CLIST_CONTACTSCOMPARE, Item1.hContact, Item2.hContact); +} + +// from FP FCL + +procedure QuickSort(FList: PSlotArray; L, R: LongInt); + + i, j: LongInt; + p, q: TSlotInfo; +{ + 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 + { + q = FList^[i]; + FList^[i] = FList^[j]; + FList^[j] = q; + inc(i); + dec(j); + } // if + until i > j; + if L < j + QuickSort(FList, L, j); + L = i; + until i >= R; +} + +{$DEFINE SHL_KEYS} +{$INCLUDE shlc.inc} +{$UNDEF SHL_KEYS} + +procedure ipcGetSkinIcons(ipch: THeaderIPC *); + + protoCount: Integer; + pp: ^PPROTOCOLDESCRIPTOR; + spi: TSlotProtoIcons; + j: Cardinal; + pct: TSlotIPC *; + szTmp: array [0 .. 63] of Char; + dwCaps: Cardinal; +{ + if (CallService(MS_PROTO_ENUMACCOUNTS, wParam(@protoCount), lParam(@pp)) = 0) && + (protoCount != 0) + { + spi.pid = GetCurrentProcessId(); + while protoCount > 0 do + { + lstrcpyA(szTmp, pp->szName); + lstrcata(szTmp, PS_GETCAPS); + dwCaps = CallService(szTmp, PFLAGNUM_1, 0); + if (dwCaps && PF1_FILESEND) != 0 + { + pct = ipcAlloc(ipch, sizeof(TSlotProtoIcons)); + if pct != NULL + { + // capture all the icons! + spi.hProto = StrHash(pp->szName); + for j = 0 to 9 do + { + spi.hIcons[j] = LoadSkinnedProtoIcon(pp->szName, ID_STATUS_OFFLINE + j); + } // for + pct->fType = REQUEST_NEWICONS; + CopyMemory(Pointer(UINT_PTR(pct) + sizeof(TSlotIPC)), @spi, sizeof(TSlotProtoIcons)); + if ipch->NewIconsBegin = NULL + ipch->NewIconsBegin = pct; + } // if + } // if + inc(pp); + dec(protoCount); + } // while + } // if + // add Miranda icon + pct = ipcAlloc(ipch, sizeof(TSlotProtoIcons)); + if pct != NULL + { + 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 = NULL + ipch->NewIconsBegin = pct; + } // if +} + +function ipcGetSortedContacts(ipch: THeaderIPC *; pSlot: pint; bGroupMode: Boolean): Boolean; + + dwContacts: Cardinal; + pContacts: PSlotArray; + hContact: HANDLE; + i: Integer; + dwOnline: Cardinal; + szProto: LPSTR; + dwStatus: Integer; + pct: TSlotIPC *; + szContact: LPSTR; + dbv: TDBVariant; + bHideOffline: Boolean; + szTmp: array [0 .. 63] of Char; + dwCaps: Cardinal; + szSlot: LPSTR; + n, rc, cch: Cardinal; +{ + 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 + { + // hide offline people + bHideOffline = true; + } + // get the number of contacts + dwContacts = CallService(MS_DB_CONTACT_GETCOUNT, 0, 0); + if dwContacts = 0 + 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 = db_find_first(); + while (hContact != 0) do + { + if i >= dwContacts + break; + (* do they have a running protocol? *) + UINT_PTR(szProto) = CallService(MS_PROTO_GETCONTACTBASEPROTO, hContact, 0); + if szProto != NULL + { + (* does it support file sends? *) + lstrcpyA(szTmp, szProto); + lstrcata(szTmp, PS_GETCAPS); + dwCaps = CallService(szTmp, PFLAGNUM_1, 0); + if (dwCaps && PF1_FILESEND) = 0 + { + hContact = db_find_next(hContact); + continue; + } + dwStatus = DBGetContactSettingWord(hContact, szProto, 'Status', ID_STATUS_OFFLINE); + if dwStatus != ID_STATUS_OFFLINE + inc(dwOnline) + else if bHideOffline + { + hContact = db_find_next(hContact); + continue; + } // if + // is HIT on? + if BST_UNCHECKED = DBGetContactSettingByte(0, SHLExt_Name, SHLExt_UseHITContacts, + BST_UNCHECKED) + { + // don't show people who are "Hidden" "NotOnList" | Ignored + if (DBGetContactSettingByte(hContact, 'CList', 'Hidden', 0) = 1) | + (DBGetContactSettingByte(hContact, 'CList', 'NotOnList', 0) = 1) | + (CallService(MS_IGNORE_ISIGNORED, hContact, IGNOREEVENT_MESSAGE | + IGNOREEVENT_URL | IGNOREEVENT_FILE) != 0) + { + hContact = db_find_next(hContact); + continue; + } // if + } // if + // is HIT2 off? + if BST_UNCHECKED = DBGetContactSettingByte(0, SHLExt_Name, SHLExt_UseHIT2Contacts, + BST_UNCHECKED) + { + if DBGetContactSettingWord(hContact, szProto, 'ApparentMode', 0) = ID_STATUS_OFFLINE + + { + hContact = db_find_next(hContact); + continue; + } // if + } // if + // store + pContacts^[i].hContact = hContact; + pContacts^[i].dwStatus = dwStatus; + pContacts^[i].hProto = StrHash(szProto); + inc(i); + } + else + { + // contact has no protocol! + } // if + hContact = db_find_next(hContact); + } // while + // if no one is online && the CList isn't showing offliners, quit + if (dwOnline = 0) && (bHideOffline) + { + FreeMem(pContacts); + Exit; + } // if + dwContacts = i; + i = 0; + // sort the array + QuickSort(pContacts, 0, dwContacts - 1); + // create an IPC slot for each contact && store display name, etc + while i < dwContacts do + { + UINT_PTR(szContact) = CallService(MS_CLIST_GETCONTACTDISPLAYNAME,pContacts^[i].hContact, 0); + if (szContact != NULL) + { + n = 0; + rc = 1; + if bGroupMode + { + rc = DBGetContactSetting(pContacts^[i].hContact, 'CList', 'Group', @dbv); + if rc = 0 + { + n = lstrlenA(dbv.szVal.a) + 1; + } + } // if + cch = lstrlenA(szContact) + 1; + pct = ipcAlloc(ipch, cch + 1 + n); + if pct = NULL + { + DBFreeVariant(@dbv); + break; + } + // lie about the actual size of the TSlotIPC + pct->cbStrSection = cch; + szSlot = LPSTR(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 = NULL + ipch->ContactsBegin = pct; + inc(szSlot, cch + 1); + if rc = 0 + { + pct->hGroup = StrHash(dbv.szVal.a); + lstrcpyA(szSlot, dbv.szVal.a); + DBFreeVariant(@dbv); + } + else + { + pct->hGroup = 0; + szSlot^ = #0; + } + inc(pSlot^); + } // if + inc(i); + } // while + FreeMem(pContacts); + // + Result = true; +} + +// worker thread to clear MRU, called by the IPC bridge +procedure ClearMRUThread(notused: Pointer); cdecl; +{$DEFINE SHL_IDC} +{$DEFINE SHL_KEYS} +{$INCLUDE shlc.inc} +{$UNDEF SHL_KEYS} +{$UNDEF SHL_IDC} + + hContact: HANDLE; +{ + { + hContact = db_find_first(); + while hContact != 0 do + { + if DBGetContactSettingByte(hContact, SHLExt_Name, SHLExt_MRU, 0) > 0 + { + DBWriteContactSettingByte(hContact, SHLExt_Name, SHLExt_MRU, 0); + } + hContact = db_find_next(hContact); + } + } +} + +// this function is called from an APC into the main thread +procedure ipcService(dwParam: DWORD); stdcall; +label + Reply; + + hMap: HANDLE; + pMMT: THeaderIPC *; + hSignal: HANDLE; + pct: TSlotIPC *; + szBuf: LPSTR; + iSlot: Integer; + szGroupStr: array [0 .. 31] of Char; + dbv: TDBVariant; + bits: pint; + bGroupMode: Boolean; + cloned: THeaderIPC *; + szMiranda: LPSTR; +{ + { 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 + { + { 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 != NULL) && (pMMT->cbSize = sizeof(THeaderIPC)) && + (pMMT->dwVersion = PLUGIN_MAKE_VERSION(2, 0, 1, 2)) + { + // toggle the right bits + bits = @pMMT->fRequests; + // jump right to a worker thread for file processing? + if (bits^ && REQUEST_XFRFILES) = REQUEST_XFRFILES + { + 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); + mir_forkThread(@IssueTransferThread, cloned); + goto Reply; + } + // the request was to clear the MRU entries, we have no return data + if (bits^ && REQUEST_CLEARMRU) = REQUEST_CLEARMRU + { + mir_forkThread(@ClearMRUThread, NULL); + goto Reply; + } + // 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 + { + pMMT->dwFlags = HIPC_NOICONS; + } + // 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); + + // && 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 && (BST_CHECKED = DBGetContactSettingByte(0, SHLExt_Name, + SHLExt_UseCListSetting, BST_UNCHECKED)) + { + bGroupMode = 1 = DBGetContactSettingByte(0, 'CList', 'UseGroups', 0); + } + iSlot = 0; + // return profile if set + if BST_UNCHECKED = DBGetContactSettingByte(0, SHLExt_Name, SHLExt_ShowNoProfile, + BST_UNCHECKED) + { + pct = ipcAlloc(pMMT, 50); + if pct != NULL + { + // 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)); + } // if + } // if + if (bits^ && REQUEST_NEWICONS) = REQUEST_NEWICONS + { + ipcGetSkinIcons(pMMT); + } + if (bits^ && REQUEST_GROUPS = REQUEST_GROUPS) + { + // return contact's grouping if it's present + while bGroupMode do + { + str(iSlot, szGroupStr); + if DBGetContactSetting(0, 'CListGroups', szGroupStr, @dbv) != 0 + break; + pct = ipcAlloc(pMMT, lstrlenA(dbv.szVal.a + 1) + 1); + // first byte has flags, need null term + if pct != NULL + { + if pMMT->GroupsBegin = NULL + pMMT->GroupsBegin = pct; + pct->fType = REQUEST_GROUPS; + pct->hContact = 0; + UINT_PTR(szBuf) = UINT_PTR(pct) + sizeof(TSlotIPC); // get the } of the slot + lstrcpyA(szBuf, dbv.szVal.a + 1); + pct->hGroup = 0; + DBFreeVariant(@dbv); // free the string + } + else + { + // outta space + DBFreeVariant(@dbv); + break; + } // if + inc(iSlot); + } { while } + // if there was no space left, it'll } on null + if pct = NULL + bits^ = (bits^ | GROUPS_NOTIMPL) && not REQUEST_GROUPS; + } { if: group request } + // SHOULD check slot space. + if (bits^ && REQUEST_CONTACTS = REQUEST_CONTACTS) + { + if not ipcGetSortedContacts(pMMT, @iSlot, bGroupMode) + { + // fail if there were no contacts AT ALL + bits^ = (bits^ | CONTACTS_NOTIMPL) && not REQUEST_CONTACTS; + } // if + } // 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 + { + { signal && close } + SetEvent(hSignal); + CloseHandle(hSignal); + } + { unmap the shared memory from this process } + UnmapViewOfFile(pMMT); + } + { close the map file } + CloseHandle(hMap); + } { if } + // +} + +procedure ThreadServer(hMainThread: Pointer); cdecl; + + hEvent: HANDLE; + retVal: Cardinal; +{ + hEvent = CreateEvent(NULL, false, false, LPSTR(CreateProcessUID(GetCurrentProcessId()))); + while true do + { + retVal = WaitForSingleObjectEx(hEvent, INFINITE, true); + if retVal = WAIT_OBJECT_0 + { + QueueUserAPC(@ipcService, HANDLE(hMainThread), 0); + } // if + if CallService(MS_SYSTEM_TERMINATED, 0, 0) = 1 + break; + } // while + CloseHandle(hEvent); + CloseHandle(HANDLE(hMainThread)); +} + +procedure InvokeThreadServer; + + hMainThread: HANDLE; +{ + hMainThread = 0; + DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), @hMainThread, + THREAD_SET_CONTEXT, false, 0); + if hMainThread != 0 + mir_forkThread(@ThreadServer, Pointer(hMainThread)); +} + +{ exported functions } + +function DllGetClassObject(const CLSID: TCLSID; const IID: TIID; Obj): HResult; stdcall; +{ + Pointer(Obj) = NULL; + Result = CLASS_E_CLASSNOTAVAILABLE; + if (IsEqualCLSID(CLSID, CLSID_ISHLCOM)) && (IsEqualIID(IID, IID_IClassFactory)) && + (FindWindow(MirandaName, NULL) != 0) + { + Pointer(Obj) = TClassFactoryRec_Create; + Result = S_OK; + } // if +} + +function DllCanUnloadNow: HResult; +{ + if ((dllpublic.FactoryCount = 0) && (dllpublic.ObjectCount = 0)) + { + Result = S_OK; + } + else + { + Result = S_FALSE; + } // if +} + +{ helper functions } + +type + + PSHELLEXECUTEINFO = ^TSHELLEXECUTEINFO; + + TSHELLEXECUTEINFO = record + cbSize: DWORD; + fMask: LongInt; + hwnd: HANDLE; + lpVerb: LPSTR; + lpFile: LPSTR; + lpParameters: LPSTR; + lpDirectory: LPSTR; + nShow: Integer; + hInstApp: HANDLE; + lpIDLIst: Pointer; + lpClass: LPSTR; + HKEY: HANDLE; + dwHotkey: DWORD; + HICON: HANDLE; // is union + hProcess: HANDLE; + } + +function ShellExecuteEx( se: TSHELLEXECUTEINFO): Boolean; stdcall; + external 'shell32.dll' name 'ShellExecuteExA'; + +function wsprintfs(lpOut, lpFmt: LPSTR; args: LPSTR): Integer; cdecl; + external 'user32.dll' name 'wsprintfA'; + +function RemoveCOMRegistryEntries: HResult; + + hRootKey: HKEY; +{ + if RegOpenKeyEx(HKEY_CLASSES_ROOT, 'miranda.shlext', 0, KEY_READ, hRootKey) = ERROR_SUCCESS + + { + (* 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 + { + MessageBox(0, + 'Unable to delete registry key for "shlext COM", this key may already be deleted | you may need admin rights.', + 'Problem', MB_ICONERROR); + } // if + } // if + if RegOpenKeyEx(HKEY_CLASSES_ROOT, '\*\shellex\ContextMenuHandlers', 0, KEY_ALL_ACCESS, + hRootKey) = ERROR_SUCCESS + { + if RegDeleteKey(hRootKey, 'miranda.shlext') != ERROR_SUCCESS + { + MessageBox(0, + 'Unable to delete registry key for "File context menu handlers", this key may already be deleted | you may need admin rights.', + 'Problem', MB_ICONERROR); + } // if + RegCloseKey(hRootKey); + } // if + if RegOpenKeyEx(HKEY_CLASSES_ROOT, 'Directory\shellex\ContextMenuHandlers', 0, KEY_ALL_ACCESS, + hRootKey) = ERROR_SUCCESS + { + if RegDeleteKey(hRootKey, 'miranda.shlext') != ERROR_SUCCESS + { + MessageBox(0, + 'Unable to delete registry key for "Directory context menu handlers", this key may already be deleted | you may need admin rights.', + 'Problem', MB_ICONERROR); + } // if + RegCloseKey(hRootKey); + } // if + if ERROR_SUCCESS = RegOpenKeyEx(HKEY_LOCAL_MACHINE, + 'Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved', 0, KEY_ALL_ACCESS, + hRootKey) + { + if RegDeleteValue(hRootKey, '{72013A26-A94C-11d6-8540-A5E62932711D}') != ERROR_SUCCESS + { + MessageBox(0, + 'Unable to delete registry entry for "Approved context menu handlers", this key may already be deleted | you may need admin rights.', + 'Problem', MB_ICONERROR); + } // if + RegCloseKey(hRootKey); + } // if + Result = S_OK; +} + +{ called by the options code to remove COM entries, && before that, get permission, if required. +} + +procedure CheckUnregisterServer; + + sei: TSHELLEXECUTEINFO; + szBuf: array [0 .. MAX_PATH * 2] of Char; + szFileName: array [0 .. MAX_PATH] of Char; +{ + if not VistaOrLater + { + RemoveCOMRegistryEntries(); + Exit; + } + // 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(); +} + +{ Wow, I can't believe there isn't a direct API for this - 'runas' will invoke the UAC && ask + for permission before installing the shell extension. note the filepath arg has to be quoted } +procedure CheckRegisterServer; + + hRegKey: HKEY; + sei: TSHELLEXECUTEINFO; + szBuf: array [0 .. MAX_PATH * 2] of Char; + szFileName: array [0 .. MAX_PATH] of Char; +{ + if ERROR_SUCCESS = RegOpenKeyEx(HKEY_CLASSES_ROOT, 'miranda.shlext', 0, KEY_READ, hRegKey) + + { + RegCloseKey(hRegKey); + } + else + { + if VistaOrLater + { + 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 | 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); + } + } +} diff --git a/plugins/ShellExt/src/shlcom.h b/plugins/ShellExt/src/shlcom.h new file mode 100644 index 0000000000..5b701b4bf6 --- /dev/null +++ b/plugins/ShellExt/src/shlcom.h @@ -0,0 +1,193 @@ + +#define REPLY_FAIL 0x88888888 +#define REPLY_OK 0x00000000 + +#define REQUEST_ICONS 1 +#define REQUEST_GROUPS (REQUEST_ICONS << 1) +#define REQUEST_CONTACTS (REQUEST_GROUPS << 1) +#define REQUEST_XFRFILES (REQUEST_CONTACTS << 1) +#define REQUEST_NEWICONS (REQUEST_XFRFILES << 1) +#define REQUEST_CLEARMRU (REQUEST_NEWICONS << 1) + +#define ICONS_NOTIMPL 0x00000008 +#define GROUPS_NOTIMPL 0x00000080 +#define CONTACTS_NOTIMPL 0x00000800 + +#define STATUS_PROFILENAME 2 + +#define ICMF_NORMAL 0x00000000 +#define ICMF_DEFAULTONLY 0x00000001 +#define ICMF_VERBSONLY 0x00000002 +#define ICMF_EXPLORE 0x00000004 +#define ICMF_NOVERBS 0x00000008 +#define ICMF_CANRENAME 0x00000010 +#define ICMF_NODEFAULT 0x00000020 +#define ICMF_INCLUDESTATIC 0x00000040 +#define ICMF_RESERVED 0xFFFF0000 + +// IContextMenu*:GetCommandString() uType flags + +#define IGCS_VERBA 0x00000000 // canonical verb +#define IGCS_HELPTEXTA 0x00000001 // help text (for status bar) +#define IGCS_VALIDATEA 0x00000002 // validate command exists +#define IGCS_VERBW 0x00000004 // canonical verb (unicode) +#define IGC_HELPTEXTW 0x00000005 // help text (unicode version) +#define IGCS_VALIDATEW 0x00000006 // validate command exists (unicode) +#define IGCS_UNICODE 0x00000004 // for bit testing - Unicode string +#define IGCS_VERB GCS_VERBA +#define IGCS_HELPTEXT GCS_HELPTEXTA +#define IGCS_VALIDATE GCS_VALIDATEA + +///////////////////////////////////////////////////////////////////////////////////////// + +struct TGroupNode +{ + TGroupNode *Left, *Right, *_prev, *_next; + int Depth; + UINT Hash; // hash of the group name alone + char *szGroup; + int cchGroup; + HMENU hMenu; + int hMenuGroupID; + DWORD dwItems; +}; + +struct TGroupNodeList +{ + TGroupNode *First, *Last; +}; + +struct TStrTokRec +{ + char *szStr, *szSet; + // need a delimiter after the token too?, e.g. FOO^BAR^ if FOO^BAR + // is the string then only FOO^ is returned, could cause infinite loops + // if the condition isn't accounted for thou. + bool bSetTerminator; +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +struct TSlotProtoIcons +{ + UINT pid; // pid of Miranda this protocol was on + UINT hProto; // hash of the protocol + HICON hIcons[10]; // each status in order of ID_STATUS_* + HBITMAP hBitmaps[10]; // each status "icon" as a bitmap +}; + +struct TSlotIPC +{ + BYTE cbSize; + int fType; // a REQUEST_* type + TSlotIPC *Next; + HANDLE hContact; + UINT hProto; // hash of the protocol the user is on + UINT hGroup; // hash of the entire path (not defined for REQUEST_GROUPS slots) + WORD Status; + // only used for contacts -- can be STATUS_PROFILENAME -- but that is because returning the profile name is optional + BYTE MRU; // if set, contact has been recently used + int cbStrSection; +}; + +struct THeaderIPC +{ + int cbSize; + DWORD dwVersion; + void *pServerBaseAddress, *pClientBaseAddress; + int fRequests; + DWORD dwFlags; + int Slots, Cardinal; + char SignalEventName[64]; + char MirandaName[64]; + char MRUMenuName[64]; + char ClearEntries[64]; + TSlotIPC *IconsBegin, *ContactsBegin, *GroupsBegin, *NewIconsBegin; + // start of an flat memory stack, which is referenced as a linked list + int DataSize; + TSlotIPC *DataPtr, *DataPtrEnd; + void *DataFramePtr; +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +struct TShlComRec +{ + IShellExtInit *ShellExtInit_Interface; + IContextMenu3 *ContextMenu3_Interface; + + LONG RefCount; + // this is owned by the shell after items are added 'n' is used to + // grab menu information directly via id rather than array indexin' + HMENU hRootMenu; + int idCmdFirst; + // most of the memory allocated is on this heap object so HeapDestroy() + // can do most of the cleanup, extremely lazy I know. + HANDLE hDllHeap; + // This is a submenu that recently used contacts are inserted into + // the contact is inserted twice, once in its normal list (| group) && here + // Note: These variables are global data, but refered to locally by each instance + // Do not rely on these variables outside the process enumeration. + HMENU hRecentMenu; + ULONG RecentCount; // number of added items + // array of all the protocol icons, for every running instance! + TSlotProtoIcons *ProtoIcons; + int ProtoIconsCount; + // maybe null, taken from IShellExtInit_Initalise() && AddRef()'d + // only used if a Miranda instance is actually running && a user + // is selected + IDataObject *pDataObject; + // DC is used for font metrics && saves on creating && destroying lots of DC handles + // during WM_MEASUREITEM + HDC hMemDC; +}; + +struct TEnumData +{ + TShlComRec *Self; + + // autodetected, don't hard code since shells that don't support it + // won't send WM_MEASUREITETM/WM_DRAWITEM at all. + BOOL bOwnerDrawSupported; + // as per user setting (maybe of multiple Mirandas) + BOOL bShouldOwnerDraw; + int idCmdFirst; + THeaderIPC *ipch; + // OpenEvent()'d handle to give each IPC server an object to set signalled + HANDLE hWaitFor; + DWORD pid; // sub-unique value used to make work object name +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +enum TSlotDrawType {dtEntry = 0x01, dtGroup = 0x02, dtContact = 0x04, dtCommand = 0x08 }; +typedef int TSlotDrawTypes; + +typedef int (__stdcall TMenuCommandCallback)( + 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 + struct TMenuDrawInfo *psd); // command/draw info + +struct TMenuDrawInfo +{ + char *szText, *szProfile; + int cch; + int wID; // should be the same as the menu item's ID + TSlotDrawTypes fTypes; + HANDLE hContact; + HICON hStatusIcon; // HICON from Self->ProtoIcons[index].hIcons[status]; Do not DestroyIcon() + HBITMAP hStatusBitmap; // HBITMAP, don't free. + int pid; + TMenuCommandCallback *MenuCommandCallback; // dtCommand must be set also. +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +void ipcPrepareRequests(int ipcPacketSize, THeaderIPC *pipch, DWORD fRequests); +DWORD ipcSendRequest(HANDLE hSignal, HANDLE hWaitFor, THeaderIPC *pipch, DWORD dwTimeoutMsecs); +TSlotIPC* ipcAlloc(THeaderIPC *pipch, int nSize); +void ipcFixupAddresses(BOOL FromServer, THeaderIPC *pipch); + +TGroupNode* AllocGroupNode(TGroupNodeList *list, TGroupNode *Root, int Depth); +TGroupNode* FindGroupNode(TGroupNode* P, const DWORD Hash, DWORD Depth); diff --git a/plugins/ShellExt/src/stdafx.cpp b/plugins/ShellExt/src/stdafx.cpp new file mode 100644 index 0000000000..1ff112382c --- /dev/null +++ b/plugins/ShellExt/src/stdafx.cpp @@ -0,0 +1,18 @@ +/* +Copyright (C) 2012-13 Miranda NG Project (http://miranda-ng.org) + +This program is free software; you can redistribute it &&/| +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY | FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "stdafx.h" \ No newline at end of file diff --git a/plugins/ShellExt/src/stdafx.h b/plugins/ShellExt/src/stdafx.h new file mode 100644 index 0000000000..f0f47bbb70 --- /dev/null +++ b/plugins/ShellExt/src/stdafx.h @@ -0,0 +1,45 @@ +#define _CRT_SECURE_NO_WARNINGS + +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "Version.h" + +using namespace std; + +#define MODULENAME "ShellExt" +#define SHLExt_Name "shlext15" +#define SHLExt_MRU "MRU" +#define SHLExt_UseGroups "UseGroups" +#define SHLExt_UseCListSetting "UseCLGroups" +#define SHLExt_UseHITContacts "UseHITContacts" +#define SHLExt_UseHIT2Contacts "UseHIT2Contacts" +#define SHLExt_ShowNoProfile "ShowNoProfile" +#define SHLExt_ShowNoIcons "ShowNoIcons" +#define SHLExt_ShowNoOffline "ShowNoOffline" + +#define COMREG_UNKNOWN 0 +#define COMREG_OK 1 +#define COMREG_APPROVED 2 + +void CheckRegisterServer(); +void CheckUnregisterServer(); +void InvokeThreadServer(); +int IsCOMRegistered(); +HRESULT RemoveCOMRegistryEntries(); + +extern HINSTANCE hInst; + +int OnOptionsInit(WPARAM wParam, LPARAM lParam); -- cgit v1.2.3