[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
This commit is contained in:
Whindmar Saksit
2025-06-21 16:52:48 +02:00
committed by GitHub
parent 6653bb5224
commit 99b99bda0f
5 changed files with 188 additions and 42 deletions

View File

@@ -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");

View File

@@ -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<IShellBrowser> 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<IShellView> pSV;
HRESULT hr = QueryActiveShellView(pUnkSite, SID_SShellBrowser, &pSV);
if (FAILED(hr))
return hr;
CComPtr<IFolderView2> pFV2;
if (SUCCEEDED(hr = pSV->QueryInterface(IID_PPV_ARG(IFolderView2, &pFV2))))
return pFV2->DoRename();
CComPtr<IShellView2> 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<CComMultiThreadModelNoCS>
{
CComPtr<IContextMenu> m_pInner;
CComPtr<IShellFolderView> 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<ITEMIDLIST> 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<CFindFolderContextMenu> *pObj;
HRESULT hResult = CComObject<CFindFolderContextMenu>::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<IShellFolder> 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<IShellFolder> 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 ***

View File

@@ -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<IContextMenu>& pCM, LPCSTR lpVerb, POINT* pt = NULL);
HRESULT InvokeContextMenuCommand(CComPtr<IContextMenu>& 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<IContextMenu>& pCM, LPCSTR lpVerb, POINT* pt)
HRESULT CDefView::InvokeContextMenuCommand(CComPtr<IContextMenu>& pCM, LPCSTR lpVerb, POINT* pt, bool TryMapVerb)
{
CMINVOKECOMMANDINFOEX cmi;
@@ -2151,6 +2152,24 @@ HRESULT CDefView::InvokeContextMenuCommand(CComPtr<IContextMenu>& 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

View File

@@ -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)

View File

@@ -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