2 * provides SendTo shell item service
4 * Copyright 2019 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
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.
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.
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
23 WINE_DEFAULT_DEBUG_CHANNEL(shell
);
25 CSendToMenu::CSendToMenu()
30 HRESULT hr
= SHGetDesktopFolder(&m_pDesktop
);
33 ERR("SHGetDesktopFolder: %08lX\n", hr
);
36 GetSpecialFolder(NULL
, &m_pSendTo
, CSIDL_SENDTO
);
39 CSendToMenu::~CSendToMenu()
45 DestroyMenu(m_hSubMenu
);
50 HRESULT
CSendToMenu::DoDrop(IDataObject
*pDataObject
, IDropTarget
*pDropTarget
)
52 DWORD dwEffect
= DROPEFFECT_MOVE
| DROPEFFECT_COPY
| DROPEFFECT_LINK
;
54 BOOL bShift
= (GetAsyncKeyState(VK_SHIFT
) < 0);
55 BOOL bCtrl
= (GetAsyncKeyState(VK_CONTROL
) < 0);
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
;
61 dwKeyState
|= MK_SHIFT
| MK_CONTROL
;
63 dwKeyState
|= MK_CONTROL
;
65 dwKeyState
|= MK_SHIFT
;
67 POINTL ptl
= { 0, 0 };
68 HRESULT hr
= pDropTarget
->DragEnter(pDataObject
, dwKeyState
, ptl
, &dwEffect
);
69 if (FAILED_UNEXPECTEDLY(hr
))
71 pDropTarget
->DragLeave();
75 if (dwEffect
== DROPEFFECT_NONE
)
77 ERR("DROPEFFECT_NONE\n");
78 pDropTarget
->DragLeave();
82 // THIS CODE IS NOT HUMAN-FRIENDLY. SORRY.
83 // (We have to translate a SendTo action to a Drop action)
85 dwEffect
= DROPEFFECT_LINK
;
87 dwEffect
= DROPEFFECT_MOVE
;
89 dwEffect
= DROPEFFECT_COPY
;
91 hr
= pDropTarget
->Drop(pDataObject
, dwKeyState
, ptl
, &dwEffect
);
92 if (FAILED_UNEXPECTEDLY(hr
))
98 // get an IShellFolder from CSIDL
100 CSendToMenu::GetSpecialFolder(HWND hwnd
, IShellFolder
**ppFolder
,
101 int csidl
, PIDLIST_ABSOLUTE
*ppidl
)
110 CComHeapPtr
<ITEMIDLIST_ABSOLUTE
> pidl
;
111 HRESULT hr
= SHGetSpecialFolderLocation(hwnd
, csidl
, &pidl
);
112 if (FAILED_UNEXPECTEDLY(hr
))
115 IShellFolder
*pFolder
= NULL
;
116 hr
= m_pDesktop
->BindToObject(pidl
, NULL
, IID_PPV_ARG(IShellFolder
, &pFolder
));
119 *ppidl
= pidl
.Detach();
121 if (FAILED_UNEXPECTEDLY(hr
))
128 // get a UI object from PIDL
129 HRESULT
CSendToMenu::GetUIObjectFromPidl(HWND hwnd
, PIDLIST_ABSOLUTE pidl
,
130 REFIID riid
, LPVOID
*ppvOut
)
134 PCITEMID_CHILD pidlLast
;
135 CComPtr
<IShellFolder
> pFolder
;
136 HRESULT hr
= SHBindToParent(pidl
, IID_PPV_ARG(IShellFolder
, &pFolder
), &pidlLast
);
137 if (FAILED_UNEXPECTEDLY(hr
))
140 hr
= pFolder
->GetUIObjectOf(hwnd
, 1, &pidlLast
, riid
, NULL
, ppvOut
);
141 if (FAILED_UNEXPECTEDLY(hr
))
147 void CSendToMenu::UnloadAllItems()
149 SENDTO_ITEM
*pItems
= m_pItems
;
153 SENDTO_ITEM
*pCurItem
= pItems
;
154 pItems
= pItems
->pNext
;
159 BOOL
CSendToMenu::FolderHasAnyItems() const
161 WCHAR szPath
[MAX_PATH
];
162 SHGetSpecialFolderPathW(NULL
, szPath
, CSIDL_SENDTO
, FALSE
);
164 PathAppendW(szPath
, L
"*");
166 WIN32_FIND_DATAW find
;
167 HANDLE hFind
= FindFirstFileW(szPath
, &find
);
168 if (hFind
== INVALID_HANDLE_VALUE
)
174 if (wcscmp(find
.cFileName
, L
".") == 0 ||
175 wcscmp(find
.cFileName
, L
"..") == 0 ||
176 _wcsicmp(find
.cFileName
, L
"desktop.ini") == 0)
183 } while (FindNextFileW(hFind
, &find
));
189 static BOOL
CreateEmptyFile(LPCWSTR pszFile
)
192 hFile
= CreateFileW(pszFile
, GENERIC_WRITE
, FILE_SHARE_READ
, NULL
,
193 CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
195 return hFile
!= INVALID_HANDLE_VALUE
;
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
)
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
))
218 hr
= psl
->SetPath(pszTargetPath
);
219 if (FAILED_UNEXPECTEDLY(hr
))
224 hr
= psl
->SetIDList(pidlTarget
);
225 if (FAILED_UNEXPECTEDLY(hr
))
230 ERR("invalid argument\n");
235 hr
= psl
->SetArguments(pszArg
);
238 hr
= psl
->SetWorkingDirectory(pszDir
);
241 hr
= psl
->SetIconLocation(pszIconPath
, iIconNr
);
244 hr
= psl
->SetDescription(pszComment
);
246 CComPtr
<IPersistFile
> ppf
;
247 hr
= psl
->QueryInterface(IID_PPV_ARG(IPersistFile
, &ppf
));
248 if (FAILED_UNEXPECTEDLY(hr
))
251 hr
= ppf
->Save(pszLinkPath
, TRUE
);
252 if (FAILED_UNEXPECTEDLY(hr
))
258 HRESULT
CSendToMenu::CreateSendToFiles(LPCWSTR pszSendTo
)
260 WCHAR szTarget
[MAX_PATH
];
261 WCHAR szSendToFile
[MAX_PATH
];
262 WCHAR szShell32
[MAX_PATH
];
265 /* create my documents */
266 SHGetSpecialFolderPathW(NULL
, szTarget
, CSIDL_MYDOCUMENTS
, FALSE
);
268 StringCbCopyW(szSendToFile
, sizeof(szSendToFile
), pszSendTo
);
269 PathAppendW(szSendToFile
, PathFindFileNameW(szTarget
));
270 StringCbCatW(szSendToFile
, sizeof(szSendToFile
), L
".lnk");
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
))
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
))
286 ERR("CreateEmptyFile\n");
289 /* create zipped compressed folder */
291 LoadLibraryExW(L
"zipfldr.dll", NULL
, LOAD_LIBRARY_AS_DATAFILE
);
294 #define IDS_FRIENDLYNAME 10195
295 LoadStringW(hZipFldr
, IDS_FRIENDLYNAME
, szTarget
, _countof(szTarget
));
296 #undef IDS_FRIENDLYNAME
297 FreeLibrary(hZipFldr
);
299 StringCbCopyW(szSendToFile
, sizeof(szSendToFile
), pszSendTo
);
300 PathAppendW(szSendToFile
, szTarget
);
301 StringCbCatW(szSendToFile
, sizeof(szSendToFile
), L
".ZFSendToTarget");
302 if (!CreateEmptyFile(szSendToFile
))
304 ERR("CreateEmptyFile\n");
311 HRESULT
CSendToMenu::LoadAllItems(HWND hwnd
)
315 if (!FolderHasAnyItems())
317 WCHAR szPath
[MAX_PATH
];
318 SHGetSpecialFolderPathW(NULL
, szPath
, CSIDL_SENDTO
, FALSE
);
319 CreateSendToFiles(szPath
);
322 CComHeapPtr
<ITEMIDLIST_ABSOLUTE
> pidlSendTo
;
325 HRESULT hr
= GetSpecialFolder(hwnd
, &m_pSendTo
, CSIDL_SENDTO
, &pidlSendTo
);
326 if (FAILED_UNEXPECTEDLY(hr
))
329 CComPtr
<IEnumIDList
> pEnumIDList
;
330 hr
= m_pSendTo
->EnumObjects(hwnd
,
331 SHCONTF_FOLDERS
| SHCONTF_NONFOLDERS
,
333 if (FAILED_UNEXPECTEDLY(hr
))
338 while (pEnumIDList
->Next(1, &child
, NULL
) == S_OK
)
340 CComHeapPtr
<ITEMID_CHILD
> pidlChild(child
);
343 hr
= m_pSendTo
->GetDisplayNameOf(pidlChild
, SHGDN_NORMAL
, &strret
);
344 if (FAILED_UNEXPECTEDLY(hr
))
347 CComHeapPtr
<WCHAR
> pszText
;
348 hr
= StrRetToStrW(&strret
, pidlChild
, &pszText
);
349 if (FAILED_UNEXPECTEDLY(hr
))
352 CComHeapPtr
<ITEMIDLIST_ABSOLUTE
> pidlAbsolute
;
353 pidlAbsolute
.Attach(ILCombine(pidlSendTo
, pidlChild
));
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
);
361 SENDTO_ITEM
*pNewItem
=
362 new SENDTO_ITEM(pidlChild
.Detach(), pszText
.Detach(), fi
.hIcon
);
365 pNewItem
->pNext
= m_pItems
;
373 UINT
CSendToMenu::InsertSendToItems(HMENU hMenu
, UINT idCmdFirst
, UINT Pos
)
375 if (m_pItems
== NULL
)
377 HRESULT hr
= LoadAllItems(NULL
);
378 if (FAILED_UNEXPECTEDLY(hr
))
382 m_idCmdFirst
= idCmdFirst
;
384 UINT idCmd
= idCmdFirst
;
385 for (SENDTO_ITEM
*pCurItem
= m_pItems
; pCurItem
; pCurItem
= pCurItem
->pNext
)
387 const UINT uFlags
= MF_BYPOSITION
| MF_STRING
| MF_ENABLED
;
388 if (InsertMenuW(hMenu
, Pos
, uFlags
, idCmd
, pCurItem
->pszText
))
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
);
402 if (idCmd
== idCmdFirst
)
404 CStringW
strNone(MAKEINTRESOURCEW(IDS_NONE
));
405 AppendMenuW(hMenu
, MF_GRAYED
| MF_DISABLED
| MF_STRING
, idCmd
, strNone
);
408 return idCmd
- idCmdFirst
;
411 CSendToMenu::SENDTO_ITEM
*CSendToMenu::FindItemFromIdOffset(UINT IdOffset
)
413 UINT idCmd
= m_idCmdFirst
+ IdOffset
;
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
);
420 ERR("GetMenuItemInfoW: %ld\n", GetLastError());
424 HRESULT
CSendToMenu::DoSendToItem(SENDTO_ITEM
*pItem
, LPCMINVOKECOMMANDINFO lpici
)
428 ERR("!m_pDataObject\n");
433 CComPtr
<IDropTarget
> pDropTarget
;
434 hr
= m_pSendTo
->GetUIObjectOf(NULL
, 1, &pItem
->pidlChild
, IID_IDropTarget
,
435 NULL
, (LPVOID
*)&pDropTarget
);
436 if (FAILED_UNEXPECTEDLY(hr
))
439 hr
= DoDrop(m_pDataObject
, pDropTarget
);
440 if (FAILED_UNEXPECTEDLY(hr
))
447 CSendToMenu::QueryContextMenu(HMENU hMenu
,
453 TRACE("%p %p %u %u %u %u\n", this,
454 hMenu
, indexMenu
, idCmdFirst
, idCmdLast
, uFlags
);
456 HMENU hSubMenu
= CreateMenu();
459 ERR("CreateMenu: %ld\n", GetLastError());
463 UINT cItems
= InsertSendToItems(hSubMenu
, idCmdFirst
, 0);
465 CStringW
strSendTo(MAKEINTRESOURCEW(IDS_SENDTO_MENU
));
467 MENUITEMINFOW mii
= { sizeof(mii
) };
468 mii
.fMask
= MIIM_TYPE
| MIIM_ID
| MIIM_STATE
| MIIM_SUBMENU
;
469 mii
.fType
= MFT_STRING
;
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
))
477 ERR("InsertMenuItemW: %ld\n", GetLastError());
481 HMENU hOldSubMenu
= m_hSubMenu
;
482 m_hSubMenu
= hSubMenu
;
483 DestroyMenu(hOldSubMenu
);
485 return MAKE_HRESULT(SEVERITY_SUCCESS
, 0, cItems
);
489 CSendToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici
)
493 WORD idCmd
= LOWORD(lpici
->lpVerb
);
494 TRACE("idCmd: %d\n", idCmd
);
496 SENDTO_ITEM
*pItem
= FindItemFromIdOffset(idCmd
);
499 hr
= DoSendToItem(pItem
, lpici
);
502 TRACE("CSendToMenu::InvokeCommand %x\n", hr
);
507 CSendToMenu::GetCommandString(UINT_PTR idCmd
,
513 FIXME("%p %lu %u %p %p %u\n", this,
514 idCmd
, uType
, pwReserved
, pszName
, cchMax
);
520 CSendToMenu::HandleMenuMsg(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
526 CSendToMenu::HandleMenuMsg2(UINT uMsg
, WPARAM wParam
, LPARAM lParam
,
529 UINT cxSmall
= GetSystemMetrics(SM_CXSMICON
);
530 UINT cySmall
= GetSystemMetrics(SM_CYSMICON
);
536 MEASUREITEMSTRUCT
* lpmis
= reinterpret_cast<MEASUREITEMSTRUCT
*>(lParam
);
537 if (!lpmis
|| lpmis
->CtlType
!= ODT_MENU
)
540 UINT cxMenuCheck
= GetSystemMetrics(SM_CXMENUCHECK
);
541 if (lpmis
->itemWidth
< cxMenuCheck
)
542 lpmis
->itemWidth
= cxMenuCheck
;
543 if (lpmis
->itemHeight
< cySmall
)
544 lpmis
->itemHeight
= cySmall
;
552 DRAWITEMSTRUCT
* lpdis
= reinterpret_cast<DRAWITEMSTRUCT
*>(lParam
);
553 if (!lpdis
|| lpdis
->CtlType
!= ODT_MENU
)
556 SENDTO_ITEM
*pItem
= reinterpret_cast<SENDTO_ITEM
*>(lpdis
->itemData
);
559 hIcon
= pItem
->hIcon
;
563 const RECT
& rcItem
= lpdis
->rcItem
;
565 INT y
= lpdis
->rcItem
.top
;
566 y
+= (rcItem
.bottom
- rcItem
.top
- cySmall
) / 2;
567 DrawIconEx(lpdis
->hDC
, x
, y
, hIcon
, cxSmall
, cySmall
,
579 CSendToMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder
,
580 IDataObject
*pdtobj
, HKEY hkeyProgID
)
582 m_pDataObject
= pdtobj
;