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)
10 #include "undocgdi.h" // for GetFontResourceInfoW
12 WINE_DEFAULT_DEBUG_CHANNEL(fontext
);
15 struct FolderViewColumns
23 static FolderViewColumns g_ColumnDefs
[] =
25 { IDS_COL_NAME
, SHCOLSTATE_TYPE_STR
| SHCOLSTATE_ONBYDEFAULT
, 25, LVCFMT_LEFT
},
26 { IDS_COL_FILENAME
, SHCOLSTATE_TYPE_STR
| SHCOLSTATE_ONBYDEFAULT
, 20, LVCFMT_LEFT
},
27 { IDS_COL_SIZE
, SHCOLSTATE_TYPE_STR
| SHCOLSTATE_ONBYDEFAULT
, 10, LVCFMT_RIGHT
},
28 { IDS_COL_MODIFIED
, SHCOLSTATE_TYPE_DATE
| SHCOLSTATE_ONBYDEFAULT
, 15, LVCFMT_LEFT
},
29 { IDS_COL_ATTR
, SHCOLSTATE_TYPE_STR
| SHCOLSTATE_ONBYDEFAULT
, 12, LVCFMT_RIGHT
},
34 // Should fix our headers..
35 EXTERN_C HRESULT WINAPI
SHCreateFileExtractIconW(LPCWSTR pszPath
, DWORD dwFileAttributes
, REFIID riid
, void **ppv
);
38 // Helper functions to translate a guid to a readable name
39 bool GetInterfaceName(const WCHAR
* InterfaceString
, WCHAR
* buf
, size_t size
)
42 DWORD dwType
= 0, dwDataSize
= size
* sizeof(WCHAR
);
44 if (!SUCCEEDED(StringCchPrintfW(LocalBuf
, _countof(LocalBuf
), L
"Interface\\%s", InterfaceString
)))
47 return RegGetValueW(HKEY_CLASSES_ROOT
, LocalBuf
, NULL
, RRF_RT_REG_SZ
, &dwType
, buf
, &dwDataSize
) == ERROR_SUCCESS
;
50 WCHAR
* g2s(REFCLSID iid
)
52 static WCHAR buf
[2][300];
58 HRESULT hr
= ProgIDFromCLSID(iid
, &tmp
);
61 wcscpy(buf
[idx
], tmp
);
65 StringFromGUID2(iid
, buf
[idx
], _countof(buf
[idx
]));
66 if (GetInterfaceName(buf
[idx
], buf
[idx
], _countof(buf
[idx
])))
70 StringFromGUID2(iid
, buf
[idx
], _countof(buf
[idx
]));
78 InterlockedIncrement(&g_ModuleRefCnt
);
83 InterlockedDecrement(&g_ModuleRefCnt
);
86 // *** IShellFolder2 methods ***
87 STDMETHODIMP
CFontExt::GetDefaultSearchGUID(GUID
*lpguid
)
89 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__
);
93 STDMETHODIMP
CFontExt::EnumSearches(IEnumExtraSearch
**ppenum
)
95 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__
);
99 STDMETHODIMP
CFontExt::GetDefaultColumn(DWORD dwReserved
, ULONG
*pSort
, ULONG
*pDisplay
)
101 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__
);
105 STDMETHODIMP
CFontExt::GetDefaultColumnState(UINT iColumn
, SHCOLSTATEF
*pcsFlags
)
107 if (!pcsFlags
|| iColumn
>= _countof(g_ColumnDefs
))
110 *pcsFlags
= g_ColumnDefs
[iColumn
].dwDefaultState
;
114 STDMETHODIMP
CFontExt::GetDetailsEx(PCUITEMID_CHILD pidl
, const SHCOLUMNID
*pscid
, VARIANT
*pv
)
116 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__
);
120 STDMETHODIMP
CFontExt::GetDetailsOf(PCUITEMID_CHILD pidl
, UINT iColumn
, SHELLDETAILS
*psd
)
122 if (iColumn
>= _countof(g_ColumnDefs
))
125 psd
->cxChar
= g_ColumnDefs
[iColumn
].cxChar
;
126 psd
->fmt
= g_ColumnDefs
[iColumn
].fmt
;
128 // No item requested, so return the column name
131 return SHSetStrRet(&psd
->str
, _AtlBaseModule
.GetResourceInstance(), g_ColumnDefs
[iColumn
].iResource
);
134 // Validate that this pidl is the last one
135 PCUIDLIST_RELATIVE curpidl
= ILGetNext(pidl
);
136 if (curpidl
->mkid
.cb
!= 0)
138 ERR("ERROR, unhandled PIDL!\n");
142 // Name, ReactOS specific?
144 return GetDisplayNameOf(pidl
, 0, &psd
->str
);
146 const FontPidlEntry
* fontEntry
= _FontFromIL(pidl
);
149 ERR("ERROR, not a font PIDL!\n");
153 // If we got here, we are in details view!
154 // Let's see if we got info about this file that we can re-use
155 if (m_LastDetailsFontName
!= fontEntry
->Name
)
157 CStringW File
= g_FontCache
->Filename(fontEntry
, true);
158 HANDLE hFile
= FindFirstFileW(File
, &m_LastDetailsFileData
);
159 if (hFile
== INVALID_HANDLE_VALUE
)
161 m_LastDetailsFontName
.Empty();
162 ERR("Unable to query info about %S\n", File
.GetString());
163 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND
);
166 m_LastDetailsFontName
= fontEntry
->Name
;
169 // Most code borrowed from CFSFolder::GetDetailsOf
173 LARGE_INTEGER FileSize
;
174 CStringA AttrLetters
;
178 return SHSetStrRet(&psd
->str
, m_LastDetailsFileData
.cFileName
);
180 psd
->str
.uType
= STRRET_CSTR
;
181 FileSize
.HighPart
= m_LastDetailsFileData
.nFileSizeHigh
;
182 FileSize
.LowPart
= m_LastDetailsFileData
.nFileSizeLow
;
183 StrFormatKBSizeA(FileSize
.QuadPart
, psd
->str
.cStr
, MAX_PATH
);
186 FileTimeToLocalFileTime(&m_LastDetailsFileData
.ftLastWriteTime
, &lft
);
187 FileTimeToSystemTime (&lft
, &time
);
188 psd
->str
.uType
= STRRET_CSTR
;
189 ret
= GetDateFormatA(LOCALE_USER_DEFAULT
, DATE_SHORTDATE
, &time
, NULL
, psd
->str
.cStr
, MAX_PATH
);
192 ERR("GetDateFormatA failed\n");
195 psd
->str
.cStr
[ret
-1] = ' ';
196 GetTimeFormatA(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, &time
, NULL
, &psd
->str
.cStr
[ret
], MAX_PATH
- ret
);
198 case 4: // Attributes
199 AttrLetters
.LoadString(IDS_COL_ATTR_LETTERS
);
200 if (AttrLetters
.GetLength() != 5)
202 ERR("IDS_COL_ATTR_LETTERS does not contain 5 letters!\n");
205 psd
->str
.uType
= STRRET_CSTR
;
207 if (m_LastDetailsFileData
.dwFileAttributes
& FILE_ATTRIBUTE_READONLY
)
208 psd
->str
.cStr
[ret
++] = AttrLetters
[0];
209 if (m_LastDetailsFileData
.dwFileAttributes
& FILE_ATTRIBUTE_HIDDEN
)
210 psd
->str
.cStr
[ret
++] = AttrLetters
[1];
211 if (m_LastDetailsFileData
.dwFileAttributes
& FILE_ATTRIBUTE_SYSTEM
)
212 psd
->str
.cStr
[ret
++] = AttrLetters
[2];
213 if (m_LastDetailsFileData
.dwFileAttributes
& FILE_ATTRIBUTE_ARCHIVE
)
214 psd
->str
.cStr
[ret
++] = AttrLetters
[3];
215 if (m_LastDetailsFileData
.dwFileAttributes
& FILE_ATTRIBUTE_COMPRESSED
)
216 psd
->str
.cStr
[ret
++] = AttrLetters
[4];
217 psd
->str
.cStr
[ret
] = '\0';
227 STDMETHODIMP
CFontExt::MapColumnToSCID(UINT iColumn
, SHCOLUMNID
*pscid
)
229 //ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
233 // *** IShellFolder2 methods ***
234 STDMETHODIMP
CFontExt::ParseDisplayName(HWND hwndOwner
, LPBC pbc
, LPOLESTR lpszDisplayName
, DWORD
*pchEaten
, PIDLIST_RELATIVE
*ppidl
, DWORD
*pdwAttributes
)
236 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__
);
240 STDMETHODIMP
CFontExt::EnumObjects(HWND hwndOwner
, DWORD dwFlags
, LPENUMIDLIST
*ppEnumIDList
)
242 return _CEnumFonts_CreateInstance(this, dwFlags
, IID_PPV_ARG(IEnumIDList
, ppEnumIDList
));
245 STDMETHODIMP
CFontExt::BindToObject(PCUIDLIST_RELATIVE pidl
, LPBC pbcReserved
, REFIID riid
, LPVOID
*ppvOut
)
247 ERR("%s(riid=%S) UNIMPLEMENTED\n", __FUNCTION__
, g2s(riid
));
251 STDMETHODIMP
CFontExt::BindToStorage(PCUIDLIST_RELATIVE pidl
, LPBC pbcReserved
, REFIID riid
, LPVOID
*ppvOut
)
253 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__
);
257 STDMETHODIMP
CFontExt::CompareIDs(LPARAM lParam
, PCUIDLIST_RELATIVE pidl1
, PCUIDLIST_RELATIVE pidl2
)
259 const FontPidlEntry
* fontEntry1
= _FontFromIL(pidl1
);
260 const FontPidlEntry
* fontEntry2
= _FontFromIL(pidl2
);
262 if (!fontEntry1
|| !fontEntry2
)
265 int result
= (int)fontEntry1
->Index
- (int)fontEntry2
->Index
;
267 return MAKE_COMPARE_HRESULT(result
);
270 STDMETHODIMP
CFontExt::CreateViewObject(HWND hwndOwner
, REFIID riid
, LPVOID
*ppvOut
)
272 HRESULT hr
= E_NOINTERFACE
;
276 if (IsEqualIID(riid
, IID_IDropTarget
))
278 ERR("IDropTarget not implemented\n");
279 *ppvOut
= static_cast<IDropTarget
*>(this);
283 else if (IsEqualIID(riid
, IID_IContextMenu
))
285 ERR("IContextMenu not implemented\n");
288 else if (IsEqualIID(riid
, IID_IShellView
))
290 // Just create a default shell folder view, and register ourself as folder
291 SFV_CREATE sfv
= { sizeof(SFV_CREATE
) };
293 hr
= SHCreateShellFolderView(&sfv
, (IShellView
**)ppvOut
);
299 STDMETHODIMP
CFontExt::GetAttributesOf(UINT cidl
, PCUITEMID_CHILD_ARRAY apidl
, DWORD
*rgfInOut
)
301 if (!rgfInOut
|| !cidl
|| !apidl
)
305 while (cidl
> 0 && *apidl
)
307 const FontPidlEntry
* fontEntry
= _FontFromIL(*apidl
);
310 // We don't support delete yet
311 rgf
|= (/*SFGAO_CANDELETE |*/ SFGAO_HASPROPSHEET
| SFGAO_CANCOPY
| SFGAO_FILESYSTEM
);
328 STDMETHODIMP
CFontExt::GetUIObjectOf(HWND hwndOwner
, UINT cidl
, PCUITEMID_CHILD_ARRAY apidl
, REFIID riid
, UINT
* prgfInOut
, LPVOID
* ppvOut
)
330 if (riid
== IID_IContextMenu
||
331 riid
== IID_IContextMenu2
||
332 riid
== IID_IContextMenu3
)
334 return _CFontMenu_CreateInstance(hwndOwner
, cidl
, apidl
, this, riid
, ppvOut
);
336 else if (riid
== IID_IExtractIconA
|| riid
== IID_IExtractIconW
)
340 const FontPidlEntry
* fontEntry
= _FontFromIL(*apidl
);
343 DWORD dwAttributes
= FILE_ATTRIBUTE_NORMAL
;
344 CStringW File
= g_FontCache
->Filename(fontEntry
);
345 // Just create a default icon extractor based on the filename
346 // We might want to create a preview with the font to get really fancy one day.
347 return SHCreateFileExtractIconW(File
, dwAttributes
, riid
, ppvOut
);
352 ERR("IID_IExtractIcon with cidl != 1 UNIMPLEMENTED\n");
355 else if (riid
== IID_IDataObject
)
359 return _CDataObject_CreateInstance(m_Folder
, cidl
, apidl
, riid
, ppvOut
);
363 ERR("IID_IDataObject with cidl == 0 UNIMPLEMENTED\n");
367 //ERR("%s(riid=%S) UNIMPLEMENTED\n", __FUNCTION__, g2s(riid));
371 STDMETHODIMP
CFontExt::GetDisplayNameOf(PCUITEMID_CHILD pidl
, DWORD dwFlags
, LPSTRRET strRet
)
376 // Validate that this pidl is the last one
377 PCUIDLIST_RELATIVE curpidl
= ILGetNext(pidl
);
378 if (curpidl
->mkid
.cb
!= 0)
380 ERR("ERROR, unhandled PIDL!\n");
384 const FontPidlEntry
* fontEntry
= _FontFromIL(pidl
);
388 return SHSetStrRet(strRet
, fontEntry
->Name
);
391 STDMETHODIMP
CFontExt::SetNameOf(HWND hwndOwner
, PCUITEMID_CHILD pidl
, LPCOLESTR lpName
, DWORD dwFlags
, PITEMID_CHILD
*pPidlOut
)
393 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__
);
397 // *** IPersistFolder2 methods ***
398 STDMETHODIMP
CFontExt::GetCurFolder(LPITEMIDLIST
*ppidl
)
400 if (ppidl
&& m_Folder
)
402 *ppidl
= ILClone(m_Folder
);
410 // *** IPersistFolder methods ***
411 STDMETHODIMP
CFontExt::Initialize(LPCITEMIDLIST pidl
)
413 WCHAR PidlPath
[MAX_PATH
+ 1] = {0}, FontsDir
[MAX_PATH
+ 1];
414 if (!SHGetPathFromIDListW(pidl
, PidlPath
))
416 ERR("Unable to extract path from pidl\n");
420 HRESULT hr
= SHGetFolderPathW(NULL
, CSIDL_FONTS
, NULL
, 0, FontsDir
);
421 if (FAILED_UNEXPECTEDLY(hr
))
423 ERR("Unable to get fonts path (0x%x)\n", hr
);
427 if (_wcsicmp(PidlPath
, FontsDir
))
429 ERR("CFontExt View initializing on unexpected folder: '%S'\n", PidlPath
);
433 m_Folder
.Attach(ILClone(pidl
));
434 StringCchCatW(FontsDir
, _countof(FontsDir
), L
"\\");
435 g_FontCache
->SetFontDir(FontsDir
);
441 // *** IPersist methods ***
442 STDMETHODIMP
CFontExt::GetClassID(CLSID
*lpClassId
)
444 *lpClassId
= CLSID_CFontExt
;
448 // *** IDropTarget methods ***
449 STDMETHODIMP
CFontExt::DragEnter(IDataObject
* pDataObj
, DWORD grfKeyState
, POINTL pt
, DWORD
* pdwEffect
)
451 *pdwEffect
= DROPEFFECT_NONE
;
453 CComHeapPtr
<CIDA
> cida
;
454 HRESULT hr
= _GetCidlFromDataObject(pDataObj
, &cida
);
455 if (FAILED_UNEXPECTEDLY(hr
))
458 *pdwEffect
= DROPEFFECT_COPY
;
462 STDMETHODIMP
CFontExt::DragOver(DWORD grfKeyState
, POINTL pt
, DWORD
* pdwEffect
)
467 STDMETHODIMP
CFontExt::DragLeave()
472 STDMETHODIMP
CFontExt::Drop(IDataObject
* pDataObj
, DWORD grfKeyState
, POINTL pt
, DWORD
* pdwEffect
)
474 *pdwEffect
= DROPEFFECT_NONE
;
476 CComHeapPtr
<CIDA
> cida
;
477 HRESULT hr
= _GetCidlFromDataObject(pDataObj
, &cida
);
478 if (FAILED_UNEXPECTEDLY(hr
))
481 PCUIDLIST_ABSOLUTE pidlParent
= HIDA_GetPIDLFolder(cida
);
484 ERR("pidlParent is NULL\n");
489 CAtlArray
<CStringW
> FontPaths
;
490 for (UINT n
= 0; n
< cida
->cidl
; ++n
)
492 PCUIDLIST_RELATIVE pidlRelative
= HIDA_GetPIDLItem(cida
, n
);
496 PIDLIST_ABSOLUTE pidl
= ILCombine(pidlParent
, pidlRelative
);
499 ERR("ILCombine failed\n");
504 WCHAR szPath
[MAX_PATH
];
505 BOOL ret
= SHGetPathFromIDListW(pidl
, szPath
);
510 ERR("SHGetPathFromIDListW failed\n");
515 if (PathIsDirectoryW(szPath
))
517 ERR("PathIsDirectory\n");
522 LPCWSTR pchDotExt
= PathFindExtensionW(szPath
);
523 if (!IsFontDotExt(pchDotExt
))
525 ERR("'%S' is not supported\n", pchDotExt
);
530 FontPaths
.Add(szPath
);
537 if (keyFonts
.Open(FONT_HIVE
, FONT_KEY
, KEY_WRITE
) != ERROR_SUCCESS
)
539 ERR("keyFonts.Open failed\n");
543 for (size_t iItem
= 0; iItem
< FontPaths
.GetCount(); ++iItem
)
545 HRESULT hr
= DoInstallFontFile(FontPaths
[iItem
], g_FontCache
->FontPath(), keyFonts
.m_hKey
);
546 if (FAILED_UNEXPECTEDLY(hr
))
553 // TODO: update g_FontCache
555 SendMessageW(HWND_BROADCAST
, WM_FONTCHANGE
, 0, 0);
557 // TODO: Show message
559 return bOK
? S_OK
: E_FAIL
;
562 HRESULT
CFontExt::DoInstallFontFile(LPCWSTR pszFontPath
, LPCWSTR pszFontsDir
, HKEY hkeyFonts
)
564 WCHAR szDestFile
[MAX_PATH
];
565 LPCWSTR pszFileTitle
= PathFindFileName(pszFontPath
);
567 CStringW strFontName
;
568 if (!DoGetFontTitle(pszFontPath
, strFontName
))
571 RemoveFontResourceW(pszFileTitle
);
573 StringCchCopyW(szDestFile
, sizeof(szDestFile
), pszFontsDir
);
574 PathAppendW(szDestFile
, pszFileTitle
);
575 if (!CopyFileW(pszFontPath
, szDestFile
, FALSE
))
577 ERR("CopyFileW('%S', '%S') failed\n", pszFontPath
, szDestFile
);
581 if (!AddFontResourceW(szDestFile
))
583 ERR("AddFontResourceW('%S') failed\n", pszFileTitle
);
584 DeleteFileW(szDestFile
);
588 DWORD cbData
= (wcslen(pszFileTitle
) + 1) * sizeof(WCHAR
);
589 LONG nError
= RegSetValueExW(hkeyFonts
, strFontName
, 0, REG_SZ
,
590 (const BYTE
*)pszFileTitle
, cbData
);
593 ERR("RegSetValueExW failed with %ld\n", nError
);
594 RemoveFontResourceW(pszFileTitle
);
595 DeleteFileW(szDestFile
);
603 CFontExt::DoGetFontTitle(IN LPCWSTR pszFontPath
, OUT CStringW
& strFontName
)
606 BOOL ret
= GetFontResourceInfoW(pszFontPath
, &cbInfo
, NULL
, 1);
609 ERR("GetFontResourceInfoW failed\n");
613 LPWSTR pszBuffer
= strFontName
.GetBuffer(cbInfo
/ sizeof(WCHAR
));
614 ret
= GetFontResourceInfoW(pszFontPath
, &cbInfo
, pszBuffer
, 1);
615 strFontName
.ReleaseBuffer();
618 TRACE("pszFontName: %S\n", (LPCWSTR
)strFontName
);
622 ERR("GetFontResourceInfoW failed\n");