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