From fa95a96e9b2caf92e1ef9b2378757d9f3305616d Mon Sep 17 00:00:00 2001 From: Whindmar Saksit Date: Fri, 19 Jul 2024 13:37:53 +0200 Subject: [PATCH] [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. --- dll/win32/shell32/CShellLink.cpp | 4 +- .../shell32/droptargets/CFSDropTarget.cpp | 175 +++++++++--------- dll/win32/shell32/shldataobject.cpp | 7 +- 3 files changed, 86 insertions(+), 100 deletions(-) diff --git a/dll/win32/shell32/CShellLink.cpp b/dll/win32/shell32/CShellLink.cpp index 3175b21f34b..e3064bbcdae 100644 --- a/dll/win32/shell32/CShellLink.cpp +++ b/dll/win32/shell32/CShellLink.cpp @@ -900,7 +900,7 @@ HRESULT STDMETHODCALLTYPE CShellLink::Save(IStream *stm, BOOL fClearDirty) if (m_pPidl) 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; if (m_sDescription && *m_sDescription) 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); if (FAILED(hr)) diff --git a/dll/win32/shell32/droptargets/CFSDropTarget.cpp b/dll/win32/shell32/droptargets/CFSDropTarget.cpp index 4d9e506f870..f7694320f57 100644 --- a/dll/win32/shell32/droptargets/CFSDropTarget.cpp +++ b/dll/win32/shell32/droptargets/CFSDropTarget.cpp @@ -24,6 +24,35 @@ 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 @@ -166,6 +195,7 @@ BOOL CFSDropTarget::_QueryDrop(DWORD dwKeyState, LPDWORD pdwEffect) { /* 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. */ + GetDefaultCopyMoveEffect(); DWORD dwEffect = m_dwDefaultEffect; @@ -193,18 +223,20 @@ HRESULT CFSDropTarget::_GetEffectFromMenu(IDataObject *pDataObject, POINTL pt, D HMENU hpopupmenu = GetSubMenu(hmenu, 0); + SHELL_LimitDropEffectToItemAttributes(pDataObject, &dwAvailableEffects); if ((dwAvailableEffects & DROPEFFECT_COPY) == 0) DeleteMenu(hpopupmenu, IDM_COPYHERE, MF_BYCOMMAND); - else if ((dwAvailableEffects & DROPEFFECT_MOVE) == 0) + if ((dwAvailableEffects & DROPEFFECT_MOVE) == 0) 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); - if ((*pdwEffect & DROPEFFECT_COPY)) + GetDefaultCopyMoveEffect(); + if (*pdwEffect & dwAvailableEffects & DROPEFFECT_COPY) SetMenuDefaultItem(hpopupmenu, IDM_COPYHERE, FALSE); - else if ((*pdwEffect & DROPEFFECT_MOVE)) + else if (*pdwEffect & dwAvailableEffects & DROPEFFECT_MOVE) SetMenuDefaultItem(hpopupmenu, IDM_MOVEHERE, FALSE); - else if ((*pdwEffect & DROPEFFECT_LINK)) + else if (dwAvailableEffects & DROPEFFECT_LINK) SetMenuDefaultItem(hpopupmenu, IDM_LINKHERE, FALSE); /* FIXME: We need to support shell extensions here */ @@ -223,6 +255,7 @@ HRESULT CFSDropTarget::_GetEffectFromMenu(IDataObject *pDataObject, POINTL pt, D NULL, NULL); + SetForegroundWindow(hwndDummy); // Required for aborting by pressing Esc when dragging from Explorer to desktop UINT uCommand = TrackPopupMenu(hpopupmenu, TPM_LEFTALIGN | TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_NONOTIFY, pt.x, pt.y, 0, hwndDummy, NULL); @@ -548,125 +581,83 @@ HRESULT CFSDropTarget::_DoDrop(IDataObject *pDataObject, if (bLinking) { - WCHAR wszPath[MAX_PATH]; - WCHAR wszTarget[MAX_PATH]; + WCHAR wszNewLnk[MAX_PATH]; 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 */ for (UINT i = 0; i < lpcida->cidl; i++) { - // Find out which file we're linking. - STRRET strFile; - hr = psfFrom->GetDisplayNameOf(apidl[i], SHGDN_FORPARSING, &strFile); + CComHeapPtr pidlFull; + hr = SHILCombine(pidl, apidl[i], &pidlFull); + + WCHAR targetName[MAX_PATH]; + if (SUCCEEDED(hr)) + hr = Shell_DisplayNameOf(psfFrom, apidl[i], SHGDN_FOREDITING | SHGDN_INFOLDER, targetName, _countof(targetName)); 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); - if (FAILED_UNEXPECTEDLY(hr)) - 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_ErrorBox(m_hwndSite, hr); + break; } + SHELL_StripIllegalFsNameCharacters(targetName); - // Creating a buffer to hold the combined path. - WCHAR wszCombined[MAX_PATH]; - PathCombineW(wszCombined, m_sPathTarget, pwszFileName); + WCHAR wszCombined[MAX_PATH + _countof(targetName)]; + PathCombineW(wszCombined, m_sPathTarget, targetName); // Check to see if the source is a link + SFGAOF att = SHGetAttributes(psfFrom, apidl[i], SFGAO_FOLDER | SFGAO_STREAM | SFGAO_FILESYSTEM); BOOL fSourceIsLink = FALSE; - if (!wcsicmp(PathFindExtensionW(wszPath), L".lnk")) + if (!wcsicmp(PathFindExtensionW(targetName), L".lnk") && (att & (SFGAO_FOLDER | SFGAO_STREAM)) != SFGAO_FOLDER) { fSourceIsLink = TRUE; PathRemoveExtensionW(wszCombined); } // Create a pathname to save the new link. - _GetUniqueFileName(wszCombined, L".lnk", wszTarget, TRUE); + _GetUniqueFileName(wszCombined, L".lnk", wszNewLnk, TRUE); CComPtr ppf; if (fSourceIsLink) { - hr = IShellLink_ConstructFromPath(wszPath, IID_PPV_ARG(IPersistFile, &ppf)); - if (FAILED_UNEXPECTEDLY(hr)) - break; + PWSTR pwszTargetFull; + hr = SHGetNameFromIDList(pidlFull, SIGDN_DESKTOPABSOLUTEPARSING, &pwszTargetFull); + if (!FAILED_UNEXPECTEDLY(hr)) + { + hr = IShellLink_ConstructFromPath(pwszTargetFull, IID_PPV_ARG(IPersistFile, &ppf)); + FAILED_UNEXPECTEDLY(hr); + SHFree(pwszTargetFull); + } } else { CComPtr pLink; hr = CShellLink::_CreatorClass::CreateInstance(NULL, IID_PPV_ARG(IShellLinkW, &pLink)); - if (FAILED_UNEXPECTEDLY(hr)) - break; + if (!FAILED_UNEXPECTEDLY(hr)) + { + hr = pLink->SetIDList(pidlFull); + if (!FAILED_UNEXPECTEDLY(hr)) + hr = pLink->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf)); - WCHAR szDirPath[MAX_PATH], *pwszFile; - GetFullPathName(wszPath, MAX_PATH, szDirPath, &pwszFile); - if (pwszFile) - pwszFile[0] = 0; - - hr = pLink->SetPath(wszPath); - if (FAILED_UNEXPECTEDLY(hr)) - break; - - hr = pLink->SetWorkingDirectory(szDirPath); - if (FAILED_UNEXPECTEDLY(hr)) - break; - - hr = pLink->QueryInterface(IID_PPV_ARG(IPersistFile, &ppf)); - if (FAILED_UNEXPECTEDLY(hr)) - break; + PWSTR pwszPath, pSep; + if ((att & SFGAO_FILESYSTEM) && SUCCEEDED(SHGetNameFromIDList(pidlFull, SIGDN_FILESYSPATH, &pwszPath))) + { + if ((pSep = PathFindFileNameW(pwszPath)) > pwszPath) + { + pSep[-1] = UNICODE_NULL; + pLink->SetWorkingDirectory(pwszPath); + } + SHFree(pwszPath); + } + } } - - hr = ppf->Save(wszTarget, !fSourceIsLink); + if (SUCCEEDED(hr)) + hr = ppf->Save(wszNewLnk, !fSourceIsLink); if (FAILED_UNEXPECTEDLY(hr)) + { + SHELL_ErrorBox(m_hwndSite, hr); break; - - SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, wszTarget, NULL); + } + SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, wszNewLnk, NULL); } } else diff --git a/dll/win32/shell32/shldataobject.cpp b/dll/win32/shell32/shldataobject.cpp index e63a0859c17..fb50c78e7ac 100644 --- a/dll/win32/shell32/shldataobject.cpp +++ b/dll/win32/shell32/shldataobject.cpp @@ -28,12 +28,7 @@ static_assert(sizeof(DataObjectAttributes) == 0xc, "Unexpected struct size!"); static HRESULT _BindToObject(PCUIDLIST_ABSOLUTE pidl, CComPtr& spFolder) { - CComPtr spDesktop; - HRESULT hr = SHGetDesktopFolder(&spDesktop); - if (FAILED(hr)) - return hr; - - return spDesktop->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &spFolder)); + return SHBindToObject(NULL, pidl, IID_PPV_ARG(IShellFolder, &spFolder)); } EXTERN_C