[SHELL32] Improve and fix SendTo (#2122)
[reactos.git] / dll / win32 / shell32 / CSendToMenu.cpp
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
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
39 CSendToMenu::~CSendToMenu()
40 {
41 UnloadAllItems();
42
43 if (m_hSubMenu)
44 {
45 DestroyMenu(m_hSubMenu);
46 m_hSubMenu = NULL;
47 }
48 }
49
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
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
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
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
159 BOOL CSendToMenu::FolderHasAnyItems() const
160 {
161 WCHAR szPath[MAX_PATH];
162 SHGetSpecialFolderPathW(NULL, szPath, CSIDL_SENDTO, FALSE);
163
164 PathAppendW(szPath, L"*");
165
166 WIN32_FIND_DATAW find;
167 HANDLE hFind = FindFirstFileW(szPath, &find);
168 if (hFind == INVALID_HANDLE_VALUE)
169 return FALSE;
170
171 BOOL bFound = FALSE;
172 do
173 {
174 if (wcscmp(find.cFileName, L".") == 0 ||
175 wcscmp(find.cFileName, L"..") == 0 ||
176 _wcsicmp(find.cFileName, L"desktop.ini") == 0)
177 {
178 continue;
179 }
180
181 bFound = TRUE;
182 break;
183 } while (FindNextFileW(hFind, &find));
184
185 FindClose(hFind);
186 return bFound;
187 }
188
189 static BOOL CreateEmptyFile(LPCWSTR pszFile)
190 {
191 HANDLE hFile;
192 hFile = CreateFileW(pszFile, GENERIC_WRITE, FILE_SHARE_READ, NULL,
193 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
194 CloseHandle(hFile);
195 return hFile != INVALID_HANDLE_VALUE;
196 }
197
198 static HRESULT
199 CreateShellLink(
200 LPCWSTR pszLinkPath,
201 LPCWSTR pszTargetPath OPTIONAL,
202 LPCITEMIDLIST pidlTarget OPTIONAL,
203 LPCWSTR pszArg OPTIONAL,
204 LPCWSTR pszDir OPTIONAL,
205 LPCWSTR pszIconPath OPTIONAL,
206 INT iIconNr OPTIONAL,
207 LPCWSTR pszComment OPTIONAL)
208 {
209 CComPtr<IShellLinkW> psl;
210 HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL,
211 CLSCTX_INPROC_SERVER,
212 IID_PPV_ARG(IShellLinkW, &psl));
213 if (FAILED_UNEXPECTEDLY(hr))
214 return hr;
215
216 if (pszTargetPath)
217 {
218 hr = psl->SetPath(pszTargetPath);
219 if (FAILED_UNEXPECTEDLY(hr))
220 return hr;
221 }
222 else if (pidlTarget)
223 {
224 hr = psl->SetIDList(pidlTarget);
225 if (FAILED_UNEXPECTEDLY(hr))
226 return hr;
227 }
228 else
229 {
230 ERR("invalid argument\n");
231 return E_INVALIDARG;
232 }
233
234 if (pszArg)
235 hr = psl->SetArguments(pszArg);
236
237 if (pszDir)
238 hr = psl->SetWorkingDirectory(pszDir);
239
240 if (pszIconPath)
241 hr = psl->SetIconLocation(pszIconPath, iIconNr);
242
243 if (pszComment)
244 hr = psl->SetDescription(pszComment);
245
246 CComPtr<IPersistFile> ppf;
247 hr = psl->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
248 if (FAILED_UNEXPECTEDLY(hr))
249 return hr;
250
251 hr = ppf->Save(pszLinkPath, TRUE);
252 if (FAILED_UNEXPECTEDLY(hr))
253 return hr;
254
255 return hr;
256 }
257
258 HRESULT CSendToMenu::CreateSendToFiles(LPCWSTR pszSendTo)
259 {
260 WCHAR szTarget[MAX_PATH];
261 WCHAR szSendToFile[MAX_PATH];
262 WCHAR szShell32[MAX_PATH];
263 HRESULT hr;
264
265 /* create my documents */
266 SHGetSpecialFolderPathW(NULL, szTarget, CSIDL_MYDOCUMENTS, FALSE);
267
268 StringCbCopyW(szSendToFile, sizeof(szSendToFile), pszSendTo);
269 PathAppendW(szSendToFile, PathFindFileNameW(szTarget));
270 StringCbCatW(szSendToFile, sizeof(szSendToFile), L".lnk");
271
272 GetSystemDirectoryW(szShell32, ARRAY_SIZE(szShell32));
273 PathAppendW(szShell32, L"shell32.dll");
274 hr = CreateShellLink(szSendToFile, szTarget, NULL, NULL, NULL,
275 szShell32, -IDI_SHELL_MY_DOCUMENTS, NULL);
276 if (FAILED_UNEXPECTEDLY(hr))
277 ;
278
279 /* create desklink */
280 StringCbCopyW(szSendToFile, sizeof(szSendToFile), pszSendTo);
281 LoadStringW(shell32_hInstance, IDS_DESKLINK, szTarget, _countof(szTarget));
282 StringCbCatW(szTarget, sizeof(szTarget), L".DeskLink");
283 PathAppendW(szSendToFile, szTarget);
284 if (!CreateEmptyFile(szSendToFile))
285 {
286 ERR("CreateEmptyFile\n");
287 }
288
289 /* create zipped compressed folder */
290 HINSTANCE hZipFldr =
291 LoadLibraryExW(L"zipfldr.dll", NULL, LOAD_LIBRARY_AS_DATAFILE);
292 if (hZipFldr)
293 {
294 #define IDS_FRIENDLYNAME 10195
295 LoadStringW(hZipFldr, IDS_FRIENDLYNAME, szTarget, _countof(szTarget));
296 #undef IDS_FRIENDLYNAME
297 FreeLibrary(hZipFldr);
298
299 StringCbCopyW(szSendToFile, sizeof(szSendToFile), pszSendTo);
300 PathAppendW(szSendToFile, szTarget);
301 StringCbCatW(szSendToFile, sizeof(szSendToFile), L".ZFSendToTarget");
302 if (!CreateEmptyFile(szSendToFile))
303 {
304 ERR("CreateEmptyFile\n");
305 }
306 }
307
308 return S_OK;
309 }
310
311 HRESULT CSendToMenu::LoadAllItems(HWND hwnd)
312 {
313 UnloadAllItems();
314
315 if (!FolderHasAnyItems())
316 {
317 WCHAR szPath[MAX_PATH];
318 SHGetSpecialFolderPathW(NULL, szPath, CSIDL_SENDTO, FALSE);
319 CreateSendToFiles(szPath);
320 }
321
322 CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlSendTo;
323
324 m_pSendTo.Release();
325 HRESULT hr = GetSpecialFolder(hwnd, &m_pSendTo, CSIDL_SENDTO, &pidlSendTo);
326 if (FAILED_UNEXPECTEDLY(hr))
327 return hr;
328
329 CComPtr<IEnumIDList> pEnumIDList;
330 hr = m_pSendTo->EnumObjects(hwnd,
331 SHCONTF_FOLDERS | SHCONTF_NONFOLDERS,
332 &pEnumIDList);
333 if (FAILED_UNEXPECTEDLY(hr))
334 return hr;
335
336 hr = S_OK;
337 PITEMID_CHILD child;
338 while (pEnumIDList->Next(1, &child, NULL) == S_OK)
339 {
340 CComHeapPtr<ITEMID_CHILD> pidlChild(child);
341
342 STRRET strret;
343 hr = m_pSendTo->GetDisplayNameOf(pidlChild, SHGDN_NORMAL, &strret);
344 if (FAILED_UNEXPECTEDLY(hr))
345 continue;
346
347 CComHeapPtr<WCHAR> pszText;
348 hr = StrRetToStrW(&strret, pidlChild, &pszText);
349 if (FAILED_UNEXPECTEDLY(hr))
350 continue;
351
352 CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlAbsolute;
353 pidlAbsolute.Attach(ILCombine(pidlSendTo, pidlChild));
354
355 SHFILEINFOW fi = { NULL };
356 const UINT uFlags = SHGFI_PIDL | SHGFI_TYPENAME |
357 SHGFI_ICON | SHGFI_SMALLICON;
358 SHGetFileInfoW(reinterpret_cast<LPWSTR>(static_cast<PIDLIST_ABSOLUTE>(pidlAbsolute)), 0,
359 &fi, sizeof(fi), uFlags);
360
361 SENDTO_ITEM *pNewItem =
362 new SENDTO_ITEM(pidlChild.Detach(), pszText.Detach(), fi.hIcon);
363 if (m_pItems)
364 {
365 pNewItem->pNext = m_pItems;
366 }
367 m_pItems = pNewItem;
368 }
369
370 return hr;
371 }
372
373 UINT CSendToMenu::InsertSendToItems(HMENU hMenu, UINT idCmdFirst, UINT Pos)
374 {
375 if (m_pItems == NULL)
376 {
377 HRESULT hr = LoadAllItems(NULL);
378 if (FAILED_UNEXPECTEDLY(hr))
379 return 0;
380 }
381
382 m_idCmdFirst = idCmdFirst;
383
384 UINT idCmd = idCmdFirst;
385 for (SENDTO_ITEM *pCurItem = m_pItems; pCurItem; pCurItem = pCurItem->pNext)
386 {
387 const UINT uFlags = MF_BYPOSITION | MF_STRING | MF_ENABLED;
388 if (InsertMenuW(hMenu, Pos, uFlags, idCmd, pCurItem->pszText))
389 {
390 MENUITEMINFOW mii;
391 mii.cbSize = sizeof(mii);
392 mii.fMask = MIIM_DATA | MIIM_BITMAP;
393 mii.dwItemData = reinterpret_cast<ULONG_PTR>(pCurItem);
394 mii.hbmpItem = HBMMENU_CALLBACK;
395 SetMenuItemInfoW(hMenu, idCmd, FALSE, &mii);
396 ++idCmd;
397
398 // successful
399 }
400 }
401
402 if (idCmd == idCmdFirst)
403 {
404 CStringW strNone(MAKEINTRESOURCEW(IDS_NONE));
405 AppendMenuW(hMenu, MF_GRAYED | MF_DISABLED | MF_STRING, idCmd, strNone);
406 }
407
408 return idCmd - idCmdFirst;
409 }
410
411 CSendToMenu::SENDTO_ITEM *CSendToMenu::FindItemFromIdOffset(UINT IdOffset)
412 {
413 UINT idCmd = m_idCmdFirst + IdOffset;
414
415 MENUITEMINFOW mii = { sizeof(mii) };
416 mii.fMask = MIIM_DATA;
417 if (GetMenuItemInfoW(m_hSubMenu, idCmd, FALSE, &mii))
418 return reinterpret_cast<SENDTO_ITEM *>(mii.dwItemData);
419
420 ERR("GetMenuItemInfoW: %ld\n", GetLastError());
421 return NULL;
422 }
423
424 HRESULT CSendToMenu::DoSendToItem(SENDTO_ITEM *pItem, LPCMINVOKECOMMANDINFO lpici)
425 {
426 if (!m_pDataObject)
427 {
428 ERR("!m_pDataObject\n");
429 return E_FAIL;
430 }
431
432 HRESULT hr;
433 CComPtr<IDropTarget> pDropTarget;
434 hr = m_pSendTo->GetUIObjectOf(NULL, 1, &pItem->pidlChild, IID_IDropTarget,
435 NULL, (LPVOID *)&pDropTarget);
436 if (FAILED_UNEXPECTEDLY(hr))
437 return hr;
438
439 hr = DoDrop(m_pDataObject, pDropTarget);
440 if (FAILED_UNEXPECTEDLY(hr))
441 return hr;
442
443 return hr;
444 }
445
446 STDMETHODIMP
447 CSendToMenu::QueryContextMenu(HMENU hMenu,
448 UINT indexMenu,
449 UINT idCmdFirst,
450 UINT idCmdLast,
451 UINT uFlags)
452 {
453 TRACE("%p %p %u %u %u %u\n", this,
454 hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
455
456 HMENU hSubMenu = CreateMenu();
457 if (!hSubMenu)
458 {
459 ERR("CreateMenu: %ld\n", GetLastError());
460 return E_FAIL;
461 }
462
463 UINT cItems = InsertSendToItems(hSubMenu, idCmdFirst, 0);
464
465 CStringW strSendTo(MAKEINTRESOURCEW(IDS_SENDTO_MENU));
466
467 MENUITEMINFOW mii = { sizeof(mii) };
468 mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE | MIIM_SUBMENU;
469 mii.fType = MFT_STRING;
470 mii.wID = -1;
471 mii.dwTypeData = strSendTo.GetBuffer();
472 mii.cch = wcslen(mii.dwTypeData);
473 mii.fState = MFS_ENABLED;
474 mii.hSubMenu = hSubMenu;
475 if (!InsertMenuItemW(hMenu, indexMenu, TRUE, &mii))
476 {
477 ERR("InsertMenuItemW: %ld\n", GetLastError());
478 return E_FAIL;
479 }
480
481 HMENU hOldSubMenu = m_hSubMenu;
482 m_hSubMenu = hSubMenu;
483 DestroyMenu(hOldSubMenu);
484
485 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cItems);
486 }
487
488 STDMETHODIMP
489 CSendToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
490 {
491 HRESULT hr = E_FAIL;
492
493 WORD idCmd = LOWORD(lpici->lpVerb);
494 TRACE("idCmd: %d\n", idCmd);
495
496 SENDTO_ITEM *pItem = FindItemFromIdOffset(idCmd);
497 if (pItem)
498 {
499 hr = DoSendToItem(pItem, lpici);
500 }
501
502 TRACE("CSendToMenu::InvokeCommand %x\n", hr);
503 return hr;
504 }
505
506 STDMETHODIMP
507 CSendToMenu::GetCommandString(UINT_PTR idCmd,
508 UINT uType,
509 UINT *pwReserved,
510 LPSTR pszName,
511 UINT cchMax)
512 {
513 FIXME("%p %lu %u %p %p %u\n", this,
514 idCmd, uType, pwReserved, pszName, cchMax);
515
516 return E_NOTIMPL;
517 }
518
519 STDMETHODIMP
520 CSendToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
521 {
522 return S_OK;
523 }
524
525 STDMETHODIMP
526 CSendToMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam,
527 LRESULT *plResult)
528 {
529 UINT cxSmall = GetSystemMetrics(SM_CXSMICON);
530 UINT cySmall = GetSystemMetrics(SM_CYSMICON);
531
532 switch (uMsg)
533 {
534 case WM_MEASUREITEM:
535 {
536 MEASUREITEMSTRUCT* lpmis = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
537 if (!lpmis || lpmis->CtlType != ODT_MENU)
538 break;
539
540 UINT cxMenuCheck = GetSystemMetrics(SM_CXMENUCHECK);
541 if (lpmis->itemWidth < cxMenuCheck)
542 lpmis->itemWidth = cxMenuCheck;
543 if (lpmis->itemHeight < cySmall)
544 lpmis->itemHeight = cySmall;
545
546 if (plResult)
547 *plResult = TRUE;
548 break;
549 }
550 case WM_DRAWITEM:
551 {
552 DRAWITEMSTRUCT* lpdis = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
553 if (!lpdis || lpdis->CtlType != ODT_MENU)
554 break;
555
556 SENDTO_ITEM *pItem = reinterpret_cast<SENDTO_ITEM *>(lpdis->itemData);
557 HICON hIcon = NULL;
558 if (pItem)
559 hIcon = pItem->hIcon;
560 if (!hIcon)
561 break;
562
563 const RECT& rcItem = lpdis->rcItem;
564 INT x = 4;
565 INT y = lpdis->rcItem.top;
566 y += (rcItem.bottom - rcItem.top - cySmall) / 2;
567 DrawIconEx(lpdis->hDC, x, y, hIcon, cxSmall, cySmall,
568 0, NULL, DI_NORMAL);
569
570 if (plResult)
571 *plResult = TRUE;
572 }
573 }
574
575 return S_OK;
576 }
577
578 STDMETHODIMP
579 CSendToMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder,
580 IDataObject *pdtobj, HKEY hkeyProgID)
581 {
582 m_pDataObject = pdtobj;
583 return S_OK;
584 }