1 /*
2 * provides SendTo shell item service
3 *
4 * Copyright 2019 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21 #include "precomp.h"
22
23 WINE_DEFAULT_DEBUG_CHANNEL(shell);
24
CSendToMenu()25 CSendToMenu::CSendToMenu()
26 : m_hSubMenu(NULL)
27 , m_pItems(NULL)
28 , m_idCmdFirst(0)
29 {
30 HRESULT hr = SHGetDesktopFolder(&m_pDesktop);
31 if (FAILED(hr))
32 {
33 ERR("SHGetDesktopFolder: %08lX\n", hr);
34 }
35
36 GetSpecialFolder(NULL, &m_pSendTo, CSIDL_SENDTO);
37 }
38
~CSendToMenu()39 CSendToMenu::~CSendToMenu()
40 {
41 UnloadAllItems();
42
43 if (m_hSubMenu)
44 {
45 DestroyMenu(m_hSubMenu);
46 m_hSubMenu = NULL;
47 }
48 }
49
DoDrop(IDataObject * pDataObject,IDropTarget * pDropTarget)50 HRESULT CSendToMenu::DoDrop(IDataObject *pDataObject, IDropTarget *pDropTarget)
51 {
52 DWORD dwEffect = DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK;
53
54 BOOL bShift = (GetAsyncKeyState(VK_SHIFT) < 0);
55 BOOL bCtrl = (GetAsyncKeyState(VK_CONTROL) < 0);
56
57 // THIS CODE IS NOT HUMAN-FRIENDLY. SORRY.
58 // (We have to translate a SendTo action to a Drop action)
59 DWORD dwKeyState = MK_LBUTTON;
60 if (bShift && bCtrl)
61 dwKeyState |= MK_SHIFT | MK_CONTROL;
62 else if (!bShift)
63 dwKeyState |= MK_CONTROL;
64 if (bCtrl)
65 dwKeyState |= MK_SHIFT;
66
67 POINTL ptl = { 0, 0 };
68 HRESULT hr = pDropTarget->DragEnter(pDataObject, dwKeyState, ptl, &dwEffect);
69 if (FAILED_UNEXPECTEDLY(hr))
70 {
71 pDropTarget->DragLeave();
72 return hr;
73 }
74
75 if (dwEffect == DROPEFFECT_NONE)
76 {
77 ERR("DROPEFFECT_NONE\n");
78 pDropTarget->DragLeave();
79 return E_FAIL;
80 }
81
82 // THIS CODE IS NOT HUMAN-FRIENDLY. SORRY.
83 // (We have to translate a SendTo action to a Drop action)
84 if (bShift && bCtrl)
85 dwEffect = DROPEFFECT_LINK;
86 else if (!bShift)
87 dwEffect = DROPEFFECT_MOVE;
88 else
89 dwEffect = DROPEFFECT_COPY;
90
91 hr = pDropTarget->Drop(pDataObject, dwKeyState, ptl, &dwEffect);
92 if (FAILED_UNEXPECTEDLY(hr))
93 return hr;
94
95 return hr;
96 }
97
98 // get an IShellFolder from CSIDL
99 HRESULT
GetSpecialFolder(HWND hwnd,IShellFolder ** ppFolder,int csidl,PIDLIST_ABSOLUTE * ppidl)100 CSendToMenu::GetSpecialFolder(HWND hwnd, IShellFolder **ppFolder,
101 int csidl, PIDLIST_ABSOLUTE *ppidl)
102 {
103 if (!ppFolder)
104 return E_POINTER;
105 *ppFolder = NULL;
106
107 if (ppidl)
108 *ppidl = NULL;
109
110 CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidl;
111 HRESULT hr = SHGetSpecialFolderLocation(hwnd, csidl, &pidl);
112 if (FAILED_UNEXPECTEDLY(hr))
113 return hr;
114
115 IShellFolder *pFolder = NULL;
116 hr = m_pDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &pFolder));
117
118 if (ppidl)
119 *ppidl = pidl.Detach();
120
121 if (FAILED_UNEXPECTEDLY(hr))
122 return hr;
123
124 *ppFolder = pFolder;
125 return hr;
126 }
127
128 // get a UI object from PIDL
GetUIObjectFromPidl(HWND hwnd,PIDLIST_ABSOLUTE pidl,REFIID riid,LPVOID * ppvOut)129 HRESULT CSendToMenu::GetUIObjectFromPidl(HWND hwnd, PIDLIST_ABSOLUTE pidl,
130 REFIID riid, LPVOID *ppvOut)
131 {
132 *ppvOut = NULL;
133
134 PCITEMID_CHILD pidlLast;
135 CComPtr<IShellFolder> pFolder;
136 HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &pFolder), &pidlLast);
137 if (FAILED_UNEXPECTEDLY(hr))
138 return hr;
139
140 hr = pFolder->GetUIObjectOf(hwnd, 1, &pidlLast, riid, NULL, ppvOut);
141 if (FAILED_UNEXPECTEDLY(hr))
142 return hr;
143
144 return hr;
145 }
146
UnloadAllItems()147 void CSendToMenu::UnloadAllItems()
148 {
149 SENDTO_ITEM *pItems = m_pItems;
150 m_pItems = NULL;
151 while (pItems)
152 {
153 SENDTO_ITEM *pCurItem = pItems;
154 pItems = pItems->pNext;
155 delete pCurItem;
156 }
157 }
158
LoadAllItems(HWND hwnd)159 HRESULT CSendToMenu::LoadAllItems(HWND hwnd)
160 {
161 UnloadAllItems();
162
163 CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlSendTo;
164
165 m_pSendTo.Release();
166 HRESULT hr = GetSpecialFolder(hwnd, &m_pSendTo, CSIDL_SENDTO, &pidlSendTo);
167 if (FAILED_UNEXPECTEDLY(hr))
168 return hr;
169
170 CComPtr<IEnumIDList> pEnumIDList;
171 hr = m_pSendTo->EnumObjects(hwnd,
172 SHCONTF_FOLDERS | SHCONTF_NONFOLDERS,
173 &pEnumIDList);
174 if (FAILED_UNEXPECTEDLY(hr))
175 return hr;
176
177 hr = S_OK;
178 PITEMID_CHILD child;
179 while (pEnumIDList->Next(1, &child, NULL) == S_OK)
180 {
181 CComHeapPtr<ITEMID_CHILD> pidlChild(child);
182
183 STRRET strret;
184 hr = m_pSendTo->GetDisplayNameOf(pidlChild, SHGDN_NORMAL, &strret);
185 if (FAILED_UNEXPECTEDLY(hr))
186 continue;
187
188 CComHeapPtr<WCHAR> pszText;
189 hr = StrRetToStrW(&strret, pidlChild, &pszText);
190 if (FAILED_UNEXPECTEDLY(hr))
191 continue;
192
193 CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlAbsolute;
194 pidlAbsolute.Attach(ILCombine(pidlSendTo, pidlChild));
195
196 SHFILEINFOW fi = { NULL };
197 const UINT uFlags = SHGFI_PIDL | SHGFI_TYPENAME |
198 SHGFI_ICON | SHGFI_SMALLICON;
199 SHGetFileInfoW(reinterpret_cast<LPWSTR>(static_cast<PIDLIST_ABSOLUTE>(pidlAbsolute)), 0,
200 &fi, sizeof(fi), uFlags);
201
202 SENDTO_ITEM *pNewItem =
203 new SENDTO_ITEM(pidlChild.Detach(), pszText.Detach(), fi.hIcon);
204 if (m_pItems)
205 {
206 pNewItem->pNext = m_pItems;
207 }
208 m_pItems = pNewItem;
209 }
210
211 return hr;
212 }
213
InsertSendToItems(HMENU hMenu,UINT idCmdFirst,UINT Pos)214 UINT CSendToMenu::InsertSendToItems(HMENU hMenu, UINT idCmdFirst, UINT Pos)
215 {
216 if (m_pItems == NULL)
217 {
218 HRESULT hr = LoadAllItems(NULL);
219 if (FAILED_UNEXPECTEDLY(hr))
220 return 0;
221 }
222
223 m_idCmdFirst = idCmdFirst;
224
225 UINT idCmd = idCmdFirst;
226 for (SENDTO_ITEM *pCurItem = m_pItems; pCurItem; pCurItem = pCurItem->pNext)
227 {
228 const UINT uFlags = MF_BYPOSITION | MF_STRING | MF_ENABLED;
229 if (InsertMenuW(hMenu, Pos, uFlags, idCmd, pCurItem->pszText))
230 {
231 MENUITEMINFOW mii;
232 mii.cbSize = sizeof(mii);
233 mii.fMask = MIIM_DATA | MIIM_BITMAP;
234 mii.dwItemData = reinterpret_cast<ULONG_PTR>(pCurItem);
235 mii.hbmpItem = HBMMENU_CALLBACK;
236 SetMenuItemInfoW(hMenu, idCmd, FALSE, &mii);
237 ++idCmd;
238
239 // successful
240 }
241 }
242
243 if (idCmd == idCmdFirst)
244 {
245 CStringW strNone(MAKEINTRESOURCEW(IDS_NONE));
246 AppendMenuW(hMenu, MF_GRAYED | MF_DISABLED | MF_STRING, idCmd, strNone);
247 ++idCmd;
248 }
249
250 return idCmd - idCmdFirst;
251 }
252
FindItemFromIdOffset(UINT IdOffset)253 CSendToMenu::SENDTO_ITEM *CSendToMenu::FindItemFromIdOffset(UINT IdOffset)
254 {
255 UINT idCmd = m_idCmdFirst + IdOffset;
256
257 MENUITEMINFOW mii = { sizeof(mii) };
258 mii.fMask = MIIM_DATA;
259 if (GetMenuItemInfoW(m_hSubMenu, idCmd, FALSE, &mii))
260 return reinterpret_cast<SENDTO_ITEM *>(mii.dwItemData);
261
262 ERR("GetMenuItemInfoW: %ld\n", GetLastError());
263 return NULL;
264 }
265
DoSendToItem(SENDTO_ITEM * pItem,LPCMINVOKECOMMANDINFO lpici)266 HRESULT CSendToMenu::DoSendToItem(SENDTO_ITEM *pItem, LPCMINVOKECOMMANDINFO lpici)
267 {
268 if (!m_pDataObject)
269 {
270 ERR("!m_pDataObject\n");
271 return E_FAIL;
272 }
273
274 HRESULT hr;
275 CComPtr<IDropTarget> pDropTarget;
276 hr = m_pSendTo->GetUIObjectOf(NULL, 1, &pItem->pidlChild, IID_IDropTarget,
277 NULL, (LPVOID *)&pDropTarget);
278 if (FAILED_UNEXPECTEDLY(hr))
279 return hr;
280
281 hr = DoDrop(m_pDataObject, pDropTarget);
282 if (FAILED_UNEXPECTEDLY(hr))
283 return hr;
284
285 return hr;
286 }
287
288 STDMETHODIMP
QueryContextMenu(HMENU hMenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags)289 CSendToMenu::QueryContextMenu(HMENU hMenu,
290 UINT indexMenu,
291 UINT idCmdFirst,
292 UINT idCmdLast,
293 UINT uFlags)
294 {
295 TRACE("%p %p %u %u %u %u\n", this,
296 hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
297
298 if (uFlags & (CMF_NOVERBS | CMF_VERBSONLY))
299 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, idCmdFirst);
300
301 HMENU hSubMenu = CreateMenu();
302 if (!hSubMenu)
303 {
304 ERR("CreateMenu: %ld\n", GetLastError());
305 return E_FAIL;
306 }
307
308 UINT cItems = InsertSendToItems(hSubMenu, idCmdFirst, 0);
309
310 CStringW strSendTo(MAKEINTRESOURCEW(IDS_SENDTO_MENU));
311
312 MENUITEMINFOW mii = { sizeof(mii) };
313 mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE | MIIM_SUBMENU;
314 mii.fType = MFT_STRING;
315 mii.wID = -1;
316 mii.dwTypeData = strSendTo.GetBuffer();
317 mii.cch = wcslen(mii.dwTypeData);
318 mii.fState = MFS_ENABLED;
319 mii.hSubMenu = hSubMenu;
320 if (!InsertMenuItemW(hMenu, indexMenu, TRUE, &mii))
321 {
322 ERR("InsertMenuItemW: %ld\n", GetLastError());
323 return E_FAIL;
324 }
325
326 HMENU hOldSubMenu = m_hSubMenu;
327 m_hSubMenu = hSubMenu;
328 DestroyMenu(hOldSubMenu);
329
330 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, idCmdFirst + cItems);
331 }
332
333 STDMETHODIMP
InvokeCommand(LPCMINVOKECOMMANDINFO lpici)334 CSendToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
335 {
336 HRESULT hr = E_FAIL;
337
338 WORD idCmd = LOWORD(lpici->lpVerb);
339 TRACE("idCmd: %d\n", idCmd);
340
341 SENDTO_ITEM *pItem = FindItemFromIdOffset(idCmd);
342 if (pItem)
343 {
344 hr = DoSendToItem(pItem, lpici);
345 }
346
347 TRACE("CSendToMenu::InvokeCommand %x\n", hr);
348 return hr;
349 }
350
351 STDMETHODIMP
GetCommandString(UINT_PTR idCmd,UINT uType,UINT * pwReserved,LPSTR pszName,UINT cchMax)352 CSendToMenu::GetCommandString(UINT_PTR idCmd,
353 UINT uType,
354 UINT *pwReserved,
355 LPSTR pszName,
356 UINT cchMax)
357 {
358 FIXME("%p %lu %u %p %p %u\n", this,
359 idCmd, uType, pwReserved, pszName, cchMax);
360
361 return E_NOTIMPL;
362 }
363
364 STDMETHODIMP
HandleMenuMsg(UINT uMsg,WPARAM wParam,LPARAM lParam)365 CSendToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
366 {
367 return S_OK;
368 }
369
370 STDMETHODIMP
HandleMenuMsg2(UINT uMsg,WPARAM wParam,LPARAM lParam,LRESULT * plResult)371 CSendToMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam,
372 LRESULT *plResult)
373 {
374 UINT cxSmall = GetSystemMetrics(SM_CXSMICON);
375 UINT cySmall = GetSystemMetrics(SM_CYSMICON);
376
377 switch (uMsg)
378 {
379 case WM_MEASUREITEM:
380 {
381 MEASUREITEMSTRUCT* lpmis = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
382 if (!lpmis || lpmis->CtlType != ODT_MENU)
383 break;
384
385 UINT cxMenuCheck = GetSystemMetrics(SM_CXMENUCHECK);
386 if (lpmis->itemWidth < cxMenuCheck)
387 lpmis->itemWidth = cxMenuCheck;
388 if (lpmis->itemHeight < cySmall)
389 lpmis->itemHeight = cySmall;
390
391 if (plResult)
392 *plResult = TRUE;
393 break;
394 }
395 case WM_DRAWITEM:
396 {
397 DRAWITEMSTRUCT* lpdis = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
398 if (!lpdis || lpdis->CtlType != ODT_MENU)
399 break;
400
401 SENDTO_ITEM *pItem = reinterpret_cast<SENDTO_ITEM *>(lpdis->itemData);
402 HICON hIcon = NULL;
403 if (pItem)
404 hIcon = pItem->hIcon;
405 if (!hIcon)
406 break;
407
408 const RECT& rcItem = lpdis->rcItem;
409 INT x = 4;
410 INT y = lpdis->rcItem.top;
411 y += (rcItem.bottom - rcItem.top - cySmall) / 2;
412 DrawIconEx(lpdis->hDC, x, y, hIcon, cxSmall, cySmall,
413 0, NULL, DI_NORMAL);
414
415 if (plResult)
416 *plResult = TRUE;
417 }
418 }
419
420 return S_OK;
421 }
422
423 STDMETHODIMP
Initialize(PCIDLIST_ABSOLUTE pidlFolder,IDataObject * pdtobj,HKEY hkeyProgID)424 CSendToMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder,
425 IDataObject *pdtobj, HKEY hkeyProgID)
426 {
427 m_pDataObject = pdtobj;
428 return S_OK;
429 }
430