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