xref: /reactos/dll/cpl/input/input_list.c (revision 37ccb9e5)
1 /*
2 * PROJECT:         input.dll
3 * FILE:            dll/cpl/input/input_list.c
4 * PURPOSE:         input.dll
5 * PROGRAMMERS:     Dmitry Chapyshev (dmitry@reactos.org)
6 *                  Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
7 */
8 
9 #include "input_list.h"
10 
11 typedef struct
12 {
13     PWCHAR FontName;
14     PWCHAR SubFontName;
15 } MUI_SUBFONT;
16 
17 #include "../../../base/setup/lib/muifonts.h"
18 
19 BOOL UpdateRegistryForFontSubstitutes(MUI_SUBFONT *pSubstitutes)
20 {
21     DWORD cbData;
22     HKEY hKey;
23     static const WCHAR pszKey[] =
24         L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes";
25 
26     if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, pszKey, 0, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)
27         return FALSE;
28 
29     /* Overwrite only */
30     for (; pSubstitutes->FontName; ++pSubstitutes)
31     {
32         cbData = (lstrlenW(pSubstitutes->SubFontName) + 1) * sizeof(WCHAR);
33         RegSetValueExW(hKey, pSubstitutes->FontName, 0,
34             REG_SZ, (LPBYTE)pSubstitutes->SubFontName, cbData);
35     }
36 
37     RegCloseKey(hKey);
38     return TRUE;
39 }
40 
41 VOID GetSystemLibraryPath(LPWSTR pszPath, INT cchPath, LPCWSTR pszFileName)
42 {
43     WCHAR szSysDir[MAX_PATH];
44     GetSystemDirectoryW(szSysDir, ARRAYSIZE(szSysDir));
45     StringCchPrintfW(pszPath, cchPath, L"%s\\%s", szSysDir, pszFileName);
46 }
47 
48 BOOL
49 InputList_SetFontSubstitutes(LCID dwLocaleId)
50 {
51     MUI_SUBFONT *pSubstitutes;
52     WORD wLangID, wPrimaryLangID, wSubLangID;
53 
54     wLangID = LANGIDFROMLCID(dwLocaleId);
55     wPrimaryLangID = PRIMARYLANGID(wLangID);
56     wSubLangID = SUBLANGID(wLangID);
57 
58     /* FIXME: Add more if necessary */
59     switch (wPrimaryLangID)
60     {
61         default:
62             pSubstitutes = LatinFonts;
63             break;
64         case LANG_AZERI:
65         case LANG_BELARUSIAN:
66         case LANG_BULGARIAN:
67         case LANG_KAZAK:
68         case LANG_RUSSIAN:
69         case LANG_SERBIAN:
70         case LANG_TATAR:
71         case LANG_UKRAINIAN:
72         case LANG_UZBEK:
73             pSubstitutes = CyrillicFonts;
74             break;
75         case LANG_GREEK:
76             pSubstitutes = GreekFonts;
77             break;
78         case LANG_HEBREW:
79             pSubstitutes = HebrewFonts;
80             break;
81         case LANG_CHINESE:
82             switch (wSubLangID)
83             {
84                 case SUBLANG_CHINESE_SIMPLIFIED:
85                 case SUBLANG_CHINESE_SINGAPORE:
86                     pSubstitutes = ChineseSimplifiedFonts;
87                     break;
88                 case SUBLANG_CHINESE_TRADITIONAL:
89                 case SUBLANG_CHINESE_HONGKONG:
90                 case SUBLANG_CHINESE_MACAU:
91                     pSubstitutes = ChineseTraditionalFonts;
92                     break;
93                 default:
94                     pSubstitutes = NULL;
95                     DebugBreak();
96                     break;
97             }
98             break;
99         case LANG_JAPANESE:
100             pSubstitutes = JapaneseFonts;
101             break;
102         case LANG_KOREAN:
103             pSubstitutes = KoreanFonts;
104             break;
105         case LANG_ARABIC:
106         case LANG_ARMENIAN:
107         case LANG_BENGALI:
108         case LANG_FARSI:
109         case LANG_GEORGIAN:
110         case LANG_GUJARATI:
111         case LANG_HINDI:
112         case LANG_KONKANI:
113         case LANG_MARATHI:
114         case LANG_PUNJABI:
115         case LANG_SANSKRIT:
116         case LANG_TAMIL:
117         case LANG_TELUGU:
118         case LANG_THAI:
119         case LANG_URDU:
120         case LANG_VIETNAMESE:
121             pSubstitutes = UnicodeFonts;
122             break;
123     }
124 
125     if (pSubstitutes)
126     {
127         UpdateRegistryForFontSubstitutes(pSubstitutes);
128         return TRUE;
129     }
130     return FALSE;
131 }
132 
133 static INPUT_LIST_NODE *_InputList = NULL;
134 
135 
136 static INPUT_LIST_NODE*
137 InputList_AppendNode(VOID)
138 {
139     INPUT_LIST_NODE *pCurrent;
140     INPUT_LIST_NODE *pNew;
141 
142     pNew = (INPUT_LIST_NODE*)malloc(sizeof(INPUT_LIST_NODE));
143     if (pNew == NULL)
144         return NULL;
145 
146     ZeroMemory(pNew, sizeof(INPUT_LIST_NODE));
147 
148     if (_InputList == NULL) /* Empty? */
149     {
150         _InputList = pNew;
151         return pNew;
152     }
153 
154     /* Find last node */
155     for (pCurrent = _InputList; pCurrent->pNext; pCurrent = pCurrent->pNext)
156     {
157         ;
158     }
159 
160     /* Add to the end */
161     pCurrent->pNext = pNew;
162     pNew->pPrev = pCurrent;
163 
164     return pNew;
165 }
166 
167 
168 static VOID
169 InputList_RemoveNode(INPUT_LIST_NODE *pNode)
170 {
171     INPUT_LIST_NODE *pCurrent = pNode;
172 
173     if (_InputList == NULL)
174         return;
175 
176     if (pCurrent != NULL)
177     {
178         INPUT_LIST_NODE *pNext = pCurrent->pNext;
179         INPUT_LIST_NODE *pPrev = pCurrent->pPrev;
180 
181         free(pCurrent->pszIndicator);
182         free(pCurrent);
183 
184         if (pNext != NULL)
185             pNext->pPrev = pPrev;
186 
187         if (pPrev != NULL)
188             pPrev->pNext = pNext;
189         else
190             _InputList = pNext;
191     }
192 }
193 
194 
195 VOID
196 InputList_Destroy(VOID)
197 {
198     INPUT_LIST_NODE *pCurrent;
199     INPUT_LIST_NODE *pNext;
200 
201     if (_InputList == NULL)
202         return;
203 
204     for (pCurrent = _InputList; pCurrent; pCurrent = pNext)
205     {
206         pNext = pCurrent->pNext;
207 
208         free(pCurrent->pszIndicator);
209         free(pCurrent);
210     }
211 
212     _InputList = NULL;
213 }
214 
215 
216 static BOOL
217 InputList_PrepareUserRegistry(PHKEY phPreloadKey, PHKEY phSubstKey)
218 {
219     BOOL bResult = FALSE;
220     HKEY hKey;
221 
222     *phPreloadKey = *phSubstKey = NULL;
223 
224     if (RegOpenKeyExW(HKEY_CURRENT_USER,
225                       L"Keyboard Layout",
226                       0,
227                       KEY_ALL_ACCESS,
228                       &hKey) == ERROR_SUCCESS)
229     {
230         RegDeleteKeyW(hKey, L"Preload");
231         RegDeleteKeyW(hKey, L"Substitutes");
232 
233         RegCloseKey(hKey);
234     }
235 
236     if (RegCreateKeyW(HKEY_CURRENT_USER, L"Keyboard Layout", &hKey) == ERROR_SUCCESS &&
237         RegCreateKeyW(hKey, L"Preload", phPreloadKey) == ERROR_SUCCESS &&
238         RegCreateKeyW(hKey, L"Substitutes", phSubstKey) == ERROR_SUCCESS)
239     {
240         bResult = TRUE;
241     }
242 
243     if (hKey)
244         RegCloseKey(hKey);
245 
246     return bResult;
247 }
248 
249 static BOOL
250 InputList_FindPreloadKLID(HKEY hPreloadKey, DWORD dwKLID)
251 {
252     DWORD dwNumber, dwType, cbValue;
253     WCHAR szNumber[16], szValue[KL_NAMELENGTH], szKLID[KL_NAMELENGTH];
254 
255     StringCchPrintfW(szKLID, ARRAYSIZE(szKLID), L"%08x", dwKLID);
256 
257     for (dwNumber = 1; dwNumber <= 1000; ++dwNumber)
258     {
259         StringCchPrintfW(szNumber, ARRAYSIZE(szNumber), L"%u", dwNumber);
260 
261         cbValue = ARRAYSIZE(szValue) * sizeof(WCHAR);
262         if (RegQueryValueExW(hPreloadKey, szNumber, NULL, &dwType,
263                              (LPBYTE)szValue, &cbValue) != ERROR_SUCCESS)
264         {
265             break;
266         }
267 
268         if (dwType != REG_SZ)
269             continue;
270 
271         szValue[ARRAYSIZE(szValue) - 1] = 0;
272         if (_wcsicmp(szKLID, szValue) == 0)
273             return TRUE;
274     }
275 
276     return FALSE;
277 }
278 
279 static BOOL
280 InputList_WriteSubst(HKEY hSubstKey, DWORD dwPhysicalKLID, DWORD dwLogicalKLID)
281 {
282     DWORD cbValue;
283     WCHAR szLogicalKLID[KL_NAMELENGTH], szPhysicalKLID[KL_NAMELENGTH];
284 
285     StringCchPrintfW(szLogicalKLID, ARRAYSIZE(szLogicalKLID), L"%08x", dwLogicalKLID);
286     StringCchPrintfW(szPhysicalKLID, ARRAYSIZE(szPhysicalKLID), L"%08x", dwPhysicalKLID);
287 
288     cbValue = (wcslen(szPhysicalKLID) + 1) * sizeof(WCHAR);
289     return RegSetValueExW(hSubstKey, szLogicalKLID, 0, REG_SZ, (LPBYTE)szPhysicalKLID,
290                           cbValue) == ERROR_SUCCESS;
291 }
292 
293 static DWORD
294 InputList_DoSubst(HKEY hPreloadKey, HKEY hSubstKey,
295                   DWORD dwPhysicalKLID, DWORD dwLogicalKLID)
296 {
297     DWORD iTrial;
298     BOOL bSubstNeeded = (dwPhysicalKLID != dwLogicalKLID) || (HIWORD(dwPhysicalKLID) != 0);
299 
300     for (iTrial = 1; iTrial <= 1000; ++iTrial)
301     {
302         if (!InputList_FindPreloadKLID(hPreloadKey, dwLogicalKLID)) /* Not found? */
303         {
304             if (bSubstNeeded)
305             {
306                 /* Write now */
307                 InputList_WriteSubst(hSubstKey, dwPhysicalKLID, dwLogicalKLID);
308             }
309             return dwLogicalKLID;
310         }
311 
312         bSubstNeeded = TRUE;
313 
314         /* Calculate the next logical KLID */
315         if (!IS_SUBST_KLID(dwLogicalKLID))
316         {
317             dwLogicalKLID |= SUBST_MASK;
318         }
319         else
320         {
321             WORD wLow = LOWORD(dwLogicalKLID);
322             WORD wHigh = HIWORD(dwLogicalKLID);
323             dwLogicalKLID = MAKELONG(wLow, wHigh + 1);
324         }
325     }
326 
327     return 0;
328 }
329 
330 static VOID
331 InputList_AddInputMethodToUserRegistry(
332     HKEY hPreloadKey,
333     HKEY hSubstKey,
334     DWORD dwNumber,
335     INPUT_LIST_NODE *pNode)
336 {
337     WCHAR szNumber[32], szLogicalKLID[KL_NAMELENGTH];
338     DWORD dwPhysicalKLID, dwLogicalKLID, cbValue;
339     HKL hKL = pNode->hkl;
340 
341     if (IS_IME_HKL(hKL)) /* IME? */
342     {
343         /* Do not substitute the IME KLID */
344         dwLogicalKLID = dwPhysicalKLID = HandleToUlong(hKL);
345     }
346     else
347     {
348         /* Substitute the KLID if necessary */
349         dwPhysicalKLID = pNode->pLayout->dwKLID;
350         dwLogicalKLID = pNode->pLocale->dwId;
351         dwLogicalKLID = InputList_DoSubst(hPreloadKey, hSubstKey, dwPhysicalKLID, dwLogicalKLID);
352     }
353 
354     /* Write the Preload value (number |--> logical KLID) */
355     StringCchPrintfW(szNumber, ARRAYSIZE(szNumber), L"%lu", dwNumber);
356     StringCchPrintfW(szLogicalKLID, ARRAYSIZE(szLogicalKLID), L"%08x", dwLogicalKLID);
357     cbValue = (wcslen(szLogicalKLID) + 1) * sizeof(WCHAR);
358     RegSetValueExW(hPreloadKey,
359                    szNumber,
360                    0,
361                    REG_SZ,
362                    (LPBYTE)szLogicalKLID,
363                    cbValue);
364 
365     if ((pNode->wFlags & INPUT_LIST_NODE_FLAG_ADDED) ||
366         (pNode->wFlags & INPUT_LIST_NODE_FLAG_EDITED))
367     {
368         UINT uFlags = KLF_SUBSTITUTE_OK | KLF_NOTELLSHELL;
369         if (pNode->wFlags & INPUT_LIST_NODE_FLAG_DEFAULT)
370             uFlags |= KLF_REPLACELANG;
371 
372         pNode->hkl = LoadKeyboardLayoutW(szLogicalKLID, uFlags);
373     }
374 }
375 
376 
377 /*
378  * Writes any changes in input methods to the registry
379  */
380 BOOL
381 InputList_Process(VOID)
382 {
383     INPUT_LIST_NODE *pCurrent;
384     DWORD dwNumber;
385     BOOL bRet = FALSE;
386     HKEY hPreloadKey, hSubstKey;
387 
388     if (!InputList_PrepareUserRegistry(&hPreloadKey, &hSubstKey))
389     {
390         if (hPreloadKey)
391             RegCloseKey(hPreloadKey);
392         if (hSubstKey)
393             RegCloseKey(hSubstKey);
394         return FALSE;
395     }
396 
397     /* Process DELETED and EDITED entries */
398     for (pCurrent = _InputList; pCurrent != NULL; pCurrent = pCurrent->pNext)
399     {
400         if ((pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DELETED) ||
401             (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_EDITED))
402         {
403             /* Only unload the DELETED and EDITED entries */
404             if (UnloadKeyboardLayout(pCurrent->hkl))
405             {
406                 /* But the EDITED entries are used later */
407                 if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
408                 {
409                     InputList_RemoveNode(pCurrent);
410                 }
411             }
412         }
413     }
414 
415     /* Add the DEFAULT entry and set font substitutes */
416     for (pCurrent = _InputList; pCurrent != NULL; pCurrent = pCurrent->pNext)
417     {
418         if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
419             continue;
420 
421         if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DEFAULT)
422         {
423             bRet = InputList_SetFontSubstitutes(pCurrent->pLocale->dwId);
424             InputList_AddInputMethodToUserRegistry(hPreloadKey, hSubstKey, 1, pCurrent);
425             break;
426         }
427     }
428 
429     /* Add entries except DEFAULT to registry */
430     dwNumber = 2;
431     for (pCurrent = _InputList; pCurrent != NULL; pCurrent = pCurrent->pNext)
432     {
433         if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
434             continue;
435         if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DEFAULT)
436             continue;
437 
438         InputList_AddInputMethodToUserRegistry(hPreloadKey, hSubstKey, dwNumber, pCurrent);
439 
440         ++dwNumber;
441     }
442 
443     /* Remove ADDED and EDITED flags */
444     for (pCurrent = _InputList; pCurrent != NULL; pCurrent = pCurrent->pNext)
445     {
446         pCurrent->wFlags &= ~(INPUT_LIST_NODE_FLAG_ADDED | INPUT_LIST_NODE_FLAG_EDITED);
447     }
448 
449     /* Change the default keyboard language */
450     if (SystemParametersInfoW(SPI_SETDEFAULTINPUTLANG, 0, &pCurrent->hkl, 0))
451     {
452         DWORD dwRecipients = BSM_ALLCOMPONENTS | BSM_ALLDESKTOPS;
453 
454         BroadcastSystemMessageW(BSF_POSTMESSAGE,
455                                 &dwRecipients,
456                                 WM_INPUTLANGCHANGEREQUEST,
457                                 0,
458                                 (LPARAM)pCurrent->hkl);
459     }
460 
461     /* Retry to delete (in case of failure to delete the default keyboard) */
462     for (pCurrent = _InputList; pCurrent != NULL; pCurrent = pCurrent->pNext)
463     {
464         if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
465         {
466             UnloadKeyboardLayout(pCurrent->hkl);
467             InputList_RemoveNode(pCurrent);
468         }
469     }
470 
471     RegCloseKey(hPreloadKey);
472     RegCloseKey(hSubstKey);
473     return bRet;
474 }
475 
476 
477 BOOL
478 InputList_Add(LOCALE_LIST_NODE *pLocale, LAYOUT_LIST_NODE *pLayout)
479 {
480     WCHAR szIndicator[MAX_STR_LEN];
481     INPUT_LIST_NODE *pInput = NULL;
482 
483     if (pLocale == NULL || pLayout == NULL)
484     {
485         return FALSE;
486     }
487 
488     for (pInput = _InputList; pInput != NULL; pInput = pInput->pNext)
489     {
490         if (pInput->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
491             continue;
492 
493         if (pInput->pLocale == pLocale && pInput->pLayout == pLayout)
494         {
495             return FALSE; /* Already exists */
496         }
497     }
498 
499     pInput = InputList_AppendNode();
500     pInput->wFlags = INPUT_LIST_NODE_FLAG_ADDED;
501     pInput->pLocale = pLocale;
502     pInput->pLayout = pLayout;
503 
504     if (GetLocaleInfoW(LOWORD(pInput->pLocale->dwId),
505                        LOCALE_SABBREVLANGNAME | LOCALE_NOUSEROVERRIDE,
506                        szIndicator,
507                        ARRAYSIZE(szIndicator)))
508     {
509         size_t len = wcslen(szIndicator);
510 
511         if (len > 0)
512         {
513             szIndicator[len - 1] = 0;
514             pInput->pszIndicator = _wcsdup(szIndicator);
515         }
516     }
517 
518     return TRUE;
519 }
520 
521 
522 VOID
523 InputList_SetDefault(INPUT_LIST_NODE *pNode)
524 {
525     INPUT_LIST_NODE *pCurrent;
526 
527     if (pNode == NULL)
528         return;
529 
530     for (pCurrent = _InputList; pCurrent != NULL; pCurrent = pCurrent->pNext)
531     {
532         if (pCurrent == pNode)
533         {
534             pCurrent->wFlags |= INPUT_LIST_NODE_FLAG_DEFAULT;
535         }
536         else
537         {
538             pCurrent->wFlags &= ~INPUT_LIST_NODE_FLAG_DEFAULT;
539         }
540     }
541 }
542 
543 INPUT_LIST_NODE *
544 InputList_FindNextDefault(INPUT_LIST_NODE *pNode)
545 {
546     INPUT_LIST_NODE *pCurrent;
547 
548     for (pCurrent = pNode->pNext; pCurrent; pCurrent = pCurrent->pNext)
549     {
550         if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
551             continue;
552 
553         return pCurrent;
554     }
555 
556     for (pCurrent = pNode->pPrev; pCurrent; pCurrent = pCurrent->pPrev)
557     {
558         if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
559             continue;
560 
561         return pCurrent;
562     }
563 
564     return NULL;
565 }
566 
567 /*
568  * It marks the input method for deletion, but does not delete it directly.
569  * To apply the changes using InputList_Process()
570  */
571 VOID
572 InputList_Remove(INPUT_LIST_NODE *pNode)
573 {
574     BOOL bRemoveNode = FALSE;
575 
576     if (pNode == NULL)
577         return;
578 
579     if (pNode->wFlags & INPUT_LIST_NODE_FLAG_ADDED)
580     {
581         /*
582          * If the input method has been added to the list, but not yet written
583          * in the registry, then simply remove it from the list
584          */
585         bRemoveNode = TRUE;
586     }
587     else
588     {
589         pNode->wFlags |= INPUT_LIST_NODE_FLAG_DELETED;
590     }
591 
592     if (pNode->wFlags & INPUT_LIST_NODE_FLAG_DEFAULT)
593     {
594         INPUT_LIST_NODE *pCurrent = InputList_FindNextDefault(pNode);
595         if (pCurrent)
596             pCurrent->wFlags |= INPUT_LIST_NODE_FLAG_DEFAULT;
597 
598         pNode->wFlags &= ~INPUT_LIST_NODE_FLAG_DEFAULT;
599     }
600 
601     if (bRemoveNode)
602     {
603         InputList_RemoveNode(pNode);
604     }
605 }
606 
607 
608 VOID
609 InputList_Create(VOID)
610 {
611     INT iLayoutCount, iIndex;
612     WCHAR szIndicator[MAX_STR_LEN];
613     INPUT_LIST_NODE *pInput;
614     HKL *pLayoutList, hklDefault;
615 
616     SystemParametersInfoW(SPI_GETDEFAULTINPUTLANG, 0, &hklDefault, 0);
617 
618     iLayoutCount = GetKeyboardLayoutList(0, NULL);
619     pLayoutList = (HKL*) malloc(iLayoutCount * sizeof(HKL));
620 
621     if (!pLayoutList || GetKeyboardLayoutList(iLayoutCount, pLayoutList) <= 0)
622     {
623         free(pLayoutList);
624         return;
625     }
626 
627     for (iIndex = 0; iIndex < iLayoutCount; ++iIndex)
628     {
629         HKL hKL = pLayoutList[iIndex];
630         LOCALE_LIST_NODE *pLocale = LocaleList_GetByHkl(hKL);
631         LAYOUT_LIST_NODE *pLayout = LayoutList_GetByHkl(hKL);
632         if (!pLocale || !pLayout)
633             continue;
634 
635         pInput = InputList_AppendNode();
636         pInput->pLocale = pLocale;
637         pInput->pLayout = pLayout;
638         pInput->hkl     = hKL;
639 
640         if (pInput->hkl == hklDefault) /* Default HKL? */
641         {
642             pInput->wFlags |= INPUT_LIST_NODE_FLAG_DEFAULT;
643             hklDefault = NULL; /* No more default item */
644         }
645 
646         /* Get abbrev language name */
647         szIndicator[0] = 0;
648         if (GetLocaleInfoW(LOWORD(pInput->pLocale->dwId),
649                            LOCALE_SABBREVLANGNAME | LOCALE_NOUSEROVERRIDE,
650                            szIndicator,
651                            ARRAYSIZE(szIndicator)))
652         {
653             size_t len = wcslen(szIndicator);
654             if (len > 0)
655             {
656                 szIndicator[len - 1] = 0;
657                 pInput->pszIndicator = _wcsdup(szIndicator);
658             }
659         }
660     }
661 
662     free(pLayoutList);
663 }
664 
665 
666 INPUT_LIST_NODE*
667 InputList_GetFirst(VOID)
668 {
669     return _InputList;
670 }
671