diff --git a/dll/win32/shell32/CDropTargetHelper.cpp b/dll/win32/shell32/CDropTargetHelper.cpp index 4533c266ad2..9cfeac0567a 100644 --- a/dll/win32/shell32/CDropTargetHelper.cpp +++ b/dll/win32/shell32/CDropTargetHelper.cpp @@ -81,7 +81,7 @@ HRESULT WINAPI CDropTargetHelper::Show(BOOL fShow) /************************************************************************* * SH32_SimulateDropWithSite [SHELL32.INTERNAL] */ -static HRESULT SH32_SimulateDropWithSite(IDropTarget *pDT, IDataObject *pDO, DWORD grfKeyState, PPOINTL pPtl, LPDWORD pdwEffect, IUnknown *pSite) +HRESULT SH32_SimulateDropWithSite(IDropTarget *pDT, IDataObject *pDO, DWORD grfKeyState, PPOINTL pPtl, LPDWORD pdwEffect, IUnknown *pSite) { CScopedSetObjectWithSite site(pDT, pSite); return SHSimulateDrop(pDT, pDO, grfKeyState, pPtl, pdwEffect); diff --git a/dll/win32/shell32/CDropTargetHelper.h b/dll/win32/shell32/CDropTargetHelper.h index ae5974dde0b..4e0450abcf1 100644 --- a/dll/win32/shell32/CDropTargetHelper.h +++ b/dll/win32/shell32/CDropTargetHelper.h @@ -54,4 +54,7 @@ BEGIN_COM_MAP(CDropTargetHelper) END_COM_MAP() }; +HRESULT +SH32_SimulateDropWithSite(IDropTarget *pDT, IDataObject *pDO, DWORD grfKeyState, PPOINTL pPtl, LPDWORD pdwEffect, IUnknown *pSite); + #endif /* _DRAGDROPHELPER_H_ */ diff --git a/dll/win32/shell32/CFolder.cpp b/dll/win32/shell32/CFolder.cpp index b3854a2a8c7..5a6a0e4f14e 100644 --- a/dll/win32/shell32/CFolder.cpp +++ b/dll/win32/shell32/CFolder.cpp @@ -6,10 +6,13 @@ */ #include "precomp.h" +#include "prop.h" WINE_DEFAULT_DEBUG_CHANNEL(shell); +EXTERN_C BOOL WINAPI Win32CreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec); + CFolder::CFolder() { } @@ -18,21 +21,19 @@ CFolder::~CFolder() { } -HRESULT CFolder::Initialize(LPITEMIDLIST idlist) +HRESULT CFolder::Initialize(LPCITEMIDLIST idlist) { m_idlist.Attach(ILClone(idlist)); + if (!m_idlist) + return E_OUTOFMEMORY; return CShellDispatch_Constructor(IID_PPV_ARG(IShellDispatch, &m_Application)); } HRESULT CFolder::GetShellFolder(CComPtr& psfCurrent) { - CComPtr psfDesktop; - - HRESULT hr = SHGetDesktopFolder(&psfDesktop); - if (FAILED_UNEXPECTEDLY(hr)) - return hr; - - return psfDesktop->BindToObject(m_idlist, NULL, IID_PPV_ARG(IShellFolder, &psfCurrent)); + HRESULT hr = SHBindToObject(NULL, GetAbsoluteIDList(), NULL, IID_PPV_ARG(IShellFolder, &psfCurrent)); + FAILED_UNEXPECTEDLY(hr); + return hr; } // *** Folder methods *** @@ -41,12 +42,9 @@ HRESULT STDMETHODCALLTYPE CFolder::get_Title(BSTR *pbs) if (!pbs) return E_POINTER; - WCHAR path[MAX_PATH+2] = {0}; + WCHAR path[MAX_PATH]; HRESULT hr = ILGetDisplayNameExW(NULL, m_idlist, path, ILGDN_INFOLDER) ? S_OK : E_FAIL; - if (FAILED_UNEXPECTEDLY(hr)) - return hr; - - *pbs = SysAllocString(path); + *pbs = SysAllocString(SUCCEEDED(hr) ? path : L""); return S_OK; } @@ -75,17 +73,23 @@ HRESULT STDMETHODCALLTYPE CFolder::get_Parent(IDispatch **ppid) HRESULT STDMETHODCALLTYPE CFolder::get_ParentFolder(Folder **ppsf) { - TRACE("(%p, %p)\n", this); + TRACE("(%p, %p)\n", this, ppsf); *ppsf = NULL; + LPCITEMIDLIST pidlAbsSelf = GetAbsoluteIDList(); + if (ILIsEmpty(pidlAbsSelf)) + return S_FALSE; - return E_NOTIMPL; + CComHeapPtr pidlParent; + if (FAILED(SHILCloneParent(pidlAbsSelf, &pidlParent))) + return E_OUTOFMEMORY; + return ShellObjectCreatorInit(static_cast(pidlParent), IID_PPV_ARG(Folder, ppsf)); } HRESULT STDMETHODCALLTYPE CFolder::Items(FolderItems **ppid) { /* FolderItems_Constructor */ - return ShellObjectCreatorInit(static_cast(m_idlist), this, IID_PPV_ARG(FolderItems, ppid)); + return ShellObjectCreatorInit(GetAbsoluteIDList(), this, IID_PPV_ARG(FolderItems, ppid)); } HRESULT STDMETHODCALLTYPE CFolder::ParseName(BSTR bName, FolderItem **ppid) @@ -97,42 +101,145 @@ HRESULT STDMETHODCALLTYPE CFolder::ParseName(BSTR bName, FolderItem **ppid) CComPtr psfCurrent; HRESULT hr = GetShellFolder(psfCurrent); - if (FAILED_UNEXPECTEDLY(hr)) + if (FAILED(hr)) return hr; CComHeapPtr relativePidl; hr = psfCurrent->ParseDisplayName(NULL, NULL, bName, NULL, &relativePidl, NULL); if (!SUCCEEDED(hr)) return S_FALSE; - CComHeapPtr combined; - combined.Attach(ILCombine(m_idlist, relativePidl)); + combined.Attach(ILCombine(GetAbsoluteIDList(), relativePidl)); + if (!combined) + return E_OUTOFMEMORY; - return ShellObjectCreatorInit(this, static_cast(combined), IID_PPV_ARG(FolderItem, ppid)); + CComHeapPtr parentPidl; + if (FAILED(hr = SHILCloneParent((LPCITEMIDLIST)combined, &parentPidl))) + return hr; + CComPtr pParent; // The parent Folder of the thing we just parsed + hr = ShellObjectCreatorInit(parentPidl, IID_PPV_ARG(Folder, &pParent)); + if (FAILED(hr)) + return hr; + + return ShellObjectCreatorInit(pParent, static_cast(combined), IID_PPV_ARG(FolderItem, ppid)); } HRESULT STDMETHODCALLTYPE CFolder::NewFolder(BSTR bName, VARIANT vOptions) { TRACE("(%p, %s, %s)\n", this, wine_dbgstr_w(bName), wine_dbgstr_variant(&vOptions)); + + // Note: MSDN says vOptions "is not currently used" + CComPtr pStg, pStgNew; + HRESULT hr = SHBindToObject(NULL, GetAbsoluteIDList(), NULL, IID_PPV_ARG(IStorage, &pStg)); + if (FAILED(hr)) + goto tryfs;//return hr; + hr = pStg->CreateStorage(bName, STGM_FAILIFTHERE, 0, 0, &pStgNew); + return (hr == STG_E_FILEALREADYEXISTS) ? S_OK : hr; + +tryfs: // HACKFIX: Our CFSFolder does not yet support IStorage, try it directly + LPCITEMIDLIST pidlAbsSelf = GetAbsoluteIDList(); + if (SHGetAttributes(NULL, pidlAbsSelf, SFGAO_FILESYSTEM) == SFGAO_FILESYSTEM) + { + CComHeapPtr pszDirPath; + if (SUCCEEDED(SHELL_DisplayNameOf(NULL, pidlAbsSelf, SHGDN_FORPARSING, &pszDirPath))) + { + DWORD cch = wcslen(pszDirPath) + 1 + wcslen(bName) + 1, attr; + if (PWSTR pszPath = (PWSTR)SHAlloc(cch * sizeof(WCHAR))) + { + StringCchPrintfW(pszPath, cch, L"%s\\%s", &pszDirPath[0], bName); + if (PathFileExistsAndAttributesW(pszPath, &attr)) + hr = (attr & FILE_ATTRIBUTE_DIRECTORY) ? S_OK : S_FALSE; + else + hr = Win32CreateDirectoryW(pszPath, NULL) ? S_OK : E_FAIL; + SHFree(pszPath); + } + } + } + return hr; +} + +static HRESULT GetUIObjectFromVariant(VARIANT &vItem, REFIID riid, void **ppv) +{ + CComHeapPtr pidlOneItem; + if (SUCCEEDED(VariantToIdlist(&vItem, &pidlOneItem))) + return SHELL_GetUIObjectOfAbsoluteItem(NULL, pidlOneItem, riid, ppv); + + // TODO: CFolderItems array return E_NOTIMPL; } +HRESULT CFolder::CopyMoveOperation(VARIANT &vItem, VARIANT vOptions, BOOL bCopy) +{ + CComPtr psfCurrent; + HRESULT hr = GetShellFolder(psfCurrent); + if (FAILED(hr)) + return hr; + + CComPtr pDT; + if (FAILED_UNEXPECTEDLY(hr = psfCurrent->CreateViewObject(GetHwnd(), IID_PPV_ARG(IDropTarget, &pDT)))) + return hr; + + CComPtr pDO; + if (FAILED_UNEXPECTEDLY(hr = GetUIObjectFromVariant(vItem, IID_PPV_ARG(IDataObject, &pDO)))) + return hr; + + if (SUCCEEDED(VariantChangeType(&vOptions, &vOptions, 0, VT_I4))) + { + UINT flags = V_I4(&vOptions) & ~(FOF_MULTIDESTFILES | FOF_WANTMAPPINGHANDLE); + if (flags) + { + extern UINT g_cf_FileOpFlags; + if (!g_cf_FileOpFlags) + g_cf_FileOpFlags = RegisterClipboardFormatW(L"FileOpFlags"); + DataObj_SetDWORD(pDO, g_cf_FileOpFlags, flags); + } + } + hr = SH32_SimulateDropWithSite(pDT, pDO, MK_LBUTTON | (bCopy ? MK_CONTROL : MK_SHIFT), NULL, NULL, GetSite()); + return SUCCEEDED(hr) ? S_OK : S_FALSE; +} + HRESULT STDMETHODCALLTYPE CFolder::MoveHere(VARIANT vItem, VARIANT vOptions) { TRACE("(%p, %s, %s)\n", this, wine_dbgstr_variant(&vItem), wine_dbgstr_variant(&vOptions)); - return E_NOTIMPL; + return CopyMoveOperation(vItem, vOptions, FALSE); } HRESULT STDMETHODCALLTYPE CFolder::CopyHere(VARIANT vItem, VARIANT vOptions) { TRACE("(%p, %s, %s)\n", this, wine_dbgstr_variant(&vItem), wine_dbgstr_variant(&vOptions)); - return E_NOTIMPL; + return CopyMoveOperation(vItem, vOptions, TRUE); } HRESULT STDMETHODCALLTYPE CFolder::GetDetailsOf(VARIANT vItem, int iColumn, BSTR *pbs) { TRACE("(%p, %s, %i, %p)\n", this, wine_dbgstr_variant(&vItem), iColumn, pbs); - return E_NOTIMPL; + + CComPtr psf; + HRESULT hr = GetShellFolder(psf); + if (FAILED(hr)) + return hr; + + PCUITEMID_CHILD pidlItem = CFolderItem::GetLeafPidlRef(&vItem); + if (pidlItem && iColumn == -1) + { + PWSTR pszTip = NULL; + if (SUCCEEDED(hr = SHELL_QueryInfoTipAlloc(psf, QITIPF_DEFAULT, pidlItem, &pszTip))) + { + hr = SHELL_SysAllocString(pszTip, pbs); + SHFree(pszTip); + return hr; + } + } + else + { + VARIANT v; + if (pbs && SUCCEEDED(SHELL_GetDetailsOfAsStringVariant(psf, pidlItem, iColumn, &v))) + { + *pbs = V_BSTR(&v); + return S_OK; + } + } + return SHELL_SysAllocString(L"", pbs); } @@ -143,7 +250,7 @@ HRESULT STDMETHODCALLTYPE CFolder::get_Self(FolderItem **ppfi) if (!ppfi) return E_POINTER; - return ShellObjectCreatorInit(this, static_cast(m_idlist), IID_PPV_ARG(FolderItem, ppfi)); + return ShellObjectCreatorInit(this, GetAbsoluteIDList(), IID_PPV_ARG(FolderItem, ppfi)); } HRESULT STDMETHODCALLTYPE CFolder::get_OfflineStatus(LONG *pul) diff --git a/dll/win32/shell32/CFolder.h b/dll/win32/shell32/CFolder.h index 49aa690fef4..cb11bac6828 100644 --- a/dll/win32/shell32/CFolder.h +++ b/dll/win32/shell32/CFolder.h @@ -16,6 +16,7 @@ class CFolder: { private: HRESULT GetShellFolder(CComPtr& psfCurrent); + HRESULT CopyMoveOperation(VARIANT &vItem, VARIANT vOptions, BOOL bCopy); CComHeapPtr m_idlist; CComPtr m_Application; @@ -24,7 +25,10 @@ public: CFolder(); ~CFolder(); - HRESULT Initialize(LPITEMIDLIST idlist); + HRESULT Initialize(LPCITEMIDLIST idlist); + LPCITEMIDLIST GetAbsoluteIDList() { return m_idlist; } + HWND GetHwnd() { return NULL; } + IUnknown* GetSite() { return NULL; } // *** Folder methods *** STDMETHOD(get_Title)(BSTR *pbs) override; diff --git a/dll/win32/shell32/CFolderItemVerbs.cpp b/dll/win32/shell32/CFolderItemVerbs.cpp index ce7f2266ba6..09be5abb458 100644 --- a/dll/win32/shell32/CFolderItemVerbs.cpp +++ b/dll/win32/shell32/CFolderItemVerbs.cpp @@ -89,7 +89,7 @@ CFolderItemVerbs::~CFolderItemVerbs() DestroyMenu(m_menu); } -HRESULT CFolderItemVerbs::Init(LPITEMIDLIST idlist) +HRESULT CFolderItemVerbs::Init(LPCITEMIDLIST idlist) { HRESULT hr = SHELL_GetUIObjectOfAbsoluteItem(NULL, idlist, IID_PPV_ARG(IContextMenu, &m_contextmenu)); if (FAILED_UNEXPECTEDLY(hr)) diff --git a/dll/win32/shell32/CFolderItemVerbs.h b/dll/win32/shell32/CFolderItemVerbs.h index f42e4c13880..ea96ba09827 100644 --- a/dll/win32/shell32/CFolderItemVerbs.h +++ b/dll/win32/shell32/CFolderItemVerbs.h @@ -67,7 +67,7 @@ public: CFolderItemVerbs(); virtual ~CFolderItemVerbs(); - HRESULT Init(LPITEMIDLIST idlist); + HRESULT Init(LPCITEMIDLIST idlist); // *** FolderItemVerbs methods *** STDMETHOD(get_Count)(LONG *plCount) override; @@ -83,6 +83,8 @@ BEGIN_COM_MAP(CFolderItemVerbs) COM_INTERFACE_ENTRY_IID(IID_FolderItemVerbs, FolderItemVerbs) COM_INTERFACE_ENTRY_IID(IID_IDispatch, IDispatch) END_COM_MAP() + + static HRESULT CreateInstance(CComPtr &objref) { return ShellObjectCreator(objref); } }; #endif diff --git a/dll/win32/shell32/CFolderItems.cpp b/dll/win32/shell32/CFolderItems.cpp index 0ba76dc5273..e9a8a73390d 100644 --- a/dll/win32/shell32/CFolderItems.cpp +++ b/dll/win32/shell32/CFolderItems.cpp @@ -6,6 +6,7 @@ */ #include "precomp.h" +#include "prop.h" WINE_DEFAULT_DEBUG_CHANNEL(shell); @@ -18,13 +19,67 @@ CFolderItem::~CFolderItem() { } -HRESULT CFolderItem::Initialize(Folder* folder, LPITEMIDLIST idlist) +HRESULT CFolderItem::Initialize(Folder* folder, LPCITEMIDLIST idlist) { m_idlist.Attach(ILClone(idlist)); + if (!m_idlist) + return E_OUTOFMEMORY; m_Folder = folder; return S_OK; } +inline HRESULT CFolderItem::GetParentShellFolderAndItem(REFIID riid, void**ppv, PCUITEMID_CHILD &pidlLast) +{ + return SHBindToParent(GetAbsoluteIDList(), riid, ppv, &pidlLast); +} + +LPCITEMIDLIST CFolderItem::GetInternalPidlRef(IUnknown *pUnk) +{ + LPCITEMIDLIST pidl = NULL; + FolderItem2 *pFI2; + if (pUnk && SUCCEEDED(pUnk->QueryInterface(IID_PPV_ARG(FolderItem2, &pFI2)))) + { + // This assumes we are the only implementer of CFolderItem (probably true) + // but when we get IParentAndItem we can do this in a safer way + pidl = static_cast(pFI2)->m_idlist; + pFI2->Release(); + } + return pidl; +} + +LPCITEMIDLIST CFolderItem::GetInternalPidlRef(const VARIANT *pV) +{ + if (!pV) + return NULL; + + if (V_VT(pV) == (VT_VARIANT | VT_BYREF) && V_VARIANTREF(pV)) + pV = V_VARIANTREF(pV); + + switch (V_VT(pV)) + { + case VT_DISPATCH | VT_BYREF: + return V_DISPATCHREF(pV) ? GetInternalPidlRef(*V_DISPATCHREF(pV)) : NULL; + case VT_DISPATCH: + return GetInternalPidlRef(V_DISPATCH(pV)); + } + return NULL; +} + +PCUITEMID_CHILD CFolderItem::GetLeafPidlRef(const VARIANT *pV) +{ + return (PCUITEMID_CHILD)ILFindLastID(GetInternalPidlRef(pV)); +} + +HRESULT CFolderItem::GetFindDataFromIDList(WIN32_FIND_DATA &wfd) +{ + CComPtr psf; + PCUITEMID_CHILD pidlLeaf; + HRESULT hr = GetParentShellFolderAndItem(IID_PPV_ARG(IShellFolder2, &psf), pidlLeaf); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + return SHGetDataFromIDListW(psf, pidlLeaf, SHGDFIL_FINDDATA, &wfd, sizeof(wfd)); +} + // *** FolderItem methods *** HRESULT STDMETHODCALLTYPE CFolderItem::get_Application(IDispatch **ppid) { @@ -35,12 +90,10 @@ HRESULT STDMETHODCALLTYPE CFolderItem::get_Application(IDispatch **ppid) HRESULT STDMETHODCALLTYPE CFolderItem::get_Parent(IDispatch **ppid) { TRACE("(%p, %p)\n", this, ppid); - if (ppid) - { - *ppid = m_Folder; - (*ppid)->AddRef(); - } - return E_NOTIMPL; + + if (!ppid) + return E_INVALIDARG; + return m_Folder->QueryInterface(IID_PPV_ARG(IDispatch, ppid)); } HRESULT STDMETHODCALLTYPE CFolderItem::get_Name(BSTR *pbs) @@ -49,9 +102,9 @@ HRESULT STDMETHODCALLTYPE CFolderItem::get_Name(BSTR *pbs) *pbs = NULL; - CComPtr Parent; - LPCITEMIDLIST last_part; - HRESULT hr = SHBindToParent(m_idlist, IID_PPV_ARG(IShellFolder2, &Parent), &last_part); + CComPtr Parent; + PCUITEMID_CHILD last_part; + HRESULT hr = GetParentShellFolderAndItem(IID_PPV_ARG(IShellFolder, &Parent), last_part); if (FAILED_UNEXPECTEDLY(hr)) return hr; @@ -66,7 +119,25 @@ HRESULT STDMETHODCALLTYPE CFolderItem::get_Name(BSTR *pbs) HRESULT STDMETHODCALLTYPE CFolderItem::put_Name(BSTR bs) { TRACE("(%p, %s)\n", this, wine_dbgstr_w(bs)); - return E_NOTIMPL; + + CComPtr psf; + PCUITEMID_CHILD pidlLeaf; + HRESULT hr = GetParentShellFolderAndItem(IID_PPV_ARG(IShellFolder, &psf), pidlLeaf); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + + LPITEMIDLIST pidlNew; + if (SUCCEEDED(hr = psf->SetNameOf(GetHwnd(), pidlLeaf, bs, SHGDN_INFOLDER, &pidlNew)) && pidlNew) + { + LPITEMIDLIST pidlClone = ILClone(m_idlist); + ILRemoveLastID(pidlClone); + if (pidlClone && SUCCEEDED(SHILAppend(pidlNew, &pidlClone))) + { + m_idlist.Free(); + m_idlist.Attach(pidlClone); + } + } + return hr; } HRESULT STDMETHODCALLTYPE CFolderItem::get_Path(BSTR *pbs) @@ -93,38 +164,54 @@ HRESULT STDMETHODCALLTYPE CFolderItem::get_GetLink(IDispatch **ppid) HRESULT STDMETHODCALLTYPE CFolderItem::get_GetFolder(IDispatch **ppid) { - TRACE("(%p, %p)\n", this, ppid); - return E_NOTIMPL; + return ShellObjectCreatorInit(const_cast(GetAbsoluteIDList()), IID_PPV_ARG(IDispatch, ppid)); +} + +HRESULT CFolderItem::HasAttribute(DWORD sfgaof, VARIANT_BOOL *pB) +{ + *pB = SHGetAttributes(NULL, GetAbsoluteIDList(), sfgaof) ? VARIANT_TRUE : VARIANT_FALSE; + return S_OK; } HRESULT STDMETHODCALLTYPE CFolderItem::get_IsLink(VARIANT_BOOL *pb) { TRACE("(%p, %p)\n", this, pb); - return E_NOTIMPL; + return HasAttribute(SFGAO_LINK, pb); } HRESULT STDMETHODCALLTYPE CFolderItem::get_IsFolder(VARIANT_BOOL *pb) { TRACE("(%p, %p)\n", this, pb); - return E_NOTIMPL; + return HasAttribute(SFGAO_FOLDER, pb); } HRESULT STDMETHODCALLTYPE CFolderItem::get_IsFileSystem(VARIANT_BOOL *pb) { TRACE("(%p, %p)\n", this, pb); - return E_NOTIMPL; + return HasAttribute(SFGAO_FILESYSTEM, pb); } HRESULT STDMETHODCALLTYPE CFolderItem::get_IsBrowsable(VARIANT_BOOL *pb) { TRACE("(%p, %p)\n", this, pb); - return E_NOTIMPL; + return HasAttribute(SFGAO_BROWSABLE, pb); } HRESULT STDMETHODCALLTYPE CFolderItem::get_ModifyDate(DATE *pdt) { TRACE("(%p, %p)\n", this, pdt); - return E_NOTIMPL; + + WIN32_FIND_DATA wfd; + if (SUCCEEDED(GetFindDataFromIDList(wfd))) + { + FILETIME ft; + FileTimeToLocalFileTime(&wfd.ftLastWriteTime, &ft); + WORD dd, dt; + if (FileTimeToDosDateTime(&ft, &dd, &dt) && DosDateTimeToVariantTime(dd, dt, pdt)) + return S_OK; + } + *pdt = 0; + return S_FALSE; } HRESULT STDMETHODCALLTYPE CFolderItem::put_ModifyDate(DATE dt) @@ -136,37 +223,98 @@ HRESULT STDMETHODCALLTYPE CFolderItem::put_ModifyDate(DATE dt) HRESULT STDMETHODCALLTYPE CFolderItem::get_Size(LONG *pul) { TRACE("(%p, %p)\n", this, pul); - return E_NOTIMPL; + + WIN32_FIND_DATA wfd; + if (SUCCEEDED(GetFindDataFromIDList(wfd))) + { + *pul = wfd.nFileSizeLow; + return S_OK; + } + *pul = 0; + return S_FALSE; } HRESULT STDMETHODCALLTYPE CFolderItem::get_Type(BSTR *pbs) { TRACE("(%p, %p)\n", this, pbs); - return E_NOTIMPL; + + VARIANT v; + V_VT(&v) = VT_EMPTY; + if (SUCCEEDED(GetExtendedProperty(PKEY_ItemTypeText, &v))) + { + if (SUCCEEDED(VariantChangeType(&v, &v, 0, VT_BSTR))) + { + *pbs = V_BSTR(&v); + return S_OK; + } + VariantClear(&v); + } + HRESULT hr = SHELL_SysAllocString(L"", pbs); + return hr == S_OK ? S_FALSE : hr; } HRESULT STDMETHODCALLTYPE CFolderItem::Verbs(FolderItemVerbs **ppfic) { if (!ppfic) return E_POINTER; - CFolderItemVerbs* verbs = new CComObject(); - HRESULT hr = verbs->Init(m_idlist); + + CComPtr pVerbs; + HRESULT hr = CFolderItemVerbs::CreateInstance(pVerbs); + if (SUCCEEDED(hr)) + hr = pVerbs->Init(GetAbsoluteIDList()); if (FAILED_UNEXPECTEDLY(hr)) - { - delete verbs; return hr; - } - verbs->AddRef(); - *ppfic = verbs; + + *ppfic = pVerbs.Detach(); return S_OK; } HRESULT STDMETHODCALLTYPE CFolderItem::InvokeVerb(VARIANT vVerb) { - TRACE("(%p, %s)\n", this, wine_dbgstr_variant(&vVerb)); - return E_NOTIMPL; + VARIANT empty; + V_VT(&empty) = VT_EMPTY; + return InvokeVerbEx(vVerb, empty); } +HRESULT STDMETHODCALLTYPE CFolderItem::InvokeVerbEx(VARIANT vVerb, VARIANT vArgs) +{ + TRACE("(%p, %s)\n", this, wine_dbgstr_variant(&vVerb)); + + SHELLEXECUTEINFOW sei; + sei.cbSize = sizeof(sei); + sei.fMask = SEE_MASK_INVOKEIDLIST | SEE_MASK_FLAG_NO_UI; + sei.hwnd = GetHwnd(); + sei.lpVerb = V_VT(&vVerb) == VT_BSTR ? V_BSTR(&vVerb) : NULL; + sei.lpFile = NULL; + sei.lpParameters = V_VT(&vArgs) == VT_BSTR ? V_BSTR(&vArgs) : NULL; + sei.lpDirectory = NULL; + sei.nShow = SW_SHOW; + sei.lpIDList = const_cast(GetAbsoluteIDList()); + ShellExecuteExW(&sei); + return S_OK; +} + +HRESULT CFolderItem::GetExtendedProperty(REFPROPERTYKEY pkey, VARIANT *pv) +{ + CComPtr psf; + PCUITEMID_CHILD pidlLeaf; + HRESULT hr = GetParentShellFolderAndItem(IID_PPV_ARG(IShellFolder2, &psf), pidlLeaf); + return FAILED(hr) ? hr : psf->GetDetailsEx(pidlLeaf, reinterpret_cast(&pkey), pv); +} + +HRESULT STDMETHODCALLTYPE CFolderItem::ExtendedProperty(BSTR bsPropName, VARIANT *pv) +{ + if (!pv) + return E_INVALIDARG; + + PROPERTYKEY pkeybuf; + const PROPERTYKEY *pPK = SHELL_GetPropertyKeyFromString(bsPropName, &pkeybuf); + if (pPK && GetExtendedProperty(*pPK, pv) == S_OK) + return S_OK; + + V_VT(pv) = VT_EMPTY; + return S_FALSE; +} CFolderItems::CFolderItems() @@ -178,11 +326,13 @@ CFolderItems::~CFolderItems() { } -HRESULT CFolderItems::Initialize(LPITEMIDLIST idlist, Folder* parent) +HRESULT CFolderItems::Initialize(LPCITEMIDLIST idlist, Folder* parent) { CComPtr psfDesktop, psfTarget; m_idlist.Attach(ILClone(idlist)); + if (!m_idlist) + return E_OUTOFMEMORY; HRESULT hr = SHGetDesktopFolder(&psfDesktop); if (FAILED_UNEXPECTEDLY(hr)) diff --git a/dll/win32/shell32/CFolderItems.h b/dll/win32/shell32/CFolderItems.h index cf62bec2955..05d5ac7a562 100644 --- a/dll/win32/shell32/CFolderItems.h +++ b/dll/win32/shell32/CFolderItems.h @@ -12,17 +12,29 @@ class CFolderItem: public CComCoClass, public CComObjectRootEx, - public IDispatchImpl + public IDispatchImpl { private: CComHeapPtr m_idlist; CComPtr m_Folder; + inline HRESULT GetParentShellFolderAndItem(REFIID riid, void**ppv, PCUITEMID_CHILD &pidlLast); + + static LPCITEMIDLIST GetInternalPidlRef(IUnknown *pUnk); + static LPCITEMIDLIST GetInternalPidlRef(const VARIANT *pV); + public: CFolderItem(); ~CFolderItem(); - HRESULT Initialize(Folder* folder, LPITEMIDLIST idlist); + HRESULT Initialize(Folder* folder, LPCITEMIDLIST idlist); + LPCITEMIDLIST GetAbsoluteIDList() { return m_idlist; } + HWND GetHwnd() { return NULL; } + HRESULT GetFindDataFromIDList(WIN32_FIND_DATA &wfd); + HRESULT HasAttribute(DWORD sfgaof, VARIANT_BOOL *pB); + HRESULT GetExtendedProperty(REFPROPERTYKEY pkey, VARIANT *pv); + + static PCUITEMID_CHILD GetLeafPidlRef(const VARIANT *pV); // *** FolderItem methods *** STDMETHOD(get_Application)(IDispatch **ppid) override; @@ -43,12 +55,16 @@ public: STDMETHOD(Verbs)(FolderItemVerbs **ppfic) override; STDMETHOD(InvokeVerb)(VARIANT vVerb) override; + // *** FolderItem2 methods *** + STDMETHOD(InvokeVerbEx)(VARIANT vVerb, VARIANT vArgs) override; + STDMETHOD(ExtendedProperty)(BSTR bsPropName, VARIANT *pv) override; DECLARE_NOT_AGGREGATABLE(CFolderItem) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CFolderItem) COM_INTERFACE_ENTRY_IID(IID_FolderItem, FolderItem) + COM_INTERFACE_ENTRY_IID(IID_FolderItem2, FolderItem2) COM_INTERFACE_ENTRY_IID(IID_IDispatch, IDispatch) END_COM_MAP() }; @@ -69,7 +85,7 @@ public: ~CFolderItems(); // Please note: CFolderItems takes ownership of idlist. - HRESULT Initialize(LPITEMIDLIST idlist, Folder* parent); + HRESULT Initialize(LPCITEMIDLIST idlist, Folder* parent); // *** FolderItems methods *** STDMETHOD(get_Count)(long *plCount) override; diff --git a/dll/win32/shell32/CMakeLists.txt b/dll/win32/shell32/CMakeLists.txt index c02f60eedfb..d2d3dd6d42e 100644 --- a/dll/win32/shell32/CMakeLists.txt +++ b/dll/win32/shell32/CMakeLists.txt @@ -63,6 +63,7 @@ list(APPEND SOURCE droptargets/CexeDropHandler.cpp droptargets/CFSDropTarget.cpp droptargets/CRecyclerDropTarget.cpp + prop.cpp shldataobject.cpp shlexec.cpp shlfileop.cpp diff --git a/dll/win32/shell32/CShellDispatch.cpp b/dll/win32/shell32/CShellDispatch.cpp index d8e354fe1f2..0bc8539cdcb 100644 --- a/dll/win32/shell32/CShellDispatch.cpp +++ b/dll/win32/shell32/CShellDispatch.cpp @@ -8,6 +8,7 @@ */ #include "precomp.h" +#include "prop.h" #include "winsvc.h" WINE_DEFAULT_DEBUG_CHANNEL(shell); @@ -61,20 +62,6 @@ HRESULT STDMETHODCALLTYPE CShellDispatch::get_Parent(IDispatch **ppid) return S_OK; } -HRESULT VariantToIdlist(VARIANT* var, LPITEMIDLIST* idlist) -{ - HRESULT hr = E_FAIL; - if(V_VT(var) == VT_I4) - { - hr = SHGetSpecialFolderLocation(NULL, V_I4(var), idlist); - } - else if(V_VT(var) == VT_BSTR) - { - hr = SHILCreateFromPathW(V_BSTR(var), idlist, NULL); - } - return hr; -} - HRESULT STDMETHODCALLTYPE CShellDispatch::NameSpace(VARIANT vDir, Folder **ppsdf) { TRACE("(%p, %s, %p)\n", this, debugstr_variant(&vDir), ppsdf); @@ -336,6 +323,27 @@ HRESULT STDMETHODCALLTYPE CShellDispatch::GetSystemInformation(BSTR name, VARIAN V_UI4(ret) = si.wProcessorArchitecture; return S_OK; } + else if (!lstrcmpiW(name, L"ProcessorSpeed")) + { + DWORD cb = sizeof(DWORD); + V_VT(ret) = VT_EMPTY; + if (RegGetValueW(HKEY_LOCAL_MACHINE, L"Hardware\\Description\\System\\CentralProcessor\\0", + L"~Mhz", RRF_RT_REG_DWORD, NULL, &V_UI4(ret), &cb)) + { + return S_FALSE; + } + V_VT(ret) = VT_I4; + return S_OK; + } + else if (!lstrcmpiW(name, L"PhysicalMemoryInstalled")) + { + MEMORYSTATUSEX mems; + mems.dwLength = sizeof(mems); + GlobalMemoryStatusEx(&mems); + V_VT(ret) = VT_UI8; + V_UI8(ret) = mems.ullTotalPhys; + return S_OK; + } UINT os = 0; if (!lstrcmpiW(name, L"IsOS_Professional")) @@ -538,7 +546,7 @@ HRESULT STDMETHODCALLTYPE CShellDispatch::GetSetting(LONG setting, VARIANT_BOOL { TRACE("(%p, %lu, %p)\n", this, setting, result); - int flag = -1; + int flag = 0; SHELLSTATE ss = { }; SHGetSetSettings(&ss, setting, FALSE); switch (setting) @@ -552,14 +560,10 @@ HRESULT STDMETHODCALLTYPE CShellDispatch::GetSetting(LONG setting, VARIANT_BOOL case SSF_SEPPROCESS: flag = ss.fSepProcess; break; case SSF_STARTPANELON: flag = ss.fStartPanelOn; break; case SSF_SERVERADMINUI: flag = IsOS(OS_SERVERADMINUI); break; + default: *result = VARIANT_FALSE; return S_FALSE; } - if (flag >= 0) - { - *result = flag ? VARIANT_TRUE : VARIANT_FALSE; - return S_OK; - } - - return S_FALSE; + *result = flag ? VARIANT_TRUE : VARIANT_FALSE; + return S_OK; } diff --git a/dll/win32/shell32/droptargets/CFSDropTarget.cpp b/dll/win32/shell32/droptargets/CFSDropTarget.cpp index ec04492878b..14e892ba0b0 100644 --- a/dll/win32/shell32/droptargets/CFSDropTarget.cpp +++ b/dll/win32/shell32/droptargets/CFSDropTarget.cpp @@ -70,13 +70,22 @@ static void GetDefaultCopyMoveEffect() // FIXME: When the source is on a different volume than the target, change default from move to copy } +UINT g_cf_FileOpFlags = 0; + +static inline DWORD GetDefaultFileOpFlags(IDataObject *pDO, DWORD fDefault = FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR) +{ + if (!g_cf_FileOpFlags) + g_cf_FileOpFlags = RegisterClipboardFormatW(L"FileOpFlags"); // github.com/dotnet/winforms/issues/5884?timeline_page=1 + return DataObj_GetDWORD(pDO, g_cf_FileOpFlags, fDefault); +} + /**************************************************************************** * CFSDropTarget::_CopyItems * * copies or moves items to this folder */ -HRESULT CFSDropTarget::_CopyItems(IShellFolder * pSFFrom, UINT cidl, - LPCITEMIDLIST * apidl, BOOL bCopy) +HRESULT CFSDropTarget::_CopyItems(IDataObject *pDO, IShellFolder * pSFFrom, + UINT cidl, LPCITEMIDLIST * apidl, BOOL bCopy) { HRESULT ret; WCHAR wszDstPath[MAX_PATH + 1] = {0}; @@ -128,7 +137,7 @@ HRESULT CFSDropTarget::_CopyItems(IShellFolder * pSFFrom, UINT cidl, fop.wFunc = bCopy ? FO_COPY : FO_MOVE; fop.pFrom = pwszSrcPathsList; fop.pTo = wszDstPath; - fop.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR; + fop.fFlags = GetDefaultFileOpFlags(pDO); if (bRenameOnCollision) fop.fFlags |= FOF_RENAMEONCOLLISION; @@ -725,7 +734,7 @@ HRESULT CFSDropTarget::_DoDrop(IDataObject *pDataObject, } else { - hr = _CopyItems(psfFrom, lpcida->cidl, (LPCITEMIDLIST*)apidl, bCopy); + hr = _CopyItems(pDataObject, psfFrom, lpcida->cidl, (LPCITEMIDLIST*)apidl, bCopy); } SHFree(pidl); @@ -760,7 +769,7 @@ HRESULT CFSDropTarget::_DoDrop(IDataObject *pDataObject, op.pTo = wszTargetPath; op.hwnd = m_hwndSite; op.wFunc = bCopy ? FO_COPY : FO_MOVE; - op.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMMKDIR; + op.fFlags = GetDefaultFileOpFlags(pDataObject); int res = SHFileOperationW(&op); if (res) { diff --git a/dll/win32/shell32/droptargets/CFSDropTarget.h b/dll/win32/shell32/droptargets/CFSDropTarget.h index 8e6f4189c5e..defe772f47f 100644 --- a/dll/win32/shell32/droptargets/CFSDropTarget.h +++ b/dll/win32/shell32/droptargets/CFSDropTarget.h @@ -43,7 +43,7 @@ class CFSDropTarget : BOOL _QueryDrop (DWORD dwKeyState, LPDWORD pdwEffect); HRESULT _DoDrop(IDataObject *pDataObject, DWORD dwKeyState, POINTL pt, DWORD *pdwEffect); - HRESULT _CopyItems(IShellFolder *pSFFrom, UINT cidl, LPCITEMIDLIST *apidl, BOOL bCopy); + HRESULT _CopyItems(IDataObject *pDO, IShellFolder *pSFFrom, UINT cidl, LPCITEMIDLIST *apidl, BOOL bCopy); BOOL _GetUniqueFileName(LPCWSTR pwszBasePath, LPCWSTR pwszExt, LPWSTR pwszTarget, BOOL bShortcut); static DWORD WINAPI _DoDropThreadProc(LPVOID lpParameter); HRESULT _GetEffectFromMenu(IDataObject *pDataObject, POINTL pt, DWORD *pdwEffect, DWORD dwAvailableEffects); diff --git a/dll/win32/shell32/folders/CFSFolder.cpp b/dll/win32/shell32/folders/CFSFolder.cpp index 9bec68f9273..1ab74a42631 100644 --- a/dll/win32/shell32/folders/CFSFolder.cpp +++ b/dll/win32/shell32/folders/CFSFolder.cpp @@ -1683,7 +1683,7 @@ HRESULT WINAPI CFSFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *p switch (pscid->pid) { case PID_STG_NAME: // Handled directly here for faster performance - return SHELL_GetDetailsOfAsStringVariant(this, pidl, SHFSF_COL_NAME, pv); + return SHELL_GetDetailsOfAsStringVariant(static_cast(this), pidl, SHFSF_COL_NAME, pv); case PID_STG_SIZE: V_VT(pv) = VT_UI4; V_UI4(pv) = _ILGetFileSize(pidl, NULL, 0); diff --git a/dll/win32/shell32/precomp.h b/dll/win32/shell32/precomp.h index d373aaf9c2e..159c71603b0 100644 --- a/dll/win32/shell32/precomp.h +++ b/dll/win32/shell32/precomp.h @@ -311,6 +311,8 @@ HRESULT DataObject_GetHIDACount(IDataObject *pdo); PIDLIST_ABSOLUTE SHELL_CIDA_ILCloneFull(_In_ const CIDA *pCIDA, _In_ UINT Index); PIDLIST_ABSOLUTE SHELL_DataObject_ILCloneFullItem(_In_ IDataObject *pDO, _In_ UINT Index); HRESULT SHELL_CloneDataObject(_In_ IDataObject *pDO, _Out_ IDataObject **ppDO); +DWORD DataObj_GetDWORD(IDataObject *pdtobj, UINT cf, DWORD dwDefault); +HRESULT DataObj_SetDWORD(IDataObject *pdtobj, UINT cf, DWORD dwValue); EXTERN_C HRESULT IUnknown_InitializeCommand( diff --git a/dll/win32/shell32/prop.cpp b/dll/win32/shell32/prop.cpp new file mode 100644 index 00000000000..68da3553fa5 --- /dev/null +++ b/dll/win32/shell32/prop.cpp @@ -0,0 +1,80 @@ +/* + * PROJECT: shell32 + * LICENSE: LGPL-2.1+ (https://spdx.org/licenses/LGPL-2.1+) + * PURPOSE: [PROP]VARIANT functions + * COPYRIGHT: Copyright 2026 Whindmar Saksit + */ + +#define SHELL32_PROP_IMPL +#include "precomp.h" +#include "prop.h" + +WINE_DEFAULT_DEBUG_CHANNEL(shell_prop); + +static const struct SHELL32PKEYMAP +{ + PCWSTR pszLegacyName; + const PROPERTYKEY *pkey; +} g_PkMap[] = +{ +// learn.microsoft.com/en-us/windows/win32/api/propsys/nf-propsys-psgetpropertydescriptionbyname +#define MKSH32PKEYMAP(sysname, legacyname, pkey) { (L##legacyname), &(pkey) }, +MKSH32PKEYMAP(?, "Name", PKEYSHELL32_Name) +MKSH32PKEYMAP("System.ItemTypeText", "Type", PKEY_ItemTypeText) +MKSH32PKEYMAP("System.Size", "Size", PKEY_Size) +MKSH32PKEYMAP("System.FileAttributes", "Attributes", PKEY_FileAttributes) +MKSH32PKEYMAP("System.DateModified", "Write", PKEY_DateModified) +MKSH32PKEYMAP("System.Comment", "DocComments", PKEYSHELL32_Comments) +}; + +static const PROPERTYKEY* +SHELL_GetPropertyKeyFromLegacyName(_In_ PCWSTR pszString) +{ + for (SIZE_T i = 0; i < _countof(g_PkMap); ++i) + { + if (!_wcsicmp(g_PkMap[i].pszLegacyName, pszString)) + return g_PkMap[i].pkey; + } + return NULL; +} + +#if /*TODO*/0 && (NTDDI_VERSION >= NTDDI_VISTA) +#define SHELL_PropertyKeyFromPropertyKeyString PSPropertyKeyFromString +#else +static HRESULT +SHELL_PropertyKeyFromPropertyKeyString(_In_ PCWSTR pszString, _Out_ PROPERTYKEY *pkey) +{ + if (GUIDFromStringW(pszString, &pkey->fmtid)) + { + for (pszString += 38; *pszString == ',' || *pszString == ' ';) + ++pszString; + pkey->pid = StrToInt(pszString); + if (pkey->pid) + return S_OK; + } + return E_INVALIDARG; +} +#endif + +const PROPERTYKEY* +SHELL_GetPropertyKeyFromString(_In_ PCWSTR pszString, _Out_ PROPERTYKEY *pkey) +{ + if (SUCCEEDED(SHELL_PropertyKeyFromPropertyKeyString(pszString, pkey))) + return pkey; + return SHELL_GetPropertyKeyFromLegacyName(pszString); +} + +HRESULT +VariantToIdlist(_In_ VARIANT *pV, _Out_ LPITEMIDLIST *ppidl) +{ + HRESULT hr = E_FAIL; + if (V_VT(pV) == VT_I4) + { + hr = SHGetSpecialFolderLocation(NULL, V_I4(pV), ppidl); + } + else if (V_VT(pV) == VT_BSTR) + { + hr = SHILCreateFromPathW(V_BSTR(pV), ppidl, NULL); + } + return hr; +} diff --git a/dll/win32/shell32/prop.h b/dll/win32/shell32/prop.h new file mode 100644 index 00000000000..d7921831bbb --- /dev/null +++ b/dll/win32/shell32/prop.h @@ -0,0 +1,36 @@ +/* + * PROJECT: shell32 + * LICENSE: LGPL-2.1+ (https://spdx.org/licenses/LGPL-2.1+) + * PURPOSE: [PROP]VARIANT header + * COPYRIGHT: Copyright 2026 Whindmar Saksit + */ + +#include "precomp.h" + +#define SHELL32_NOPROPSYSLIB // PropSys does not yet have all the PKEY_'s we need + +#ifdef SHELL32_NOPROPSYSLIB +#ifdef SHELL32_PROP_IMPL +#ifndef PSGUID_SUMMARYINFORMATION +#define PSGUID_SUMMARYINFORMATION { 0xf29f85e0L, 0x4ff9, 0x1068, 0xab, 0x91, 0x08, 0x00, 0x2b, 0x27, 0xb3, 0xd9 } +#endif +#define DEFINE_SHELL32PROPERTYKEY(name, psguid, pid) \ + EXTERN_C const PROPERTYKEY DECLSPEC_SELECTANY name = { psguid, pid } +#else +#define DEFINE_SHELL32PROPERTYKEY(name, psguid, pid) EXTERN_C const PROPERTYKEY name +#endif // SHELL32_PROP_IMPL + +DEFINE_SHELL32PROPERTYKEY(PKEY_Size, PSGUID_STORAGE, PID_STG_SIZE); +DEFINE_SHELL32PROPERTYKEY(PKEY_FileAttributes, PSGUID_STORAGE, PID_STG_ATTRIBUTES); +DEFINE_SHELL32PROPERTYKEY(PKEY_DateModified, PSGUID_STORAGE, PID_STG_WRITETIME); +DEFINE_SHELL32PROPERTYKEY(PKEY_ItemTypeText, PSGUID_STORAGE, PID_STG_STORAGETYPE); +DEFINE_SHELL32PROPERTYKEY(PKEYSHELL32_Name, PSGUID_STORAGE, PID_STG_NAME); +DEFINE_SHELL32PROPERTYKEY(PKEYSHELL32_Comments, PSGUID_SUMMARYINFORMATION, PIDSI_COMMENTS); + +#endif // SHELL32_NOPROPSYSLIB + +const PROPERTYKEY* +SHELL_GetPropertyKeyFromString(_In_ PCWSTR pszString, _Out_ PROPERTYKEY *pkey); + +HRESULT +VariantToIdlist(_In_ VARIANT *pV, _Out_ LPITEMIDLIST *ppidl); diff --git a/dll/win32/shell32/shfldr.h b/dll/win32/shell32/shfldr.h index 86a929ae7bf..bbdac69b40c 100644 --- a/dll/win32/shell32/shfldr.h +++ b/dll/win32/shell32/shfldr.h @@ -106,12 +106,17 @@ MakeSCID(SHCOLUMNID &scid, REFCLSID fmtid, UINT pid) HRESULT SHELL_MapSCIDToColumn(IShellFolder2 *pSF, const SHCOLUMNID *pscid); HRESULT +SHELL_GetDetailsOfAsStringVariant(IShellFolder *pSF, PCUITEMID_CHILD pidl, UINT Column, VARIANT *pVar); +HRESULT SHELL_GetDetailsOfAsStringVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARIANT *pVar); HRESULT SHELL_GetDetailsOfColumnAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARTYPE vt, VARIANT *pVar); HRESULT SH32_GetDetailsOfPKeyAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pVar, BOOL UseFsColMap); +HRESULT +SHELL_QueryInfoTipAlloc(_In_ IShellFolder* pSF, _In_ DWORD dwFlags, _In_ LPCITEMIDLIST pidl, _Out_ PWSTR *ppTip); + HRESULT SHELL_CreateAbsolutePidl(IShellFolder *pSF, PCUIDLIST_RELATIVE pidlChild, PIDLIST_ABSOLUTE *ppPidl); diff --git a/dll/win32/shell32/shldataobject.cpp b/dll/win32/shell32/shldataobject.cpp index 2a78c23766e..ea70fc44b41 100644 --- a/dll/win32/shell32/shldataobject.cpp +++ b/dll/win32/shell32/shldataobject.cpp @@ -142,3 +142,20 @@ HRESULT SHELL_CloneDataObject(_In_ IDataObject *pDO, _Out_ IDataObject **ppDO) } return hr; } + +static inline HRESULT DataObj_GetDWORD(IDataObject *pdtobj, UINT cf, DWORD *pdwOut) +{ + // learn.microsoft.com/en-us/windows/win32/shell/dataobject + return DataObject_GetData(pdtobj, (CLIPFORMAT)cf, pdwOut, sizeof(*pdwOut)); +} + +DWORD DataObj_GetDWORD(IDataObject *pdtobj, UINT cf, DWORD dwDefault) +{ + DWORD dwValue; + return DataObj_GetDWORD(pdtobj, cf, &dwValue) == S_OK ? dwValue : dwDefault; +} + +HRESULT DataObj_SetDWORD(IDataObject *pdtobj, UINT cf, DWORD dwValue) +{ + return DataObject_SetData(pdtobj, cf, &dwValue, sizeof(dwValue)); +} diff --git a/dll/win32/shell32/shlfolder.cpp b/dll/win32/shell32/shlfolder.cpp index f64773da0cf..932bfae044d 100644 --- a/dll/win32/shell32/shlfolder.cpp +++ b/dll/win32/shell32/shlfolder.cpp @@ -91,8 +91,8 @@ SHELL_MapSCIDToColumn(IShellFolder2 *pSF, const SHCOLUMNID *pscid) return E_FAIL; } -HRESULT -SHELL_GetDetailsOfAsStringVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARIANT *pVar) +template static inline HRESULT +GetDetailsOfAsStringVariant(T &pSF, PCUITEMID_CHILD pidl, UINT Column, VARIANT *pVar) { V_VT(pVar) = VT_EMPTY; SHELLDETAILS sd; @@ -106,6 +106,25 @@ SHELL_GetDetailsOfAsStringVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT return hr; } +HRESULT +SHELL_GetDetailsOfAsStringVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARIANT *pVar) +{ + return GetDetailsOfAsStringVariant(pSF, pidl, Column, pVar); +} + +HRESULT +SHELL_GetDetailsOfAsStringVariant(IShellFolder *pSF, PCUITEMID_CHILD pidl, UINT Column, VARIANT *pVar) +{ + HRESULT hr; + CComPtr pSF2; + if (SUCCEEDED(hr = pSF->QueryInterface(IID_PPV_ARG(IShellFolder2, &pSF2)))) + return SHELL_GetDetailsOfAsStringVariant(pSF2, pidl, Column, pVar); + CComPtr pSD; + if (SUCCEEDED(hr = pSF->CreateViewObject(NULL, IID_PPV_ARG(IShellDetails, &pSD)))) + return GetDetailsOfAsStringVariant(pSD, pidl, Column, pVar); + return hr; +} + HRESULT SHELL_GetDetailsOfColumnAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, UINT Column, VARTYPE vt, VARIANT *pVar) { @@ -139,6 +158,19 @@ SH32_GetDetailsOfPKeyAsVariant(IShellFolder2 *pSF, PCUITEMID_CHILD pidl, const S return SUCCEEDED(hr) ? SHELL_GetDetailsOfColumnAsVariant(pSF, pidl, hr, vt, pVar) : hr; } +HRESULT +SHELL_QueryInfoTipAlloc(_In_ IShellFolder* pSF, _In_ DWORD dwFlags, _In_ LPCITEMIDLIST pidl, _Out_ PWSTR *ppTip) +{ + if (!pidl) + return E_INVALIDARG; + CComPtr pQI; + HRESULT hr = pSF->GetUIObjectOf(NULL, 1, &pidl, IID_NULL_PPV_ARG(IQueryInfo, &pQI)); + if (FAILED(hr)) + return hr; + hr = pQI->GetInfoTip(dwFlags, ppTip); // Note: *ppTip can be NULL if there is no tip + return SUCCEEDED(hr) && !*ppTip ? S_FALSE : hr; +} + HRESULT SHELL_CreateAbsolutePidl(IShellFolder *pSF, PCUIDLIST_RELATIVE pidlChild, PIDLIST_ABSOLUTE *ppPidl) { PIDLIST_ABSOLUTE pidlFolder; diff --git a/dll/win32/shell32/wine/shell32_main.h b/dll/win32/shell32/wine/shell32_main.h index a3cc22a7bb7..c5b11b17e53 100644 --- a/dll/win32/shell32/wine/shell32_main.h +++ b/dll/win32/shell32/wine/shell32_main.h @@ -255,6 +255,14 @@ HRESULT WINAPI DoRegisterServer(void); HRESULT WINAPI DoUnregisterServer(void); /* Property system */ +static inline HRESULT +SHELL_SysAllocString(PCWSTR in, BSTR *out) +{ + if (!out) + return E_INVALIDARG; + return (*out = SysAllocString(in ? in : L"")) ? S_OK : E_OUTOFMEMORY; +} + static inline HRESULT SHELL_CreateVariantBufferEx(VARIANT *pVar, UINT cb, VARTYPE vt) { diff --git a/modules/rostests/apitests/shell32/CMakeLists.txt b/modules/rostests/apitests/shell32/CMakeLists.txt index e2f793d26c2..63e5865618b 100644 --- a/modules/rostests/apitests/shell32/CMakeLists.txt +++ b/modules/rostests/apitests/shell32/CMakeLists.txt @@ -9,6 +9,7 @@ list(APPEND SOURCE CMyComputer.cpp CommandLineToArgvW.cpp CShellDesktop.cpp + CShellDispatch.cpp CShellLink.cpp #CUserNotification.cpp # Test is broken on Win 2003 Control_RunDLLW.cpp diff --git a/modules/rostests/apitests/shell32/CShellDispatch.cpp b/modules/rostests/apitests/shell32/CShellDispatch.cpp new file mode 100644 index 00000000000..f7fcd6c8c9f --- /dev/null +++ b/modules/rostests/apitests/shell32/CShellDispatch.cpp @@ -0,0 +1,234 @@ +/* + * PROJECT: ReactOS API tests + * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) + * PURPOSE: Tests for CShellDispatch + * COPYRIGHT: Copyright 2026 Whindmar Saksit + */ + +#include "shelltest.h" +#include +#include + +static int GetFileSize(PCWSTR Path) +{ + WIN32_FIND_DATA wfd; + HANDLE hFind = FindFirstFileW(Path, &wfd); + if (hFind == INVALID_HANDLE_VALUE) + return -1; + FindClose(hFind); + return wfd.nFileSizeLow; +} + +static void VariantClearAndInvalidate(VARIANT *pV) +{ + if (!pV) + return; + VariantClear(pV); + V_BSTR(pV) = (BSTR)(SIZE_T)0xBAADF00Dul; +} + +template static void SafeRelease(T &p) +{ + if (!p) + return; + p->Release(); + p = NULL; +} + +template static HRESULT CreateFolderFromNameSpace(T Dir, Folder **ppsdf) +{ + *ppsdf = NULL; + CComPtr pSD; + HRESULT hr = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_ALL, IID_PPV_ARG(IShellDispatch, &pSD)); + if (FAILED(hr)) + return -1; // Special return value so we know to skip + + VARIANT vDir = { VT_I4 }; + V_I4(&vDir) = (UINT)(SIZE_T)Dir; + if (!IS_INTRESOURCE((SIZE_T)Dir)) + { + BSTR bs = SysAllocString((PCWSTR)(SIZE_T)Dir); + if (!bs) + return E_OUTOFMEMORY; + V_VT(&vDir) = VT_BSTR; + V_BSTR(&vDir) = bs; + } + hr = pSD->NameSpace(vDir, ppsdf); + VariantClear(&vDir); + return hr; +} + +#define ok_NameSpaceFolderParseIsPath(ns, parse, path, mustexist, hrparse) do \ +{ \ + if ((mustexist) && GetFileAttributesW((path)) == INVALID_FILE_ATTRIBUTES) \ + { \ + skip("Can't ParseName because \"%ls\" does not exist\n", (path)); \ + break; \ + } \ + hr = CreateFolderFromNameSpace((ns), &psdf); \ + ok_long(hr, S_OK); \ + if (SUCCEEDED(hr) && psdf) \ + { \ + FolderItem *pfi = NULL; \ + hr = psdf->ParseName((BSTR)(parse), &pfi); \ + ok_long(hr, (hrparse)); \ + if (SUCCEEDED(hr) && pfi) \ + { \ + BSTR bs = NULL; \ + hr = pfi->get_Path(&bs); \ + ok_long(hr, S_OK); \ + ok(SUCCEEDED(hr) && !lstrcmpiW(bs, (path)), "ParseName path \"%ls\" is not \"%ls\"\n", bs, (path)); \ + SysFreeString(SUCCEEDED(hr) ? bs : NULL); \ + SafeRelease(pfi); \ + } \ + } \ + SafeRelease(psdf); \ +} while (0) + +static void TestNameSpaceFolder() +{ + HRESULT hr; + Folder *psdf = NULL; + FolderItem *psdfi = NULL; + WCHAR buf1[MAX_PATH], buf2[MAX_PATH]; + + hr = CreateFolderFromNameSpace(CSIDL_DESKTOP, &psdf); // Root folder + ok_long(hr, S_OK); + if (hr == -1) + { + skip("Unable to initialize test\n"); + return; + } + SafeRelease(psdf); + + hr = CreateFolderFromNameSpace(CSIDL_CONTROLS, &psdf); // Virtual folder + ok_long(hr, S_OK); + SafeRelease(psdf); + + hr = CreateFolderFromNameSpace(CSIDL_DESKTOP, &psdf); + ok_long(hr, S_OK); + if (SUCCEEDED(hr)) + { + hr = psdf->ParseName((BSTR)L"X:\\DoesNotExist.xyz", (psdfi = NULL, &psdfi)); + ok(hr != S_OK, "Must fail parsing\n"); + SafeRelease(psdfi); + } + SafeRelease(psdf); + + hr = CreateFolderFromNameSpace(CSIDL_DESKTOP, &psdf); + ok_long(hr, S_OK); + if (SUCCEEDED(hr)) + { + hr = psdf->ParseName((BSTR)L"", (psdfi = NULL, &psdfi)); + ok_long(hr, S_OK); + SafeRelease(psdfi); + } + SafeRelease(psdf); + + hr = CreateFolderFromNameSpace(L"", &psdf); + ok(hr == S_OK || hr == S_FALSE, "Empty NS"); // S_FALSE on XP + SafeRelease(psdf); + + ok_NameSpaceFolderParseIsPath(CSIDL_DESKTOP, L"", L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}", false, S_OK); // My Computer + + SHGetFolderPathW(NULL, CSIDL_PROGRAM_FILES, NULL, SHGFP_TYPE_CURRENT, buf1); + ok_NameSpaceFolderParseIsPath(CSIDL_DESKTOP, buf1, buf1, true, S_OK); + + GetShortPathNameW(buf1, buf2, _countof(buf2)); // c:\progra~1 + ok_NameSpaceFolderParseIsPath(CSIDL_DESKTOP, buf2, buf1, true, S_OK); // ParseName+get_Path returns the long path +} + +static void TestFolderItem() +{ + WCHAR buf[MAX_PATH]; + VARIANT v = {}; + Folder *psdf = NULL; + FolderItem *psdfi = NULL; + FolderItem2 *psdfi2; + HRESULT hr = CreateFolderFromNameSpace(CSIDL_DESKTOP, &psdf); + if (hr == -1) + { + skip("Unable to initialize test\n"); + return; + } + GetModuleFileName(NULL, buf, _countof(buf)); + hr = psdf->ParseName(buf, &psdfi); + if ((ok(SUCCEEDED(hr), "ParseName %ls failed\n", buf), SUCCEEDED(hr))) + { + hr = psdfi->get_IsFileSystem(&V_BOOL(&v)); + ok(hr == S_OK && V_BOOL(&v), "IsFileSystem"); + + hr = psdfi->get_IsFolder(&V_BOOL(&v)); + ok(hr == S_OK && !V_BOOL(&v), "!IsFolder"); + + hr = psdfi->get_Size(&V_I4(&v)); + ok(hr == S_OK && V_I4(&v) == GetFileSize(buf), "Size"); + + hr = psdfi->QueryInterface(IID_PPV_ARG(FolderItem2, &psdfi2)); + if ((ok(SUCCEEDED(hr), "QI FolderItem2\n", buf), SUCCEEDED(hr))) + { + hr = psdfi2->ExtendedProperty((BSTR)L"Size", &v); + if ((ok(SUCCEEDED(hr), "ExtendedProperty failed\n"), SUCCEEDED(hr))) + { + VariantChangeType(&v, &v, 0, VT_I4); + ok(V_I4(&v) == GetFileSize(buf), "Wrong size from property\n"); + VariantClearAndInvalidate(&v); + } + SafeRelease(psdfi2); + } + SafeRelease(psdfi); + } + SafeRelease(psdf); +} + +START_TEST(CShellDispatch) +{ + CCoInit ComInit; + + TestNameSpaceFolder(); + TestFolderItem(); + + HRESULT hr; + CComPtr pSD2; + if (FAILED(hr = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_ALL, IID_PPV_ARG(IShellDispatch2, &pSD2)))) + { + skip("Unable to initialize test\n"); + return; + } + + IDispatch *pDisp = NULL; + hr = pSD2->get_Application(&pDisp); + ok_long(hr, S_OK); + ok(!!pDisp, "get_Application\n"); + SafeRelease(pDisp); + hr = pSD2->get_Application(NULL); + ok(FAILED(hr), "get_Application NULL\n"); + + LONG lVal; + hr = pSD2->IsRestricted((BSTR)L"DoesNotExist", (BSTR)L"DoesNotExist", (lVal = 42, &lVal)); + ok_long(hr, S_OK); + ok(lVal == 0, "IsRestricted\n"); + + VARIANT v = {}; + V_I4(&v) = PROCESSOR_ARCHITECTURE_UNKNOWN; + hr = pSD2->GetSystemInformation((BSTR)L"ProcessorArchitecture", &v); + ok_long(hr, S_OK); + ok(V_I4(&v) != PROCESSOR_ARCHITECTURE_UNKNOWN, "GetSystemInformation ProcessorArchitecture\n"); + VariantClearAndInvalidate(&v); + + hr = pSD2->IsServiceRunning((BSTR)L"DoesNotExist", &v); + ok_long(hr, S_OK); + ok_long(V_VT(&v), VT_BOOL); + ok_long(V_BOOL(&v), VARIANT_FALSE); + VariantClearAndInvalidate(&v); + + CComPtr pSD4; + if (FAILED(hr = CoCreateInstance(CLSID_Shell, NULL, CLSCTX_ALL, IID_PPV_ARG(IShellDispatch4, &pSD4)))) + { + skip("Unable to initialize test\n"); + return; + } + hr = pSD4->GetSetting(SSF_NOCONFIRMRECYCLE, &V_BOOL(&v)); + ok_long(hr, S_OK); + ok(V_BOOL(&v) == VARIANT_FALSE || V_BOOL(&v) == VARIANT_TRUE, "VARIANT_BOOL\n"); +} diff --git a/modules/rostests/apitests/shell32/testlist.c b/modules/rostests/apitests/shell32/testlist.c index 9bf2c1cf66d..153c72b31a0 100644 --- a/modules/rostests/apitests/shell32/testlist.c +++ b/modules/rostests/apitests/shell32/testlist.c @@ -10,6 +10,7 @@ extern void func_CIDLData(void); extern void func_CMyComputer(void); extern void func_CommandLineToArgvW(void); extern void func_CShellDesktop(void); +extern void func_CShellDispatch(void); extern void func_CShellLink(void); extern void func_CUserNotification(void); extern void func_DragDrop(void); @@ -70,6 +71,7 @@ const struct test winetest_testlist[] = { "CMyComputer", func_CMyComputer }, { "CommandLineToArgvW", func_CommandLineToArgvW }, { "CShellDesktop", func_CShellDesktop }, + { "CShellDispatch", func_CShellDispatch }, { "CShellLink", func_CShellLink }, //{ "CUserNotification", func_CUserNotification }, // Test is broken on Win 2003 { "DragDrop", func_DragDrop }, diff --git a/sdk/include/psdk/shlobj.h b/sdk/include/psdk/shlobj.h index 0f67321a267..c99c62d6748 100644 --- a/sdk/include/psdk/shlobj.h +++ b/sdk/include/psdk/shlobj.h @@ -696,6 +696,15 @@ DECLARE_INTERFACE_(IQueryInfo,IUnknown) #define IQueryInfo_GetInfoFlags(p,a) (p)->lpVtbl->GetInfoFlags(p,a) #endif +#define QITIPF_DEFAULT 0x00000000 +#define QITIPF_USENAME 0x00000001 +#define QITIPF_LINKNOTARGET 0x00000002 +#define QITIPF_LINKUSETARGET 0x00000004 +#define QITIPF_USESLOWTIP 0x00000008 // (NTDDI_VERSION >= NTDDI_WINXP) +#define QITIPF_SINGLELINE 0x00000010 // (NTDDI_VERSION >= NTDDI_VISTA) +#define QIF_CACHED 0x00000001 +#define QIF_DONTEXPANDFOLDER 0x00000002 + /* IInputObject interface */ #define INTERFACE IInputObject DECLARE_INTERFACE_(IInputObject,IUnknown) diff --git a/sdk/include/reactos/shellutils.h b/sdk/include/reactos/shellutils.h index d25671dd6a5..cb8b24053ce 100644 --- a/sdk/include/reactos/shellutils.h +++ b/sdk/include/reactos/shellutils.h @@ -465,6 +465,14 @@ template static HRESULT SHILClone(P pidl, R *ppOut) return r ? S_OK : E_OUTOFMEMORY; } +template static HRESULT SHILCloneParent(P pidl, R *ppOut) +{ + R r = *ppOut = (R)ILClone((PIDLIST_RELATIVE)pidl); + if (r) + ILRemoveLastID(r); // "c:\folder\thisitem" => "c:\folder" + return r ? S_OK : E_OUTOFMEMORY; +} + template static HRESULT SHILCombine(B base, PCUIDLIST_RELATIVE sub, R *ppOut) { R r = *ppOut = (R)ILCombine((PCIDLIST_ABSOLUTE)base, sub);