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