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