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