/* * provides SendTo shell item service * * Copyright 2019 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include "precomp.h" WINE_DEFAULT_DEBUG_CHANNEL(shell); CSendToMenu::CSendToMenu() : m_hSubMenu(NULL) , m_pItems(NULL) , m_idCmdFirst(0) { HRESULT hr = SHGetDesktopFolder(&m_pDesktop); if (FAILED(hr)) { ERR("SHGetDesktopFolder: %08lX\n", hr); } GetSpecialFolder(NULL, &m_pSendTo, CSIDL_SENDTO); } CSendToMenu::~CSendToMenu() { UnloadAllItems(); if (m_hSubMenu) { DestroyMenu(m_hSubMenu); m_hSubMenu = NULL; } } HRESULT CSendToMenu::DoDrop(IDataObject *pDataObject, IDropTarget *pDropTarget) { DWORD dwEffect = DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK; BOOL bShift = (GetAsyncKeyState(VK_SHIFT) < 0); BOOL bCtrl = (GetAsyncKeyState(VK_CONTROL) < 0); // THIS CODE IS NOT HUMAN-FRIENDLY. SORRY. // (We have to translate a SendTo action to a Drop action) DWORD dwKeyState = MK_LBUTTON; if (bShift && bCtrl) dwKeyState |= MK_SHIFT | MK_CONTROL; else if (!bShift) dwKeyState |= MK_CONTROL; if (bCtrl) dwKeyState |= MK_SHIFT; POINTL ptl = { 0, 0 }; HRESULT hr = pDropTarget->DragEnter(pDataObject, dwKeyState, ptl, &dwEffect); if (FAILED_UNEXPECTEDLY(hr)) { pDropTarget->DragLeave(); return hr; } if (dwEffect == DROPEFFECT_NONE) { ERR("DROPEFFECT_NONE\n"); pDropTarget->DragLeave(); return E_FAIL; } // THIS CODE IS NOT HUMAN-FRIENDLY. SORRY. // (We have to translate a SendTo action to a Drop action) if (bShift && bCtrl) dwEffect = DROPEFFECT_LINK; else if (!bShift) dwEffect = DROPEFFECT_MOVE; else dwEffect = DROPEFFECT_COPY; hr = pDropTarget->Drop(pDataObject, dwKeyState, ptl, &dwEffect); if (FAILED_UNEXPECTEDLY(hr)) return hr; return hr; } // get an IShellFolder from CSIDL HRESULT CSendToMenu::GetSpecialFolder(HWND hwnd, IShellFolder **ppFolder, int csidl, PIDLIST_ABSOLUTE *ppidl) { if (!ppFolder) return E_POINTER; *ppFolder = NULL; if (ppidl) *ppidl = NULL; CComHeapPtr pidl; HRESULT hr = SHGetSpecialFolderLocation(hwnd, csidl, &pidl); if (FAILED_UNEXPECTEDLY(hr)) return hr; IShellFolder *pFolder = NULL; hr = m_pDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &pFolder)); if (ppidl) *ppidl = pidl.Detach(); if (FAILED_UNEXPECTEDLY(hr)) return hr; *ppFolder = pFolder; return hr; } // get a UI object from PIDL HRESULT CSendToMenu::GetUIObjectFromPidl(HWND hwnd, PIDLIST_ABSOLUTE pidl, REFIID riid, LPVOID *ppvOut) { *ppvOut = NULL; PCITEMID_CHILD pidlLast; CComPtr pFolder; HRESULT hr = SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &pFolder), &pidlLast); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = pFolder->GetUIObjectOf(hwnd, 1, &pidlLast, riid, NULL, ppvOut); if (FAILED_UNEXPECTEDLY(hr)) return hr; return hr; } void CSendToMenu::UnloadAllItems() { SENDTO_ITEM *pItems = m_pItems; m_pItems = NULL; while (pItems) { SENDTO_ITEM *pCurItem = pItems; pItems = pItems->pNext; delete pCurItem; } } BOOL CSendToMenu::FolderHasAnyItems() const { WCHAR szPath[MAX_PATH]; SHGetSpecialFolderPathW(NULL, szPath, CSIDL_SENDTO, FALSE); PathAppendW(szPath, L"*"); WIN32_FIND_DATAW find; HANDLE hFind = FindFirstFileW(szPath, &find); if (hFind == INVALID_HANDLE_VALUE) return FALSE; BOOL bFound = FALSE; do { if (wcscmp(find.cFileName, L".") == 0 || wcscmp(find.cFileName, L"..") == 0 || _wcsicmp(find.cFileName, L"desktop.ini") == 0) { continue; } bFound = TRUE; break; } while (FindNextFileW(hFind, &find)); FindClose(hFind); return bFound; } static BOOL CreateEmptyFile(LPCWSTR pszFile) { HANDLE hFile; hFile = CreateFileW(pszFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); CloseHandle(hFile); return hFile != INVALID_HANDLE_VALUE; } static HRESULT CreateShellLink( LPCWSTR pszLinkPath, LPCWSTR pszTargetPath OPTIONAL, LPCITEMIDLIST pidlTarget OPTIONAL, LPCWSTR pszArg OPTIONAL, LPCWSTR pszDir OPTIONAL, LPCWSTR pszIconPath OPTIONAL, INT iIconNr OPTIONAL, LPCWSTR pszComment OPTIONAL) { CComPtr psl; HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellLinkW, &psl)); if (FAILED_UNEXPECTEDLY(hr)) return hr; if (pszTargetPath) { hr = psl->SetPath(pszTargetPath); if (FAILED_UNEXPECTEDLY(hr)) return hr; } else if (pidlTarget) { hr = psl->SetIDList(pidlTarget); if (FAILED_UNEXPECTEDLY(hr)) return hr; } else { ERR("invalid argument\n"); return E_INVALIDARG; } if (pszArg) hr = psl->SetArguments(pszArg); if (pszDir) hr = psl->SetWorkingDirectory(pszDir); if (pszIconPath) hr = psl->SetIconLocation(pszIconPath, iIconNr); if (pszComment) hr = psl->SetDescription(pszComment); CComPtr ppf; hr = psl->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf)); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = ppf->Save(pszLinkPath, TRUE); if (FAILED_UNEXPECTEDLY(hr)) return hr; return hr; } HRESULT CSendToMenu::CreateSendToFiles(LPCWSTR pszSendTo) { WCHAR szTarget[MAX_PATH]; WCHAR szSendToFile[MAX_PATH]; WCHAR szShell32[MAX_PATH]; HRESULT hr; /* create my documents */ SHGetSpecialFolderPathW(NULL, szTarget, CSIDL_MYDOCUMENTS, FALSE); StringCbCopyW(szSendToFile, sizeof(szSendToFile), pszSendTo); PathAppendW(szSendToFile, PathFindFileNameW(szTarget)); StringCbCatW(szSendToFile, sizeof(szSendToFile), L".lnk"); GetSystemDirectoryW(szShell32, ARRAY_SIZE(szShell32)); PathAppendW(szShell32, L"shell32.dll"); hr = CreateShellLink(szSendToFile, szTarget, NULL, NULL, NULL, szShell32, -IDI_SHELL_MY_DOCUMENTS, NULL); if (FAILED_UNEXPECTEDLY(hr)) ERR("CreateShellLink(%S, %S) failed!\n", szSendToFile, szTarget); /* create desklink */ StringCbCopyW(szSendToFile, sizeof(szSendToFile), pszSendTo); LoadStringW(shell32_hInstance, IDS_DESKLINK, szTarget, _countof(szTarget)); StringCbCatW(szTarget, sizeof(szTarget), L".DeskLink"); PathAppendW(szSendToFile, szTarget); if (!CreateEmptyFile(szSendToFile)) { ERR("CreateEmptyFile\n"); } /* create zipped compressed folder */ HINSTANCE hZipFldr = LoadLibraryExW(L"zipfldr.dll", NULL, LOAD_LIBRARY_AS_DATAFILE); if (hZipFldr) { #define IDS_FRIENDLYNAME 10195 LoadStringW(hZipFldr, IDS_FRIENDLYNAME, szTarget, _countof(szTarget)); #undef IDS_FRIENDLYNAME FreeLibrary(hZipFldr); StringCbCopyW(szSendToFile, sizeof(szSendToFile), pszSendTo); PathAppendW(szSendToFile, szTarget); StringCbCatW(szSendToFile, sizeof(szSendToFile), L".ZFSendToTarget"); if (!CreateEmptyFile(szSendToFile)) { ERR("CreateEmptyFile\n"); } } return S_OK; } HRESULT CSendToMenu::LoadAllItems(HWND hwnd) { UnloadAllItems(); if (!FolderHasAnyItems()) { WCHAR szPath[MAX_PATH]; SHGetSpecialFolderPathW(NULL, szPath, CSIDL_SENDTO, FALSE); CreateSendToFiles(szPath); } CComHeapPtr pidlSendTo; m_pSendTo.Release(); HRESULT hr = GetSpecialFolder(hwnd, &m_pSendTo, CSIDL_SENDTO, &pidlSendTo); if (FAILED_UNEXPECTEDLY(hr)) return hr; CComPtr pEnumIDList; hr = m_pSendTo->EnumObjects(hwnd, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &pEnumIDList); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = S_OK; PITEMID_CHILD child; while (pEnumIDList->Next(1, &child, NULL) == S_OK) { CComHeapPtr pidlChild(child); STRRET strret; hr = m_pSendTo->GetDisplayNameOf(pidlChild, SHGDN_NORMAL, &strret); if (FAILED_UNEXPECTEDLY(hr)) continue; CComHeapPtr pszText; hr = StrRetToStrW(&strret, pidlChild, &pszText); if (FAILED_UNEXPECTEDLY(hr)) continue; CComHeapPtr pidlAbsolute; pidlAbsolute.Attach(ILCombine(pidlSendTo, pidlChild)); SHFILEINFOW fi = { NULL }; const UINT uFlags = SHGFI_PIDL | SHGFI_TYPENAME | SHGFI_ICON | SHGFI_SMALLICON; SHGetFileInfoW(reinterpret_cast(static_cast(pidlAbsolute)), 0, &fi, sizeof(fi), uFlags); SENDTO_ITEM *pNewItem = new SENDTO_ITEM(pidlChild.Detach(), pszText.Detach(), fi.hIcon); if (m_pItems) { pNewItem->pNext = m_pItems; } m_pItems = pNewItem; } return hr; } UINT CSendToMenu::InsertSendToItems(HMENU hMenu, UINT idCmdFirst, UINT Pos) { if (m_pItems == NULL) { HRESULT hr = LoadAllItems(NULL); if (FAILED_UNEXPECTEDLY(hr)) return 0; } m_idCmdFirst = idCmdFirst; UINT idCmd = idCmdFirst; for (SENDTO_ITEM *pCurItem = m_pItems; pCurItem; pCurItem = pCurItem->pNext) { const UINT uFlags = MF_BYPOSITION | MF_STRING | MF_ENABLED; if (InsertMenuW(hMenu, Pos, uFlags, idCmd, pCurItem->pszText)) { MENUITEMINFOW mii; mii.cbSize = sizeof(mii); mii.fMask = MIIM_DATA | MIIM_BITMAP; mii.dwItemData = reinterpret_cast(pCurItem); mii.hbmpItem = HBMMENU_CALLBACK; SetMenuItemInfoW(hMenu, idCmd, FALSE, &mii); ++idCmd; // successful } } if (idCmd == idCmdFirst) { CStringW strNone(MAKEINTRESOURCEW(IDS_NONE)); AppendMenuW(hMenu, MF_GRAYED | MF_DISABLED | MF_STRING, idCmd, strNone); } return idCmd - idCmdFirst; } CSendToMenu::SENDTO_ITEM *CSendToMenu::FindItemFromIdOffset(UINT IdOffset) { UINT idCmd = m_idCmdFirst + IdOffset; MENUITEMINFOW mii = { sizeof(mii) }; mii.fMask = MIIM_DATA; if (GetMenuItemInfoW(m_hSubMenu, idCmd, FALSE, &mii)) return reinterpret_cast(mii.dwItemData); ERR("GetMenuItemInfoW: %ld\n", GetLastError()); return NULL; } HRESULT CSendToMenu::DoSendToItem(SENDTO_ITEM *pItem, LPCMINVOKECOMMANDINFO lpici) { if (!m_pDataObject) { ERR("!m_pDataObject\n"); return E_FAIL; } HRESULT hr; CComPtr pDropTarget; hr = m_pSendTo->GetUIObjectOf(NULL, 1, &pItem->pidlChild, IID_IDropTarget, NULL, (LPVOID *)&pDropTarget); if (FAILED_UNEXPECTEDLY(hr)) return hr; hr = DoDrop(m_pDataObject, pDropTarget); if (FAILED_UNEXPECTEDLY(hr)) return hr; return hr; } STDMETHODIMP CSendToMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { TRACE("%p %p %u %u %u %u\n", this, hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags); HMENU hSubMenu = CreateMenu(); if (!hSubMenu) { ERR("CreateMenu: %ld\n", GetLastError()); return E_FAIL; } UINT cItems = InsertSendToItems(hSubMenu, idCmdFirst, 0); CStringW strSendTo(MAKEINTRESOURCEW(IDS_SENDTO_MENU)); MENUITEMINFOW mii = { sizeof(mii) }; mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_STATE | MIIM_SUBMENU; mii.fType = MFT_STRING; mii.wID = -1; mii.dwTypeData = strSendTo.GetBuffer(); mii.cch = wcslen(mii.dwTypeData); mii.fState = MFS_ENABLED; mii.hSubMenu = hSubMenu; if (!InsertMenuItemW(hMenu, indexMenu, TRUE, &mii)) { ERR("InsertMenuItemW: %ld\n", GetLastError()); return E_FAIL; } HMENU hOldSubMenu = m_hSubMenu; m_hSubMenu = hSubMenu; DestroyMenu(hOldSubMenu); return MAKE_HRESULT(SEVERITY_SUCCESS, 0, cItems); } STDMETHODIMP CSendToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpici) { HRESULT hr = E_FAIL; WORD idCmd = LOWORD(lpici->lpVerb); TRACE("idCmd: %d\n", idCmd); SENDTO_ITEM *pItem = FindItemFromIdOffset(idCmd); if (pItem) { hr = DoSendToItem(pItem, lpici); } TRACE("CSendToMenu::InvokeCommand %x\n", hr); return hr; } STDMETHODIMP CSendToMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pwReserved, LPSTR pszName, UINT cchMax) { FIXME("%p %lu %u %p %p %u\n", this, idCmd, uType, pwReserved, pszName, cchMax); return E_NOTIMPL; } STDMETHODIMP CSendToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam) { return S_OK; } STDMETHODIMP CSendToMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult) { UINT cxSmall = GetSystemMetrics(SM_CXSMICON); UINT cySmall = GetSystemMetrics(SM_CYSMICON); switch (uMsg) { case WM_MEASUREITEM: { MEASUREITEMSTRUCT* lpmis = reinterpret_cast(lParam); if (!lpmis || lpmis->CtlType != ODT_MENU) break; UINT cxMenuCheck = GetSystemMetrics(SM_CXMENUCHECK); if (lpmis->itemWidth < cxMenuCheck) lpmis->itemWidth = cxMenuCheck; if (lpmis->itemHeight < cySmall) lpmis->itemHeight = cySmall; if (plResult) *plResult = TRUE; break; } case WM_DRAWITEM: { DRAWITEMSTRUCT* lpdis = reinterpret_cast(lParam); if (!lpdis || lpdis->CtlType != ODT_MENU) break; SENDTO_ITEM *pItem = reinterpret_cast(lpdis->itemData); HICON hIcon = NULL; if (pItem) hIcon = pItem->hIcon; if (!hIcon) break; const RECT& rcItem = lpdis->rcItem; INT x = 4; INT y = lpdis->rcItem.top; y += (rcItem.bottom - rcItem.top - cySmall) / 2; DrawIconEx(lpdis->hDC, x, y, hIcon, cxSmall, cySmall, 0, NULL, DI_NORMAL); if (plResult) *plResult = TRUE; } } return S_OK; } STDMETHODIMP CSendToMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID) { m_pDataObject = pdtobj; return S_OK; }