diff --git a/dll/shellext/fontext/CDataObject.cpp b/dll/shellext/fontext/CDataObject.cpp index 8c94a5bc4a0..cace1988a46 100644 --- a/dll/shellext/fontext/CDataObject.cpp +++ b/dll/shellext/fontext/CDataObject.cpp @@ -73,7 +73,7 @@ HRESULT _CDataObject_CreateInstance(PCIDLIST_ABSOLUTE folder, UINT cidl, PCUITEM } else { - ERR("No file found for %S\n", fontEntry->Name); + ERR("No file found for %S\n", fontEntry->Name()); } } } diff --git a/dll/shellext/fontext/CEnumFonts.cpp b/dll/shellext/fontext/CEnumFonts.cpp index 093e7cc99dc..e9bd8163900 100644 --- a/dll/shellext/fontext/CEnumFonts.cpp +++ b/dll/shellext/fontext/CEnumFonts.cpp @@ -42,25 +42,33 @@ public: while (celt) { - celt--; - - if (m_Index < g_FontCache->Size()) - { - CStringW Name = g_FontCache->Name(m_Index); - LPITEMIDLIST item = _ILCreate(Name); - if (!item) - { - hr = Fetched ? S_FALSE : E_OUTOFMEMORY; - break; - } - rgelt[Fetched] = item; - m_Index++; - Fetched++; - } - else + if (m_Index >= g_FontCache->Size()) { hr = S_FALSE; + break; } + + if (!g_FontCache->IsMarkDeleted(m_Index)) + { + CStringW Name = g_FontCache->Name(m_Index), FileName = g_FontCache->File(m_Index); + if (Name.IsEmpty() || FileName.IsEmpty()) + { + ERR("Why is Name or FileName empty?\n"); + } + else + { + // Create a PIDL + PITEMID_CHILD item = _ILCreate(Name, FileName); + if (!item) + { + hr = Fetched ? S_FALSE : E_OUTOFMEMORY; + break; + } + rgelt[Fetched++] = item; + --celt; + } + } + m_Index++; } if (pceltFetched) @@ -70,7 +78,12 @@ public: STDMETHODIMP Skip(ULONG celt) override { - m_Index += celt; + for (ULONG i = 0; i < celt && m_Index < g_FontCache->Size(); ++i) + { + while (m_Index < g_FontCache->Size() && g_FontCache->IsMarkDeleted(m_Index)) + ++m_Index; + ++m_Index; + } return S_OK; } diff --git a/dll/shellext/fontext/CFontBackgroundMenu.cpp b/dll/shellext/fontext/CFontBackgroundMenu.cpp index 88797a1ac44..ba2c0e9e686 100644 --- a/dll/shellext/fontext/CFontBackgroundMenu.cpp +++ b/dll/shellext/fontext/CFontBackgroundMenu.cpp @@ -67,9 +67,8 @@ STDMETHODIMP CFontBackgroundMenu::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi) if (FAILED_UNEXPECTEDLY(hr) || !CheckDataObject(pDataObj)) { // Show error message - CStringW text, title; - title.LoadStringW(IDS_REACTOS_FONTS_FOLDER); - text.LoadStringW(IDS_INSTALL_FAILED); + CStringW text(MAKEINTRESOURCEW(IDS_INSTALL_FAILED)); + CStringW title(MAKEINTRESOURCEW(IDS_REACTOS_FONTS_FOLDER)); MessageBoxW(m_hwnd, text, title, MB_ICONERROR); return E_FAIL; } diff --git a/dll/shellext/fontext/CFontCache.cpp b/dll/shellext/fontext/CFontCache.cpp index 3e9f63a855e..b27532b87a2 100644 --- a/dll/shellext/fontext/CFontCache.cpp +++ b/dll/shellext/fontext/CFontCache.cpp @@ -11,8 +11,9 @@ WINE_DEFAULT_DEBUG_CHANNEL(fontext); CFontCache* g_FontCache = NULL; -CFontInfo::CFontInfo(LPCWSTR name) +CFontInfo::CFontInfo(PCWSTR name, PCWSTR value) : m_Name(name) + , m_File(value) , m_FileRead(false) , m_AttrsRead(false) , m_FileWriteTime({}) @@ -35,7 +36,7 @@ const CStringW& CFontInfo::File() { if (!m_FileRead) { - if (Valid()) + if (Valid() && m_File.IsEmpty()) { // Read the filename stored in the registry. // This can be either a filename or a full path @@ -43,7 +44,7 @@ const CStringW& CFontInfo::File() if (key.Open(FONT_HIVE, FONT_KEY, KEY_READ) == ERROR_SUCCESS) { CStringW Value; - DWORD dwAllocated = 128; + DWORD dwAllocated = MAX_PATH; LSTATUS Status; do { @@ -125,35 +126,69 @@ void CFontCache::SetFontDir(const LPCWSTR Path) size_t CFontCache::Size() { - if (m_Fonts.GetCount() == 0u) + if (m_Fonts.GetSize() == 0) Read(); - return m_Fonts.GetCount(); + return m_Fonts.GetSize(); } CStringW CFontCache::Name(size_t Index) { - if (m_Fonts.GetCount() == 0u) + if (m_Fonts.GetSize() == 0) Read(); - if (Index >= m_Fonts.GetCount()) - return CStringW(); + if ((INT)Index >= m_Fonts.GetSize()) + return L""; return m_Fonts[Index].Name(); } +CStringW CFontCache::File(size_t Index) +{ + if (m_Fonts.GetSize() == 0) + Read(); + + if ((INT)Index >= m_Fonts.GetSize()) + return L""; + + return m_Fonts[Index].File(); +} + CFontInfo* CFontCache::Find(const FontPidlEntry* fontEntry) { - for (UINT n = 0; n < Size(); ++n) + if (m_Fonts.GetSize() == 0) + Read(); + + for (INT i = 0; i < m_Fonts.GetSize(); ++i) { - if (m_Fonts[n].Name().CompareNoCase(fontEntry->Name) == 0) + if (m_Fonts[i].Name().CompareNoCase(fontEntry->Name()) == 0) { - return &m_Fonts[n]; + return &m_Fonts[i]; } } return nullptr; } +BOOL CFontCache::IsMarkDeleted(size_t Index) const +{ + if ((INT)Index >= m_Fonts.GetSize()) + return FALSE; + return m_Fonts[Index].IsMarkDeleted(); +} + +// The item must exist until its visibility is removed, because the change +// notification UI must work for existing items. +void CFontCache::MarkDeleted(const FontPidlEntry* fontEntry) +{ + for (INT i = 0; i < m_Fonts.GetSize(); ++i) + { + if (m_Fonts[i].Name().CompareNoCase(fontEntry->Name()) == 0) + { + m_Fonts[i].MarkDeleted(); + break; + } + } +} CStringW CFontCache::Filename(CFontInfo* info, bool alwaysFullPath) { @@ -175,8 +210,14 @@ CStringW CFontCache::Filename(CFontInfo* info, bool alwaysFullPath) return File; } -void CFontCache::Insert(CAtlList& fonts, const CStringW& KeyName) +void CFontCache::Insert(CAtlList& fonts, const CStringW& KeyName, PCWSTR Value) { + CFontInfo newInfo(KeyName, Value); + + CStringW strFontFile = g_FontCache->GetFontFilePath(newInfo.File()); + if (!PathFileExistsW(strFontFile)) + return; + POSITION it = fonts.GetHeadPosition(); while (it != NULL) { @@ -184,54 +225,72 @@ void CFontCache::Insert(CAtlList& fonts, const CStringW& KeyName) const CFontInfo& info = fonts.GetNext(it); if (info.Name().CompareNoCase(KeyName) >= 0) { - fonts.InsertBefore(lastit, CFontInfo(KeyName)); + fonts.InsertBefore(lastit, newInfo); return; } } - fonts.AddTail(CFontInfo(KeyName)); + + fonts.AddTail(newInfo); +} + +CStringW CFontCache::GetFontFilePath(const PCWSTR Path) const +{ + if (PathIsRelativeW(Path)) + return m_FontFolderPath + Path; + return Path; } void CFontCache::Read() { - CAtlList fonts; CRegKey key; + LSTATUS error = key.Open(FONT_HIVE, FONT_KEY, KEY_READ); + if (error != ERROR_SUCCESS) + { + ERR("Can't open registry: %ld\n", error); + return; + } + + CComHeapPtr Name, Value; + DWORD cchName = MAX_PATH, cchValue = MAX_PATH; + if (!Name.Allocate(cchName) || !Value.Allocate(cchValue)) + { + ERR("Out of memory\n"); + return; + } // Enumerate all registered font names - if (key.Open(FONT_HIVE, FONT_KEY, KEY_READ) == ERROR_SUCCESS) + CAtlList fonts; + for (DWORD iItem = 0;; ++iItem) { - LSTATUS Status; - DWORD dwAllocated = 128; - DWORD ilIndex = 0; - CStringW KeyName; - do + DWORD cchName2 = cchName, cbValue = cchValue * sizeof(WCHAR); + error = RegEnumValueW(key, iItem, Name, &cchName2, NULL, NULL, + (PBYTE)(PWSTR)Value, &cbValue); + if (error == ERROR_MORE_DATA) { - DWORD dwSize = dwAllocated; - PWSTR Buffer = KeyName.GetBuffer(dwSize); - Status = RegEnumValueW(key.m_hKey, ilIndex, Buffer, &dwSize, NULL, NULL, NULL, NULL); - KeyName.ReleaseBuffer(dwSize); - if (Status == ERROR_SUCCESS) + cchName += 128; + cchValue += 128; + if (!Name.Reallocate(cchName) || !Value.Reallocate(cchValue)) { - // Insert will create an ordered list - Insert(fonts, KeyName); - ilIndex++; - continue; - } - if (Status == ERROR_NO_MORE_ITEMS) + ERR("Out of memory\n"); break; - else if (Status == ERROR_MORE_DATA) - { - dwAllocated += 128; } - } while (Status == ERROR_MORE_DATA || Status == ERROR_SUCCESS); + --iItem; + continue; + } + + if (error != ERROR_SUCCESS) + break; + + if (Name[0] && Value[0]) + { + // Insert will create an ordered list (sorted) + Insert(fonts, (PCWSTR)Name, (PCWSTR)Value); + } } // Move the fonts from a list to an array (for easy indexing) - m_Fonts.SetCount(fonts.GetCount()); - size_t Index = 0; + m_Fonts.RemoveAll(); POSITION it = fonts.GetHeadPosition(); while (it != NULL) - { - m_Fonts[Index] = fonts.GetNext(it); - Index++; - } + m_Fonts.Add(fonts.GetNext(it)); } diff --git a/dll/shellext/fontext/CFontCache.hpp b/dll/shellext/fontext/CFontCache.hpp index 48435bf008c..0610c189667 100644 --- a/dll/shellext/fontext/CFontCache.hpp +++ b/dll/shellext/fontext/CFontCache.hpp @@ -12,8 +12,8 @@ class CFontInfo private: CStringW m_Name; CStringW m_File; + BOOL m_bMarkDeleted = FALSE; bool m_FileRead; - bool m_AttrsRead; LARGE_INTEGER m_FileSize; FILETIME m_FileWriteTime; @@ -22,11 +22,14 @@ private: void ReadAttrs(); public: - CFontInfo(LPCWSTR name = L""); + CFontInfo(PCWSTR name = L"", PCWSTR value = L""); const CStringW& Name() const; // Font display name stored in the registry const bool Valid() const; + BOOL IsMarkDeleted() const { return m_bMarkDeleted; } + void MarkDeleted() { m_bMarkDeleted = TRUE; } + const CStringW& File(); // Full path or file, depending on how it's stored in the registry const LARGE_INTEGER& FileSize(); const FILETIME& FileWriteTime(); @@ -36,24 +39,28 @@ public: class CFontCache { private: - CAtlArray m_Fonts; + CSimpleArray m_Fonts; CStringW m_FontFolderPath; protected: CFontCache(); - void Insert(CAtlList& fonts, const CStringW& KeyName); + void Insert(CAtlList& fonts, const CStringW& KeyName, PCWSTR Value); public: void Read(); void SetFontDir(const LPCWSTR Path); const CStringW& FontPath() const { return m_FontFolderPath; } + CStringW GetFontFilePath(const PCWSTR Path) const; size_t Size(); CStringW Name(size_t Index); // Font display name stored in the registry + CStringW File(size_t Index); CFontInfo* Find(const FontPidlEntry* fontEntry); + BOOL IsMarkDeleted(size_t Index) const; + void MarkDeleted(const FontPidlEntry* fontEntry); CStringW Filename(CFontInfo* info, bool alwaysFullPath = false); friend class CFontExtModule; diff --git a/dll/shellext/fontext/CFontExt.cpp b/dll/shellext/fontext/CFontExt.cpp index 8bff3dec509..e43b9d0d237 100644 --- a/dll/shellext/fontext/CFontExt.cpp +++ b/dll/shellext/fontext/CFontExt.cpp @@ -77,6 +77,37 @@ WCHAR* g2s(REFCLSID iid) return buf[idx]; } +static HRESULT FONTEXT_GetAttributeString(DWORD dwAttributes, LPWSTR pszOut, UINT cchMax) +{ + CStringW AttrLetters; + AttrLetters.LoadString(IDS_COL_ATTR_LETTERS); + + if (AttrLetters.GetLength() != 5) + { + ERR("IDS_COL_ATTR_LETTERS does not contain 5 letters!\n"); + return E_FAIL; + } + + UINT ich = 0; + if ((dwAttributes & FILE_ATTRIBUTE_READONLY) && ich < cchMax) + pszOut[ich++] = AttrLetters[0]; + if ((dwAttributes & FILE_ATTRIBUTE_HIDDEN) && ich < cchMax) + pszOut[ich++] = AttrLetters[1]; + if ((dwAttributes & FILE_ATTRIBUTE_SYSTEM) && ich < cchMax) + pszOut[ich++] = AttrLetters[2]; + if ((dwAttributes & FILE_ATTRIBUTE_ARCHIVE) && ich < cchMax) + pszOut[ich++] = AttrLetters[3]; + if ((dwAttributes & FILE_ATTRIBUTE_COMPRESSED) && ich < cchMax) + pszOut[ich++] = AttrLetters[4]; + if (ich < cchMax) + { + pszOut[ich] = UNICODE_NULL; + return S_OK; + } + ERR("Buffer too short: %u\n", cchMax); + return E_FAIL; +} + CFontExt::CFontExt() { InterlockedIncrement(&g_ModuleRefCnt); @@ -92,20 +123,18 @@ void CFontExt::SetViewWindow(HWND hwndView) m_hwndView = hwndView; } -HRESULT CALLBACK -CFontExt::MenuCallBack(IShellFolder *psf, HWND hwndOwner, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam) +HRESULT CFontExt::DeleteItems(IDataObject* pDataObj) { - TRACE("%u, %p, %p\n", uMsg, wParam, lParam); - switch (uMsg) - { - case DFM_MERGECONTEXTMENU: - return S_OK; // Yes, I want verbs - case DFM_INVOKECOMMAND: - return S_FALSE; // Do it for me please - case DFM_GETDEFSTATICID: - return S_FALSE; // Supposedly "required for Windows 7 to pick a default" - } - return E_NOTIMPL; + HRESULT hr = DoDeleteFontFiles(m_hwndView, pDataObj); + FAILED_UNEXPECTEDLY(hr); + return S_OK; // Override default action +} + +HRESULT CFontExt::PreviewItems(IDataObject* pDataObj) +{ + HRESULT hr = DoPreviewFontFiles(m_hwndView, pDataObj); + FAILED_UNEXPECTEDLY(hr); + return S_OK; // Override default action } // *** IShellFolder2 methods *** @@ -179,13 +208,11 @@ STDMETHODIMP CFontExt::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDET auto info = g_FontCache->Find(fontEntry); if (info == nullptr) { - ERR("Unable to query info about %S\n", fontEntry->Name); + ERR("Unable to query info about %S\n", fontEntry->Name()); return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); } int ret; - CStringA AttrLetters; - DWORD dwAttributes; SYSTEMTIME time; switch (iColumn) { @@ -208,27 +235,13 @@ STDMETHODIMP CFontExt::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDET GetTimeFormatA(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &time, NULL, &psd->str.cStr[ret], MAX_PATH - ret); return S_OK; case FONTEXT_COL_ATTR: - AttrLetters.LoadString(IDS_COL_ATTR_LETTERS); - if (AttrLetters.GetLength() != 5) { - ERR("IDS_COL_ATTR_LETTERS does not contain 5 letters!\n"); - return E_FAIL; + WCHAR szAttr[8]; + HRESULT hr = FONTEXT_GetAttributeString(info->FileAttributes(), szAttr, _countof(szAttr)); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + return SHSetStrRet(&psd->str, szAttr); } - psd->str.uType = STRRET_CSTR; - dwAttributes = info->FileAttributes(); - ret = 0; - if (dwAttributes & FILE_ATTRIBUTE_READONLY) - psd->str.cStr[ret++] = AttrLetters[0]; - if (dwAttributes & FILE_ATTRIBUTE_HIDDEN) - psd->str.cStr[ret++] = AttrLetters[1]; - if (dwAttributes & FILE_ATTRIBUTE_SYSTEM) - psd->str.cStr[ret++] = AttrLetters[2]; - if (dwAttributes & FILE_ATTRIBUTE_ARCHIVE) - psd->str.cStr[ret++] = AttrLetters[3]; - if (dwAttributes & FILE_ATTRIBUTE_COMPRESSED) - psd->str.cStr[ret++] = AttrLetters[4]; - psd->str.cStr[ret] = '\0'; - return S_OK; default: break; } @@ -246,8 +259,53 @@ STDMETHODIMP CFontExt::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid) // *** IShellFolder2 methods *** STDMETHODIMP CFontExt::ParseDisplayName(HWND hwndOwner, LPBC pbc, LPOLESTR lpszDisplayName, DWORD *pchEaten, PIDLIST_RELATIVE *ppidl, DWORD *pdwAttributes) { - ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); - return E_NOTIMPL; + if (!lpszDisplayName || !lpszDisplayName[0] || !ppidl) + return E_INVALIDARG; + + *ppidl = NULL; + if (pchEaten) + *pchEaten = 0; + + if (lpszDisplayName[0] == L':' && lpszDisplayName[1] == L':') + return E_INVALIDARG; + + if (PathIsRelativeW(lpszDisplayName)) // Not full path? + return E_INVALIDARG; + + // Load font cache + if (g_FontCache->Size() == 0) + g_FontCache->Read(); + + for (SIZE_T iFont = 0; iFont < g_FontCache->Size(); ++iFont) + { + CStringW fileName = g_FontCache->File(iFont); + if (fileName.IsEmpty()) + continue; + + CStringW filePath = g_FontCache->GetFontFilePath(fileName); + if (filePath.CompareNoCase(lpszDisplayName) != 0) + continue; + + CStringW fontName = g_FontCache->Name(iFont); + if (fontName.IsEmpty()) + continue; + + // Create a PIDL + *ppidl = _ILCreate(fontName, fileName); + if (!*ppidl) + return E_OUTOFMEMORY; + + if (pchEaten) + *pchEaten = wcslen(lpszDisplayName); + + if (pdwAttributes && *pdwAttributes) + *pdwAttributes &= (SFGAO_CANDELETE | SFGAO_HASPROPSHEET | SFGAO_CANCOPY | + SFGAO_FILESYSTEM); + + return S_OK; + } + + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); // Not found } STDMETHODIMP CFontExt::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList) @@ -271,7 +329,6 @@ STDMETHODIMP CFontExt::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUID { const FontPidlEntry* fontEntry1 = _FontFromIL(pidl1); const FontPidlEntry* fontEntry2 = _FontFromIL(pidl2); - if (!fontEntry1 || !fontEntry2) return E_INVALIDARG; @@ -280,7 +337,7 @@ STDMETHODIMP CFontExt::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUID DWORD column = lParam & 0x0000FFFF; if (sortMode == SHCIDS_ALLFIELDS) { - result = StrCmpIW(fontEntry1->Name, fontEntry2->Name); + result = StrCmpIW(fontEntry1->Name(), fontEntry2->Name()); } else { @@ -289,7 +346,7 @@ STDMETHODIMP CFontExt::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUID if (!info1 || !info2) { - ERR("Unable to find font %S or %S in cache!\n", fontEntry1->Name, fontEntry2->Name); + ERR("Unable to find font %S or %S in cache!\n", fontEntry1->Name(), fontEntry2->Name()); return E_INVALIDARG; } @@ -298,22 +355,32 @@ STDMETHODIMP CFontExt::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUID case 0xffff: /* ROS bug? */ case FONTEXT_COL_NAME: - result = StrCmpIW(fontEntry1->Name, fontEntry2->Name); + result = StrCmpIW(fontEntry1->Name(), fontEntry2->Name()); break; case FONTEXT_COL_FILENAME: result = StrCmpIW(PathFindFileNameW(info1->File()), PathFindFileNameW(info2->File())); break; case FONTEXT_COL_SIZE: - result = (int)info1->FileSize().HighPart - info2->FileSize().HighPart; - if (result == 0) - result = (int)info1->FileSize().LowPart - info2->FileSize().LowPart; + { + ULONGLONG size1 = info1->FileSize().QuadPart, size2 = info2->FileSize().QuadPart; + result = (size1 < size2) ? -1 : ((size1 > size2) ? 1 : 0); + } break; case FONTEXT_COL_MODIFIED: result = CompareFileTime(&info1->FileWriteTime(), &info2->FileWriteTime()); break; case FONTEXT_COL_ATTR: - // FIXME: how to compare attributes? - result = (int)info1->FileAttributes() - info2->FileAttributes(); + { + HRESULT hr; + WCHAR szAttr1[8], szAttr2[8]; + hr = FONTEXT_GetAttributeString(info1->FileAttributes(), szAttr1, _countof(szAttr1)); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + hr = FONTEXT_GetAttributeString(info2->FileAttributes(), szAttr2, _countof(szAttr2)); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + result = _wcsicmp(szAttr1, szAttr2); + } break; default: ERR("Unimplemented column %u\n", column); @@ -361,16 +428,11 @@ STDMETHODIMP CFontExt::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, D if (!rgfInOut || !cidl || !apidl) return E_INVALIDARG; - DWORD rgf = 0; + DWORD rgf = (SFGAO_CANDELETE | SFGAO_HASPROPSHEET | SFGAO_CANCOPY | SFGAO_FILESYSTEM); while (cidl > 0 && *apidl) { const FontPidlEntry* fontEntry = _FontFromIL(*apidl); - if (fontEntry) - { - // We don't support delete yet - rgf |= (/*SFGAO_CANDELETE |*/ SFGAO_HASPROPSHEET | SFGAO_CANCOPY | SFGAO_FILESYSTEM); - } - else + if (!fontEntry) { rgf = 0; break; @@ -380,46 +442,81 @@ STDMETHODIMP CFontExt::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, D cidl--; } - *rgfInOut = rgf; + *rgfInOut &= rgf; return S_OK; } -HRESULT CFontExt::CreateForegroundMenu(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, LPVOID* ppvOut) +HRESULT CALLBACK CFontExt::MenuCallback( + IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, + UINT uMsg, WPARAM wParam, LPARAM lParam) { - if (cidl <= 0) - { - ERR("cidl: %u\n", cidl); - return E_NOTIMPL; - } - - const FontPidlEntry* pEntry = _FontFromIL(apidl[0]); - if (!pEntry) - { - ERR("!pEntry\n"); + CFontExt* pThis = static_cast(psf); + if (!pThis) return E_FAIL; - } - auto info = g_FontCache->Find(pEntry); - if (!info) + + switch (uMsg) { - ERR("!info\n"); - return E_FAIL; + case DFM_MERGECONTEXTMENU: + { + QCMINFO* pqcminfo = (QCMINFO*)lParam; + // Insert [Preview] menu item + CString strPreview(MAKEINTRESOURCEW(IDS_FONT_PREVIEW)); + ::InsertMenuW(pqcminfo->hmenu, 0, MF_BYPOSITION | MF_STRING, + pqcminfo->idCmdFirst++, strPreview); // Command 0 + // Make it default + ::SetMenuDefaultItem(pqcminfo->hmenu, pqcminfo->idCmdFirst - 1, FALSE); + return S_OK; + } + case DFM_GETVERBA: + case DFM_GETVERBW: + { + // Replace default "open" command action + UINT idCmd = LOWORD(wParam), cchMax = HIWORD(wParam); + if (idCmd == 0) + { + if (uMsg == DFM_GETVERBA) + lstrcpynA((PSTR)lParam, "open", cchMax); + else + lstrcpynW((PWSTR)lParam, L"open", cchMax); + return S_OK; + } + break; + } + case DFM_INVOKECOMMANDEX: + return E_NOTIMPL; + case DFM_INVOKECOMMAND: + { + if (wParam == 0) + return pThis->PreviewItems(pdtobj); + if (wParam == DFM_CMD_COPY) + return S_FALSE; + if (wParam == DFM_CMD_DELETE) + return pThis->DeleteItems(pdtobj); + if (wParam == DFM_CMD_PASTE) + return S_FALSE; + if (wParam == DFM_CMD_PROPERTIES) + return S_FALSE; + if (wParam == DFM_CMD_MOVE) + { + ERR("DFM_CMD_MOVE not supported\n"); + return E_NOTIMPL; + } + ERR("wParam: %p\n", wParam); + return E_FAIL; + } + case DFM_GETDEFSTATICID: // Required for Windows 7 to pick a default + return S_FALSE; + case DFM_WM_INITMENUPOPUP: + { + HMENU hMenu = (HMENU)wParam; + // Delete default [Open] menu item + ::DeleteMenu(hMenu, FCIDM_SHVIEW_OPEN, MF_BYCOMMAND); + // Disable [Paste link] menu item + ::EnableMenuItem(hMenu, FCIDM_SHVIEW_INSERTLINK, MF_BYCOMMAND | MF_GRAYED); + break; + } } - LPCWSTR extension = PathFindExtensionW(info->File()); - - CRegKeyHandleArray keys; - - WCHAR wszClass[MAX_PATH]; - DWORD dwSize = sizeof(wszClass); - if (RegGetValueW(HKEY_CLASSES_ROOT, extension, NULL, RRF_RT_REG_SZ, NULL, wszClass, &dwSize) != ERROR_SUCCESS || - !*wszClass || AddClassKeyToArray(wszClass, keys, keys) != ERROR_SUCCESS) - { - AddClassKeyToArray(extension, keys, keys); - - if (cidl == 1) - AddClassKeyToArray(L"Unknown", keys, keys); - } - - return CDefFolderMenu_Create2(m_Folder, hwndOwner, cidl, apidl, this, MenuCallBack, keys, keys, (IContextMenu**)ppvOut); + return E_NOTIMPL; } STDMETHODIMP CFontExt::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT * prgfInOut, LPVOID * ppvOut) @@ -428,7 +525,11 @@ STDMETHODIMP CFontExt::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ riid == IID_IContextMenu2 || riid == IID_IContextMenu3) { - return CreateForegroundMenu(hwndOwner, cidl, apidl, ppvOut); + if (cidl <= 0) + return E_FAIL; + + return CDefFolderMenu_Create2(NULL, hwndOwner, cidl, apidl, this, MenuCallback, + 0, NULL, (IContextMenu**)ppvOut); } else if (riid == IID_IExtractIconA || riid == IID_IExtractIconW) { @@ -438,10 +539,8 @@ STDMETHODIMP CFontExt::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ if (fontEntry) { DWORD dwAttributes = FILE_ATTRIBUTE_NORMAL; - CStringW File = g_FontCache->Filename(g_FontCache->Find(fontEntry)); - // Just create a default icon extractor based on the filename - // We might want to create a preview with the font to get really fancy one day. - return SHCreateFileExtractIconW(File, dwAttributes, riid, ppvOut); + CStringW strFileName = g_FontCache->GetFontFilePath(fontEntry->FileName()); + return SHCreateFileExtractIconW(strFileName, dwAttributes, riid, ppvOut); } } else @@ -478,12 +577,12 @@ STDMETHODIMP CFontExt::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ STDMETHODIMP CFontExt::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET strRet) { - if (!pidl) - return E_NOTIMPL; + if (!pidl || !strRet) + return E_INVALIDARG; // Validate that this pidl is the last one - PCUIDLIST_RELATIVE curpidl = ILGetNext(pidl); - if (curpidl->mkid.cb != 0) + PCUIDLIST_RELATIVE nextPidl = ILGetNext(pidl); + if (nextPidl && nextPidl->mkid.cb != 0) { ERR("ERROR, unhandled PIDL!\n"); return E_FAIL; @@ -495,14 +594,17 @@ STDMETHODIMP CFontExt::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPS if (dwFlags & SHGDN_FORPARSING) { - CStringW File = g_FontCache->Filename(g_FontCache->Find(fontEntry), true); - if (!File.IsEmpty()) + CStringW fileName = fontEntry->FileName(); + if (!(dwFlags & SHGDN_INFOLDER)) { - return SHSetStrRet(strRet, File); + CStringW fullPath = g_FontCache->GetFontFilePath(fileName); + if (!fullPath.IsEmpty()) + return SHSetStrRet(strRet, fullPath); } + return SHSetStrRet(strRet, fileName); } - return SHSetStrRet(strRet, fontEntry->Name); + return SHSetStrRet(strRet, fontEntry->Name()); } STDMETHODIMP CFontExt::SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl, LPCOLESTR lpName, DWORD dwFlags, PITEMID_CHILD *pPidlOut) @@ -546,7 +648,7 @@ STDMETHODIMP CFontExt::Initialize(LPCITEMIDLIST pidl) return E_FAIL; } - m_Folder.Attach(ILClone(pidl)); + m_Folder.Attach(ILCreateFromPathW(FontsDir)); StringCchCatW(FontsDir, _countof(FontsDir), L"\\"); g_FontCache->SetFontDir(FontsDir); @@ -609,6 +711,7 @@ STDMETHODIMP CFontExt::Drop(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, if (g_FontCache) g_FontCache->Read(); + SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)L"fonts", SMTO_ABORTIFHUNG, 1000, NULL); SendMessageTimeoutW(HWND_BROADCAST, WM_FONTCHANGE, 0, 0, SMTO_ABORTIFHUNG, 1000, NULL); // Show successful message diff --git a/dll/shellext/fontext/CFontExt.hpp b/dll/shellext/fontext/CFontExt.hpp index b6404d7ebbc..47a738cdd8a 100644 --- a/dll/shellext/fontext/CFontExt.hpp +++ b/dll/shellext/fontext/CFontExt.hpp @@ -19,13 +19,17 @@ class CFontExt : BOOL m_bDragAccepted = FALSE; HWND m_hwndView = nullptr; + static HRESULT CALLBACK MenuCallback( + IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, + UINT uMsg, WPARAM wParam, LPARAM lParam); + HRESULT PreviewItems(IDataObject* pDataObj); + HRESULT DeleteItems(IDataObject* pDataObj); + public: CFontExt(); ~CFontExt(); void SetViewWindow(HWND hwndView); - static HRESULT CALLBACK MenuCallBack(IShellFolder *psf, HWND hwndOwner, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam); - HRESULT CreateForegroundMenu(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, LPVOID* ppvOut); // *** IShellFolder2 methods *** STDMETHODIMP GetDefaultSearchGUID(GUID *lpguid) override; @@ -93,6 +97,7 @@ public: COM_INTERFACE_ENTRY_IID(IID_IShellFolder, IShellFolder) COM_INTERFACE_ENTRY_IID(IID_IPersistFolder2, IPersistFolder2) COM_INTERFACE_ENTRY_IID(IID_IPersistFolder, IPersistFolder) + COM_INTERFACE_ENTRY_IID(IID_IPersist, IPersist) COM_INTERFACE_ENTRY_IID(IID_IDropTarget, IDropTarget) //COM_INTERFACE_ENTRY_FUNC_BLIND(0, log_stuff) END_COM_MAP() diff --git a/dll/shellext/fontext/CFontFolderViewCB.cpp b/dll/shellext/fontext/CFontFolderViewCB.cpp index 582d91b66b9..7a8311d7eba 100644 --- a/dll/shellext/fontext/CFontFolderViewCB.cpp +++ b/dll/shellext/fontext/CFontFolderViewCB.cpp @@ -7,7 +7,7 @@ #include "precomp.h" -WINE_DEFAULT_DEBUG_CHANNEL(shell_ad); +WINE_DEFAULT_DEBUG_CHANNEL(fontext); void CFontFolderViewCB::Initialize(CFontExt* pFontExt, IShellView *psv, LPCITEMIDLIST pidlParent) { @@ -18,70 +18,29 @@ void CFontFolderViewCB::Initialize(CFontExt* pFontExt, IShellView *psv, LPCITEMI m_pShellView = psv; m_pidlParent.Attach(ILClone(pidlParent)); if (!m_pidlParent) - ERR("!m_pidlParent\n"); + ERR("m_pidlParent was null\n"); } -HRESULT CFontFolderViewCB::TranslatePidl(LPITEMIDLIST* ppidlNew, LPCITEMIDLIST pidl) +BOOL CFontFolderViewCB::FilterEvent(PIDLIST_ABSOLUTE* apidls, LONG lEvent) const { - ATLASSERT(ppidlNew); + lEvent &= ~SHCNE_INTERRUPT; - *ppidlNew = NULL; - - WCHAR szFontFile[MAX_PATH]; - if (!SHGetPathFromIDListW(pidl, szFontFile)) - return E_FAIL; - - CStringW strFontName; - HRESULT hr = DoGetFontTitle(szFontFile, strFontName); - if (FAILED_UNEXPECTEDLY(hr)) - return E_FAIL; - - LPITEMIDLIST pidlChild = _ILCreate(strFontName); - if (!pidlChild) - { - ERR("!pidlChild\n"); - return E_OUTOFMEMORY; - } - - *ppidlNew = ILCombine(m_pidlParent, pidlChild); - ILFree(pidlChild); - - return *ppidlNew ? S_OK : E_OUTOFMEMORY; -} - -void CFontFolderViewCB::TranslateTwoPIDLs(PIDLIST_ABSOLUTE* pidls) -{ - ATLASSERT(pidls); - - HRESULT hr; - if (pidls[0]) - { - m_pidl0.Free(); - hr = TranslatePidl(&m_pidl0, pidls[0]); - if (!FAILED_UNEXPECTEDLY(hr)) - pidls[0] = m_pidl0; - } - if (pidls[1]) - { - m_pidl1.Free(); - hr = TranslatePidl(&m_pidl1, pidls[1]); - if (!FAILED_UNEXPECTEDLY(hr)) - pidls[1] = m_pidl1; - } -} - -BOOL CFontFolderViewCB::FilterEvent(LONG lEvent) const -{ - switch (lEvent & ~SHCNE_INTERRUPT) + switch (lEvent) { case SHCNE_CREATE: - case SHCNE_DELETE: case SHCNE_RENAMEITEM: case SHCNE_UPDATEDIR: - return FALSE; // OK + // Refresh font cache and notify the system about the font change + if (g_FontCache) + g_FontCache->Read(); + break; + case SHCNE_DELETE: + break; default: return TRUE; // We don't want this event } + + return FALSE; } STDMETHODIMP @@ -91,19 +50,17 @@ CFontFolderViewCB::MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam) { case SFVM_QUERYFSNOTIFY: // Registering change notification { + if (!m_pShellView || !m_pFontExt) + return E_FAIL; // Now, we can get the view window - ATLASSERT(m_pShellView); - ATLASSERT(m_pFontExt); m_pShellView->GetWindow(&m_hwndView); m_pFontExt->SetViewWindow(m_hwndView); return S_OK; } case SFVM_FSNOTIFY: // Change notification { - if (FilterEvent((LONG)lParam)) + if (FilterEvent((PIDLIST_ABSOLUTE*)wParam, (LONG)lParam)) return S_FALSE; // Don't process - - TranslateTwoPIDLs((PIDLIST_ABSOLUTE*)wParam); return S_OK; } } diff --git a/dll/shellext/fontext/CFontFolderViewCB.h b/dll/shellext/fontext/CFontFolderViewCB.h index 5fbb42733c4..7afef1b3d70 100644 --- a/dll/shellext/fontext/CFontFolderViewCB.h +++ b/dll/shellext/fontext/CFontFolderViewCB.h @@ -11,23 +11,19 @@ class CFontFolderViewCB : public CComObjectRootEx , public IShellFolderViewCB { - CFontExt* m_pFontExt = nullptr; // Not ref-counted! - IShellView* m_pShellView = nullptr; // Not ref-counted! + CFontExt* m_pFontExt = nullptr; + CComPtr m_pShellView; HWND m_hwndView = nullptr; CComHeapPtr m_pidlParent; - CComHeapPtr m_pidl0; - CComHeapPtr m_pidl1; - HRESULT TranslatePidl(LPITEMIDLIST* ppidlNew, LPCITEMIDLIST pidl); - void TranslateTwoPIDLs(PIDLIST_ABSOLUTE* pidls); - BOOL FilterEvent(LONG lEvent) const; + BOOL FilterEvent(PIDLIST_ABSOLUTE* apidls, LONG lEvent) const; public: CFontFolderViewCB() { } void Initialize(CFontExt* pFontExt, IShellView *psv, LPCITEMIDLIST pidlParent); // IShellFolderViewCB - STDMETHOD(MessageSFVCB)(UINT uMsg, WPARAM wParam, LPARAM lParam) override; + STDMETHODIMP MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam) override; DECLARE_NO_REGISTRY() DECLARE_NOT_AGGREGATABLE(CFontFolderViewCB) diff --git a/dll/shellext/fontext/fontext.cpp b/dll/shellext/fontext/fontext.cpp index eeac271e2c0..5e3e90c28d4 100644 --- a/dll/shellext/fontext/fontext.cpp +++ b/dll/shellext/fontext/fontext.cpp @@ -111,14 +111,14 @@ LSTATUS AddClassKeyToArray(const WCHAR* szClass, HKEY* array, UINT* cKeys) if (*cKeys >= 16) return ERROR_MORE_DATA; - HKEY hkey; - LSTATUS result = RegOpenKeyExW(HKEY_CLASSES_ROOT, szClass, 0, KEY_READ | KEY_QUERY_VALUE, &hkey); - if (result == ERROR_SUCCESS) + CRegKey key; + LSTATUS error = key.Open(HKEY_CLASSES_ROOT, szClass, KEY_READ | KEY_QUERY_VALUE); + if (error == ERROR_SUCCESS) { - array[*cKeys] = hkey; + array[*cKeys] = key.Detach(); *cKeys += 1; } - return result; + return error; } HRESULT @@ -436,6 +436,137 @@ HRESULT InstallFontsFromDataObject(HWND hwndView, IDataObject* pDataObj) return FAILED_UNEXPECTEDLY(data.hrResult) ? E_FAIL : S_OK; } +HRESULT DoPreviewFontFiles(HWND hwnd, IDataObject* pDataObj) +{ + CDataObjectHIDA cida(pDataObj); + if (!cida || cida->cidl <= 0) + { + ERR("Invalid IDataObject\n"); + return E_FAIL; + } + + for (UINT iItem = 0; iItem < cida->cidl; ++iItem) + { + PCUIDLIST_RELATIVE pidlChild = HIDA_GetPIDLItem(cida, iItem); + const FontPidlEntry* fontEntry = _FontFromIL(pidlChild); + if (fontEntry) + RunFontViewer(hwnd, fontEntry); + } + + return S_OK; +} + +HRESULT DeleteFontFiles(HWND hwnd, IDataObject* pDataObj) +{ + CDataObjectHIDA cida(pDataObj); + if (!cida || cida->cidl <= 0) + { + ERR("E_FAIL\n"); + return E_FAIL; + } + + PCUIDLIST_ABSOLUTE pidlParent = HIDA_GetPIDLFolder(cida); + if (!pidlParent) + { + ERR("pidlParent is NULL\n"); + return E_FAIL; + } + + // Delete files + for (UINT iItem = 0; iItem < cida->cidl; ++iItem) + { + PCUIDLIST_RELATIVE pidlChild = HIDA_GetPIDLItem(cida, iItem); + const FontPidlEntry* pEntry = _FontFromIL(pidlChild); + if (!pEntry) + { + ERR("Invalid pEntry: %p\n", pEntry); + return E_FAIL; + } + + CStringW szPath = g_FontCache->GetFontFilePath(pEntry->FileName()); + + // WINDOWS BUG: Removing once is not enough + for (INT iTry = 0; iTry < 3; ++iTry) + { + if (!RemoveFontResourceW(szPath) && !RemoveFontResourceExW(szPath, FR_PRIVATE, NULL)) + break; + } + + if (!DeleteFileW(szPath)) + { + ERR("Unable to delete font file: %S: %ld\n", (PCWSTR)szPath, GetLastError()); + return E_FAIL; + } + + CComHeapPtr pidl(ILCombine(pidlParent, pidlChild)); + if (pidl) + SHChangeNotify(SHCNE_DELETE, SHCNF_IDLIST, (LPCITEMIDLIST)pidl, NULL); + else + ERR("Out of memory\n"); + } + + // Delete registry values and mark the entry as deleted + CRegKey key; + if (key.Open(FONT_HIVE, FONT_KEY, KEY_WRITE) == ERROR_SUCCESS) + { + for (UINT iItem = 0; iItem < cida->cidl; ++iItem) + { + PCUIDLIST_RELATIVE pidlChild = HIDA_GetPIDLItem(cida, iItem); + const FontPidlEntry* pEntry = _FontFromIL(pidlChild); + if (pEntry) + { + CStringW strFontName = pEntry->Name(); + key.DeleteValue(strFontName); + g_FontCache->MarkDeleted(pEntry); + } + } + key.Close(); + } + + return S_OK; +} + +HRESULT DoDeleteFontFiles(HWND hwnd, IDataObject* pDataObj) +{ + CStringW title(MAKEINTRESOURCEW(IDS_REACTOS_FONTS_FOLDER)); + CStringW msg(MAKEINTRESOURCEW(IDS_CONFIRM_DELETE_FONT)); + if (MessageBoxW(hwnd, msg, title, MB_YESNOCANCEL | MB_ICONWARNING) != IDYES) + return S_FALSE; + + HRESULT hr = DeleteFontFiles(hwnd, pDataObj); + if (FAILED_UNEXPECTEDLY(hr)) + { + msg.LoadString(IDS_CANTDELETEFONT); + MessageBoxW(hwnd, msg, title, MB_ICONERROR); + return hr; + } + + SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)L"fonts", + SMTO_ABORTIFHUNG, 1000, NULL); + SendMessageTimeoutW(HWND_BROADCAST, WM_FONTCHANGE, 0, 0, SMTO_ABORTIFHUNG, 1000, NULL); + return S_OK; +} + +void RunFontViewer(HWND hwnd, const FontPidlEntry* fontEntry) +{ + CStringW Path = g_FontCache->GetFontFilePath(fontEntry->FileName()); + if (Path.IsEmpty()) + return; + + // '/d' disables the install button + WCHAR FontPathArg[MAX_PATH + 3]; + StringCchPrintfW(FontPathArg, _countof(FontPathArg), L"/d %s", Path.GetString()); + PathQuoteSpacesW(FontPathArg + 3); + + SHELLEXECUTEINFOW si = { sizeof(si) }; + si.fMask = SEE_MASK_DOENVSUBST; + si.hwnd = hwnd; + si.lpFile = L"%SystemRoot%\\System32\\fontview.exe"; + si.lpParameters = FontPathArg; + si.nShow = SW_SHOWNORMAL; + ShellExecuteExW(&si); +} + EXTERN_C BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { diff --git a/dll/shellext/fontext/fontpidl.cpp b/dll/shellext/fontext/fontpidl.cpp index 9b83201873e..aba1844d239 100644 --- a/dll/shellext/fontext/fontpidl.cpp +++ b/dll/shellext/fontext/fontpidl.cpp @@ -3,33 +3,109 @@ * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) * PURPOSE: pidl handling * COPYRIGHT: Copyright 2019,2020 Mark Jansen + * Copyright 2026 Katayama Hirofumi MZ */ #include "precomp.h" -LPITEMIDLIST _ILCreate(LPCWSTR lpString) +WINE_DEFAULT_DEBUG_CHANNEL(fontext); + +#define FONTPIDL_MAGIC 0x7066 // 'fp' +#define ALIGN_DWORD(size) (((size) + 3) & ~3) + +PITEMID_CHILD _ILCreate(LPCWSTR lpName, LPCWSTR lpFileName) { - // Because the FontPidlEntry contains one WCHAR, we do not need to take the null terminator into account - size_t cbData = sizeof(FontPidlEntry) + wcslen(lpString) * sizeof(WCHAR); + ATLASSERT(lpName); + ATLASSERT(lpFileName); + + if (!lpName[0] || !lpFileName[0]) + { + ERR("Invalid parameters: lpName '%S', lpFileName '%S'\n", lpName, lpFileName); + return NULL; + } + + // SECURITY: Check string length + HRESULT hr; + size_t cbName, cbFileName; + hr = StringCbLengthW(lpName, min(MAXWORD - 1, STRSAFE_MAX_CCH) * sizeof(WCHAR), &cbName); + if (FAILED_UNEXPECTEDLY(hr)) + return NULL; + hr = StringCbLengthW(lpFileName, min(MAXWORD - 1, STRSAFE_MAX_CCH) * sizeof(WCHAR), &cbFileName); + if (FAILED_UNEXPECTEDLY(hr)) + return NULL; + cbName += sizeof(UNICODE_NULL); + cbFileName += sizeof(UNICODE_NULL); + + size_t ibName = ALIGN_DWORD(sizeof(FontPidlEntry)); + size_t ibFileName = ALIGN_DWORD(ibName + cbName); + size_t cbData = ibFileName + cbFileName; + if (cbData > MAXWORD - sizeof(WORD)) + { + ATLASSERT(FALSE); + return NULL; + } + FontPidlEntry* pidl = (FontPidlEntry*)CoTaskMemAlloc(cbData + sizeof(WORD)); if (!pidl) + { + ERR("Out of memory\n"); return NULL; + } ZeroMemory(pidl, cbData + sizeof(WORD)); - pidl->cb = (WORD)cbData; - pidl->Magic = 'fp'; - wcscpy(pidl->Name, lpString); - // Should be zero already, but make sure it is - *(WORD*)((char*)pidl + cbData) = 0; + pidl->Magic = FONTPIDL_MAGIC; + pidl->ibName = (WORD)ibName; + pidl->ibFileName = (WORD)ibFileName; - return (LPITEMIDLIST)pidl; + // SECURITY: Copy strings + hr = StringCbCopyW(pidl->Name(), cbName, lpName); + if (FAILED_UNEXPECTEDLY(hr)) + { + CoTaskMemFree(pidl); + return NULL; + } + hr = StringCbCopyW(pidl->FileName(), cbFileName, lpFileName); + if (FAILED_UNEXPECTEDLY(hr)) + { + CoTaskMemFree(pidl); + return NULL; + } + + *(PWORD)((PBYTE)pidl + cbData) = UNICODE_NULL; + + ATLASSERT(_FontFromIL((PITEMID_CHILD)pidl)); + + return (PITEMID_CHILD)pidl; } -const FontPidlEntry* _FontFromIL(LPCITEMIDLIST pidl) +const FontPidlEntry* _FontFromIL(PCITEMID_CHILD pidl) { + if (!pidl || pidl->mkid.cb < sizeof(FontPidlEntry)) + return NULL; + const FontPidlEntry* fontEntry = (const FontPidlEntry*)pidl; - if (fontEntry->Magic == 'fp') - return fontEntry; - return NULL; + if (fontEntry->Magic != FONTPIDL_MAGIC) + return NULL; + + // The function gets an arbitrary PIDL here. Security is important. + // SECURITY: Check ibName and ibFileName + if (fontEntry->ibName < sizeof(FontPidlEntry) || fontEntry->ibFileName < sizeof(FontPidlEntry) || + fontEntry->ibName >= fontEntry->cb || fontEntry->ibFileName >= fontEntry->cb || + fontEntry->ibName % sizeof(WCHAR) != 0 || fontEntry->ibFileName % sizeof(WCHAR) != 0) + { + ERR("Invalid fontEntry %p (ibName %d, ibFileName %d, cb %d)\n", fontEntry, + fontEntry->ibName, fontEntry->ibFileName, fontEntry->cb); + return NULL; + } + + // SECURITY: Check null termination + size_t cbName, cbNameMax = fontEntry->cb - fontEntry->ibName; + if (FAILED_UNEXPECTEDLY(StringCbLengthW(fontEntry->Name(), cbNameMax, &cbName))) + return NULL; + size_t cbFileName, cbFileNameMax = fontEntry->cb - fontEntry->ibFileName; + if (FAILED_UNEXPECTEDLY(StringCbLengthW(fontEntry->FileName(), cbFileNameMax, &cbFileName))) + return NULL; + + return fontEntry; } diff --git a/dll/shellext/fontext/fontpidl.hpp b/dll/shellext/fontext/fontpidl.hpp index b71414eda39..5ed66c53471 100644 --- a/dll/shellext/fontext/fontpidl.hpp +++ b/dll/shellext/fontext/fontpidl.hpp @@ -3,6 +3,7 @@ * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) * PURPOSE: pidl handling * COPYRIGHT: Copyright 2019 Mark Jansen + * Copyright 2026 Katayama Hirofumi MZ */ #pragma once @@ -12,9 +13,14 @@ struct FontPidlEntry { WORD cb; WORD Magic; - WCHAR Name[1]; + WORD ibName; + WORD ibFileName; + LPWSTR Name() { return (LPWSTR)((PBYTE)this + ibName); } + LPWSTR FileName() { return (LPWSTR)((PBYTE)this + ibFileName); } + LPCWSTR Name() const { return (LPCWSTR)((PBYTE)this + ibName); } + LPCWSTR FileName() const { return (LPCWSTR)((PBYTE)this + ibFileName); } }; #include -LPITEMIDLIST _ILCreate(LPCWSTR lpString); -const FontPidlEntry* _FontFromIL(LPCITEMIDLIST pidl); +PITEMID_CHILD _ILCreate(LPCWSTR lpName, LPCWSTR lpFileName); +const FontPidlEntry* _FontFromIL(PCITEMID_CHILD pidl); diff --git a/dll/shellext/fontext/lang/de-DE.rc b/dll/shellext/fontext/lang/de-DE.rc index 98c17f3b10d..61af8f15f3d 100644 --- a/dll/shellext/fontext/lang/de-DE.rc +++ b/dll/shellext/fontext/lang/de-DE.rc @@ -20,9 +20,11 @@ END STRINGTABLE BEGIN IDS_REACTOS_FONTS_FOLDER "ReactOS Schriftartenordner" - IDS_INSTALL_OK "The font files have been installed successfully." - IDS_INSTALL_FAILED "Failed to install the font files." + IDS_INSTALL_OK "The font(s) have been installed successfully." + IDS_INSTALL_FAILED "Failed to install the font(s)." + IDS_CONFIRM_DELETE_FONT "Do you want to delete the font(s)?" IDS_PROPERTIES "P&roperties" + IDS_CANTDELETEFONT "Failed to delete the font(s)." END STRINGTABLE diff --git a/dll/shellext/fontext/lang/en-US.rc b/dll/shellext/fontext/lang/en-US.rc index c8cbe6f3873..a499afb70d0 100644 --- a/dll/shellext/fontext/lang/en-US.rc +++ b/dll/shellext/fontext/lang/en-US.rc @@ -15,9 +15,11 @@ END STRINGTABLE BEGIN IDS_REACTOS_FONTS_FOLDER "ReactOS Font Folder" - IDS_INSTALL_OK "The font files have been installed successfully." - IDS_INSTALL_FAILED "Failed to install the font files." + IDS_INSTALL_OK "The font(s) have been installed successfully." + IDS_INSTALL_FAILED "Failed to install the font(s)." + IDS_CONFIRM_DELETE_FONT "Do you want to delete the font(s)?" IDS_PROPERTIES "P&roperties" + IDS_CANTDELETEFONT "Failed to delete the font(s)." END STRINGTABLE diff --git a/dll/shellext/fontext/lang/it-IT.rc b/dll/shellext/fontext/lang/it-IT.rc index 90f284d2eb3..5b6c96dcb5c 100644 --- a/dll/shellext/fontext/lang/it-IT.rc +++ b/dll/shellext/fontext/lang/it-IT.rc @@ -20,9 +20,11 @@ END STRINGTABLE BEGIN IDS_REACTOS_FONTS_FOLDER "Cartella dei font di ReactOS" - IDS_INSTALL_OK "The font files have been installed successfully." - IDS_INSTALL_FAILED "Failed to install the font files." + IDS_INSTALL_OK "The font(s) have been installed successfully." + IDS_INSTALL_FAILED "Failed to install the font(s)." + IDS_CONFIRM_DELETE_FONT "Do you want to delete the font(s)?" IDS_PROPERTIES "P&roperties" + IDS_CANTDELETEFONT "Failed to delete the font(s)." END STRINGTABLE diff --git a/dll/shellext/fontext/lang/pl-PL.rc b/dll/shellext/fontext/lang/pl-PL.rc index 390f80945f4..e6419b43a18 100644 --- a/dll/shellext/fontext/lang/pl-PL.rc +++ b/dll/shellext/fontext/lang/pl-PL.rc @@ -15,9 +15,11 @@ END STRINGTABLE BEGIN IDS_REACTOS_FONTS_FOLDER "Folder czcionek ReactOS" - IDS_INSTALL_OK "The font files have been installed successfully." - IDS_INSTALL_FAILED "Failed to install the font files." + IDS_INSTALL_OK "The font(s) have been installed successfully." + IDS_INSTALL_FAILED "Failed to install the font(s)." + IDS_CONFIRM_DELETE_FONT "Do you want to delete the font(s)?" IDS_PROPERTIES "P&roperties" + IDS_CANTDELETEFONT "Failed to delete the font(s)." END STRINGTABLE diff --git a/dll/shellext/fontext/lang/ro-RO.rc b/dll/shellext/fontext/lang/ro-RO.rc index 5831d194a13..7a6cd755823 100644 --- a/dll/shellext/fontext/lang/ro-RO.rc +++ b/dll/shellext/fontext/lang/ro-RO.rc @@ -21,9 +21,11 @@ END STRINGTABLE BEGIN IDS_REACTOS_FONTS_FOLDER "Folder-ul de fonturi de ReactOS" - IDS_INSTALL_OK "The font files have been installed successfully." - IDS_INSTALL_FAILED "Failed to install the font files." + IDS_INSTALL_OK "The font(s) have been installed successfully." + IDS_INSTALL_FAILED "Failed to install the font(s)." + IDS_CONFIRM_DELETE_FONT "Do you want to delete the font(s)?" IDS_PROPERTIES "P&roperties" + IDS_CANTDELETEFONT "Failed to delete the font(s)." END STRINGTABLE diff --git a/dll/shellext/fontext/lang/tr-TR.rc b/dll/shellext/fontext/lang/tr-TR.rc index 7eccac73bde..8da84432cf3 100644 --- a/dll/shellext/fontext/lang/tr-TR.rc +++ b/dll/shellext/fontext/lang/tr-TR.rc @@ -15,9 +15,11 @@ END STRINGTABLE BEGIN IDS_REACTOS_FONTS_FOLDER "ReactOS Yazı Tipi Dizini" - IDS_INSTALL_OK "The font files have been installed successfully." - IDS_INSTALL_FAILED "Failed to install the font files." + IDS_INSTALL_OK "The font(s) have been installed successfully." + IDS_INSTALL_FAILED "Failed to install the font(s)." + IDS_CONFIRM_DELETE_FONT "Do you want to delete the font(s)?" IDS_PROPERTIES "P&roperties" + IDS_CANTDELETEFONT "Failed to delete the font(s)." END STRINGTABLE diff --git a/dll/shellext/fontext/lang/zh-CN.rc b/dll/shellext/fontext/lang/zh-CN.rc index 8595bf2fd0c..92a5bb37acd 100644 --- a/dll/shellext/fontext/lang/zh-CN.rc +++ b/dll/shellext/fontext/lang/zh-CN.rc @@ -20,9 +20,11 @@ END STRINGTABLE BEGIN IDS_REACTOS_FONTS_FOLDER "ReactOS 字体文件夹" - IDS_INSTALL_OK "The font files have been installed successfully." - IDS_INSTALL_FAILED "Failed to install the font files." + IDS_INSTALL_OK "The font(s) have been installed successfully." + IDS_INSTALL_FAILED "Failed to install the font(s)." + IDS_CONFIRM_DELETE_FONT "Do you want to delete the font(s)?" IDS_PROPERTIES "P&roperties" + IDS_CANTDELETEFONT "Failed to delete the font(s)." END STRINGTABLE diff --git a/dll/shellext/fontext/lang/zh-HK.rc b/dll/shellext/fontext/lang/zh-HK.rc index 8e3a5ee3315..2e77103160b 100644 --- a/dll/shellext/fontext/lang/zh-HK.rc +++ b/dll/shellext/fontext/lang/zh-HK.rc @@ -20,9 +20,11 @@ END STRINGTABLE BEGIN IDS_REACTOS_FONTS_FOLDER "ReactOS 字型資料夾" - IDS_INSTALL_OK "The font files have been installed successfully." - IDS_INSTALL_FAILED "Failed to install the font files." + IDS_INSTALL_OK "The font(s) have been installed successfully." + IDS_INSTALL_FAILED "Failed to install the font(s)." + IDS_CONFIRM_DELETE_FONT "Do you want to delete the font(s)?" IDS_PROPERTIES "P&roperties" + IDS_CANTDELETEFONT "Failed to delete the font(s)." END STRINGTABLE diff --git a/dll/shellext/fontext/lang/zh-TW.rc b/dll/shellext/fontext/lang/zh-TW.rc index 270b24b6978..7a95fd7cd12 100644 --- a/dll/shellext/fontext/lang/zh-TW.rc +++ b/dll/shellext/fontext/lang/zh-TW.rc @@ -20,9 +20,11 @@ END STRINGTABLE BEGIN IDS_REACTOS_FONTS_FOLDER "ReactOS 字型資料夾" - IDS_INSTALL_OK "The font files have been installed successfully." - IDS_INSTALL_FAILED "Failed to install the font files." + IDS_INSTALL_OK "The font(s) have been installed successfully." + IDS_INSTALL_FAILED "Failed to install the font(s)." + IDS_CONFIRM_DELETE_FONT "Do you want to delete the font(s)?" IDS_PROPERTIES "P&roperties" + IDS_CANTDELETEFONT "Failed to delete the font(s)." END STRINGTABLE diff --git a/dll/shellext/fontext/precomp.h b/dll/shellext/fontext/precomp.h index 0727b10f975..f07e826b40a 100644 --- a/dll/shellext/fontext/precomp.h +++ b/dll/shellext/fontext/precomp.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +83,9 @@ HRESULT DoGetFontTitle( BOOL CheckDropFontFiles(HDROP hDrop); BOOL CheckDataObject(IDataObject *pDataObj); HRESULT InstallFontsFromDataObject(HWND hwndView, IDataObject* pDataObj); +HRESULT DoPreviewFontFiles(HWND hwnd, IDataObject* pDataObj); +HRESULT DoDeleteFontFiles(HWND hwnd, IDataObject* pDataObj); +void RunFontViewer(HWND hwnd, const FontPidlEntry* fontEntry); HRESULT APIENTRY diff --git a/dll/shellext/fontext/res/fontext.rgs b/dll/shellext/fontext/res/fontext.rgs index 24de92faea8..cd1096a90e7 100644 --- a/dll/shellext/fontext/res/fontext.rgs +++ b/dll/shellext/fontext/res/fontext.rgs @@ -4,7 +4,7 @@ HKCR { ForceRemove {BD84B380-8CA2-1069-AB1D-08000948F534} = s 'Fonts' { - InprocServer32 = s 'fontext.dll' + InprocServer32 = s '%MODULE%' { val ThreadingModel = s 'Apartment' } diff --git a/dll/shellext/fontext/resource.h b/dll/shellext/fontext/resource.h index b1b871d934b..bed1e51ac9d 100644 --- a/dll/shellext/fontext/resource.h +++ b/dll/shellext/fontext/resource.h @@ -7,7 +7,9 @@ #define IDS_REACTOS_FONTS_FOLDER 151 #define IDS_INSTALL_OK 152 #define IDS_INSTALL_FAILED 153 -#define IDS_PROPERTIES 154 +#define IDS_CONFIRM_DELETE_FONT 154 +#define IDS_PROPERTIES 155 +#define IDS_CANTDELETEFONT 156 #define IDS_COL_NAME 301 #define IDS_COL_FILENAME 304 diff --git a/dll/win32/shell32/CDefaultContextMenu.cpp b/dll/win32/shell32/CDefaultContextMenu.cpp index 3f0023fa44f..2e0fd193136 100644 --- a/dll/win32/shell32/CDefaultContextMenu.cpp +++ b/dll/win32/shell32/CDefaultContextMenu.cpp @@ -1159,6 +1159,10 @@ HRESULT CDefaultContextMenu::DoCopyOrCut(LPCMINVOKECOMMANDINFOEX lpcmi, BOOL bCo if (!m_cidl || !m_pDataObj) return E_FAIL; + HRESULT hr = _DoInvokeCommandCallback(lpcmi, bCopy ? DFM_CMD_COPY : DFM_CMD_MOVE); + if (hr == S_OK) + return hr; + FORMATETC formatetc; InitFormatEtc(formatetc, RegisterClipboardFormatW(CFSTR_PREFERREDDROPEFFECT), TYMED_HGLOBAL); STGMEDIUM medium = {0}; @@ -1174,7 +1178,7 @@ HRESULT CDefaultContextMenu::DoCopyOrCut(LPCMINVOKECOMMANDINFOEX lpcmi, BOOL bCo if (SUCCEEDED(IUnknown_QueryService(m_site, SID_SFolderView, IID_PPV_ARG(IShellFolderView, &psfv)))) psfv->SetPoints(m_pDataObj); - HRESULT hr = OleSetClipboard(m_pDataObj); + hr = OleSetClipboard(m_pDataObj); if (FAILED_UNEXPECTEDLY(hr)) return hr; @@ -1775,6 +1779,15 @@ CDefaultContextMenu::GetCommandString( UINT CmdId = LOWORD(idCommand); + if (uFlags == GCS_VERBA || uFlags == GCS_VERBW) + { + UINT uMsg = (uFlags == GCS_VERBA) ? DFM_GETVERBA : DFM_GETVERBW; + WPARAM wParam = MAKEWPARAM(idCommand, uMaxNameLen); + HRESULT hr = _DoCallback(uMsg, wParam, lpszName); + if (hr == S_OK) + return S_OK; + } + if (!m_DynamicEntries.IsEmpty() && CmdId >= m_iIdSHEFirst && CmdId < m_iIdSHELast) { idCommand -= m_iIdSHEFirst; diff --git a/sdk/include/psdk/shlobj.h b/sdk/include/psdk/shlobj.h index 03d98578ad5..0f67321a267 100644 --- a/sdk/include/psdk/shlobj.h +++ b/sdk/include/psdk/shlobj.h @@ -417,6 +417,8 @@ PathMakeUniqueName( _In_opt_ PCWSTR pszLongPlate, _In_opt_ PCWSTR pszDir); +HRESULT WINAPI SHMultiFileProperties(_In_ IDataObject *pDataObject, _In_ DWORD dwFlags); + /***************************************************************************** * IContextMenu interface */