/* * PROJECT: ReactOS Font Shell Extension * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) * PURPOSE: CFontExt implementation * COPYRIGHT: Copyright 2019,2020 Mark Jansen (mark.jansen@reactos.org) * Copyright 2019 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com) */ #include "precomp.h" #include "undocgdi.h" // for GetFontResourceInfoW WINE_DEFAULT_DEBUG_CHANNEL(fontext); struct FolderViewColumns { int iResource; DWORD dwDefaultState; int cxChar; int fmt; }; static FolderViewColumns g_ColumnDefs[] = { { IDS_COL_NAME, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 25, LVCFMT_LEFT }, { IDS_COL_FILENAME, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 20, LVCFMT_LEFT }, { IDS_COL_SIZE, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 10, LVCFMT_RIGHT }, { IDS_COL_MODIFIED, SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT, 15, LVCFMT_LEFT }, { IDS_COL_ATTR, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, 12, LVCFMT_RIGHT }, }; // Should fix our headers.. EXTERN_C HRESULT WINAPI SHCreateFileExtractIconW(LPCWSTR pszPath, DWORD dwFileAttributes, REFIID riid, void **ppv); // Helper functions to translate a guid to a readable name bool GetInterfaceName(const WCHAR* InterfaceString, WCHAR* buf, size_t size) { WCHAR LocalBuf[100]; DWORD dwType = 0, dwDataSize = size * sizeof(WCHAR); if (!SUCCEEDED(StringCchPrintfW(LocalBuf, _countof(LocalBuf), L"Interface\\%s", InterfaceString))) return false; return RegGetValueW(HKEY_CLASSES_ROOT, LocalBuf, NULL, RRF_RT_REG_SZ, &dwType, buf, &dwDataSize) == ERROR_SUCCESS; } WCHAR* g2s(REFCLSID iid) { static WCHAR buf[2][300]; static int idx = 0; idx ^= 1; LPOLESTR tmp; HRESULT hr = ProgIDFromCLSID(iid, &tmp); if (SUCCEEDED(hr)) { wcscpy(buf[idx], tmp); CoTaskMemFree(tmp); return buf[idx]; } StringFromGUID2(iid, buf[idx], _countof(buf[idx])); if (GetInterfaceName(buf[idx], buf[idx], _countof(buf[idx]))) { return buf[idx]; } StringFromGUID2(iid, buf[idx], _countof(buf[idx])); return buf[idx]; } CFontExt::CFontExt() { InterlockedIncrement(&g_ModuleRefCnt); } CFontExt::~CFontExt() { InterlockedDecrement(&g_ModuleRefCnt); } // *** IShellFolder2 methods *** STDMETHODIMP CFontExt::GetDefaultSearchGUID(GUID *lpguid) { ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); return E_NOTIMPL; } STDMETHODIMP CFontExt::EnumSearches(IEnumExtraSearch **ppenum) { ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); return E_NOTIMPL; } STDMETHODIMP CFontExt::GetDefaultColumn(DWORD dwReserved, ULONG *pSort, ULONG *pDisplay) { ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); return E_NOTIMPL; } STDMETHODIMP CFontExt::GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *pcsFlags) { if (!pcsFlags || iColumn >= _countof(g_ColumnDefs)) return E_INVALIDARG; *pcsFlags = g_ColumnDefs[iColumn].dwDefaultState; return S_OK; } STDMETHODIMP CFontExt::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv) { ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); return E_NOTIMPL; } STDMETHODIMP CFontExt::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd) { if (iColumn >= _countof(g_ColumnDefs)) return E_FAIL; psd->cxChar = g_ColumnDefs[iColumn].cxChar; psd->fmt = g_ColumnDefs[iColumn].fmt; // No item requested, so return the column name if (pidl == NULL) { return SHSetStrRet(&psd->str, _AtlBaseModule.GetResourceInstance(), g_ColumnDefs[iColumn].iResource); } // Validate that this pidl is the last one PCUIDLIST_RELATIVE curpidl = ILGetNext(pidl); if (curpidl->mkid.cb != 0) { ERR("ERROR, unhandled PIDL!\n"); return E_FAIL; } // Name, ReactOS specific? if (iColumn == 0) return GetDisplayNameOf(pidl, 0, &psd->str); const FontPidlEntry* fontEntry = _FontFromIL(pidl); if (!fontEntry) { ERR("ERROR, not a font PIDL!\n"); return E_FAIL; } // If we got here, we are in details view! // Let's see if we got info about this file that we can re-use if (m_LastDetailsFontName != fontEntry->Name) { CStringW File = g_FontCache->Filename(fontEntry, true); HANDLE hFile = FindFirstFileW(File, &m_LastDetailsFileData); if (hFile == INVALID_HANDLE_VALUE) { m_LastDetailsFontName.Empty(); ERR("Unable to query info about %S\n", File.GetString()); return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); } FindClose(hFile); m_LastDetailsFontName = fontEntry->Name; } // Most code borrowed from CFSFolder::GetDetailsOf FILETIME lft; SYSTEMTIME time; int ret; LARGE_INTEGER FileSize; CStringA AttrLetters; switch (iColumn) { case 1: // Filename return SHSetStrRet(&psd->str, m_LastDetailsFileData.cFileName); case 2: // Size psd->str.uType = STRRET_CSTR; FileSize.HighPart = m_LastDetailsFileData.nFileSizeHigh; FileSize.LowPart = m_LastDetailsFileData.nFileSizeLow; StrFormatKBSizeA(FileSize.QuadPart, psd->str.cStr, MAX_PATH); return S_OK; case 3: // Modified FileTimeToLocalFileTime(&m_LastDetailsFileData.ftLastWriteTime, &lft); FileTimeToSystemTime (&lft, &time); psd->str.uType = STRRET_CSTR; ret = GetDateFormatA(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &time, NULL, psd->str.cStr, MAX_PATH); if (ret < 1) { ERR("GetDateFormatA failed\n"); return E_FAIL; } psd->str.cStr[ret-1] = ' '; GetTimeFormatA(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &time, NULL, &psd->str.cStr[ret], MAX_PATH - ret); return S_OK; case 4: // Attributes AttrLetters.LoadString(IDS_COL_ATTR_LETTERS); if (AttrLetters.GetLength() != 5) { ERR("IDS_COL_ATTR_LETTERS does not contain 5 letters!\n"); return E_FAIL; } psd->str.uType = STRRET_CSTR; ret = 0; if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) psd->str.cStr[ret++] = AttrLetters[0]; if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) psd->str.cStr[ret++] = AttrLetters[1]; if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) psd->str.cStr[ret++] = AttrLetters[2]; if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) psd->str.cStr[ret++] = AttrLetters[3]; if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) psd->str.cStr[ret++] = AttrLetters[4]; psd->str.cStr[ret] = '\0'; return S_OK; default: break; } UNIMPLEMENTED; return E_NOTIMPL; } STDMETHODIMP CFontExt::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid) { //ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); return E_NOTIMPL; } // *** 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; } STDMETHODIMP CFontExt::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList) { return _CEnumFonts_CreateInstance(this, dwFlags, IID_PPV_ARG(IEnumIDList, ppEnumIDList)); } STDMETHODIMP CFontExt::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut) { ERR("%s(riid=%S) UNIMPLEMENTED\n", __FUNCTION__, g2s(riid)); return E_NOTIMPL; } STDMETHODIMP CFontExt::BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut) { ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); return E_NOTIMPL; } STDMETHODIMP CFontExt::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2) { const FontPidlEntry* fontEntry1 = _FontFromIL(pidl1); const FontPidlEntry* fontEntry2 = _FontFromIL(pidl2); if (!fontEntry1 || !fontEntry2) return E_INVALIDARG; int result = (int)fontEntry1->Index - (int)fontEntry2->Index; return MAKE_COMPARE_HRESULT(result); } STDMETHODIMP CFontExt::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID *ppvOut) { HRESULT hr = E_NOINTERFACE; *ppvOut = NULL; if (IsEqualIID(riid, IID_IDropTarget)) { ERR("IDropTarget not implemented\n"); *ppvOut = static_cast(this); AddRef(); hr = S_OK; } else if (IsEqualIID(riid, IID_IContextMenu)) { ERR("IContextMenu not implemented\n"); hr = E_NOTIMPL; } else if (IsEqualIID(riid, IID_IShellView)) { // Just create a default shell folder view, and register ourself as folder SFV_CREATE sfv = { sizeof(SFV_CREATE) }; sfv.pshf = this; hr = SHCreateShellFolderView(&sfv, (IShellView**)ppvOut); } return hr; } STDMETHODIMP CFontExt::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD *rgfInOut) { if (!rgfInOut || !cidl || !apidl) return E_INVALIDARG; DWORD rgf = 0; 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 { rgf = 0; break; } apidl++; cidl--; } *rgfInOut = rgf; return S_OK; } STDMETHODIMP CFontExt::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT * prgfInOut, LPVOID * ppvOut) { if (riid == IID_IContextMenu || riid == IID_IContextMenu2 || riid == IID_IContextMenu3) { return _CFontMenu_CreateInstance(hwndOwner, cidl, apidl, this, riid, ppvOut); } else if (riid == IID_IExtractIconA || riid == IID_IExtractIconW) { if (cidl == 1) { const FontPidlEntry* fontEntry = _FontFromIL(*apidl); if (fontEntry) { DWORD dwAttributes = FILE_ATTRIBUTE_NORMAL; CStringW File = g_FontCache->Filename(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); } } else { ERR("IID_IExtractIcon with cidl != 1 UNIMPLEMENTED\n"); } } else if (riid == IID_IDataObject) { if (cidl >= 1) { return _CDataObject_CreateInstance(m_Folder, cidl, apidl, riid, ppvOut); } else { ERR("IID_IDataObject with cidl == 0 UNIMPLEMENTED\n"); } } //ERR("%s(riid=%S) UNIMPLEMENTED\n", __FUNCTION__, g2s(riid)); return E_NOTIMPL; } STDMETHODIMP CFontExt::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET strRet) { if (!pidl) return E_NOTIMPL; // Validate that this pidl is the last one PCUIDLIST_RELATIVE curpidl = ILGetNext(pidl); if (curpidl->mkid.cb != 0) { ERR("ERROR, unhandled PIDL!\n"); return E_FAIL; } const FontPidlEntry* fontEntry = _FontFromIL(pidl); if (!fontEntry) return E_FAIL; return SHSetStrRet(strRet, fontEntry->Name); } STDMETHODIMP CFontExt::SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl, LPCOLESTR lpName, DWORD dwFlags, PITEMID_CHILD *pPidlOut) { ERR("%s() UNIMPLEMENTED\n", __FUNCTION__); return E_NOTIMPL; } // *** IPersistFolder2 methods *** STDMETHODIMP CFontExt::GetCurFolder(LPITEMIDLIST *ppidl) { if (ppidl && m_Folder) { *ppidl = ILClone(m_Folder); return S_OK; } return E_POINTER; } // *** IPersistFolder methods *** STDMETHODIMP CFontExt::Initialize(LPCITEMIDLIST pidl) { WCHAR PidlPath[MAX_PATH + 1] = {0}, FontsDir[MAX_PATH + 1]; if (!SHGetPathFromIDListW(pidl, PidlPath)) { ERR("Unable to extract path from pidl\n"); return E_FAIL; } HRESULT hr = SHGetFolderPathW(NULL, CSIDL_FONTS, NULL, 0, FontsDir); if (FAILED_UNEXPECTEDLY(hr)) { ERR("Unable to get fonts path (0x%x)\n", hr); return hr; } if (_wcsicmp(PidlPath, FontsDir)) { ERR("CFontExt View initializing on unexpected folder: '%S'\n", PidlPath); return E_FAIL; } m_Folder.Attach(ILClone(pidl)); StringCchCatW(FontsDir, _countof(FontsDir), L"\\"); g_FontCache->SetFontDir(FontsDir); return S_OK; } // *** IPersist methods *** STDMETHODIMP CFontExt::GetClassID(CLSID *lpClassId) { *lpClassId = CLSID_CFontExt; return S_OK; } // *** IDropTarget methods *** STDMETHODIMP CFontExt::DragEnter(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { *pdwEffect = DROPEFFECT_NONE; CComHeapPtr cida; HRESULT hr = _GetCidlFromDataObject(pDataObj, &cida); if (FAILED_UNEXPECTEDLY(hr)) return hr; *pdwEffect = DROPEFFECT_COPY; return S_OK; } STDMETHODIMP CFontExt::DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { return S_OK; } STDMETHODIMP CFontExt::DragLeave() { return S_OK; } STDMETHODIMP CFontExt::Drop(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { *pdwEffect = DROPEFFECT_NONE; CComHeapPtr cida; HRESULT hr = _GetCidlFromDataObject(pDataObj, &cida); if (FAILED_UNEXPECTEDLY(hr)) return hr; PCUIDLIST_ABSOLUTE pidlParent = HIDA_GetPIDLFolder(cida); if (!pidlParent) { ERR("pidlParent is NULL\n"); return E_FAIL; } BOOL bOK = TRUE; CAtlArray FontPaths; for (UINT n = 0; n < cida->cidl; ++n) { PCUIDLIST_RELATIVE pidlRelative = HIDA_GetPIDLItem(cida, n); if (!pidlRelative) continue; PIDLIST_ABSOLUTE pidl = ILCombine(pidlParent, pidlRelative); if (!pidl) { ERR("ILCombine failed\n"); bOK = FALSE; break; } WCHAR szPath[MAX_PATH]; BOOL ret = SHGetPathFromIDListW(pidl, szPath); ILFree(pidl); if (!ret) { ERR("SHGetPathFromIDListW failed\n"); bOK = FALSE; break; } if (PathIsDirectoryW(szPath)) { ERR("PathIsDirectory\n"); bOK = FALSE; break; } LPCWSTR pchDotExt = PathFindExtensionW(szPath); if (!IsFontDotExt(pchDotExt)) { ERR("'%S' is not supported\n", pchDotExt); bOK = FALSE; break; } FontPaths.Add(szPath); } if (!bOK) return E_FAIL; CRegKey keyFonts; if (keyFonts.Open(FONT_HIVE, FONT_KEY, KEY_WRITE) != ERROR_SUCCESS) { ERR("keyFonts.Open failed\n"); return E_FAIL; } for (size_t iItem = 0; iItem < FontPaths.GetCount(); ++iItem) { HRESULT hr = DoInstallFontFile(FontPaths[iItem], g_FontCache->FontPath(), keyFonts.m_hKey); if (FAILED_UNEXPECTEDLY(hr)) { bOK = FALSE; break; } } // TODO: update g_FontCache SendMessageW(HWND_BROADCAST, WM_FONTCHANGE, 0, 0); // TODO: Show message return bOK ? S_OK : E_FAIL; } HRESULT CFontExt::DoInstallFontFile(LPCWSTR pszFontPath, LPCWSTR pszFontsDir, HKEY hkeyFonts) { WCHAR szDestFile[MAX_PATH]; LPCWSTR pszFileTitle = PathFindFileName(pszFontPath); CStringW strFontName; if (!DoGetFontTitle(pszFontPath, strFontName)) return E_FAIL; RemoveFontResourceW(pszFileTitle); StringCchCopyW(szDestFile, sizeof(szDestFile), pszFontsDir); PathAppendW(szDestFile, pszFileTitle); if (!CopyFileW(pszFontPath, szDestFile, FALSE)) { ERR("CopyFileW('%S', '%S') failed\n", pszFontPath, szDestFile); return E_FAIL; } if (!AddFontResourceW(szDestFile)) { ERR("AddFontResourceW('%S') failed\n", pszFileTitle); DeleteFileW(szDestFile); return E_FAIL; } DWORD cbData = (wcslen(pszFileTitle) + 1) * sizeof(WCHAR); LONG nError = RegSetValueExW(hkeyFonts, strFontName, 0, REG_SZ, (const BYTE *)pszFileTitle, cbData); if (nError) { ERR("RegSetValueExW failed with %ld\n", nError); RemoveFontResourceW(pszFileTitle); DeleteFileW(szDestFile); return E_FAIL; } return S_OK; } HRESULT CFontExt::DoGetFontTitle(IN LPCWSTR pszFontPath, OUT CStringW& strFontName) { DWORD cbInfo = 0; BOOL ret = GetFontResourceInfoW(pszFontPath, &cbInfo, NULL, 1); if (!ret || !cbInfo) { ERR("GetFontResourceInfoW failed\n"); return E_FAIL; } LPWSTR pszBuffer = strFontName.GetBuffer(cbInfo / sizeof(WCHAR)); ret = GetFontResourceInfoW(pszFontPath, &cbInfo, pszBuffer, 1); strFontName.ReleaseBuffer(); if (ret) { TRACE("pszFontName: %S\n", (LPCWSTR)strFontName); return S_OK; } ERR("GetFontResourceInfoW failed\n"); return E_FAIL; }