OSDN Git Service

WInMergeContextMenu.dll: Disable advanced menu due to stability issues
[winmerge-jp/winmerge-jp.git] / ShellExtension / WinMergeContextMenu / dllmain.cpp
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.
4 #include "pch.h"
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>
11 #include <string>
12 #include <vector>
13 #include <sstream>
14 #include <memory>
15 #include <Shlwapi.h>
16 #include <ExDisp.h>
17 #include <ShlObj.h>
18 #include "../Common/WinMergeContextMenu.h"
19 #include "resource.h"
20
21 #pragma comment(lib, "shlwapi.lib")
22
23 using namespace Microsoft::WRL;
24
25 BOOL APIENTRY DllMain( HMODULE hModule,
26                        DWORD  ul_reason_for_call,
27                        LPVOID lpReserved
28                      )
29 {
30     switch (ul_reason_for_call)
31     {
32     case DLL_PROCESS_ATTACH:
33     case DLL_THREAD_ATTACH:
34     case DLL_THREAD_DETACH:
35     case DLL_PROCESS_DETACH:
36         break;
37     }
38     return TRUE;
39 }
40
41 class WinMergeExplorerCommandBase : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IExplorerCommand>
42 {
43 public:
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; }
50
51     // IExplorerCommand
52     IFACEMETHODIMP GetTitle(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* name)
53     {
54         *name = nullptr;
55         auto title = wil::make_cotaskmem_string_nothrow(Title());
56         RETURN_IF_NULL_ALLOC(title);
57         *name = title.release();
58         return S_OK;
59     }
60     IFACEMETHODIMP GetIcon(_In_opt_ IShellItemArray* items, _Outptr_result_nullonfailure_ PWSTR* icon)
61     {
62         const int id = IconId(items);
63         if (id == 0)
64         {
65             *icon = nullptr;
66             return E_NOTIMPL;
67         }
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);
71         return S_OK;
72     }
73     IFACEMETHODIMP GetToolTip(_In_opt_ IShellItemArray*, _Outptr_result_nullonfailure_ PWSTR* infoTip)
74     {
75         *infoTip = nullptr;
76         return E_NOTIMPL;
77     }
78     IFACEMETHODIMP GetCanonicalName(_Out_ GUID* guidCommandName)
79     {
80         *guidCommandName = GUID_NULL;
81         return S_OK;
82     }
83     IFACEMETHODIMP GetState(_In_opt_ IShellItemArray* selection, _In_ BOOL okToBeSlow, _Out_ EXPCMDSTATE* cmdState)
84     {
85         *cmdState = State(selection);
86         return S_OK;
87     }
88     IFACEMETHODIMP Invoke(_In_opt_ IShellItemArray* selection, _In_opt_ IBindCtx*) noexcept try
89     {
90         m_pContextMenu->InvokeCommand(Verb());
91         return S_OK;
92     }
93     CATCH_RETURN();
94
95     IFACEMETHODIMP GetFlags(_Out_ EXPCMDFLAGS* flags)
96     {
97         *flags = Flags();
98         return S_OK;
99     }
100     IFACEMETHODIMP EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** enumCommands)
101     {
102         *enumCommands = nullptr;
103         return E_NOTIMPL;
104     }
105
106 protected:
107
108     // code from https://github.com/microsoft/terminal/blob/main/src/cascadia/ShellExtension/OpenTerminalHere.cpp
109     std::wstring _GetPathFromExplorer() const
110     {
111         std::wstring path;
112         HRESULT hr = NOERROR;
113
114         auto hwnd = ::GetForegroundWindow();
115         if (hwnd == nullptr)
116         {
117             return path;
118         }
119
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"))
124         {
125             //special folder: desktop
126             hr = ::SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, szName);
127             if (FAILED(hr))
128             {
129                 return path;
130             }
131
132             path = szName;
133             return path;
134         }
135
136         if (0 != StrCmp(szName, L"CabinetWClass"))
137         {
138             return path;
139         }
140
141         ComPtr<IShellWindows> shell;
142         hr = CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL, IID_IShellWindows, &shell);
143         if (FAILED(hr) || shell == nullptr)
144         {
145             return path;
146         }
147
148         ComPtr<IDispatch> disp;
149         wil::unique_variant variant;
150         variant.vt = VT_I4;
151
152         ComPtr<IWebBrowserApp> browser;
153         // look for correct explorer window
154         for (variant.intVal = 0;
155             shell->Item(variant, &disp) == S_OK;
156             variant.intVal++)
157         {
158             ComPtr<IWebBrowserApp> tmp;
159             if (FAILED(disp->QueryInterface<IWebBrowserApp>(&tmp)))
160             {
161                 disp = nullptr; // get rid of DEBUG non-nullptr warning
162                 continue;
163             }
164
165             HWND tmpHWND = NULL;
166             hr = tmp->get_HWND(reinterpret_cast<SHANDLE_PTR*>(&tmpHWND));
167             if (hwnd == tmpHWND)
168             {
169                 browser = tmp;
170                 disp = nullptr; // get rid of DEBUG non-nullptr warning
171                 break; //found
172             }
173
174             disp = nullptr; // get rid of DEBUG non-nullptr warning
175         }
176
177         if (browser != nullptr)
178         {
179             wil::unique_bstr url;
180             hr = browser->get_LocationURL(&url);
181             if (FAILED(hr))
182             {
183                 return path;
184             }
185
186             std::wstring sUrl(url.get(), SysStringLen(url.get()));
187             DWORD size = MAX_PATH;
188             hr = ::PathCreateFromUrl(sUrl.c_str(), szName, &size, NULL);
189             if (SUCCEEDED(hr))
190             {
191                 path = szName;
192             }
193         }
194
195         return path;
196     }
197
198     std::vector<std::wstring> GetPaths(_In_opt_ IShellItemArray* selection)
199     {
200         std::vector<std::wstring> paths;
201         DWORD dwNumItems = 0;
202         if (!selection)
203         {
204             std::wstring path = _GetPathFromExplorer();
205             if (!path.empty())
206                 paths.push_back(path);
207         }
208         else
209         {
210             selection->GetCount(&dwNumItems);
211             for (DWORD i = 0; i < dwNumItems; ++i)
212             {
213                 ComPtr<IShellItem> psi;
214                 wil::unique_cotaskmem_string pszFilePath;
215                 if (SUCCEEDED(selection->GetItemAt(i, &psi)) &&
216                     SUCCEEDED(psi->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath)))
217                 {
218                     paths.push_back(pszFilePath.get());
219                 }
220             }
221         }
222         return paths;
223     }
224     WinMergeContextMenu* m_pContextMenu;
225 };
226
227 class SubExplorerCommandHandler final : public WinMergeExplorerCommandBase
228 {
229 public:
230     SubExplorerCommandHandler(WinMergeContextMenu *pContextMenu, const MenuItem& menuItem)
231         : WinMergeExplorerCommandBase(pContextMenu)
232         , m_menuItem(menuItem)
233     {
234     }
235     const wchar_t* Title() override
236     {
237         return m_menuItem.text.c_str();
238     }
239     const int IconId(_In_opt_ IShellItemArray* selection) override
240     {
241         return m_menuItem.icon;
242     }
243     const int Verb() override
244     {
245         return m_menuItem.verb;
246     }
247     const EXPCMDSTATE State(_In_opt_ IShellItemArray* selection) override
248     {
249         return m_menuItem.enabled ? ECS_ENABLED : ECS_DISABLED;
250     }
251 private:
252     MenuItem m_menuItem;
253 };
254
255 class EnumCommands : public RuntimeClass<RuntimeClassFlags<ClassicCom>, IEnumExplorerCommand>
256 {
257 public:
258     explicit EnumCommands(WinMergeContextMenu* pContextMenu)
259     {
260         for (auto& menuItem : pContextMenu->GetMenuItemList())
261             m_commands.push_back(Make<SubExplorerCommandHandler>(pContextMenu, menuItem));
262         m_current = m_commands.cbegin();
263     }
264
265     // IEnumExplorerCommand
266     IFACEMETHODIMP Next(ULONG celt, __out_ecount_part(celt, *pceltFetched) IExplorerCommand** apUICommand, __out_opt ULONG* pceltFetched)
267     {
268         ULONG fetched{ 0 };
269         wil::assign_to_opt_param(pceltFetched, 0ul);
270
271         for (ULONG i = 0; (i < celt) && (m_current != m_commands.cend()); i++)
272         {
273             m_current->CopyTo(&apUICommand[0]);
274             m_current++;
275             fetched++;
276         }
277
278         wil::assign_to_opt_param(pceltFetched, fetched);
279         return (fetched == celt) ? S_OK : S_FALSE;
280     }
281
282     IFACEMETHODIMP Skip(ULONG /*celt*/) { return E_NOTIMPL; }
283     IFACEMETHODIMP Reset()
284     {
285         m_current = m_commands.cbegin();
286         return S_OK;
287     }
288     IFACEMETHODIMP Clone(__deref_out IEnumExplorerCommand** ppenum) { *ppenum = nullptr; return E_NOTIMPL; }
289
290 private:
291     std::vector<ComPtr<IExplorerCommand>> m_commands;
292     std::vector<ComPtr<IExplorerCommand>>::const_iterator m_current;
293 };
294
295 class __declspec(uuid("90340779-F37E-468E-9728-A2593498ED32")) WinMergeFileDirExplorerCommandHandler : public WinMergeExplorerCommandBase
296 {
297 public:
298     WinMergeFileDirExplorerCommandHandler()
299         : m_contextMenu(wil::GetModuleInstanceHandle())
300         , WinMergeExplorerCommandBase(&m_contextMenu)
301     {
302     }
303     const wchar_t* Title() override { return L"&WinMerge"; }
304     const int IconId(_In_opt_ IShellItemArray* selection) override
305     {
306         auto paths = GetPaths(selection);
307         if (paths.size() > 0 && PathIsDirectory(paths[0].c_str()))
308             return IDI_WINMERGEDIR;
309         return IDI_WINMERGE;
310     }
311     const int Verb() override { return WinMergeContextMenu::CMD_COMPARE; }
312     const EXPCMDFLAGS Flags() override
313     {
314         // Due to stability issues, the advanced menu is currently disabled.
315         return ECF_DEFAULT;
316         /*
317         if ((m_contextMenu.GetContextMenuEnabled() & (WinMergeContextMenu::EXT_ENABLED | WinMergeContextMenu::EXT_ADVANCED)) == (WinMergeContextMenu::EXT_ENABLED | WinMergeContextMenu::EXT_ADVANCED))
318             return ECF_HASSUBCOMMANDS;
319         else
320             return ECF_DEFAULT;
321          */
322     }
323     const EXPCMDSTATE State(_In_opt_ IShellItemArray* selection) override
324     {
325         auto paths = GetPaths(selection);
326         if (paths.size() > 3)
327             return ECS_DISABLED;
328         m_contextMenu.UpdateMenuState(paths);
329         if (m_contextMenu.GetMenuState() == WinMergeContextMenu::MENU_HIDDEN)
330         {
331             return ECS_HIDDEN;
332         }
333         return ECS_ENABLED;
334     }
335     IFACEMETHODIMP EnumSubCommands(_COM_Outptr_ IEnumExplorerCommand** enumCommands)
336     {
337         *enumCommands = nullptr;
338         auto e = Make<EnumCommands>(&m_contextMenu);
339         return e->QueryInterface(IID_PPV_ARGS(enumCommands));
340     }
341 private:
342     WinMergeContextMenu m_contextMenu;
343 };
344
345 CoCreatableClass(WinMergeFileDirExplorerCommandHandler)
346
347 CoCreatableClassWrlCreatorMapInclude(WinMergeFileDirExplorerCommandHandler)
348
349
350 STDAPI DllGetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ IActivationFactory** factory)
351 {
352     return Module<ModuleType::InProc>::GetModule().GetActivationFactory(activatableClassId, factory);
353 }
354
355 STDAPI DllCanUnloadNow()
356 {
357     return Module<InProc>::GetModule().GetObjectCount() == 0 ? S_OK : S_FALSE;
358 }
359
360 STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _COM_Outptr_ void** instance)
361 {
362     return Module<InProc>::GetModule().GetClassObject(rclsid, riid, instance);
363 }