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 HMENU hSubMenu = CreateMenu(); 299 if (!hSubMenu) 300 { 301 ERR("CreateMenu: %ld\n", GetLastError()); 302 return E_FAIL; 303 } 304 305 UINT cItems = InsertSendToItems(hSubMenu, idCmdFirst, 0); 306 307 CStringW strSendTo(MAKEINTRESOURCEW(IDS_SENDTO_MENU)); 308 309 MENUITEMINFOW mii = { sizeof(mii) }; 310 mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE | MIIM_SUBMENU; 311 mii.fType = MFT_STRING; 312 mii.wID = -1; 313 mii.dwTypeData = strSendTo.GetBuffer(); 314 mii.cch = wcslen(mii.dwTypeData); 315 mii.fState = MFS_ENABLED; 316 mii.hSubMenu = hSubMenu; 317 if (!InsertMenuItemW(hMenu, indexMenu, TRUE, &mii)) 318 { 319 ERR("InsertMenuItemW: %ld\n", GetLastError()); 320 return E_FAIL; 321 } 322 323 HMENU hOldSubMenu = m_hSubMenu; 324 m_hSubMenu = hSubMenu; 325 DestroyMenu(hOldSubMenu); 326 327 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cItems); 328 } 329 330 STDMETHODIMP 331 CSendToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici) 332 { 333 HRESULT hr = E_FAIL; 334 335 WORD idCmd = LOWORD(lpici->lpVerb); 336 TRACE("idCmd: %d\n", idCmd); 337 338 SENDTO_ITEM *pItem = FindItemFromIdOffset(idCmd); 339 if (pItem) 340 { 341 hr = DoSendToItem(pItem, lpici); 342 } 343 344 TRACE("CSendToMenu::InvokeCommand %x\n", hr); 345 return hr; 346 } 347 348 STDMETHODIMP 349 CSendToMenu::GetCommandString(UINT_PTR idCmd, 350 UINT uType, 351 UINT *pwReserved, 352 LPSTR pszName, 353 UINT cchMax) 354 { 355 FIXME("%p %lu %u %p %p %u\n", this, 356 idCmd, uType, pwReserved, pszName, cchMax); 357 358 return E_NOTIMPL; 359 } 360 361 STDMETHODIMP 362 CSendToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam) 363 { 364 return S_OK; 365 } 366 367 STDMETHODIMP 368 CSendToMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, 369 LRESULT *plResult) 370 { 371 UINT cxSmall = GetSystemMetrics(SM_CXSMICON); 372 UINT cySmall = GetSystemMetrics(SM_CYSMICON); 373 374 switch (uMsg) 375 { 376 case WM_MEASUREITEM: 377 { 378 MEASUREITEMSTRUCT* lpmis = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam); 379 if (!lpmis || lpmis->CtlType != ODT_MENU) 380 break; 381 382 UINT cxMenuCheck = GetSystemMetrics(SM_CXMENUCHECK); 383 if (lpmis->itemWidth < cxMenuCheck) 384 lpmis->itemWidth = cxMenuCheck; 385 if (lpmis->itemHeight < cySmall) 386 lpmis->itemHeight = cySmall; 387 388 if (plResult) 389 *plResult = TRUE; 390 break; 391 } 392 case WM_DRAWITEM: 393 { 394 DRAWITEMSTRUCT* lpdis = reinterpret_cast<DRAWITEMSTRUCT*>(lParam); 395 if (!lpdis || lpdis->CtlType != ODT_MENU) 396 break; 397 398 SENDTO_ITEM *pItem = reinterpret_cast<SENDTO_ITEM *>(lpdis->itemData); 399 HICON hIcon = NULL; 400 if (pItem) 401 hIcon = pItem->hIcon; 402 if (!hIcon) 403 break; 404 405 const RECT& rcItem = lpdis->rcItem; 406 INT x = 4; 407 INT y = lpdis->rcItem.top; 408 y += (rcItem.bottom - rcItem.top - cySmall) / 2; 409 DrawIconEx(lpdis->hDC, x, y, hIcon, cxSmall, cySmall, 410 0, NULL, DI_NORMAL); 411 412 if (plResult) 413 *plResult = TRUE; 414 } 415 } 416 417 return S_OK; 418 } 419 420 STDMETHODIMP 421 CSendToMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder, 422 IDataObject *pdtobj, HKEY hkeyProgID) 423 { 424 m_pDataObject = pdtobj; 425 return S_OK; 426 } 427