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)
11 WINE_DEFAULT_DEBUG_CHANNEL(fontext
);
14 struct FolderViewColumns
22 static FolderViewColumns g_ColumnDefs
[] =
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
},
33 // Should fix our headers..
34 EXTERN_C HRESULT WINAPI
SHCreateFileExtractIconW(LPCWSTR pszPath
, DWORD dwFileAttributes
, REFIID riid
, void **ppv
);
37 // Helper functions to translate a guid to a readable name
38 bool GetInterfaceName(const WCHAR
* InterfaceString
, WCHAR
* buf
, size_t size
)
41 DWORD dwType
= 0, dwDataSize
= size
* sizeof(WCHAR
);
43 if (!SUCCEEDED(StringCchPrintfW(LocalBuf
, _countof(LocalBuf
), L
"Interface\\%s", InterfaceString
)))
46 return RegGetValueW(HKEY_CLASSES_ROOT
, LocalBuf
, NULL
, RRF_RT_REG_SZ
, &dwType
, buf
, &dwDataSize
) == ERROR_SUCCESS
;
49 WCHAR
* g2s(REFCLSID iid
)
51 static WCHAR buf
[2][300];
57 HRESULT hr
= ProgIDFromCLSID(iid
, &tmp
);
60 wcscpy(buf
[idx
], tmp
);
64 StringFromGUID2(iid
, buf
[idx
], _countof(buf
[idx
]));
65 if (GetInterfaceName(buf
[idx
], buf
[idx
], _countof(buf
[idx
])))
69 StringFromGUID2(iid
, buf
[idx
], _countof(buf
[idx
]));
77 InterlockedIncrement(&g_ModuleRefCnt
);
82 InterlockedDecrement(&g_ModuleRefCnt
);
85 // *** IShellFolder2 methods ***
86 STDMETHODIMP
CFontExt::GetDefaultSearchGUID(GUID
*lpguid
)
88 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__
);
92 STDMETHODIMP
CFontExt::EnumSearches(IEnumExtraSearch
**ppenum
)
94 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__
);
98 STDMETHODIMP
CFontExt::GetDefaultColumn(DWORD dwReserved
, ULONG
*pSort
, ULONG
*pDisplay
)
100 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__
);
104 STDMETHODIMP
CFontExt::GetDefaultColumnState(UINT iColumn
, SHCOLSTATEF
*pcsFlags
)
106 if (!pcsFlags
|| iColumn
>= _countof(g_ColumnDefs
))
109 *pcsFlags
= g_ColumnDefs
[iColumn
].dwDefaultState
;
113 STDMETHODIMP
CFontExt::GetDetailsEx(PCUITEMID_CHILD pidl
, const SHCOLUMNID
*pscid
, VARIANT
*pv
)
115 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__
);
119 STDMETHODIMP
CFontExt::GetDetailsOf(PCUITEMID_CHILD pidl
, UINT iColumn
, SHELLDETAILS
*psd
)
121 if (iColumn
>= _countof(g_ColumnDefs
))
124 psd
->cxChar
= g_ColumnDefs
[iColumn
].cxChar
;
125 psd
->fmt
= g_ColumnDefs
[iColumn
].fmt
;
127 // No item requested, so return the column name
130 return SHSetStrRet(&psd
->str
, _AtlBaseModule
.GetResourceInstance(), g_ColumnDefs
[iColumn
].iResource
);
133 // Validate that this pidl is the last one
134 PCUIDLIST_RELATIVE curpidl
= ILGetNext(pidl
);
135 if (curpidl
->mkid
.cb
!= 0)
137 ERR("ERROR, unhandled PIDL!\n");
141 // Name, ReactOS specific?
143 return GetDisplayNameOf(pidl
, 0, &psd
->str
);
145 const FontPidlEntry
* fontEntry
= _FontFromIL(pidl
);
148 ERR("ERROR, not a font PIDL!\n");
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
)
156 CStringW File
= g_FontCache
->Filename(fontEntry
, true);
157 HANDLE hFile
= FindFirstFileW(File
, &m_LastDetailsFileData
);
158 if (hFile
== INVALID_HANDLE_VALUE
)
160 m_LastDetailsFontName
.Empty();
161 ERR("Unable to query info about %S\n", File
.GetString());
162 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND
);
165 m_LastDetailsFontName
= fontEntry
->Name
;
168 // Most code borrowed from CFSFolder::GetDetailsOf
172 LARGE_INTEGER FileSize
;
173 CStringA AttrLetters
;
177 return SHSetStrRet(&psd
->str
, m_LastDetailsFileData
.cFileName
);
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
);
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
);
191 ERR("GetDateFormatA failed\n");
194 psd
->str
.cStr
[ret
-1] = ' ';
195 GetTimeFormatA(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, &time
, NULL
, &psd
->str
.cStr
[ret
], MAX_PATH
- ret
);
197 case 4: // Attributes
198 AttrLetters
.LoadString(IDS_COL_ATTR_LETTERS
);
199 if (AttrLetters
.GetLength() != 5)
201 ERR("IDS_COL_ATTR_LETTERS does not contain 5 letters!\n");
204 psd
->str
.uType
= STRRET_CSTR
;
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';
226 STDMETHODIMP
CFontExt::MapColumnToSCID(UINT iColumn
, SHCOLUMNID
*pscid
)
228 //ERR("%s() UNIMPLEMENTED\n", __FUNCTION__);
232 // *** IShellFolder2 methods ***
233 STDMETHODIMP
CFontExt::ParseDisplayName(HWND hwndOwner
, LPBC pbc
, LPOLESTR lpszDisplayName
, DWORD
*pchEaten
, PIDLIST_RELATIVE
*ppidl
, DWORD
*pdwAttributes
)
235 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__
);
239 STDMETHODIMP
CFontExt::EnumObjects(HWND hwndOwner
, DWORD dwFlags
, LPENUMIDLIST
*ppEnumIDList
)
241 return _CEnumFonts_CreateInstance(this, dwFlags
, IID_PPV_ARG(IEnumIDList
, ppEnumIDList
));
244 STDMETHODIMP
CFontExt::BindToObject(PCUIDLIST_RELATIVE pidl
, LPBC pbcReserved
, REFIID riid
, LPVOID
*ppvOut
)
246 ERR("%s(riid=%S) UNIMPLEMENTED\n", __FUNCTION__
, g2s(riid
));
250 STDMETHODIMP
CFontExt::BindToStorage(PCUIDLIST_RELATIVE pidl
, LPBC pbcReserved
, REFIID riid
, LPVOID
*ppvOut
)
252 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__
);
256 STDMETHODIMP
CFontExt::CompareIDs(LPARAM lParam
, PCUIDLIST_RELATIVE pidl1
, PCUIDLIST_RELATIVE pidl2
)
258 const FontPidlEntry
* fontEntry1
= _FontFromIL(pidl1
);
259 const FontPidlEntry
* fontEntry2
= _FontFromIL(pidl2
);
261 if (!fontEntry1
|| !fontEntry2
)
264 int result
= (int)fontEntry1
->Index
- (int)fontEntry2
->Index
;
266 return MAKE_COMPARE_HRESULT(result
);
269 STDMETHODIMP
CFontExt::CreateViewObject(HWND hwndOwner
, REFIID riid
, LPVOID
*ppvOut
)
271 HRESULT hr
= E_NOINTERFACE
;
275 if (IsEqualIID(riid
, IID_IDropTarget
))
277 ERR("IDropTarget not implemented\n");
278 *ppvOut
= static_cast<IDropTarget
*>(this);
282 else if (IsEqualIID(riid
, IID_IContextMenu
))
284 ERR("IContextMenu not implemented\n");
287 else if (IsEqualIID(riid
, IID_IShellView
))
289 // Just create a default shell folder view, and register ourself as folder
290 SFV_CREATE sfv
= { sizeof(SFV_CREATE
) };
292 hr
= SHCreateShellFolderView(&sfv
, (IShellView
**)ppvOut
);
298 STDMETHODIMP
CFontExt::GetAttributesOf(UINT cidl
, PCUITEMID_CHILD_ARRAY apidl
, DWORD
*rgfInOut
)
300 if (!rgfInOut
|| !cidl
|| !apidl
)
304 while (cidl
> 0 && *apidl
)
306 const FontPidlEntry
* fontEntry
= _FontFromIL(*apidl
);
309 // We don't support delete yet
310 rgf
|= (/*SFGAO_CANDELETE |*/ SFGAO_HASPROPSHEET
| SFGAO_CANCOPY
| SFGAO_FILESYSTEM
);
327 STDMETHODIMP
CFontExt::GetUIObjectOf(HWND hwndOwner
, UINT cidl
, PCUITEMID_CHILD_ARRAY apidl
, REFIID riid
, UINT
* prgfInOut
, LPVOID
* ppvOut
)
329 if (riid
== IID_IContextMenu
||
330 riid
== IID_IContextMenu2
||
331 riid
== IID_IContextMenu3
)
333 return _CFontMenu_CreateInstance(hwndOwner
, cidl
, apidl
, this, riid
, ppvOut
);
335 else if (riid
== IID_IExtractIconA
|| riid
== IID_IExtractIconW
)
339 const FontPidlEntry
* fontEntry
= _FontFromIL(*apidl
);
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
);
351 ERR("IID_IExtractIcon with cidl != 1 UNIMPLEMENTED\n");
354 else if (riid
== IID_IDataObject
)
358 return _CDataObject_CreateInstance(m_Folder
, cidl
, apidl
, riid
, ppvOut
);
362 ERR("IID_IDataObject with cidl == 0 UNIMPLEMENTED\n");
366 //ERR("%s(riid=%S) UNIMPLEMENTED\n", __FUNCTION__, g2s(riid));
370 STDMETHODIMP
CFontExt::GetDisplayNameOf(PCUITEMID_CHILD pidl
, DWORD dwFlags
, LPSTRRET strRet
)
375 // Validate that this pidl is the last one
376 PCUIDLIST_RELATIVE curpidl
= ILGetNext(pidl
);
377 if (curpidl
->mkid
.cb
!= 0)
379 ERR("ERROR, unhandled PIDL!\n");
383 const FontPidlEntry
* fontEntry
= _FontFromIL(pidl
);
387 return SHSetStrRet(strRet
, fontEntry
->Name
);
390 STDMETHODIMP
CFontExt::SetNameOf(HWND hwndOwner
, PCUITEMID_CHILD pidl
, LPCOLESTR lpName
, DWORD dwFlags
, PITEMID_CHILD
*pPidlOut
)
392 ERR("%s() UNIMPLEMENTED\n", __FUNCTION__
);
396 // *** IPersistFolder2 methods ***
397 STDMETHODIMP
CFontExt::GetCurFolder(LPITEMIDLIST
*ppidl
)
399 if (ppidl
&& m_Folder
)
401 *ppidl
= ILClone(m_Folder
);
409 // *** IPersistFolder methods ***
410 STDMETHODIMP
CFontExt::Initialize(LPCITEMIDLIST pidl
)
412 WCHAR PidlPath
[MAX_PATH
+ 1] = {0}, FontsDir
[MAX_PATH
+ 1];
413 if (!SHGetPathFromIDListW(pidl
, PidlPath
))
415 ERR("Unable to extract path from pidl\n");
419 HRESULT hr
= SHGetFolderPathW(NULL
, CSIDL_FONTS
, NULL
, 0, FontsDir
);
420 if (FAILED_UNEXPECTEDLY(hr
))
422 ERR("Unable to get fonts path (0x%x)\n", hr
);
426 if (_wcsicmp(PidlPath
, FontsDir
))
428 ERR("CFontExt View initializing on unexpected folder: '%S'\n", PidlPath
);
432 m_Folder
.Attach(ILClone(pidl
));
433 StringCchCatW(FontsDir
, _countof(FontsDir
), L
"\\");
434 g_FontCache
->SetFontDir(FontsDir
);
440 // *** IPersist methods ***
441 STDMETHODIMP
CFontExt::GetClassID(CLSID
*lpClassId
)
443 *lpClassId
= CLSID_CFontExt
;
447 // *** IDropTarget methods ***
448 STDMETHODIMP
CFontExt::DragEnter(IDataObject
* pDataObj
, DWORD grfKeyState
, POINTL pt
, DWORD
* pdwEffect
)
450 *pdwEffect
= DROPEFFECT_NONE
;
452 CComHeapPtr
<CIDA
> cida
;
453 HRESULT hr
= _GetCidlFromDataObject(pDataObj
, &cida
);
454 if (FAILED_UNEXPECTEDLY(hr
))
457 #if 1 // Please implement DoGetFontTitle
458 return DRAGDROP_S_CANCEL
;
460 *pdwEffect
= DROPEFFECT_COPY
;
465 STDMETHODIMP
CFontExt::DragOver(DWORD grfKeyState
, POINTL pt
, DWORD
* pdwEffect
)
470 STDMETHODIMP
CFontExt::DragLeave()
475 STDMETHODIMP
CFontExt::Drop(IDataObject
* pDataObj
, DWORD grfKeyState
, POINTL pt
, DWORD
* pdwEffect
)
477 *pdwEffect
= DROPEFFECT_NONE
;
479 CComHeapPtr
<CIDA
> cida
;
480 HRESULT hr
= _GetCidlFromDataObject(pDataObj
, &cida
);
481 if (FAILED_UNEXPECTEDLY(hr
))
484 PCUIDLIST_ABSOLUTE pidlParent
= HIDA_GetPIDLFolder(cida
);
487 ERR("pidlParent is NULL\n");
492 CAtlArray
<CStringW
> FontPaths
;
493 for (UINT n
= 0; n
< cida
->cidl
; ++n
)
495 PCUIDLIST_RELATIVE pidlRelative
= HIDA_GetPIDLItem(cida
, n
);
499 PIDLIST_ABSOLUTE pidl
= ILCombine(pidlParent
, pidlRelative
);
502 ERR("ILCombine failed\n");
507 WCHAR szPath
[MAX_PATH
];
508 BOOL ret
= SHGetPathFromIDListW(pidl
, szPath
);
513 ERR("SHGetPathFromIDListW failed\n");
518 if (PathIsDirectoryW(szPath
))
520 ERR("PathIsDirectory\n");
525 LPCWSTR pchDotExt
= PathFindExtensionW(szPath
);
526 if (!IsFontDotExt(pchDotExt
))
528 ERR("'%S' is not supported\n", pchDotExt
);
533 FontPaths
.Add(szPath
);
540 if (keyFonts
.Open(FONT_HIVE
, FONT_KEY
, KEY_WRITE
) != ERROR_SUCCESS
)
542 ERR("keyFonts.Open failed\n");
546 for (size_t iItem
= 0; iItem
< FontPaths
.GetCount(); ++iItem
)
548 HRESULT hr
= DoInstallFontFile(FontPaths
[iItem
], g_FontCache
->FontPath(), keyFonts
.m_hKey
);
549 if (FAILED_UNEXPECTEDLY(hr
))
556 // TODO: update g_FontCache
558 SendMessageW(HWND_BROADCAST
, WM_FONTCHANGE
, 0, 0);
560 // TODO: Show message
562 return bOK
? S_OK
: E_FAIL
;
565 HRESULT
CFontExt::DoInstallFontFile(LPCWSTR pszFontPath
, LPCWSTR pszFontsDir
, HKEY hkeyFonts
)
567 WCHAR szDestFile
[MAX_PATH
];
568 LPCWSTR pszFileTitle
= PathFindFileName(pszFontPath
);
570 WCHAR szFontName
[512];
571 if (!DoGetFontTitle(pszFontPath
, szFontName
))
574 RemoveFontResourceW(pszFileTitle
);
576 StringCchCopyW(szDestFile
, sizeof(szDestFile
), pszFontsDir
);
577 PathAppendW(szDestFile
, pszFileTitle
);
578 if (!CopyFileW(pszFontPath
, szDestFile
, FALSE
))
580 ERR("CopyFileW('%S', '%S') failed\n", pszFontPath
, szDestFile
);
584 if (!AddFontResourceW(pszFileTitle
))
586 ERR("AddFontResourceW('%S') failed\n", pszFileTitle
);
587 DeleteFileW(szDestFile
);
591 DWORD cbData
= (wcslen(pszFileTitle
) + 1) * sizeof(WCHAR
);
592 LONG nError
= RegSetValueExW(hkeyFonts
, szFontName
, 0, REG_SZ
, (const BYTE
*)szFontName
, cbData
);
595 ERR("RegSetValueExW failed with %ld\n", nError
);
596 RemoveFontResourceW(pszFileTitle
);
597 DeleteFileW(szDestFile
);
604 HRESULT
CFontExt::DoGetFontTitle(LPCWSTR pszFontPath
, LPCWSTR pszFontName
)