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 HRESULT hr = SHELL_GetUIObjectOfAbsoluteItem(hwnd, pidl, riid, ppvOut); 134 if (FAILED_UNEXPECTEDLY(hr)) 135 return hr; 136 137 return hr; 138 } 139 140 void CSendToMenu::UnloadAllItems() 141 { 142 SENDTO_ITEM *pItems = m_pItems; 143 m_pItems = NULL; 144 while (pItems) 145 { 146 SENDTO_ITEM *pCurItem = pItems; 147 pItems = pItems->pNext; 148 delete pCurItem; 149 } 150 } 151 152 HRESULT CSendToMenu::LoadAllItems(HWND hwnd) 153 { 154 UnloadAllItems(); 155 156 CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlSendTo; 157 158 m_pSendTo.Release(); 159 HRESULT hr = GetSpecialFolder(hwnd, &m_pSendTo, CSIDL_SENDTO, &pidlSendTo); 160 if (FAILED_UNEXPECTEDLY(hr)) 161 return hr; 162 163 CComPtr<IEnumIDList> pEnumIDList; 164 hr = m_pSendTo->EnumObjects(hwnd, 165 SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, 166 &pEnumIDList); 167 if (FAILED_UNEXPECTEDLY(hr)) 168 return hr; 169 170 hr = S_OK; 171 PITEMID_CHILD child; 172 while (pEnumIDList->Next(1, &child, NULL) == S_OK) 173 { 174 CComHeapPtr<ITEMID_CHILD> pidlChild(child); 175 176 STRRET strret; 177 hr = m_pSendTo->GetDisplayNameOf(pidlChild, SHGDN_NORMAL, &strret); 178 if (FAILED_UNEXPECTEDLY(hr)) 179 continue; 180 181 CComHeapPtr<WCHAR> pszText; 182 hr = StrRetToStrW(&strret, pidlChild, &pszText); 183 if (FAILED_UNEXPECTEDLY(hr)) 184 continue; 185 186 CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlAbsolute; 187 pidlAbsolute.Attach(ILCombine(pidlSendTo, pidlChild)); 188 189 SHFILEINFOW fi = { NULL }; 190 const UINT uFlags = SHGFI_PIDL | SHGFI_TYPENAME | 191 SHGFI_ICON | SHGFI_SMALLICON; 192 SHGetFileInfoW(reinterpret_cast<LPWSTR>(static_cast<PIDLIST_ABSOLUTE>(pidlAbsolute)), 0, 193 &fi, sizeof(fi), uFlags); 194 195 SENDTO_ITEM *pNewItem = 196 new SENDTO_ITEM(pidlChild.Detach(), pszText.Detach(), fi.hIcon); 197 if (m_pItems) 198 { 199 pNewItem->pNext = m_pItems; 200 } 201 m_pItems = pNewItem; 202 } 203 204 return hr; 205 } 206 207 UINT CSendToMenu::InsertSendToItems(HMENU hMenu, UINT idCmdFirst, UINT Pos) 208 { 209 if (m_pItems == NULL) 210 { 211 HRESULT hr = LoadAllItems(NULL); 212 if (FAILED_UNEXPECTEDLY(hr)) 213 return 0; 214 } 215 216 m_idCmdFirst = idCmdFirst; 217 218 UINT idCmd = idCmdFirst; 219 for (SENDTO_ITEM *pCurItem = m_pItems; pCurItem; pCurItem = pCurItem->pNext) 220 { 221 const UINT uFlags = MF_BYPOSITION | MF_STRING | MF_ENABLED; 222 if (InsertMenuW(hMenu, Pos, uFlags, idCmd, pCurItem->pszText)) 223 { 224 MENUITEMINFOW mii; 225 mii.cbSize = sizeof(mii); 226 mii.fMask = MIIM_DATA | MIIM_BITMAP; 227 mii.dwItemData = reinterpret_cast<ULONG_PTR>(pCurItem); 228 mii.hbmpItem = HBMMENU_CALLBACK; 229 SetMenuItemInfoW(hMenu, idCmd, FALSE, &mii); 230 ++idCmd; 231 232 // successful 233 } 234 } 235 236 if (idCmd == idCmdFirst) 237 { 238 CStringW strNone(MAKEINTRESOURCEW(IDS_NONE)); 239 AppendMenuW(hMenu, MF_GRAYED | MF_DISABLED | MF_STRING, idCmd, strNone); 240 ++idCmd; 241 } 242 243 return idCmd - idCmdFirst; 244 } 245 246 CSendToMenu::SENDTO_ITEM *CSendToMenu::FindItemFromIdOffset(UINT IdOffset) 247 { 248 UINT idCmd = m_idCmdFirst + IdOffset; 249 250 MENUITEMINFOW mii = { sizeof(mii) }; 251 mii.fMask = MIIM_DATA; 252 if (GetMenuItemInfoW(m_hSubMenu, idCmd, FALSE, &mii)) 253 return reinterpret_cast<SENDTO_ITEM *>(mii.dwItemData); 254 255 ERR("GetMenuItemInfoW: %ld\n", GetLastError()); 256 return NULL; 257 } 258 259 HRESULT CSendToMenu::DoSendToItem(SENDTO_ITEM *pItem, LPCMINVOKECOMMANDINFO lpici) 260 { 261 if (!m_pDataObject) 262 { 263 ERR("!m_pDataObject\n"); 264 return E_FAIL; 265 } 266 267 HRESULT hr; 268 CComPtr<IDropTarget> pDropTarget; 269 hr = m_pSendTo->GetUIObjectOf(NULL, 1, &pItem->pidlChild, IID_IDropTarget, 270 NULL, (LPVOID *)&pDropTarget); 271 if (FAILED_UNEXPECTEDLY(hr)) 272 return hr; 273 274 hr = DoDrop(m_pDataObject, pDropTarget); 275 if (FAILED_UNEXPECTEDLY(hr)) 276 return hr; 277 278 return hr; 279 } 280 281 STDMETHODIMP 282 CSendToMenu::QueryContextMenu(HMENU hMenu, 283 UINT indexMenu, 284 UINT idCmdFirst, 285 UINT idCmdLast, 286 UINT uFlags) 287 { 288 TRACE("%p %p %u %u %u %u\n", this, 289 hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags); 290 291 if (uFlags & (CMF_NOVERBS | CMF_VERBSONLY)) 292 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, idCmdFirst); 293 294 HMENU hSubMenu = CreateMenu(); 295 if (!hSubMenu) 296 { 297 ERR("CreateMenu: %ld\n", GetLastError()); 298 return E_FAIL; 299 } 300 301 UINT cItems = InsertSendToItems(hSubMenu, idCmdFirst, 0); 302 303 CStringW strSendTo(MAKEINTRESOURCEW(IDS_SENDTO_MENU)); 304 305 MENUITEMINFOW mii = { sizeof(mii) }; 306 mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE | MIIM_SUBMENU; 307 mii.fType = MFT_STRING; 308 mii.wID = -1; 309 mii.dwTypeData = strSendTo.GetBuffer(); 310 mii.cch = wcslen(mii.dwTypeData); 311 mii.fState = MFS_ENABLED; 312 mii.hSubMenu = hSubMenu; 313 if (!InsertMenuItemW(hMenu, indexMenu, TRUE, &mii)) 314 { 315 ERR("InsertMenuItemW: %ld\n", GetLastError()); 316 return E_FAIL; 317 } 318 319 HMENU hOldSubMenu = m_hSubMenu; 320 m_hSubMenu = hSubMenu; 321 DestroyMenu(hOldSubMenu); 322 323 return MAKE_HRESULT(SEVERITY_SUCCESS, 0, idCmdFirst + cItems); 324 } 325 326 STDMETHODIMP 327 CSendToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici) 328 { 329 HRESULT hr = E_FAIL; 330 331 WORD idCmd = LOWORD(lpici->lpVerb); 332 TRACE("idCmd: %d\n", idCmd); 333 334 SENDTO_ITEM *pItem = FindItemFromIdOffset(idCmd); 335 if (pItem) 336 { 337 hr = DoSendToItem(pItem, lpici); 338 } 339 340 TRACE("CSendToMenu::InvokeCommand %x\n", hr); 341 return hr; 342 } 343 344 STDMETHODIMP 345 CSendToMenu::GetCommandString(UINT_PTR idCmd, 346 UINT uType, 347 UINT *pwReserved, 348 LPSTR pszName, 349 UINT cchMax) 350 { 351 FIXME("%p %lu %u %p %p %u\n", this, 352 idCmd, uType, pwReserved, pszName, cchMax); 353 354 return E_NOTIMPL; 355 } 356 357 STDMETHODIMP 358 CSendToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam) 359 { 360 return S_OK; 361 } 362 363 STDMETHODIMP 364 CSendToMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, 365 LRESULT *plResult) 366 { 367 UINT cxSmall = GetSystemMetrics(SM_CXSMICON); 368 UINT cySmall = GetSystemMetrics(SM_CYSMICON); 369 370 switch (uMsg) 371 { 372 case WM_MEASUREITEM: 373 { 374 MEASUREITEMSTRUCT* lpmis = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam); 375 if (!lpmis || lpmis->CtlType != ODT_MENU) 376 break; 377 378 UINT cxMenuCheck = GetSystemMetrics(SM_CXMENUCHECK); 379 if (lpmis->itemWidth < cxMenuCheck) 380 lpmis->itemWidth = cxMenuCheck; 381 if (lpmis->itemHeight < cySmall) 382 lpmis->itemHeight = cySmall; 383 384 if (plResult) 385 *plResult = TRUE; 386 break; 387 } 388 case WM_DRAWITEM: 389 { 390 DRAWITEMSTRUCT* lpdis = reinterpret_cast<DRAWITEMSTRUCT*>(lParam); 391 if (!lpdis || lpdis->CtlType != ODT_MENU) 392 break; 393 394 SENDTO_ITEM *pItem = reinterpret_cast<SENDTO_ITEM *>(lpdis->itemData); 395 HICON hIcon = NULL; 396 if (pItem) 397 hIcon = pItem->hIcon; 398 if (!hIcon) 399 break; 400 401 const RECT& rcItem = lpdis->rcItem; 402 INT x = 4; 403 INT y = lpdis->rcItem.top; 404 y += (rcItem.bottom - rcItem.top - cySmall) / 2; 405 DrawIconEx(lpdis->hDC, x, y, hIcon, cxSmall, cySmall, 406 0, NULL, DI_NORMAL); 407 408 if (plResult) 409 *plResult = TRUE; 410 } 411 } 412 413 return S_OK; 414 } 415 416 STDMETHODIMP 417 CSendToMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder, 418 IDataObject *pdtobj, HKEY hkeyProgID) 419 { 420 m_pDataObject = pdtobj; 421 return S_OK; 422 } 423