Reactos
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}