[SHELL32] Use correct PIDL and name when dropping to create a shortcut (#6999)

Ask for the editing name instead of the parsing name to get a suitable shortcut name for PIDL items.
This commit is contained in:
Whindmar Saksit 2024-07-19 13:37:53 +02:00 committed by GitHub
parent 089788a52a
commit fa95a96e9b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 86 additions and 100 deletions

View file

@ -900,7 +900,7 @@ HRESULT STDMETHODCALLTYPE CShellLink::Save(IStream *stm, BOOL fClearDirty)
if (m_pPidl) if (m_pPidl)
m_Header.dwFlags |= SLDF_HAS_ID_LIST; m_Header.dwFlags |= SLDF_HAS_ID_LIST;
if (m_sPath) if (m_sPath && *m_sPath && !(m_Header.dwFlags & SLDF_FORCE_NO_LINKINFO))
m_Header.dwFlags |= SLDF_HAS_LINK_INFO; m_Header.dwFlags |= SLDF_HAS_LINK_INFO;
if (m_sDescription && *m_sDescription) if (m_sDescription && *m_sDescription)
m_Header.dwFlags |= SLDF_HAS_NAME; m_Header.dwFlags |= SLDF_HAS_NAME;
@ -936,7 +936,7 @@ HRESULT STDMETHODCALLTYPE CShellLink::Save(IStream *stm, BOOL fClearDirty)
} }
} }
if (m_sPath) if (m_Header.dwFlags & SLDF_HAS_LINK_INFO)
{ {
hr = Stream_WriteLocationInfo(stm, m_sPath, &volume); hr = Stream_WriteLocationInfo(stm, m_sPath, &volume);
if (FAILED(hr)) if (FAILED(hr))

View file

@ -24,6 +24,35 @@
WINE_DEFAULT_DEBUG_CHANNEL (shell); WINE_DEFAULT_DEBUG_CHANNEL (shell);
static void SHELL_StripIllegalFsNameCharacters(_Inout_ LPWSTR Buf)
{
for (LPWSTR src = Buf, dst = src;;)
{
*dst = *src;
if (!*dst)
break;
else if (wcschr(INVALID_FILETITLE_CHARACTERSW, *dst))
src = CharNextW(src);
else
++src, ++dst;
}
}
static HRESULT
SHELL_LimitDropEffectToItemAttributes(_In_ IDataObject *pDataObject, _Inout_ PDWORD pdwEffect)
{
DWORD att = *pdwEffect & (SFGAO_CANCOPY | SFGAO_CANMOVE | SFGAO_CANLINK); // DROPEFFECT maps perfectly to these SFGAO bits
HRESULT hr = SHGetAttributesFromDataObject(pDataObject, att, &att, NULL);
if (FAILED(hr))
return S_FALSE;
*pdwEffect &= ~(SFGAO_CANCOPY | SFGAO_CANMOVE | SFGAO_CANLINK) | att;
return hr;
}
static void GetDefaultCopyMoveEffect()
{
// FIXME: When the source is on a different volume than the target, change default from move to copy
}
/**************************************************************************** /****************************************************************************
* CFSDropTarget::_CopyItems * CFSDropTarget::_CopyItems
@ -166,6 +195,7 @@ BOOL CFSDropTarget::_QueryDrop(DWORD dwKeyState, LPDWORD pdwEffect)
{ {
/* TODO Windows does different drop effects if dragging across drives. /* TODO Windows does different drop effects if dragging across drives.
i.e., it will copy instead of move if the directories are on different disks. */ i.e., it will copy instead of move if the directories are on different disks. */
GetDefaultCopyMoveEffect();
DWORD dwEffect = m_dwDefaultEffect; DWORD dwEffect = m_dwDefaultEffect;
@ -193,18 +223,20 @@ HRESULT CFSDropTarget::_GetEffectFromMenu(IDataObject *pDataObject, POINTL pt, D
HMENU hpopupmenu = GetSubMenu(hmenu, 0); HMENU hpopupmenu = GetSubMenu(hmenu, 0);
SHELL_LimitDropEffectToItemAttributes(pDataObject, &dwAvailableEffects);
if ((dwAvailableEffects & DROPEFFECT_COPY) == 0) if ((dwAvailableEffects & DROPEFFECT_COPY) == 0)
DeleteMenu(hpopupmenu, IDM_COPYHERE, MF_BYCOMMAND); DeleteMenu(hpopupmenu, IDM_COPYHERE, MF_BYCOMMAND);
else if ((dwAvailableEffects & DROPEFFECT_MOVE) == 0) if ((dwAvailableEffects & DROPEFFECT_MOVE) == 0)
DeleteMenu(hpopupmenu, IDM_MOVEHERE, MF_BYCOMMAND); DeleteMenu(hpopupmenu, IDM_MOVEHERE, MF_BYCOMMAND);
else if ((dwAvailableEffects & DROPEFFECT_LINK) == 0) if ((dwAvailableEffects & DROPEFFECT_LINK) == 0 && (dwAvailableEffects & (DROPEFFECT_COPY | DROPEFFECT_MOVE)))
DeleteMenu(hpopupmenu, IDM_LINKHERE, MF_BYCOMMAND); DeleteMenu(hpopupmenu, IDM_LINKHERE, MF_BYCOMMAND);
if ((*pdwEffect & DROPEFFECT_COPY)) GetDefaultCopyMoveEffect();
if (*pdwEffect & dwAvailableEffects & DROPEFFECT_COPY)
SetMenuDefaultItem(hpopupmenu, IDM_COPYHERE, FALSE); SetMenuDefaultItem(hpopupmenu, IDM_COPYHERE, FALSE);
else if ((*pdwEffect & DROPEFFECT_MOVE)) else if (*pdwEffect & dwAvailableEffects & DROPEFFECT_MOVE)
SetMenuDefaultItem(hpopupmenu, IDM_MOVEHERE, FALSE); SetMenuDefaultItem(hpopupmenu, IDM_MOVEHERE, FALSE);
else if ((*pdwEffect & DROPEFFECT_LINK)) else if (dwAvailableEffects & DROPEFFECT_LINK)
SetMenuDefaultItem(hpopupmenu, IDM_LINKHERE, FALSE); SetMenuDefaultItem(hpopupmenu, IDM_LINKHERE, FALSE);
/* FIXME: We need to support shell extensions here */ /* FIXME: We need to support shell extensions here */
@ -223,6 +255,7 @@ HRESULT CFSDropTarget::_GetEffectFromMenu(IDataObject *pDataObject, POINTL pt, D
NULL, NULL,
NULL); NULL);
SetForegroundWindow(hwndDummy); // Required for aborting by pressing Esc when dragging from Explorer to desktop
UINT uCommand = TrackPopupMenu(hpopupmenu, UINT uCommand = TrackPopupMenu(hpopupmenu,
TPM_LEFTALIGN | TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_NONOTIFY, TPM_LEFTALIGN | TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_NONOTIFY,
pt.x, pt.y, 0, hwndDummy, NULL); pt.x, pt.y, 0, hwndDummy, NULL);
@ -548,125 +581,83 @@ HRESULT CFSDropTarget::_DoDrop(IDataObject *pDataObject,
if (bLinking) if (bLinking)
{ {
WCHAR wszPath[MAX_PATH]; WCHAR wszNewLnk[MAX_PATH];
WCHAR wszTarget[MAX_PATH];
TRACE("target path = %s\n", debugstr_w(m_sPathTarget)); TRACE("target path = %s\n", debugstr_w(m_sPathTarget));
/* We need to create a link for each pidl in the copied items, so step through the pidls from the clipboard */ /* We need to create a link for each pidl in the copied items, so step through the pidls from the clipboard */
for (UINT i = 0; i < lpcida->cidl; i++) for (UINT i = 0; i < lpcida->cidl; i++)
{ {
// Find out which file we're linking. CComHeapPtr<ITEMIDLIST_ABSOLUTE> pidlFull;
STRRET strFile; hr = SHILCombine(pidl, apidl[i], &pidlFull);
hr = psfFrom->GetDisplayNameOf(apidl[i], SHGDN_FORPARSING, &strFile);
WCHAR targetName[MAX_PATH];
if (SUCCEEDED(hr))
hr = Shell_DisplayNameOf(psfFrom, apidl[i], SHGDN_FOREDITING | SHGDN_INFOLDER, targetName, _countof(targetName));
if (FAILED_UNEXPECTEDLY(hr)) if (FAILED_UNEXPECTEDLY(hr))
break;
hr = StrRetToBufW(&strFile, apidl[i], wszPath, _countof(wszPath));
if (FAILED_UNEXPECTEDLY(hr))
break;
TRACE("source path = %s\n", debugstr_w(wszPath));
WCHAR wszDisplayName[MAX_PATH];
LPWSTR pwszFileName = PathFindFileNameW(wszPath);
if (PathIsRootW(wszPath)) // Drive?
{ {
hr = psfFrom->GetDisplayNameOf(apidl[i], SHGDN_NORMAL, &strFile); SHELL_ErrorBox(m_hwndSite, hr);
if (FAILED_UNEXPECTEDLY(hr)) break;
break;
hr = StrRetToBufW(&strFile, apidl[i], wszDisplayName, _countof(wszDisplayName));
if (FAILED_UNEXPECTEDLY(hr))
break;
// Delete a ':' in wszDisplayName.
LPWSTR pch0 = wcschr(wszDisplayName, L':');
if (pch0)
{
do
{
*pch0 = *(pch0 + 1);
++pch0;
} while (*pch0);
}
pwszFileName = wszDisplayName; // Use wszDisplayName
}
else if (wszPath[0] == L':' && wszPath[1] == L':') // ::{GUID}?
{
CLSID clsid;
hr = ::CLSIDFromString(&wszPath[2], &clsid);
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidl = ILCreateFromPathW(wszPath);
if (pidl)
{
SHFILEINFOW fi = { NULL };
SHGetFileInfoW((LPCWSTR)pidl, 0, &fi, sizeof(fi),
SHGFI_DISPLAYNAME | SHGFI_PIDL);
if (fi.szDisplayName[0])
{
lstrcpynW(wszDisplayName, fi.szDisplayName, _countof(wszDisplayName));
pwszFileName = wszDisplayName; // Use wszDisplayName
}
ILFree(pidl);
}
}
} }
SHELL_StripIllegalFsNameCharacters(targetName);
// Creating a buffer to hold the combined path. WCHAR wszCombined[MAX_PATH + _countof(targetName)];
WCHAR wszCombined[MAX_PATH]; PathCombineW(wszCombined, m_sPathTarget, targetName);
PathCombineW(wszCombined, m_sPathTarget, pwszFileName);
// Check to see if the source is a link // Check to see if the source is a link
SFGAOF att = SHGetAttributes(psfFrom, apidl[i], SFGAO_FOLDER | SFGAO_STREAM | SFGAO_FILESYSTEM);
BOOL fSourceIsLink = FALSE; BOOL fSourceIsLink = FALSE;
if (!wcsicmp(PathFindExtensionW(wszPath), L".lnk")) if (!wcsicmp(PathFindExtensionW(targetName), L".lnk") && (att & (SFGAO_FOLDER | SFGAO_STREAM)) != SFGAO_FOLDER)
{ {
fSourceIsLink = TRUE; fSourceIsLink = TRUE;
PathRemoveExtensionW(wszCombined); PathRemoveExtensionW(wszCombined);
} }
// Create a pathname to save the new link. // Create a pathname to save the new link.
_GetUniqueFileName(wszCombined, L".lnk", wszTarget, TRUE); _GetUniqueFileName(wszCombined, L".lnk", wszNewLnk, TRUE);
CComPtr<IPersistFile> ppf; CComPtr<IPersistFile> ppf;
if (fSourceIsLink) if (fSourceIsLink)
{ {
hr = IShellLink_ConstructFromPath(wszPath, IID_PPV_ARG(IPersistFile, &ppf)); PWSTR pwszTargetFull;
if (FAILED_UNEXPECTEDLY(hr)) hr = SHGetNameFromIDList(pidlFull, SIGDN_DESKTOPABSOLUTEPARSING, &pwszTargetFull);
break; if (!FAILED_UNEXPECTEDLY(hr))
{
hr = IShellLink_ConstructFromPath(pwszTargetFull, IID_PPV_ARG(IPersistFile, &ppf));
FAILED_UNEXPECTEDLY(hr);
SHFree(pwszTargetFull);
}
} }
else else
{ {
CComPtr<IShellLinkW> pLink; CComPtr<IShellLinkW> pLink;
hr = CShellLink::_CreatorClass::CreateInstance(NULL, IID_PPV_ARG(IShellLinkW, &pLink)); hr = CShellLink::_CreatorClass::CreateInstance(NULL, IID_PPV_ARG(IShellLinkW, &pLink));
if (FAILED_UNEXPECTEDLY(hr)) if (!FAILED_UNEXPECTEDLY(hr))
break; {
hr = pLink->SetIDList(pidlFull);
if (!FAILED_UNEXPECTEDLY(hr))
hr = pLink->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
WCHAR szDirPath[MAX_PATH], *pwszFile; PWSTR pwszPath, pSep;
GetFullPathName(wszPath, MAX_PATH, szDirPath, &pwszFile); if ((att & SFGAO_FILESYSTEM) && SUCCEEDED(SHGetNameFromIDList(pidlFull, SIGDN_FILESYSPATH, &pwszPath)))
if (pwszFile) {
pwszFile[0] = 0; if ((pSep = PathFindFileNameW(pwszPath)) > pwszPath)
{
hr = pLink->SetPath(wszPath); pSep[-1] = UNICODE_NULL;
if (FAILED_UNEXPECTEDLY(hr)) pLink->SetWorkingDirectory(pwszPath);
break; }
SHFree(pwszPath);
hr = pLink->SetWorkingDirectory(szDirPath); }
if (FAILED_UNEXPECTEDLY(hr)) }
break;
hr = pLink->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf));
if (FAILED_UNEXPECTEDLY(hr))
break;
} }
if (SUCCEEDED(hr))
hr = ppf->Save(wszTarget, !fSourceIsLink); hr = ppf->Save(wszNewLnk, !fSourceIsLink);
if (FAILED_UNEXPECTEDLY(hr)) if (FAILED_UNEXPECTEDLY(hr))
{
SHELL_ErrorBox(m_hwndSite, hr);
break; break;
}
SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, wszTarget, NULL); SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, wszNewLnk, NULL);
} }
} }
else else

View file

@ -28,12 +28,7 @@ static_assert(sizeof(DataObjectAttributes) == 0xc, "Unexpected struct size!");
static static
HRESULT _BindToObject(PCUIDLIST_ABSOLUTE pidl, CComPtr<IShellFolder>& spFolder) HRESULT _BindToObject(PCUIDLIST_ABSOLUTE pidl, CComPtr<IShellFolder>& spFolder)
{ {
CComPtr<IShellFolder> spDesktop; return SHBindToObject(NULL, pidl, IID_PPV_ARG(IShellFolder, &spFolder));
HRESULT hr = SHGetDesktopFolder(&spDesktop);
if (FAILED(hr))
return hr;
return spDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &spFolder));
} }
EXTERN_C EXTERN_C