From 99b99bda0f07d2e3bf51d054b5fe48b858a61db3 Mon Sep 17 00:00:00 2001 From: Whindmar Saksit Date: Sat, 21 Jun 2025 16:52:48 +0200 Subject: [PATCH] [BROWSEUI][SHELL32] Fixed FindFiles rename operation (#8151) DefView will now map its internal (FCIDM) commands (when sent from the toolbar and accelerators) to canonical verb strings so clients outside shell32 can detect the verb. This is critical for "rename" and handy for "properties". CORE-18326 --- dll/shellext/netshell/shfldr_netconnect.cpp | 8 -- dll/win32/browseui/shellfind/CFindFolder.cpp | 140 +++++++++++++++++-- dll/win32/shell32/CDefView.cpp | 70 +++++++--- dll/win32/shell32/CDefaultContextMenu.cpp | 10 ++ dll/win32/shell32/precomp.h | 2 + 5 files changed, 188 insertions(+), 42 deletions(-) diff --git a/dll/shellext/netshell/shfldr_netconnect.cpp b/dll/shellext/netshell/shfldr_netconnect.cpp index 38e743fb533..dfd1a0d8927 100644 --- a/dll/shellext/netshell/shfldr_netconnect.cpp +++ b/dll/shellext/netshell/shfldr_netconnect.cpp @@ -641,14 +641,6 @@ HRESULT WINAPI CNetConUiObject::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi) { CmdId = IDS_NET_PROPERTIES; } - else if ((UINT_PTR)lpcmi->lpVerb == FCIDM_SHVIEW_RENAME) // DefView accelerator - { - CmdId = IDS_NET_RENAME; - } - else if ((UINT_PTR)lpcmi->lpVerb == FCIDM_SHVIEW_PROPERTIES) // DefView accelerator - { - CmdId = IDS_NET_PROPERTIES; - } else if (!IS_INTRESOURCE(lpcmi->lpVerb) || LOWORD(lpcmi->lpVerb) > 7) { FIXME("Got invalid command\n"); diff --git a/dll/win32/browseui/shellfind/CFindFolder.cpp b/dll/win32/browseui/shellfind/CFindFolder.cpp index b84fad26800..fb072923a19 100644 --- a/dll/win32/browseui/shellfind/CFindFolder.cpp +++ b/dll/win32/browseui/shellfind/CFindFolder.cpp @@ -10,6 +10,32 @@ WINE_DEFAULT_DEBUG_CHANNEL(shellfind); +#ifndef _SHELL32_ + +static HRESULT WINAPI DisplayNameOfW(_In_ IShellFolder *psf, _In_ LPCITEMIDLIST pidl, + _In_ DWORD dwFlags, _Out_ LPWSTR pszBuf, _In_ UINT cchBuf) +{ + *pszBuf = UNICODE_NULL; + STRRET sr; + HRESULT hr = psf->GetDisplayNameOf(pidl, dwFlags, &sr); + return FAILED(hr) ? hr : StrRetToBufW(&sr, pidl, pszBuf, cchBuf); +} + +static HRESULT +GetCommandStringA(_In_ IContextMenu *pCM, _In_ UINT_PTR Id, _In_ UINT GCS, + _Out_writes_(cchMax) LPSTR Buf, _In_ UINT cchMax) +{ + HRESULT hr = pCM->GetCommandString(Id, GCS & ~GCS_UNICODE, NULL, Buf, cchMax); + if (FAILED(hr)) + { + WCHAR buf[MAX_PATH]; + hr = pCM->GetCommandString(Id, GCS | GCS_UNICODE, NULL, (LPSTR)buf, _countof(buf)); + if (SUCCEEDED(hr)) + hr = SHUnicodeToAnsi(buf, Buf, cchMax) > 0 ? S_OK : E_FAIL; + } + return hr; +} + static HRESULT SHELL32_CoCreateInitSF(LPCITEMIDLIST pidlRoot, PERSIST_FOLDER_TARGET_INFO* ppfti, LPCITEMIDLIST pidlChild, const GUID* clsid, REFIID riid, LPVOID *ppvOut) { @@ -37,6 +63,8 @@ static HRESULT SHELL32_CoCreateInitSF(LPCITEMIDLIST pidlRoot, PERSIST_FOLDER_TAR return pShellFolder->QueryInterface(riid, ppvOut); } +#endif // _SHELL32_ + static void WINAPI _InsertMenuItemW( HMENU hMenu, UINT indexMenu, @@ -76,6 +104,28 @@ static void WINAPI _InsertMenuItemW( InsertMenuItemW(hMenu, indexMenu, fByPosition, &mii); } +static HRESULT QueryActiveShellView(IUnknown *pUnkSite, REFGUID rService, IShellView **ppSV) +{ + CComPtr pSB; + HRESULT hr = IUnknown_QueryService(pUnkSite, rService, IID_PPV_ARG(IShellBrowser, &pSB)); + return SUCCEEDED(hr) ? pSB->QueryActiveShellView(ppSV) : hr; +} + +static HRESULT BeginRenameOfShellViewSelection(IUnknown *pUnkSite) +{ + CComPtr pSV; + HRESULT hr = QueryActiveShellView(pUnkSite, SID_SShellBrowser, &pSV); + if (FAILED(hr)) + return hr; + CComPtr pFV2; + if (SUCCEEDED(hr = pSV->QueryInterface(IID_PPV_ARG(IFolderView2, &pFV2)))) + return pFV2->DoRename(); + CComPtr pSV2; + if (SUCCEEDED(hr = pSV->QueryInterface(IID_PPV_ARG(IShellView2, &pSV2)))) + return pSV2->HandleRename(NULL); + return hr; +} + struct FolderViewColumns { int iResource; @@ -894,22 +944,27 @@ STDMETHODIMP CFindFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl if (FAILED(hr = pFolder->GetAttributesOf(1, &pidlChild, rgfInOut))) break; } - *rgfInOut &= ~SFGAO_CANRENAME; // FIXME: Handle SetNameOf return hr; } class CFindFolderContextMenu : public IContextMenu, + public IObjectWithSite, public CComObjectRootEx { CComPtr m_pInner; CComPtr m_shellFolderView; + IUnknown *m_pUnkSite = NULL; + UINT m_cidl; UINT m_MyFirstId = 0; static const UINT ADDITIONAL_MENU_ITEMS = 2; - //// *** IContextMenu methods *** + // *** IContextMenu *** STDMETHODIMP QueryContextMenu(HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { + if (m_cidl > 1) + uFlags &= ~CMF_CANRENAME; + m_MyFirstId = 0; if (idCmdLast - idCmdFirst > ADDITIONAL_MENU_ITEMS) { @@ -927,12 +982,7 @@ class CFindFolderContextMenu : STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi) { - if (!IS_INTRESOURCE(lpcmi->lpVerb)) - { - return m_pInner->InvokeCommand(lpcmi); - } - - UINT idCmd = LOWORD(lpcmi->lpVerb); + WORD idCmd = IS_INTRESOURCE(lpcmi->lpVerb) ? LOWORD(lpcmi->lpVerb) : 0; if (m_MyFirstId && idCmd >= m_MyFirstId && idCmd < m_MyFirstId + ADDITIONAL_MENU_ITEMS) { PCUITEMID_CHILD *apidl; @@ -945,12 +995,27 @@ class CFindFolderContextMenu : { CComHeapPtr folderPidl(ILCreateFromPathW(_ILGetPath(apidl[i]))); if (!folderPidl) - return E_OUTOFMEMORY; + { + hResult = E_OUTOFMEMORY; + break; + } LPCITEMIDLIST child = _ILGetFSPidl(apidl[i]); SHOpenFolderAndSelectItems(folderPidl, 1, &child, 0); } LocalFree(apidl); // Yes, LocalFree - return S_OK; + return hResult; + } + + // TODO: Use GetDfmCmd instead after moving all of this to shell32 + CHAR szVerb[42]; + PCSTR pszVerb = !idCmd ? lpcmi->lpVerb : NULL; + if (!pszVerb && SUCCEEDED(GetCommandStringA(m_pInner, LOWORD(lpcmi->lpVerb), GCS_VERBA, szVerb, _countof(szVerb)))) + pszVerb = szVerb; + + if (pszVerb && !lstrcmpiA(pszVerb, "rename")) + { + // Note: We can't invoke m_pInner (CDefaultContextMenu::DoRename) because the pidl is incorrect and SelectItem will fail. + return BeginRenameOfShellViewSelection(m_pUnkSite); } // FIXME: We can't block FCIDM_SHVIEW_REFRESH here, add items on SFVM_LISTREFRESHED instead @@ -962,8 +1027,27 @@ class CFindFolderContextMenu : return m_pInner->GetCommandString(idCommand, uFlags, lpReserved, lpszName, uMaxNameLen); } + // *** IObjectWithSite *** + STDMETHODIMP SetSite(IUnknown *pUnkSite) override + { + IUnknown_Set(&m_pUnkSite, pUnkSite); + IUnknown_SetSite(m_pInner, pUnkSite); + return S_OK; + } + + STDMETHODIMP GetSite(REFIID riid, void **ppvSite) override + { + *ppvSite = NULL; + return m_pUnkSite ? m_pUnkSite->QueryInterface(riid, ppvSite) : E_FAIL; + } + public: - static HRESULT Create(IShellFolderView *pShellFolderView, IContextMenu *pInnerContextMenu, IContextMenu **pContextMenu) + ~CFindFolderContextMenu() + { + SetSite(NULL); + } + + static HRESULT Create(IShellFolderView *pShellFolderView, UINT cidl, IContextMenu *pInnerContextMenu, IContextMenu **pContextMenu) { CComObject *pObj; HRESULT hResult = CComObject::CreateInstance(&pObj); @@ -971,11 +1055,13 @@ public: return hResult; pObj->m_shellFolderView = pShellFolderView; pObj->m_pInner = pInnerContextMenu; + pObj->m_cidl = cidl; return pObj->QueryInterface(IID_PPV_ARG(IContextMenu, pContextMenu)); } BEGIN_COM_MAP(CFindFolderContextMenu) COM_INTERFACE_ENTRY_IID(IID_IContextMenu, IContextMenu) + COM_INTERFACE_ENTRY_IID(IID_IObjectWithSite, IObjectWithSite) END_COM_MAP() }; @@ -1034,7 +1120,7 @@ STDMETHODIMP CFindFolder::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHI hr = pShellFolder->GetUIObjectOf(hwndOwner, cidl, aFSPidl, riid, prgfInOut, (void**)&pContextMenu); if (FAILED_UNEXPECTEDLY(hr)) return hr; - return CFindFolderContextMenu::Create(m_shellFolderView, pContextMenu, (IContextMenu **)ppvOut); + return CFindFolderContextMenu::Create(m_shellFolderView, cidl, pContextMenu, (IContextMenu **)ppvOut); } CComPtr pFolder; @@ -1056,8 +1142,34 @@ STDMETHODIMP CFindFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, STDMETHODIMP CFindFolder::SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl, LPCOLESTR lpName, DWORD dwFlags, PITEMID_CHILD *pPidlOut) { - UNIMPLEMENTED; - return E_NOTIMPL; + if (!pPidlOut) + return E_INVALIDARG; // Our pidls are special, the caller must get it from us and not from parsing + *pPidlOut = NULL; + + HRESULT hr; + CComPtr pFolder; + PCUITEMID_CHILD pidlChild; + if (FAILED_UNEXPECTEDLY(hr = GetFSFolderAndChild(pidl, &pFolder, &pidlChild))) + return hr; + + PCWSTR pszDir = _ILGetPath(pidl); + PWSTR pszFull = (PWSTR)SHAlloc((wcslen(pszDir) + MAX_PATH) * sizeof(*pszDir)); + if (!pszFull) + return E_OUTOFMEMORY; + + PITEMID_CHILD pidlRawNew = NULL; + hr = pFolder->SetNameOf(hwndOwner, pidlChild, lpName, dwFlags, &pidlRawNew); + if (SUCCEEDED(hr) && pidlRawNew) + { + WCHAR szFileName[MAX_PATH]; + hr = DisplayNameOfW(pFolder, pidlRawNew, SHGDN_FORPARSING | SHGDN_INFOLDER, szFileName, _countof(szFileName)); + ILFree(pidlRawNew); + PathCombineW(pszFull, pszDir, szFileName); + if (SUCCEEDED(hr)) + hr = ((*pPidlOut = _ILCreate(pszFull)) != NULL) ? S_OK : E_OUTOFMEMORY; + } + SHFree(pszFull); + return hr; } //// *** IShellFolderViewCB method *** diff --git a/dll/win32/shell32/CDefView.cpp b/dll/win32/shell32/CDefView.cpp index f865e7d28d3..7fdf61c5c65 100644 --- a/dll/win32/shell32/CDefView.cpp +++ b/dll/win32/shell32/CDefView.cpp @@ -344,13 +344,14 @@ public: HRESULT FillArrangeAsMenu(HMENU hmenuArrange); HRESULT CheckViewMode(HMENU hmenuView); LRESULT DoColumnContextMenu(LRESULT lParam); + HRESULT SelectAndPositionItem(int Idx, UINT fSVSI, POINT *ppt); UINT GetSelections(); SFGAOF GetSelectionAttributes(SFGAOF Query); HRESULT OpenSelectedItems(PCSTR pszVerb = NULL); void OnDeactivate(); void DoActivate(UINT uState); HRESULT drag_notify_subitem(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect); - HRESULT InvokeContextMenuCommand(CComPtr& pCM, LPCSTR lpVerb, POINT* pt = NULL); + HRESULT InvokeContextMenuCommand(CComPtr& pCM, LPCSTR lpVerb, POINT* pt = NULL, bool TryMapVerb = false); LRESULT OnExplorerCommand(UINT uCommand, BOOL bUseSelection); FOLDERVIEWMODE GetDefaultViewMode(); HRESULT GetDefaultViewStream(DWORD Stgm, IStream **ppStream); @@ -391,7 +392,7 @@ public: // *** IShellView2 methods *** STDMETHOD(GetView)(SHELLVIEWID *view_guid, ULONG view_type) override; STDMETHOD(CreateViewWindow2)(LPSV2CVW2_PARAMS view_params) override; - STDMETHOD(HandleRename)(LPCITEMIDLIST new_pidl) override; + STDMETHOD(HandleRename)(LPCITEMIDLIST pidl) override; STDMETHOD(SelectAndPositionItem)(LPCITEMIDLIST item, UINT flags, POINT *point) override; // *** IShellView3 methods *** @@ -2141,7 +2142,7 @@ SFGAOF CDefView::GetSelectionAttributes(SFGAOF Query) return SUCCEEDED(m_pSFParent->GetAttributesOf(m_cidl, m_apidl, &Attr)) ? (Attr & Query) : 0; } -HRESULT CDefView::InvokeContextMenuCommand(CComPtr& pCM, LPCSTR lpVerb, POINT* pt) +HRESULT CDefView::InvokeContextMenuCommand(CComPtr& pCM, LPCSTR lpVerb, POINT* pt, bool TryMapVerb) { CMINVOKECOMMANDINFOEX cmi; @@ -2151,6 +2152,24 @@ HRESULT CDefView::InvokeContextMenuCommand(CComPtr& pCM, LPCSTR lp cmi.lpVerb = lpVerb; cmi.nShow = SW_SHOW; + WCHAR szverbW[sizeof("properties")]; + static const WORD verbmap[] = { FCIDM_SHVIEW_DELETE, FCIDM_SHVIEW_RENAME, + FCIDM_SHVIEW_PROPERTIES, FCIDM_SHVIEW_CREATELINK, + FCIDM_SHVIEW_CUT, FCIDM_SHVIEW_COPY, FCIDM_SHVIEW_INSERT }; + for (SIZE_T i = 0; TryMapVerb && i < _countof(verbmap); ++i) + { + if (cmi.lpVerb != MAKEINTRESOURCEA(verbmap[i])) + continue; + if (PCSTR pszverbA = MapFcidmCmdToVerb((UINT_PTR)cmi.lpVerb)) + { + // Map our internal commands to canonical verbs so non-shell32 menus can understand us + SHAnsiToUnicode(pszverbA, szverbW, _countof(szverbW)); + cmi.lpVerb = pszverbA; + cmi.lpVerbW = szverbW; + break; + } + } + if (GetKeyState(VK_SHIFT) < 0) cmi.fMask |= CMIC_MASK_SHIFT_DOWN; @@ -2388,7 +2407,7 @@ LRESULT CDefView::OnExplorerCommand(UINT uCommand, BOOL bUseSelection) } // FIXME: We should probably use the objects position? - InvokeContextMenuCommand(pCM, MAKEINTRESOURCEA(uCommand), NULL); + InvokeContextMenuCommand(pCM, MAKEINTRESOURCEA(uCommand), NULL, true); return 0; } @@ -3790,21 +3809,13 @@ HRESULT STDMETHODCALLTYPE CDefView::SelectItem(int iItem, DWORD dwFlags) HRESULT STDMETHODCALLTYPE CDefView::SelectAndPositionItems(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, POINT *apt, DWORD dwFlags) { ASSERT(m_ListView); - - /* Reset the selection */ - m_ListView.SetItemState(-1, 0, LVIS_SELECTED); - - int lvIndex; + m_ListView.SetItemState(-1, 0, LVIS_SELECTED); // Reset the selection for (UINT i = 0 ; i < cidl; i++) { - lvIndex = LV_FindItemByPidl(apidl[i]); + int lvIndex = LV_FindItemByPidl(apidl[i]); if (lvIndex != -1) - { - SelectItem(lvIndex, dwFlags); - m_ListView.SetItemPosition(lvIndex, &apt[i]); - } + SelectAndPositionItem(lvIndex, dwFlags, apt ? &apt[i] : NULL); } - return S_OK; } @@ -3918,16 +3929,35 @@ HRESULT STDMETHODCALLTYPE CDefView::CreateViewWindow3(IShellBrowser *psb, IShell return S_OK; } -HRESULT STDMETHODCALLTYPE CDefView::HandleRename(LPCITEMIDLIST new_pidl) +HRESULT STDMETHODCALLTYPE CDefView::HandleRename(LPCITEMIDLIST pidl) { - FIXME("(%p)->(%p) stub\n", this, new_pidl); - return E_NOTIMPL; + if (!pidl) + { + int idx = m_ListView.GetNextItem(-1, LVNI_SELECTED | LVNI_FOCUSED); + if (idx < 0) + idx = m_ListView.GetNextItem(-1, LVNI_SELECTED); + pidl = _PidlByItem(idx); + } + if (ILFindLastID(pidl) != pidl) + return E_INVALIDARG; + return SelectItem(pidl, SVSI_EDIT | SVSI_ENSUREVISIBLE | SVSI_FOCUSED | SVSI_SELECT); +} + +HRESULT CDefView::SelectAndPositionItem(int Idx, UINT fSVSI, POINT *ppt) +{ + if (Idx == -1) + return fSVSI == SVSI_DESELECTOTHERS ? SelectItem(-2, fSVSI) : E_INVALIDARG; + if (ppt) + m_ListView.SetItemPosition(Idx, ppt); + return SelectItem(Idx, fSVSI); // After SetItemPosition for SVSI_ENSUREVISIBLE } HRESULT STDMETHODCALLTYPE CDefView::SelectAndPositionItem(LPCITEMIDLIST item, UINT flags, POINT *point) { - FIXME("(%p)->(%p, %u, %p) stub\n", this, item, flags, point); - return E_NOTIMPL; + if (!item) + return SelectAndPositionItem(-1, flags, point); + int idx = LV_FindItemByPidl(item); + return idx != -1 ? SelectAndPositionItem(idx, flags, point) : S_FALSE; } // IShellFolderView implementation diff --git a/dll/win32/shell32/CDefaultContextMenu.cpp b/dll/win32/shell32/CDefaultContextMenu.cpp index 554aeedbc01..2f361901745 100644 --- a/dll/win32/shell32/CDefaultContextMenu.cpp +++ b/dll/win32/shell32/CDefaultContextMenu.cpp @@ -180,6 +180,16 @@ static const struct _StaticInvokeCommandMap_ { "moveto", FCIDM_SHVIEW_MOVETO }, }; +PCSTR MapFcidmCmdToVerb(_In_ UINT_PTR CmdId) +{ + for (SIZE_T i = 0; i < _countof(g_StaticInvokeCmdMap); ++i) + { + if (g_StaticInvokeCmdMap[i].IntVerb == CmdId && CmdId) + return g_StaticInvokeCmdMap[i].szStringVerb; + } + return NULL; +} + UINT MapVerbToDfmCmd(_In_ LPCSTR verba) { for (UINT i = 0; i < _countof(g_StaticInvokeCmdMap); ++i) diff --git a/dll/win32/shell32/precomp.h b/dll/win32/shell32/precomp.h index 3d0bda0ec5e..28930d528d2 100644 --- a/dll/win32/shell32/precomp.h +++ b/dll/win32/shell32/precomp.h @@ -205,6 +205,8 @@ DCMA_InsertMenuItems( HRESULT SHELL32_DefaultContextMenuCallBack(IShellFolder *psf, IDataObject *pdo, UINT msg); +PCSTR +MapFcidmCmdToVerb(_In_ UINT_PTR CmdId); UINT MapVerbToDfmCmd(_In_ LPCSTR verba); UINT