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 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 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 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 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 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 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 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 365 CSendToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam) 366 { 367 return S_OK; 368 } 369 370 STDMETHODIMP 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 424 CSendToMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder, 425 IDataObject *pdtobj, HKEY hkeyProgID) 426 { 427 m_pDataObject = pdtobj; 428 return S_OK; 429 } 430