1 /* 2 * PROJECT: ReactOS msctf.dll 3 * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) 4 * PURPOSE: Multi-language handling of Cicero 5 * COPYRIGHT: Copyright 2024 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com> 6 */ 7 8 #define WIN32_NO_STATUS 9 10 #include <windows.h> 11 #include <shellapi.h> 12 #include <imm.h> 13 #include <imm32_undoc.h> 14 #include <shlobj.h> 15 #include <shlwapi.h> 16 #include <shlwapi_undoc.h> 17 #include <msctf.h> 18 #include <strsafe.h> 19 #include <assert.h> 20 21 #include <cicreg.h> 22 #include <cicarray.h> 23 24 #include <wine/debug.h> 25 26 #include "mlng.h" 27 28 WINE_DEFAULT_DEBUG_CHANNEL(msctf); 29 30 extern CRITICAL_SECTION g_cs; 31 32 CicArray<MLNGINFO> *g_pMlngInfo = NULL; 33 INT CStaticIconList::s_cx = 0; 34 INT CStaticIconList::s_cy = 0; 35 CStaticIconList g_IconList; 36 37 // Cache for GetSpecialKLID 38 static HKL s_hCacheKL = NULL; 39 static DWORD s_dwCacheKLID = 0; 40 41 /*********************************************************************** 42 * The helper funtions 43 */ 44 45 /// @implemented 46 DWORD GetSpecialKLID(_In_ HKL hKL) 47 { 48 assert(IS_SPECIAL_HKL(hKL)); 49 50 if (s_hCacheKL == hKL && s_dwCacheKLID != 0) 51 return s_dwCacheKLID; 52 53 s_dwCacheKLID = 0; 54 55 CicRegKey regKey1; 56 LSTATUS error = regKey1.Open(HKEY_LOCAL_MACHINE, 57 L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts"); 58 if (error != ERROR_SUCCESS) 59 return 0; 60 61 WCHAR szName[16], szLayoutId[16]; 62 const DWORD dwSpecialId = SPECIALIDFROMHKL(hKL); 63 for (DWORD dwIndex = 0; ; ++dwIndex) 64 { 65 error = ::RegEnumKeyW(regKey1, dwIndex, szName, _countof(szName)); 66 szName[_countof(szName) - 1] = UNICODE_NULL; // Avoid buffer overrun 67 if (error != ERROR_SUCCESS) 68 break; 69 70 CicRegKey regKey2; 71 error = regKey2.Open(regKey1, szName); 72 if (error != ERROR_SUCCESS) 73 break; 74 75 error = regKey2.QuerySz(L"Layout Id", szLayoutId, _countof(szLayoutId)); 76 szLayoutId[_countof(szLayoutId) - 1] = UNICODE_NULL; // Avoid buffer overrun 77 if (error == ERROR_SUCCESS) 78 continue; 79 80 DWORD dwLayoutId = wcstoul(szLayoutId, NULL, 16); 81 if (dwLayoutId == dwSpecialId) 82 { 83 s_hCacheKL = hKL; 84 s_dwCacheKLID = wcstoul(szName, NULL, 16); 85 break; 86 } 87 } 88 89 return s_dwCacheKLID; 90 } 91 92 /// @implemented 93 DWORD GetHKLSubstitute(_In_ HKL hKL) 94 { 95 if (IS_IME_HKL(hKL)) 96 return HandleToUlong(hKL); 97 98 DWORD dwKLID; 99 if (HIWORD(hKL) == LOWORD(hKL)) 100 dwKLID = LOWORD(hKL); 101 else if (IS_SPECIAL_HKL(hKL)) 102 dwKLID = GetSpecialKLID(hKL); 103 else 104 dwKLID = HandleToUlong(hKL); 105 106 if (dwKLID == 0) 107 return HandleToUlong(hKL); 108 109 CicRegKey regKey; 110 LSTATUS error = regKey.Open(HKEY_CURRENT_USER, L"Keyboard Layout\\Substitutes"); 111 if (error == ERROR_SUCCESS) 112 { 113 WCHAR szName[MAX_PATH], szValue[MAX_PATH]; 114 DWORD dwIndex, dwValue; 115 for (dwIndex = 0; ; ++dwIndex) 116 { 117 error = regKey.EnumValue(dwIndex, szName, _countof(szName)); 118 szName[_countof(szName) - 1] = UNICODE_NULL; // Avoid buffer overrun 119 if (error != ERROR_SUCCESS) 120 break; 121 122 error = regKey.QuerySz(szName, szValue, _countof(szValue)); 123 szValue[_countof(szValue) - 1] = UNICODE_NULL; // Avoid buffer overrun 124 if (error != ERROR_SUCCESS) 125 break; 126 127 dwValue = wcstoul(szValue, NULL, 16); 128 if ((dwKLID & ~SPECIAL_MASK) == dwValue) 129 { 130 dwKLID = wcstoul(szName, NULL, 16); 131 break; 132 } 133 } 134 } 135 136 return dwKLID; 137 } 138 139 /// @implemented 140 static BOOL 141 GetKbdLayoutNameFromReg(_In_ HKL hKL, _Out_ LPWSTR pszDesc, _In_ UINT cchDesc) 142 { 143 const DWORD dwKLID = GetHKLSubstitute(hKL); 144 145 WCHAR szSubKey[MAX_PATH]; 146 StringCchPrintfW(szSubKey, _countof(szSubKey), 147 L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%08lX", 148 dwKLID); 149 150 CicRegKey regKey; 151 LSTATUS error = regKey.Open(HKEY_LOCAL_MACHINE, szSubKey); 152 if (error != ERROR_SUCCESS) 153 return FALSE; 154 155 if (SHLoadRegUIStringW(regKey, L"Layout Display Name", pszDesc, cchDesc) == S_OK) 156 { 157 pszDesc[cchDesc - 1] = UNICODE_NULL; // Avoid buffer overrun 158 return TRUE; 159 } 160 161 error = regKey.QuerySz(L"Layout Text", pszDesc, cchDesc); 162 pszDesc[cchDesc - 1] = UNICODE_NULL; // Avoid buffer overrun 163 return (error == ERROR_SUCCESS); 164 } 165 166 /// @implemented 167 static BOOL 168 GetHKLName(_In_ HKL hKL, _Out_ LPWSTR pszDesc, _In_ UINT cchDesc) 169 { 170 if (::GetLocaleInfoW(LOWORD(hKL), LOCALE_SLANGUAGE, pszDesc, cchDesc)) 171 return TRUE; 172 173 *pszDesc = UNICODE_NULL; 174 175 if (LOWORD(hKL) == HIWORD(hKL)) 176 return FALSE; 177 178 return GetKbdLayoutNameFromReg(hKL, pszDesc, cchDesc); 179 } 180 181 /// @implemented 182 static BOOL 183 GetHKLDesctription( 184 _In_ HKL hKL, 185 _Out_ LPWSTR pszDesc, 186 _In_ UINT cchDesc, 187 _Out_ LPWSTR pszImeFileName, 188 _In_ UINT cchImeFileName) 189 { 190 pszDesc[0] = pszImeFileName[0] = UNICODE_NULL; 191 192 if (!IS_IME_HKL(hKL)) 193 return GetHKLName(hKL, pszDesc, cchDesc); 194 195 if (GetKbdLayoutNameFromReg(hKL, pszDesc, cchDesc)) 196 return TRUE; 197 198 if (!::ImmGetDescriptionW(hKL, pszDesc, cchDesc)) 199 { 200 *pszDesc = UNICODE_NULL; 201 return GetHKLName(hKL, pszDesc, cchDesc); 202 } 203 204 if (!::ImmGetIMEFileNameW(hKL, pszImeFileName, cchImeFileName)) 205 *pszImeFileName = UNICODE_NULL; 206 207 return TRUE; 208 } 209 210 /// @implemented 211 HICON GetIconFromFile(_In_ INT cx, _In_ INT cy, _In_ LPCWSTR pszFileName, _In_ INT iIcon) 212 { 213 HICON hIcon; 214 215 if (cx <= GetSystemMetrics(SM_CXSMICON)) 216 ::ExtractIconExW(pszFileName, iIcon, NULL, &hIcon, 1); 217 else 218 ::ExtractIconExW(pszFileName, iIcon, &hIcon, NULL, 1); 219 220 return hIcon; 221 } 222 223 /// @implemented 224 static BOOL EnsureIconImageList(VOID) 225 { 226 if (!CStaticIconList::s_cx) 227 g_IconList.Init(::GetSystemMetrics(SM_CYSMICON), ::GetSystemMetrics(SM_CXSMICON)); 228 229 return TRUE; 230 } 231 232 /// @implemented 233 static INT GetPhysicalFontHeight(LOGFONTW *plf) 234 { 235 HDC hDC = ::GetDC(NULL); 236 HFONT hFont = ::CreateFontIndirectW(plf); 237 HGDIOBJ hFontOld = ::SelectObject(hDC, hFont); 238 TEXTMETRICW tm; 239 ::GetTextMetricsW(hDC, &tm); 240 INT ret = tm.tmExternalLeading + tm.tmHeight; 241 ::SelectObject(hDC, hFontOld); 242 ::DeleteObject(hFont); 243 ::ReleaseDC(NULL, hDC); 244 return ret; 245 } 246 247 /*********************************************************************** 248 * Inat helper functions 249 */ 250 251 /// @implemented 252 INT InatAddIcon(_In_ HICON hIcon) 253 { 254 if (!EnsureIconImageList()) 255 return -1; 256 return g_IconList.AddIcon(hIcon); 257 } 258 259 /// @implemented 260 HICON 261 InatCreateIconBySize( 262 _In_ LANGID LangID, 263 _In_ INT nWidth, 264 _In_ INT nHeight, 265 _In_ const LOGFONTW *plf) 266 { 267 WCHAR szText[64]; 268 BOOL ret = ::GetLocaleInfoW(LangID, LOCALE_NOUSEROVERRIDE | LOCALE_SABBREVLANGNAME, 269 szText, _countof(szText)); 270 if (!ret) 271 szText[0] = szText[1] = L'?'; 272 273 szText[2] = UNICODE_NULL; 274 CharUpperW(szText); 275 276 HFONT hFont = ::CreateFontIndirectW(plf); 277 if (!hFont) 278 return NULL; 279 280 HDC hDC = ::GetDC(NULL); 281 HDC hMemDC = ::CreateCompatibleDC(hDC); 282 HBITMAP hbmColor = ::CreateCompatibleBitmap(hDC, nWidth, nHeight); 283 HBITMAP hbmMask = ::CreateBitmap(nWidth, nHeight, 1, 1, NULL); 284 ::ReleaseDC(NULL, hDC); 285 286 HICON hIcon = NULL; 287 HGDIOBJ hbmOld = ::SelectObject(hMemDC, hbmColor); 288 HGDIOBJ hFontOld = ::SelectObject(hMemDC, hFont); 289 if (hMemDC && hbmColor && hbmMask) 290 { 291 ::SetBkColor(hMemDC, ::GetSysColor(COLOR_HIGHLIGHT)); 292 ::SetTextColor(hMemDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT)); 293 294 RECT rc = { 0, 0, nWidth, nHeight }; 295 ::ExtTextOutW(hMemDC, 0, 0, ETO_OPAQUE, &rc, L"", 0, NULL); 296 297 ::DrawTextW(hMemDC, szText, 2, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); 298 ::SelectObject(hMemDC, hbmMask); 299 300 ::PatBlt(hMemDC, 0, 0, nWidth, nHeight, BLACKNESS); 301 302 ICONINFO IconInfo = { TRUE, 0, 0, hbmMask, hbmColor }; 303 hIcon = ::CreateIconIndirect(&IconInfo); 304 } 305 ::SelectObject(hMemDC, hFontOld); 306 ::SelectObject(hMemDC, hbmOld); 307 308 ::DeleteObject(hbmMask); 309 ::DeleteObject(hbmColor); 310 ::DeleteDC(hMemDC); 311 ::DeleteObject(hFont); 312 return hIcon; 313 } 314 315 /// @implemented 316 HICON InatCreateIcon(_In_ LANGID LangID) 317 { 318 INT cxSmIcon = ::GetSystemMetrics(SM_CXSMICON), cySmIcon = ::GetSystemMetrics(SM_CYSMICON); 319 320 LOGFONTW lf; 321 if (!SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(LOGFONTW), &lf, 0)) 322 return NULL; 323 324 if (cySmIcon < GetPhysicalFontHeight(&lf)) 325 { 326 lf.lfWidth = 0; 327 lf.lfHeight = - (7 * cySmIcon) / 10; 328 } 329 330 return InatCreateIconBySize(LangID, cxSmIcon, cySmIcon, &lf); 331 } 332 333 /// @implemented 334 BOOL InatGetIconSize(_Out_ INT *pcx, _Out_ INT *pcy) 335 { 336 g_IconList.GetIconSize(pcx, pcy); 337 return TRUE; 338 } 339 340 /// @implemented 341 INT InatGetImageCount(VOID) 342 { 343 return g_IconList.GetImageCount(); 344 } 345 346 /// @implemented 347 VOID InatRemoveAll(VOID) 348 { 349 if (CStaticIconList::s_cx) 350 g_IconList.RemoveAll(FALSE); 351 } 352 353 /// @implemented 354 VOID UninitINAT(VOID) 355 { 356 g_IconList.RemoveAll(TRUE); 357 358 if (g_pMlngInfo) 359 { 360 delete g_pMlngInfo; 361 g_pMlngInfo = NULL; 362 } 363 } 364 365 /*********************************************************************** 366 * MLNGINFO 367 */ 368 369 /// @implemented 370 void MLNGINFO::InitDesc() 371 { 372 if (m_bInitDesc) 373 return; 374 375 WCHAR szDesc[MAX_PATH], szImeFileName[MAX_PATH]; 376 GetHKLDesctription(m_hKL, szDesc, (UINT)_countof(szDesc), 377 szImeFileName, (UINT)_countof(szImeFileName)); 378 SetDesc(szDesc); 379 m_bInitDesc = TRUE; 380 } 381 382 /// @implemented 383 void MLNGINFO::InitIcon() 384 { 385 if (m_bInitIcon) 386 return; 387 388 WCHAR szDesc[MAX_PATH], szImeFileName[MAX_PATH]; 389 GetHKLDesctription(m_hKL, szDesc, (UINT)_countof(szDesc), 390 szImeFileName, (UINT)_countof(szImeFileName)); 391 SetDesc(szDesc); 392 m_bInitDesc = TRUE; 393 394 INT cxIcon, cyIcon; 395 InatGetIconSize(&cxIcon, &cyIcon); 396 397 HICON hIcon = NULL; 398 if (szImeFileName[0]) 399 hIcon = GetIconFromFile(cxIcon, cyIcon, szImeFileName, 0); 400 401 if (!hIcon) 402 hIcon = InatCreateIcon(LOWORD(m_hKL)); 403 404 if (hIcon) 405 { 406 m_iIconIndex = InatAddIcon(hIcon); 407 ::DestroyIcon(hIcon); 408 } 409 410 m_bInitIcon = TRUE; 411 } 412 413 /// @implemented 414 LPCWSTR MLNGINFO::GetDesc() 415 { 416 if (!m_bInitDesc) 417 InitDesc(); 418 419 return m_szDesc; 420 } 421 422 /// @implemented 423 void MLNGINFO::SetDesc(LPCWSTR pszDesc) 424 { 425 StringCchCopyW(m_szDesc, _countof(m_szDesc), pszDesc); 426 } 427 428 /// @implemented 429 INT MLNGINFO::GetIconIndex() 430 { 431 if (!m_bInitIcon) 432 InitIcon(); 433 434 return m_iIconIndex; 435 } 436 437 /*********************************************************************** 438 * CStaticIconList 439 */ 440 441 /// @implemented 442 void CStaticIconList::Init(INT cxIcon, INT cyIcon) 443 { 444 ::EnterCriticalSection(&g_cs); 445 s_cx = cxIcon; 446 s_cy = cyIcon; 447 ::LeaveCriticalSection(&g_cs); 448 } 449 450 /// @implemented 451 INT CStaticIconList::AddIcon(HICON hIcon) 452 { 453 ::EnterCriticalSection(&g_cs); 454 455 INT iItem = -1; 456 HICON hCopyIcon = ::CopyIcon(hIcon); 457 if (hCopyIcon) 458 { 459 if (g_IconList.Add(hIcon)) 460 iItem = INT(g_IconList.size() - 1); 461 } 462 463 ::LeaveCriticalSection(&g_cs); 464 return iItem; 465 } 466 467 /// @implemented 468 HICON CStaticIconList::ExtractIcon(INT iIcon) 469 { 470 HICON hCopyIcon = NULL; 471 ::EnterCriticalSection(&g_cs); 472 if (iIcon <= (INT)g_IconList.size()) 473 hCopyIcon = ::CopyIcon(g_IconList[iIcon]); 474 ::LeaveCriticalSection(&g_cs); 475 return hCopyIcon; 476 } 477 478 /// @implemented 479 void CStaticIconList::GetIconSize(INT *pcx, INT *pcy) 480 { 481 ::EnterCriticalSection(&g_cs); 482 *pcx = s_cx; 483 *pcy = s_cy; 484 ::LeaveCriticalSection(&g_cs); 485 } 486 487 /// @implemented 488 INT CStaticIconList::GetImageCount() 489 { 490 ::EnterCriticalSection(&g_cs); 491 INT cItems = (INT)g_IconList.size(); 492 ::LeaveCriticalSection(&g_cs); 493 return cItems; 494 } 495 496 /// @implemented 497 void CStaticIconList::RemoveAll(BOOL bNoLock) 498 { 499 if (!bNoLock) 500 ::EnterCriticalSection(&g_cs); 501 502 for (size_t iItem = 0; iItem < g_IconList.size(); ++iItem) 503 { 504 ::DestroyIcon(g_IconList[iItem]); 505 } 506 507 clear(); 508 509 if (!bNoLock) 510 ::LeaveCriticalSection(&g_cs); 511 } 512 513 /// @implemented 514 static BOOL CheckMlngInfo(VOID) 515 { 516 if (!g_pMlngInfo) 517 return TRUE; // Needs creation 518 519 INT cKLs = ::GetKeyboardLayoutList(0, NULL); 520 if (cKLs != TF_MlngInfoCount()) 521 return TRUE; // Needs refresh 522 523 if (!cKLs) 524 return FALSE; 525 526 HKL *phKLs = (HKL*)cicMemAlloc(cKLs * sizeof(HKL)); 527 if (!phKLs) 528 return FALSE; 529 530 ::GetKeyboardLayoutList(cKLs, phKLs); 531 532 assert(g_pMlngInfo); 533 534 BOOL ret = FALSE; 535 for (INT iKL = 0; iKL < cKLs; ++iKL) 536 { 537 if ((*g_pMlngInfo)[iKL].m_hKL != phKLs[iKL]) 538 { 539 ret = TRUE; // Needs refresh 540 break; 541 } 542 } 543 544 cicMemFree(phKLs); 545 return ret; 546 } 547 548 /// @implemented 549 static VOID DestroyMlngInfo(VOID) 550 { 551 if (!g_pMlngInfo) 552 return; 553 554 delete g_pMlngInfo; 555 g_pMlngInfo = NULL; 556 } 557 558 /// @implemented 559 static VOID CreateMlngInfo(VOID) 560 { 561 if (!g_pMlngInfo) 562 { 563 g_pMlngInfo = new(cicNoThrow) CicArray<MLNGINFO>(); 564 if (!g_pMlngInfo) 565 return; 566 } 567 568 if (!EnsureIconImageList()) 569 return; 570 571 INT cKLs = ::GetKeyboardLayoutList(0, NULL); 572 HKL *phKLs = (HKL*)cicMemAllocClear(cKLs * sizeof(HKL)); 573 if (!phKLs) 574 return; 575 576 ::GetKeyboardLayoutList(cKLs, phKLs); 577 578 for (INT iKL = 0; iKL < cKLs; ++iKL) 579 { 580 MLNGINFO& info = (*g_pMlngInfo)[iKL]; 581 info.m_hKL = phKLs[iKL]; 582 info.m_bInitDesc = FALSE; 583 info.m_bInitIcon = FALSE; 584 } 585 586 cicMemFree(phKLs); 587 } 588 589 /*********************************************************************** 590 * TF_InitMlngInfo (MSCTF.@) 591 * 592 * @implemented 593 */ 594 EXTERN_C VOID WINAPI TF_InitMlngInfo(VOID) 595 { 596 TRACE("()\n"); 597 598 ::EnterCriticalSection(&g_cs); 599 600 if (CheckMlngInfo()) 601 { 602 DestroyMlngInfo(); 603 CreateMlngInfo(); 604 } 605 606 ::LeaveCriticalSection(&g_cs); 607 } 608 609 /*********************************************************************** 610 * TF_MlngInfoCount (MSCTF.@) 611 * 612 * @implemented 613 */ 614 EXTERN_C INT WINAPI TF_MlngInfoCount(VOID) 615 { 616 TRACE("()\n"); 617 618 if (!g_pMlngInfo) 619 return 0; 620 621 return (INT)g_pMlngInfo->size(); 622 } 623 624 /*********************************************************************** 625 * TF_InatExtractIcon (MSCTF.@) 626 * 627 * @implemented 628 */ 629 EXTERN_C HICON WINAPI TF_InatExtractIcon(_In_ INT iKL) 630 { 631 TRACE("(%d)\n", iKL); 632 return g_IconList.ExtractIcon(iKL); 633 } 634 635 /*********************************************************************** 636 * TF_GetMlngIconIndex (MSCTF.@) 637 * 638 * @implemented 639 */ 640 EXTERN_C INT WINAPI TF_GetMlngIconIndex(_In_ INT iKL) 641 { 642 TRACE("(%d)\n", iKL); 643 644 INT iIcon = -1; 645 646 ::EnterCriticalSection(&g_cs); 647 648 assert(g_pMlngInfo); 649 650 if (iKL < (INT)g_pMlngInfo->size()) 651 iIcon = (*g_pMlngInfo)[iKL].GetIconIndex(); 652 653 ::LeaveCriticalSection(&g_cs); 654 655 return iIcon; 656 } 657 658 /*********************************************************************** 659 * TF_GetMlngHKL (MSCTF.@) 660 * 661 * @implemented 662 */ 663 EXTERN_C BOOL WINAPI 664 TF_GetMlngHKL( 665 _In_ INT iKL, 666 _Out_opt_ HKL *phKL, 667 _Out_opt_ LPWSTR pszDesc, 668 _In_ INT cchDesc) 669 { 670 TRACE("(%d, %p, %p, %d)\n", iKL, phKL, pszDesc, cchDesc); 671 672 BOOL ret = FALSE; 673 674 ::EnterCriticalSection(&g_cs); 675 676 assert(g_pMlngInfo); 677 678 if (iKL < (INT)g_pMlngInfo->size()) 679 { 680 MLNGINFO& info = (*g_pMlngInfo)[iKL]; 681 682 if (phKL) 683 *phKL = info.m_hKL; 684 685 if (pszDesc) 686 StringCchCopyW(pszDesc, cchDesc, info.GetDesc()); 687 688 ret = TRUE; 689 } 690 691 ::LeaveCriticalSection(&g_cs); 692 693 return ret; 694 } 695