xref: /reactos/dll/shellext/fontext/CFontExt.cpp (revision 1a83762c)
1 /*
2  * PROJECT:     ReactOS Font Shell Extension
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     CFontExt implementation
5  * COPYRIGHT:   Copyright 2019,2020 Mark Jansen (mark.jansen@reactos.org)
6  *              Copyright 2019 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
7  */
8 
9 #include "precomp.h"
10 
11 WINE_DEFAULT_DEBUG_CHANNEL(fontext);
12 
13 
14 struct FolderViewColumns
15 {
16     int iResource;
17     DWORD dwDefaultState;
18     int cxChar;
19     int fmt;
20 };
21 
22 static FolderViewColumns g_ColumnDefs[] =
23 {
24     { IDS_COL_NAME,      SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT,   25, LVCFMT_LEFT },
25     { IDS_COL_FILENAME,  SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT,   20, LVCFMT_LEFT },
26     { IDS_COL_SIZE,      SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT,   10, LVCFMT_RIGHT },
27     { IDS_COL_MODIFIED,  SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT,  15, LVCFMT_LEFT },
28     { IDS_COL_ATTR,      SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT,   12, LVCFMT_RIGHT },
29 };
30 
31 
32 
33 // Should fix our headers..
34 EXTERN_C HRESULT WINAPI SHCreateFileExtractIconW(LPCWSTR pszPath, DWORD dwFileAttributes, REFIID riid, void **ppv);
35 
36 
37 // Helper functions to translate a guid to a readable name
38 bool GetInterfaceName(const WCHAR* InterfaceString, WCHAR* buf, size_t size)
39 {
40     WCHAR LocalBuf[100];
41     DWORD dwType = 0, dwDataSize = size * sizeof(WCHAR);
42 
43     if (!SUCCEEDED(StringCchPrintfW(LocalBuf, _countof(LocalBuf), L"Interface\\%s", InterfaceString)))
44         return false;
45 
46     return RegGetValueW(HKEY_CLASSES_ROOT, LocalBuf, NULL, RRF_RT_REG_SZ, &dwType, buf, &dwDataSize) == ERROR_SUCCESS;
47 }
48 
49 WCHAR* g2s(REFCLSID iid)
50 {
51     static WCHAR buf[2][300];
52     static int idx = 0;
53 
54     idx ^= 1;
55 
56     LPOLESTR tmp;
57     HRESULT hr = ProgIDFromCLSID(iid, &tmp);
58     if (SUCCEEDED(hr))
59     {
60         wcscpy(buf[idx], tmp);
61         CoTaskMemFree(tmp);
62         return buf[idx];
63     }
64     StringFromGUID2(iid, buf[idx], _countof(buf[idx]));
65     if (GetInterfaceName(buf[idx], buf[idx], _countof(buf[idx])))
66     {
67         return buf[idx];
68     }
69     StringFromGUID2(iid, buf[idx], _countof(buf[idx]));
70 
71     return buf[idx];
72 }
73 
74 
75 CFontExt::CFontExt()
76 {
77     InterlockedIncrement(&g_ModuleRefCnt);
78 }
79 
80 CFontExt::~CFontExt()
81 {
82     InterlockedDecrement(&g_ModuleRefCnt);
83 }
84 
85 // *** IShellFolder2 methods ***
86 STDMETHODIMP CFontExt::GetDefaultSearchGUID(GUID *lpguid)
87 {
88     ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
89     return E_NOTIMPL;
90 }
91 
92 STDMETHODIMP CFontExt::EnumSearches(IEnumExtraSearch **ppenum)
93 {
94     ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
95     return E_NOTIMPL;
96 }
97 
98 STDMETHODIMP CFontExt::GetDefaultColumn(DWORD dwReserved, ULONG *pSort, ULONG *pDisplay)
99 {
100     ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
101     return E_NOTIMPL;
102 }
103 
104 STDMETHODIMP CFontExt::GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *pcsFlags)
105 {
106     if (!pcsFlags || iColumn >= _countof(g_ColumnDefs))
107         return E_INVALIDARG;
108 
109     *pcsFlags = g_ColumnDefs[iColumn].dwDefaultState;
110     return S_OK;
111 }
112 
113 STDMETHODIMP CFontExt::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv)
114 {
115     ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
116     return E_NOTIMPL;
117 }
118 
119 STDMETHODIMP CFontExt::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd)
120 {
121     if (iColumn >= _countof(g_ColumnDefs))
122         return E_FAIL;
123 
124     psd->cxChar = g_ColumnDefs[iColumn].cxChar;
125     psd->fmt = g_ColumnDefs[iColumn].fmt;
126 
127     // No item requested, so return the column name
128     if (pidl == NULL)
129     {
130         return SHSetStrRet(&psd->str, _AtlBaseModule.GetResourceInstance(), g_ColumnDefs[iColumn].iResource);
131     }
132 
133     // Validate that this pidl is the last one
134     PCUIDLIST_RELATIVE curpidl = ILGetNext(pidl);
135     if (curpidl->mkid.cb != 0)
136     {
137         ERR("ERROR, unhandled PIDL!\n");
138         return E_FAIL;
139     }
140 
141     // Name, ReactOS specific?
142     if (iColumn == 0)
143         return GetDisplayNameOf(pidl, 0, &psd->str);
144 
145     const FontPidlEntry* fontEntry = _FontFromIL(pidl);
146     if (!fontEntry)
147     {
148         ERR("ERROR, not a font PIDL!\n");
149         return E_FAIL;
150     }
151 
152     // If we got here, we are in details view!
153     // Let's see if we got info about this file that we can re-use
154     if (m_LastDetailsFontName != fontEntry->Name)
155     {
156         CStringW File = g_FontCache->Filename(fontEntry, true);
157         HANDLE hFile = FindFirstFileW(File, &m_LastDetailsFileData);
158         if (hFile == INVALID_HANDLE_VALUE)
159         {
160             m_LastDetailsFontName.Empty();
161             ERR("Unable to query info about %S\n", File.GetString());
162             return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
163         }
164         FindClose(hFile);
165         m_LastDetailsFontName = fontEntry->Name;
166     }
167 
168     // Most code borrowed from CFSFolder::GetDetailsOf
169     FILETIME lft;
170     SYSTEMTIME time;
171     int ret;
172     LARGE_INTEGER FileSize;
173     CStringA AttrLetters;
174     switch (iColumn)
175     {
176     case 1: // Filename
177         return SHSetStrRet(&psd->str, m_LastDetailsFileData.cFileName);
178     case 2: // Size
179         psd->str.uType = STRRET_CSTR;
180         FileSize.HighPart = m_LastDetailsFileData.nFileSizeHigh;
181         FileSize.LowPart = m_LastDetailsFileData.nFileSizeLow;
182         StrFormatKBSizeA(FileSize.QuadPart, psd->str.cStr, MAX_PATH);
183         return S_OK;
184     case 3: // Modified
185         FileTimeToLocalFileTime(&m_LastDetailsFileData.ftLastWriteTime, &lft);
186         FileTimeToSystemTime (&lft, &time);
187         psd->str.uType = STRRET_CSTR;
188         ret = GetDateFormatA(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &time, NULL, psd->str.cStr, MAX_PATH);
189         if (ret < 1)
190         {
191             ERR("GetDateFormatA failed\n");
192             return E_FAIL;
193         }
194         psd->str.cStr[ret-1] = ' ';
195         GetTimeFormatA(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &time, NULL, &psd->str.cStr[ret], MAX_PATH - ret);
196         return S_OK;
197     case 4: // Attributes
198         AttrLetters.LoadString(IDS_COL_ATTR_LETTERS);
199         if (AttrLetters.GetLength() != 5)
200         {
201             ERR("IDS_COL_ATTR_LETTERS does not contain 5 letters!\n");
202             return E_FAIL;
203         }
204         psd->str.uType = STRRET_CSTR;
205         ret = 0;
206         if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
207             psd->str.cStr[ret++] = AttrLetters[0];
208         if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
209             psd->str.cStr[ret++] = AttrLetters[1];
210         if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
211             psd->str.cStr[ret++] = AttrLetters[2];
212         if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
213             psd->str.cStr[ret++] = AttrLetters[3];
214         if (m_LastDetailsFileData.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED)
215             psd->str.cStr[ret++] = AttrLetters[4];
216         psd->str.cStr[ret] = '\0';
217         return S_OK;
218     default:
219         break;
220     }
221 
222     UNIMPLEMENTED;
223     return E_NOTIMPL;
224 }
225 
226 STDMETHODIMP CFontExt::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid)
227 {
228     //ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
229     return E_NOTIMPL;
230 }
231 
232 // *** IShellFolder2 methods ***
233 STDMETHODIMP CFontExt::ParseDisplayName(HWND hwndOwner, LPBC pbc, LPOLESTR lpszDisplayName, DWORD *pchEaten, PIDLIST_RELATIVE *ppidl, DWORD *pdwAttributes)
234 {
235     ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
236     return E_NOTIMPL;
237 }
238 
239 STDMETHODIMP CFontExt::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList)
240 {
241     return _CEnumFonts_CreateInstance(this, dwFlags, IID_PPV_ARG(IEnumIDList, ppEnumIDList));
242 }
243 
244 STDMETHODIMP CFontExt::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
245 {
246     ERR("%s(riid=%S) UNIMPLEMENTED\n", __FUNCTION__, g2s(riid));
247     return E_NOTIMPL;
248 }
249 
250 STDMETHODIMP CFontExt::BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
251 {
252     ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
253     return E_NOTIMPL;
254 }
255 
256 STDMETHODIMP CFontExt::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
257 {
258     const FontPidlEntry* fontEntry1 = _FontFromIL(pidl1);
259     const FontPidlEntry* fontEntry2 = _FontFromIL(pidl2);
260 
261     if (!fontEntry1 || !fontEntry2)
262         return E_INVALIDARG;
263 
264     int result = (int)fontEntry1->Index - (int)fontEntry2->Index;
265 
266     return MAKE_COMPARE_HRESULT(result);
267 }
268 
269 STDMETHODIMP CFontExt::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID *ppvOut)
270 {
271     HRESULT hr = E_NOINTERFACE;
272 
273     *ppvOut = NULL;
274 
275     if (IsEqualIID(riid, IID_IDropTarget))
276     {
277         ERR("IDropTarget not implemented\n");
278         *ppvOut = static_cast<IDropTarget *>(this);
279         AddRef();
280         hr = S_OK;
281     }
282     else if (IsEqualIID(riid, IID_IContextMenu))
283     {
284         ERR("IContextMenu not implemented\n");
285         hr = E_NOTIMPL;
286     }
287     else if (IsEqualIID(riid, IID_IShellView))
288     {
289         // Just create a default shell folder view, and register ourself as folder
290         SFV_CREATE sfv = { sizeof(SFV_CREATE) };
291         sfv.pshf = this;
292         hr = SHCreateShellFolderView(&sfv, (IShellView**)ppvOut);
293     }
294 
295     return hr;
296 }
297 
298 STDMETHODIMP CFontExt::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD *rgfInOut)
299 {
300     if (!rgfInOut || !cidl || !apidl)
301         return E_INVALIDARG;
302 
303     DWORD rgf = 0;
304     while (cidl > 0 && *apidl)
305     {
306         const FontPidlEntry* fontEntry = _FontFromIL(*apidl);
307         if (fontEntry)
308         {
309             // We don't support delete yet
310             rgf |= (/*SFGAO_CANDELETE |*/ SFGAO_HASPROPSHEET | SFGAO_CANCOPY | SFGAO_FILESYSTEM);
311         }
312         else
313         {
314             rgf = 0;
315             break;
316         }
317 
318         apidl++;
319         cidl--;
320     }
321 
322     *rgfInOut = rgf;
323     return S_OK;
324 }
325 
326 
327 STDMETHODIMP CFontExt::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT * prgfInOut, LPVOID * ppvOut)
328 {
329     if (riid == IID_IContextMenu ||
330         riid == IID_IContextMenu2 ||
331         riid == IID_IContextMenu3)
332     {
333         return _CFontMenu_CreateInstance(hwndOwner, cidl, apidl, this, riid, ppvOut);
334     }
335     else if (riid == IID_IExtractIconA || riid == IID_IExtractIconW)
336     {
337         if (cidl == 1)
338         {
339             const FontPidlEntry* fontEntry = _FontFromIL(*apidl);
340             if (fontEntry)
341             {
342                 DWORD dwAttributes = FILE_ATTRIBUTE_NORMAL;
343                 CStringW File = g_FontCache->Filename(fontEntry);
344                 // Just create a default icon extractor based on the filename
345                 // We might want to create a preview with the font to get really fancy one day.
346                 return SHCreateFileExtractIconW(File, dwAttributes, riid, ppvOut);
347             }
348         }
349         else
350         {
351             ERR("IID_IExtractIcon with cidl != 1 UNIMPLEMENTED\n");
352         }
353     }
354     else if (riid == IID_IDataObject)
355     {
356         if (cidl >= 1)
357         {
358             return _CDataObject_CreateInstance(m_Folder, cidl, apidl, riid, ppvOut);
359         }
360         else
361         {
362             ERR("IID_IDataObject with cidl == 0 UNIMPLEMENTED\n");
363         }
364     }
365 
366     //ERR("%s(riid=%S) UNIMPLEMENTED\n", __FUNCTION__, g2s(riid));
367     return E_NOTIMPL;
368 }
369 
370 STDMETHODIMP CFontExt::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET strRet)
371 {
372     if (!pidl)
373         return E_NOTIMPL;
374 
375     // Validate that this pidl is the last one
376     PCUIDLIST_RELATIVE curpidl = ILGetNext(pidl);
377     if (curpidl->mkid.cb != 0)
378     {
379         ERR("ERROR, unhandled PIDL!\n");
380         return E_FAIL;
381     }
382 
383     const FontPidlEntry* fontEntry = _FontFromIL(pidl);
384     if (!fontEntry)
385         return E_FAIL;
386 
387     return SHSetStrRet(strRet, fontEntry->Name);
388 }
389 
390 STDMETHODIMP CFontExt::SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl, LPCOLESTR lpName, DWORD dwFlags, PITEMID_CHILD *pPidlOut)
391 {
392     ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
393     return E_NOTIMPL;
394 }
395 
396 // *** IPersistFolder2 methods ***
397 STDMETHODIMP CFontExt::GetCurFolder(LPITEMIDLIST *ppidl)
398 {
399     if (ppidl && m_Folder)
400     {
401         *ppidl = ILClone(m_Folder);
402         return S_OK;
403     }
404 
405     return E_POINTER;
406 }
407 
408 
409 // *** IPersistFolder methods ***
410 STDMETHODIMP CFontExt::Initialize(LPCITEMIDLIST pidl)
411 {
412     WCHAR PidlPath[MAX_PATH + 1] = {0}, FontsDir[MAX_PATH + 1];
413     if (!SHGetPathFromIDListW(pidl, PidlPath))
414     {
415         ERR("Unable to extract path from pidl\n");
416         return E_FAIL;
417     }
418 
419     HRESULT hr = SHGetFolderPathW(NULL, CSIDL_FONTS, NULL, 0, FontsDir);
420     if (FAILED_UNEXPECTEDLY(hr))
421     {
422         ERR("Unable to get fonts path (0x%x)\n", hr);
423         return hr;
424     }
425 
426     if (_wcsicmp(PidlPath, FontsDir))
427     {
428         ERR("CFontExt View initializing on unexpected folder: '%S'\n", PidlPath);
429         return E_FAIL;
430     }
431 
432     m_Folder.Attach(ILClone(pidl));
433     StringCchCatW(FontsDir, _countof(FontsDir), L"\\");
434     g_FontCache->SetFontDir(FontsDir);
435 
436     return S_OK;
437 }
438 
439 
440 // *** IPersist methods ***
441 STDMETHODIMP CFontExt::GetClassID(CLSID *lpClassId)
442 {
443     *lpClassId = CLSID_CFontExt;
444     return S_OK;
445 }
446 
447 // *** IDropTarget methods ***
448 STDMETHODIMP CFontExt::DragEnter(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
449 {
450     *pdwEffect = DROPEFFECT_NONE;
451 
452     CComHeapPtr<CIDA> cida;
453     HRESULT hr = _GetCidlFromDataObject(pDataObj, &cida);
454     if (FAILED_UNEXPECTEDLY(hr))
455         return hr;
456 
457 #if 1   // Please implement DoGetFontTitle
458     return DRAGDROP_S_CANCEL;
459 #else
460     *pdwEffect = DROPEFFECT_COPY;
461     return S_OK;
462 #endif
463 }
464 
465 STDMETHODIMP CFontExt::DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
466 {
467     return S_OK;
468 }
469 
470 STDMETHODIMP CFontExt::DragLeave()
471 {
472     return S_OK;
473 }
474 
475 STDMETHODIMP CFontExt::Drop(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
476 {
477     *pdwEffect = DROPEFFECT_NONE;
478 
479     CComHeapPtr<CIDA> cida;
480     HRESULT hr = _GetCidlFromDataObject(pDataObj, &cida);
481     if (FAILED_UNEXPECTEDLY(hr))
482         return hr;
483 
484     PCUIDLIST_ABSOLUTE pidlParent = HIDA_GetPIDLFolder(cida);
485     if (!pidlParent)
486     {
487         ERR("pidlParent is NULL\n");
488         return E_FAIL;
489     }
490 
491     BOOL bOK = TRUE;
492     CAtlArray<CStringW> FontPaths;
493     for (UINT n = 0; n < cida->cidl; ++n)
494     {
495         PCUIDLIST_RELATIVE pidlRelative = HIDA_GetPIDLItem(cida, n);
496         if (!pidlRelative)
497             continue;
498 
499         PIDLIST_ABSOLUTE pidl = ILCombine(pidlParent, pidlRelative);
500         if (!pidl)
501         {
502             ERR("ILCombine failed\n");
503             bOK = FALSE;
504             break;
505         }
506 
507         WCHAR szPath[MAX_PATH];
508         BOOL ret = SHGetPathFromIDListW(pidl, szPath);
509         ILFree(pidl);
510 
511         if (!ret)
512         {
513             ERR("SHGetPathFromIDListW failed\n");
514             bOK = FALSE;
515             break;
516         }
517 
518         if (PathIsDirectoryW(szPath))
519         {
520             ERR("PathIsDirectory\n");
521             bOK = FALSE;
522             break;
523         }
524 
525         LPCWSTR pchDotExt = PathFindExtensionW(szPath);
526         if (!IsFontDotExt(pchDotExt))
527         {
528             ERR("'%S' is not supported\n", pchDotExt);
529             bOK = FALSE;
530             break;
531         }
532 
533         FontPaths.Add(szPath);
534     }
535 
536     if (!bOK)
537         return E_FAIL;
538 
539     CRegKey keyFonts;
540     if (keyFonts.Open(FONT_HIVE, FONT_KEY, KEY_WRITE) != ERROR_SUCCESS)
541     {
542         ERR("keyFonts.Open failed\n");
543         return E_FAIL;
544     }
545 
546     for (size_t iItem = 0; iItem < FontPaths.GetCount(); ++iItem)
547     {
548         HRESULT hr = DoInstallFontFile(FontPaths[iItem], g_FontCache->FontPath(), keyFonts.m_hKey);
549         if (FAILED_UNEXPECTEDLY(hr))
550         {
551             bOK = FALSE;
552             break;
553         }
554     }
555 
556     // TODO: update g_FontCache
557 
558     SendMessageW(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
559 
560     // TODO: Show message
561 
562     return bOK ? S_OK : E_FAIL;
563 }
564 
565 HRESULT CFontExt::DoInstallFontFile(LPCWSTR pszFontPath, LPCWSTR pszFontsDir, HKEY hkeyFonts)
566 {
567     WCHAR szDestFile[MAX_PATH];
568     LPCWSTR pszFileTitle = PathFindFileName(pszFontPath);
569 
570     WCHAR szFontName[512];
571     if (!DoGetFontTitle(pszFontPath, szFontName))
572         return E_FAIL;
573 
574     RemoveFontResourceW(pszFileTitle);
575 
576     StringCchCopyW(szDestFile, sizeof(szDestFile), pszFontsDir);
577     PathAppendW(szDestFile, pszFileTitle);
578     if (!CopyFileW(pszFontPath, szDestFile, FALSE))
579     {
580         ERR("CopyFileW('%S', '%S') failed\n", pszFontPath, szDestFile);
581         return E_FAIL;
582     }
583 
584     if (!AddFontResourceW(pszFileTitle))
585     {
586         ERR("AddFontResourceW('%S') failed\n", pszFileTitle);
587         DeleteFileW(szDestFile);
588         return E_FAIL;
589     }
590 
591     DWORD cbData = (wcslen(pszFileTitle) + 1) * sizeof(WCHAR);
592     LONG nError = RegSetValueExW(hkeyFonts, szFontName, 0, REG_SZ, (const BYTE *)szFontName, cbData);
593     if (nError)
594     {
595         ERR("RegSetValueExW failed with %ld\n", nError);
596         RemoveFontResourceW(pszFileTitle);
597         DeleteFileW(szDestFile);
598         return E_FAIL;
599     }
600 
601     return S_OK;
602 }
603 
604 HRESULT CFontExt::DoGetFontTitle(LPCWSTR pszFontPath, LPCWSTR pszFontName)
605 {
606     // TODO:
607     return E_FAIL;
608 }
609