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