xref: /reactos/dll/win32/shell32/CSendToMenu.cpp (revision 1ee511a1)
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         ERR("CreateShellLink(%S, %S) failed!\n", szSendToFile, szTarget);
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 }
585