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