/* * PROJECT: ReactOS msctf.dll * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) * PURPOSE: Multi-language handling of Cicero * COPYRIGHT: Copyright 2024 Katayama Hirofumi MZ */ #define WIN32_NO_STATUS #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mlng.h" WINE_DEFAULT_DEBUG_CHANNEL(msctf); extern CRITICAL_SECTION g_cs; CicArray *g_pMlngInfo = NULL; INT CStaticIconList::s_cx = 0; INT CStaticIconList::s_cy = 0; CStaticIconList g_IconList; // Cache for GetSpecialKLID static HKL s_hCacheKL = NULL; static DWORD s_dwCacheKLID = 0; /*********************************************************************** * The helper funtions */ /// @implemented DWORD GetSpecialKLID(_In_ HKL hKL) { assert(IS_SPECIAL_HKL(hKL)); if (s_hCacheKL == hKL && s_dwCacheKLID != 0) return s_dwCacheKLID; s_dwCacheKLID = 0; CicRegKey regKey1; LSTATUS error = regKey1.Open(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts"); if (error != ERROR_SUCCESS) return 0; WCHAR szName[16], szLayoutId[16]; const DWORD dwSpecialId = SPECIALIDFROMHKL(hKL); for (DWORD dwIndex = 0; ; ++dwIndex) { error = ::RegEnumKeyW(regKey1, dwIndex, szName, _countof(szName)); szName[_countof(szName) - 1] = UNICODE_NULL; // Avoid buffer overrun if (error != ERROR_SUCCESS) break; CicRegKey regKey2; error = regKey2.Open(regKey1, szName); if (error != ERROR_SUCCESS) break; error = regKey2.QuerySz(L"Layout Id", szLayoutId, _countof(szLayoutId)); szLayoutId[_countof(szLayoutId) - 1] = UNICODE_NULL; // Avoid buffer overrun if (error == ERROR_SUCCESS) continue; DWORD dwLayoutId = wcstoul(szLayoutId, NULL, 16); if (dwLayoutId == dwSpecialId) { s_hCacheKL = hKL; s_dwCacheKLID = wcstoul(szName, NULL, 16); break; } } return s_dwCacheKLID; } /// @implemented DWORD GetHKLSubstitute(_In_ HKL hKL) { if (IS_IME_HKL(hKL)) return HandleToUlong(hKL); DWORD dwKLID; if (HIWORD(hKL) == LOWORD(hKL)) dwKLID = LOWORD(hKL); else if (IS_SPECIAL_HKL(hKL)) dwKLID = GetSpecialKLID(hKL); else dwKLID = HandleToUlong(hKL); if (dwKLID == 0) return HandleToUlong(hKL); CicRegKey regKey; LSTATUS error = regKey.Open(HKEY_CURRENT_USER, L"Keyboard Layout\\Substitutes"); if (error == ERROR_SUCCESS) { WCHAR szName[MAX_PATH], szValue[MAX_PATH]; DWORD dwIndex, dwValue; for (dwIndex = 0; ; ++dwIndex) { error = regKey.EnumValue(dwIndex, szName, _countof(szName)); szName[_countof(szName) - 1] = UNICODE_NULL; // Avoid buffer overrun if (error != ERROR_SUCCESS) break; error = regKey.QuerySz(szName, szValue, _countof(szValue)); szValue[_countof(szValue) - 1] = UNICODE_NULL; // Avoid buffer overrun if (error != ERROR_SUCCESS) break; dwValue = wcstoul(szValue, NULL, 16); if ((dwKLID & ~SPECIAL_MASK) == dwValue) { dwKLID = wcstoul(szName, NULL, 16); break; } } } return dwKLID; } /// @implemented static BOOL GetKbdLayoutNameFromReg(_In_ HKL hKL, _Out_ LPWSTR pszDesc, _In_ UINT cchDesc) { const DWORD dwKLID = GetHKLSubstitute(hKL); WCHAR szSubKey[MAX_PATH]; StringCchPrintfW(szSubKey, _countof(szSubKey), L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%08lX", dwKLID); CicRegKey regKey; LSTATUS error = regKey.Open(HKEY_LOCAL_MACHINE, szSubKey); if (error != ERROR_SUCCESS) return FALSE; if (SHLoadRegUIStringW(regKey, L"Layout Display Name", pszDesc, cchDesc) == S_OK) { pszDesc[cchDesc - 1] = UNICODE_NULL; // Avoid buffer overrun return TRUE; } error = regKey.QuerySz(L"Layout Text", pszDesc, cchDesc); pszDesc[cchDesc - 1] = UNICODE_NULL; // Avoid buffer overrun return (error == ERROR_SUCCESS); } /// @implemented static BOOL GetHKLName(_In_ HKL hKL, _Out_ LPWSTR pszDesc, _In_ UINT cchDesc) { if (::GetLocaleInfoW(LOWORD(hKL), LOCALE_SLANGUAGE, pszDesc, cchDesc)) return TRUE; *pszDesc = UNICODE_NULL; if (LOWORD(hKL) == HIWORD(hKL)) return FALSE; return GetKbdLayoutNameFromReg(hKL, pszDesc, cchDesc); } /// @implemented static BOOL GetHKLDesctription( _In_ HKL hKL, _Out_ LPWSTR pszDesc, _In_ UINT cchDesc, _Out_ LPWSTR pszImeFileName, _In_ UINT cchImeFileName) { pszDesc[0] = pszImeFileName[0] = UNICODE_NULL; if (!IS_IME_HKL(hKL)) return GetHKLName(hKL, pszDesc, cchDesc); if (GetKbdLayoutNameFromReg(hKL, pszDesc, cchDesc)) return TRUE; if (!::ImmGetDescriptionW(hKL, pszDesc, cchDesc)) { *pszDesc = UNICODE_NULL; return GetHKLName(hKL, pszDesc, cchDesc); } if (!::ImmGetIMEFileNameW(hKL, pszImeFileName, cchImeFileName)) *pszImeFileName = UNICODE_NULL; return TRUE; } /// @implemented HICON GetIconFromFile(_In_ INT cx, _In_ INT cy, _In_ LPCWSTR pszFileName, _In_ INT iIcon) { HICON hIcon; if (cx <= GetSystemMetrics(SM_CXSMICON)) ::ExtractIconExW(pszFileName, iIcon, NULL, &hIcon, 1); else ::ExtractIconExW(pszFileName, iIcon, &hIcon, NULL, 1); return hIcon; } /// @implemented static BOOL EnsureIconImageList(VOID) { if (!CStaticIconList::s_cx) g_IconList.Init(::GetSystemMetrics(SM_CYSMICON), ::GetSystemMetrics(SM_CXSMICON)); return TRUE; } /// @implemented static INT GetPhysicalFontHeight(LOGFONTW *plf) { HDC hDC = ::GetDC(NULL); HFONT hFont = ::CreateFontIndirectW(plf); HGDIOBJ hFontOld = ::SelectObject(hDC, hFont); TEXTMETRICW tm; ::GetTextMetricsW(hDC, &tm); INT ret = tm.tmExternalLeading + tm.tmHeight; ::SelectObject(hDC, hFontOld); ::DeleteObject(hFont); ::ReleaseDC(NULL, hDC); return ret; } /*********************************************************************** * Inat helper functions */ /// @implemented INT InatAddIcon(_In_ HICON hIcon) { if (!EnsureIconImageList()) return -1; return g_IconList.AddIcon(hIcon); } /// @implemented HICON InatCreateIconBySize( _In_ LANGID LangID, _In_ INT nWidth, _In_ INT nHeight, _In_ const LOGFONTW *plf) { WCHAR szText[64]; BOOL ret = ::GetLocaleInfoW(LangID, LOCALE_NOUSEROVERRIDE | LOCALE_SABBREVLANGNAME, szText, _countof(szText)); if (!ret) szText[0] = szText[1] = L'?'; szText[2] = UNICODE_NULL; CharUpperW(szText); HFONT hFont = ::CreateFontIndirectW(plf); if (!hFont) return NULL; HDC hDC = ::GetDC(NULL); HDC hMemDC = ::CreateCompatibleDC(hDC); HBITMAP hbmColor = ::CreateCompatibleBitmap(hDC, nWidth, nHeight); HBITMAP hbmMask = ::CreateBitmap(nWidth, nHeight, 1, 1, NULL); ::ReleaseDC(NULL, hDC); HICON hIcon = NULL; HGDIOBJ hbmOld = ::SelectObject(hMemDC, hbmColor); HGDIOBJ hFontOld = ::SelectObject(hMemDC, hFont); if (hMemDC && hbmColor && hbmMask) { ::SetBkColor(hMemDC, ::GetSysColor(COLOR_HIGHLIGHT)); ::SetTextColor(hMemDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT)); RECT rc = { 0, 0, nWidth, nHeight }; ::ExtTextOutW(hMemDC, 0, 0, ETO_OPAQUE, &rc, L"", 0, NULL); ::DrawTextW(hMemDC, szText, 2, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); ::SelectObject(hMemDC, hbmMask); ::PatBlt(hMemDC, 0, 0, nWidth, nHeight, BLACKNESS); ICONINFO IconInfo = { TRUE, 0, 0, hbmMask, hbmColor }; hIcon = ::CreateIconIndirect(&IconInfo); } ::SelectObject(hMemDC, hFontOld); ::SelectObject(hMemDC, hbmOld); ::DeleteObject(hbmMask); ::DeleteObject(hbmColor); ::DeleteDC(hMemDC); ::DeleteObject(hFont); return hIcon; } /// @implemented HICON InatCreateIcon(_In_ LANGID LangID) { INT cxSmIcon = ::GetSystemMetrics(SM_CXSMICON), cySmIcon = ::GetSystemMetrics(SM_CYSMICON); LOGFONTW lf; if (!SystemParametersInfoW(SPI_GETICONTITLELOGFONT, sizeof(LOGFONTW), &lf, 0)) return NULL; if (cySmIcon < GetPhysicalFontHeight(&lf)) { lf.lfWidth = 0; lf.lfHeight = - (7 * cySmIcon) / 10; } return InatCreateIconBySize(LangID, cxSmIcon, cySmIcon, &lf); } /// @implemented BOOL InatGetIconSize(_Out_ INT *pcx, _Out_ INT *pcy) { g_IconList.GetIconSize(pcx, pcy); return TRUE; } /// @implemented INT InatGetImageCount(VOID) { return g_IconList.GetImageCount(); } /// @implemented VOID InatRemoveAll(VOID) { if (CStaticIconList::s_cx) g_IconList.RemoveAll(FALSE); } /// @implemented VOID UninitINAT(VOID) { g_IconList.RemoveAll(TRUE); if (g_pMlngInfo) { delete g_pMlngInfo; g_pMlngInfo = NULL; } } /*********************************************************************** * MLNGINFO */ /// @implemented void MLNGINFO::InitDesc() { if (m_bInitDesc) return; WCHAR szDesc[MAX_PATH], szImeFileName[MAX_PATH]; GetHKLDesctription(m_hKL, szDesc, (UINT)_countof(szDesc), szImeFileName, (UINT)_countof(szImeFileName)); SetDesc(szDesc); m_bInitDesc = TRUE; } /// @implemented void MLNGINFO::InitIcon() { if (m_bInitIcon) return; WCHAR szDesc[MAX_PATH], szImeFileName[MAX_PATH]; GetHKLDesctription(m_hKL, szDesc, (UINT)_countof(szDesc), szImeFileName, (UINT)_countof(szImeFileName)); SetDesc(szDesc); m_bInitDesc = TRUE; INT cxIcon, cyIcon; InatGetIconSize(&cxIcon, &cyIcon); HICON hIcon = NULL; if (szImeFileName[0]) hIcon = GetIconFromFile(cxIcon, cyIcon, szImeFileName, 0); if (!hIcon) hIcon = InatCreateIcon(LOWORD(m_hKL)); if (hIcon) { m_iIconIndex = InatAddIcon(hIcon); ::DestroyIcon(hIcon); } m_bInitIcon = TRUE; } /// @implemented LPCWSTR MLNGINFO::GetDesc() { if (!m_bInitDesc) InitDesc(); return m_szDesc; } /// @implemented void MLNGINFO::SetDesc(LPCWSTR pszDesc) { StringCchCopyW(m_szDesc, _countof(m_szDesc), pszDesc); } /// @implemented INT MLNGINFO::GetIconIndex() { if (!m_bInitIcon) InitIcon(); return m_iIconIndex; } /*********************************************************************** * CStaticIconList */ /// @implemented void CStaticIconList::Init(INT cxIcon, INT cyIcon) { ::EnterCriticalSection(&g_cs); s_cx = cxIcon; s_cy = cyIcon; ::LeaveCriticalSection(&g_cs); } /// @implemented INT CStaticIconList::AddIcon(HICON hIcon) { ::EnterCriticalSection(&g_cs); INT iItem = -1; HICON hCopyIcon = ::CopyIcon(hIcon); if (hCopyIcon) { if (g_IconList.Add(hIcon)) iItem = INT(g_IconList.size() - 1); } ::LeaveCriticalSection(&g_cs); return iItem; } /// @implemented HICON CStaticIconList::ExtractIcon(INT iIcon) { HICON hCopyIcon = NULL; ::EnterCriticalSection(&g_cs); if (iIcon <= (INT)g_IconList.size()) hCopyIcon = ::CopyIcon(g_IconList[iIcon]); ::LeaveCriticalSection(&g_cs); return hCopyIcon; } /// @implemented void CStaticIconList::GetIconSize(INT *pcx, INT *pcy) { ::EnterCriticalSection(&g_cs); *pcx = s_cx; *pcy = s_cy; ::LeaveCriticalSection(&g_cs); } /// @implemented INT CStaticIconList::GetImageCount() { ::EnterCriticalSection(&g_cs); INT cItems = (INT)g_IconList.size(); ::LeaveCriticalSection(&g_cs); return cItems; } /// @implemented void CStaticIconList::RemoveAll(BOOL bNoLock) { if (!bNoLock) ::EnterCriticalSection(&g_cs); for (size_t iItem = 0; iItem < g_IconList.size(); ++iItem) { ::DestroyIcon(g_IconList[iItem]); } clear(); if (!bNoLock) ::LeaveCriticalSection(&g_cs); } /// @implemented static BOOL CheckMlngInfo(VOID) { if (!g_pMlngInfo) return TRUE; // Needs creation INT cKLs = ::GetKeyboardLayoutList(0, NULL); if (cKLs != TF_MlngInfoCount()) return TRUE; // Needs refresh if (!cKLs) return FALSE; HKL *phKLs = (HKL*)cicMemAlloc(cKLs * sizeof(HKL)); if (!phKLs) return FALSE; ::GetKeyboardLayoutList(cKLs, phKLs); assert(g_pMlngInfo); BOOL ret = FALSE; for (INT iKL = 0; iKL < cKLs; ++iKL) { if ((*g_pMlngInfo)[iKL].m_hKL != phKLs[iKL]) { ret = TRUE; // Needs refresh break; } } cicMemFree(phKLs); return ret; } /// @implemented static VOID DestroyMlngInfo(VOID) { if (!g_pMlngInfo) return; delete g_pMlngInfo; g_pMlngInfo = NULL; } /// @implemented static VOID CreateMlngInfo(VOID) { if (!g_pMlngInfo) { g_pMlngInfo = new(cicNoThrow) CicArray(); if (!g_pMlngInfo) return; } if (!EnsureIconImageList()) return; INT cKLs = ::GetKeyboardLayoutList(0, NULL); HKL *phKLs = (HKL*)cicMemAllocClear(cKLs * sizeof(HKL)); if (!phKLs) return; ::GetKeyboardLayoutList(cKLs, phKLs); for (INT iKL = 0; iKL < cKLs; ++iKL) { MLNGINFO& info = (*g_pMlngInfo)[iKL]; info.m_hKL = phKLs[iKL]; info.m_bInitDesc = FALSE; info.m_bInitIcon = FALSE; } cicMemFree(phKLs); } /*********************************************************************** * TF_InitMlngInfo (MSCTF.@) * * @implemented */ EXTERN_C VOID WINAPI TF_InitMlngInfo(VOID) { TRACE("()\n"); ::EnterCriticalSection(&g_cs); if (CheckMlngInfo()) { DestroyMlngInfo(); CreateMlngInfo(); } ::LeaveCriticalSection(&g_cs); } /*********************************************************************** * TF_MlngInfoCount (MSCTF.@) * * @implemented */ EXTERN_C INT WINAPI TF_MlngInfoCount(VOID) { TRACE("()\n"); if (!g_pMlngInfo) return 0; return (INT)g_pMlngInfo->size(); } /*********************************************************************** * TF_InatExtractIcon (MSCTF.@) * * @implemented */ EXTERN_C HICON WINAPI TF_InatExtractIcon(_In_ INT iKL) { TRACE("(%d)\n", iKL); return g_IconList.ExtractIcon(iKL); } /*********************************************************************** * TF_GetMlngIconIndex (MSCTF.@) * * @implemented */ EXTERN_C INT WINAPI TF_GetMlngIconIndex(_In_ INT iKL) { TRACE("(%d)\n", iKL); INT iIcon = -1; ::EnterCriticalSection(&g_cs); assert(g_pMlngInfo); if (iKL < (INT)g_pMlngInfo->size()) iIcon = (*g_pMlngInfo)[iKL].GetIconIndex(); ::LeaveCriticalSection(&g_cs); return iIcon; } /*********************************************************************** * TF_GetMlngHKL (MSCTF.@) * * @implemented */ EXTERN_C BOOL WINAPI TF_GetMlngHKL( _In_ INT iKL, _Out_opt_ HKL *phKL, _Out_opt_ LPWSTR pszDesc, _In_ INT cchDesc) { TRACE("(%d, %p, %p, %d)\n", iKL, phKL, pszDesc, cchDesc); BOOL ret = FALSE; ::EnterCriticalSection(&g_cs); assert(g_pMlngInfo); if (iKL < (INT)g_pMlngInfo->size()) { MLNGINFO& info = (*g_pMlngInfo)[iKL]; if (phKL) *phKL = info.m_hKL; if (pszDesc) StringCchCopyW(pszDesc, cchDesc, info.GetDesc()); ret = TRUE; } ::LeaveCriticalSection(&g_cs); return ret; }