xref: /reactos/dll/win32/msctf/mlng.cpp (revision 84344399)
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