1 // SPDX-License-Identifier: MIT
2 // Based on code from https://github.com/microsoft/AppModelSamples/tree/master/Samples/SparsePackages
3 // dllmain.cpp : Defines the entry point for the DLL application.
5 #include <wrl/module.h>
6 #include <wrl/implements.h>
7 #include <wrl/client.h>
8 #include <shobjidl_core.h>
9 #include <wil/resource.h>
10 #include <wil/win32_helpers.h>
18 #include "../Common/WinMergeContextMenu.h"
21 #pragma comment(lib, "shlwapi.lib")
23 using namespace Microsoft::WRL;
25 BOOL APIENTRY DllMain( HMODULE hModule,
26 DWORD ul_reason_for_call,
30 switch (ul_reason_for_call)
32 case DLL_PROCESS_ATTACH:
33 case DLL_THREAD_ATTACH:
34 case DLL_THREAD_DETACH:
35 case DLL_PROCESS_DETACH:
41 class WinMergeExplorerCommandBase : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IExplorerCommand>
44 WinMergeExplorerCommandBase(WinMergeContextMenu* pContextMenu) : m_pContextMenu(pContextMenu) {}
45 virtual const wchar_t* Title() = 0;
46 virtual const EXPCMDFLAGS Flags() { return ECF_DEFAULT; }
47 virtual const EXPCMDSTATE State(_In_opt_ IShellItemArray* selection) { return ECS_ENABLED; }
48 virtual const int IconId(_In_opt_ IShellItemArray* selection) { return 0; }
49 virtual const int Verb() { return 0; }
52 IFACEMETHODIMP GetTitle(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* name)
55 auto title = wil::make_cotaskmem_string_nothrow(Title());
56 RETURN_IF_NULL_ALLOC(title);
57 *name = title.release();
60 IFACEMETHODIMP GetIcon(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* icon)
62 const int id = IconId(items);
68 auto modulefilename = wil::GetModuleFileNameW(wil::GetModuleInstanceHandle());
69 std::wstring path = modulefilename.get() + std::wstring(L",-") + std::to_wstring(id);
70 SHStrDupW(path.c_str(), icon);
73 IFACEMETHODIMP GetToolTip(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* infoTip)
78 IFACEMETHODIMP GetCanonicalName(_Out_ GUID* guidCommandName)
80 *guidCommandName = GUID_NULL;
83 IFACEMETHODIMP GetState(_In_opt_ IShellItemArray* selection, _In_ BOOL okToBeSlow, _Out_ EXPCMDSTATE* cmdState)
85 *cmdState = State(selection);
88 IFACEMETHODIMP Invoke(_In_opt_ IShellItemArray* selection, _In_opt_ IBindCtx*) noexcept try
90 m_pContextMenu->InvokeCommand(Verb());
95 IFACEMETHODIMP GetFlags(_Out_ EXPCMDFLAGS* flags)
100 IFACEMETHODIMP EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** enumCommands)
102 *enumCommands = nullptr;
108 // code from https://github.com/microsoft/terminal/blob/main/src/cascadia/ShellExtension/OpenTerminalHere.cpp
109 std::wstring _GetPathFromExplorer() const
112 HRESULT hr = NOERROR;
114 auto hwnd = ::GetForegroundWindow();
120 TCHAR szName[MAX_PATH] = { 0 };
121 ::GetClassName(hwnd, szName, MAX_PATH);
122 if (0 == StrCmp(szName, L"WorkerW") ||
123 0 == StrCmp(szName, L"Progman"))
125 //special folder: desktop
126 hr = ::SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, szName);
136 if (0 != StrCmp(szName, L"CabinetWClass"))
141 ComPtr<IShellWindows> shell;
142 hr = CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL, IID_IShellWindows, &shell);
143 if (FAILED(hr) || shell == nullptr)
148 ComPtr<IDispatch> disp;
149 wil::unique_variant variant;
152 ComPtr<IWebBrowserApp> browser;
153 // look for correct explorer window
154 for (variant.intVal = 0;
155 shell->Item(variant, &disp) == S_OK;
158 ComPtr<IWebBrowserApp> tmp;
159 if (FAILED(disp->QueryInterface<IWebBrowserApp>(&tmp)))
161 disp = nullptr; // get rid of DEBUG non-nullptr warning
166 hr = tmp->get_HWND(reinterpret_cast<SHANDLE_PTR*>(&tmpHWND));
170 disp = nullptr; // get rid of DEBUG non-nullptr warning
174 disp = nullptr; // get rid of DEBUG non-nullptr warning
177 if (browser != nullptr)
179 wil::unique_bstr url;
180 hr = browser->get_LocationURL(&url);
186 std::wstring sUrl(url.get(), SysStringLen(url.get()));
187 DWORD size = MAX_PATH;
188 hr = ::PathCreateFromUrl(sUrl.c_str(), szName, &size, NULL);
198 std::vector<std::wstring> GetPaths(_In_opt_ IShellItemArray* selection)
200 std::vector<std::wstring> paths;
201 DWORD dwNumItems = 0;
204 std::wstring path = _GetPathFromExplorer();
206 paths.push_back(path);
210 selection->GetCount(&dwNumItems);
211 for (DWORD i = 0; i < dwNumItems; ++i)
213 ComPtr<IShellItem> psi;
214 wil::unique_cotaskmem_string pszFilePath;
215 if (SUCCEEDED(selection->GetItemAt(i, &psi)) &&
216 SUCCEEDED(psi->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath)))
218 paths.push_back(pszFilePath.get());
224 WinMergeContextMenu* m_pContextMenu;
227 class SubExplorerCommandHandler final : public WinMergeExplorerCommandBase
230 SubExplorerCommandHandler(WinMergeContextMenu *pContextMenu, const MenuItem& menuItem)
231 : WinMergeExplorerCommandBase(pContextMenu)
232 , m_menuItem(menuItem)
235 const wchar_t* Title() override
237 return m_menuItem.text.c_str();
239 const int IconId(_In_opt_ IShellItemArray* selection) override
241 return m_menuItem.icon;
243 const int Verb() override
245 return m_menuItem.verb;
247 const EXPCMDSTATE State(_In_opt_ IShellItemArray* selection) override
249 return m_menuItem.enabled ? ECS_ENABLED : ECS_DISABLED;
255 class EnumCommands : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IEnumExplorerCommand>
258 explicit EnumCommands(WinMergeContextMenu* pContextMenu)
260 for (auto& menuItem : pContextMenu->GetMenuItemList())
261 m_commands.push_back(Make<SubExplorerCommandHandler>(pContextMenu, menuItem));
262 m_current = m_commands.cbegin();
265 // IEnumExplorerCommand
266 IFACEMETHODIMP Next(ULONG celt, __out_ecount_part(celt, *pceltFetched) IExplorerCommand** apUICommand, __out_opt ULONG* pceltFetched)
269 wil::assign_to_opt_param(pceltFetched, 0ul);
271 for (ULONG i = 0; (i < celt) && (m_current != m_commands.cend()); i++)
273 m_current->CopyTo(&apUICommand[0]);
278 wil::assign_to_opt_param(pceltFetched, fetched);
279 return (fetched == celt) ? S_OK : S_FALSE;
282 IFACEMETHODIMP Skip(ULONG /*celt*/) { return E_NOTIMPL; }
283 IFACEMETHODIMP Reset()
285 m_current = m_commands.cbegin();
288 IFACEMETHODIMP Clone(__deref_out IEnumExplorerCommand** ppenum) { *ppenum = nullptr; return E_NOTIMPL; }
291 std::vector<ComPtr<IExplorerCommand>> m_commands;
292 std::vector<ComPtr<IExplorerCommand>>::const_iterator m_current;
295 class __declspec(uuid("90340779-F37E-468E-9728-A2593498ED32")) WinMergeFileDirExplorerCommandHandler : public WinMergeExplorerCommandBase
298 WinMergeFileDirExplorerCommandHandler()
299 : m_contextMenu(wil::GetModuleInstanceHandle())
300 , WinMergeExplorerCommandBase(&m_contextMenu)
303 const wchar_t* Title() override { return L"&WinMerge"; }
304 const int IconId(_In_opt_ IShellItemArray* selection) override
306 auto paths = GetPaths(selection);
307 if (paths.size() > 0 && PathIsDirectory(paths[0].c_str()))
308 return IDI_WINMERGEDIR;
311 const int Verb() override { return WinMergeContextMenu::CMD_COMPARE; }
312 const EXPCMDFLAGS Flags() override
314 // Due to stability issues, the advanced menu is currently disabled.
317 if ((m_contextMenu.GetContextMenuEnabled() & (WinMergeContextMenu::EXT_ENABLED | WinMergeContextMenu::EXT_ADVANCED)) == (WinMergeContextMenu::EXT_ENABLED | WinMergeContextMenu::EXT_ADVANCED))
318 return ECF_HASSUBCOMMANDS;
323 const EXPCMDSTATE State(_In_opt_ IShellItemArray* selection) override
325 auto paths = GetPaths(selection);
326 if (paths.size() > 3)
328 m_contextMenu.UpdateMenuState(paths);
329 if (m_contextMenu.GetMenuState() == WinMergeContextMenu::MENU_HIDDEN)
335 IFACEMETHODIMP EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** enumCommands)
337 *enumCommands = nullptr;
338 auto e = Make<EnumCommands>(&m_contextMenu);
339 return e->QueryInterface(IID_PPV_ARGS(enumCommands));
342 WinMergeContextMenu m_contextMenu;
345 CoCreatableClass(WinMergeFileDirExplorerCommandHandler)
347 CoCreatableClassWrlCreatorMapInclude(WinMergeFileDirExplorerCommandHandler)
350 STDAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ IActivationFactory** factory)
352 return Module<ModuleType::InProc>::GetModule().GetActivationFactory(activatableClassId, factory);
355 STDAPI DllCanUnloadNow()
357 return Module<InProc>::GetModule().GetObjectCount() == 0 ? S_OK : S_FALSE;
360 STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _COM_Outptr_ void** instance)
362 return Module<InProc>::GetModule().GetClassObject(rclsid, riid, instance);