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