xref: /reactos/dll/cpl/input/input_list.c (revision 1734f297)
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     /* Process DELETED and EDITED entries */
399     for (pCurrent = _InputList; pCurrent != NULL; pCurrent = pCurrent->pNext)
400     {
401         if ((pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DELETED) ||
402             (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_EDITED))
403         {
404             /* Only unload the DELETED and EDITED entries */
405             if (UnloadKeyboardLayout(pCurrent->hkl))
406             {
407                 /* But the EDITED entries are used later */
408                 if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
409                 {
410                     InputList_RemoveNode(pCurrent);
411                 }
412             }
413         }
414     }
415 
416     /* Add the DEFAULT entry and set font substitutes */
417     for (pCurrent = _InputList; pCurrent != NULL; pCurrent = pCurrent->pNext)
418     {
419         if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
420             continue;
421 
422         if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DEFAULT)
423         {
424             bRet = InputList_SetFontSubstitutes(pCurrent->pLocale->dwId);
425             InputList_AddInputMethodToUserRegistry(hPreloadKey, hSubstKey, 1, pCurrent);
426 
427             /* Activate the DEFAULT entry */
428             ActivateKeyboardLayout(pCurrent->hkl, KLF_RESET);
429             break;
430         }
431     }
432 
433     /* Add entries except DEFAULT to registry */
434     dwNumber = 2;
435     for (pCurrent = _InputList; pCurrent != NULL; pCurrent = pCurrent->pNext)
436     {
437         if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
438             continue;
439         if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DEFAULT)
440             continue;
441 
442         InputList_AddInputMethodToUserRegistry(hPreloadKey, hSubstKey, dwNumber, pCurrent);
443 
444         ++dwNumber;
445     }
446 
447     /* Remove ADDED and EDITED flags */
448     for (pCurrent = _InputList; pCurrent != NULL; pCurrent = pCurrent->pNext)
449     {
450         pCurrent->wFlags &= ~(INPUT_LIST_NODE_FLAG_ADDED | INPUT_LIST_NODE_FLAG_EDITED);
451     }
452 
453     /* Change the default keyboard language */
454     if (SystemParametersInfoW(SPI_SETDEFAULTINPUTLANG, 0, &pCurrent->hkl, 0))
455     {
456         DWORD dwRecipients = BSM_ALLDESKTOPS | BSM_APPLICATIONS;
457 
458         BroadcastSystemMessageW(BSF_POSTMESSAGE,
459                                 &dwRecipients,
460                                 WM_INPUTLANGCHANGEREQUEST,
461                                 INPUTLANGCHANGE_SYSCHARSET,
462                                 (LPARAM)pCurrent->hkl);
463     }
464 
465     /* Retry to delete (in case of failure to delete the default keyboard) */
466     for (pCurrent = _InputList; pCurrent != NULL; pCurrent = pCurrent->pNext)
467     {
468         if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
469         {
470             UnloadKeyboardLayout(pCurrent->hkl);
471             InputList_RemoveNode(pCurrent);
472         }
473     }
474 
475     RegCloseKey(hPreloadKey);
476     RegCloseKey(hSubstKey);
477     return bRet;
478 }
479 
480 
481 BOOL
482 InputList_Add(LOCALE_LIST_NODE *pLocale, LAYOUT_LIST_NODE *pLayout)
483 {
484     WCHAR szIndicator[MAX_STR_LEN];
485     INPUT_LIST_NODE *pInput = NULL;
486 
487     if (pLocale == NULL || pLayout == NULL)
488     {
489         return FALSE;
490     }
491 
492     for (pInput = _InputList; pInput != NULL; pInput = pInput->pNext)
493     {
494         if (pInput->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
495             continue;
496 
497         if (pInput->pLocale == pLocale && pInput->pLayout == pLayout)
498         {
499             return FALSE; /* Already exists */
500         }
501     }
502 
503     pInput = InputList_AppendNode();
504     pInput->wFlags = INPUT_LIST_NODE_FLAG_ADDED;
505     pInput->pLocale = pLocale;
506     pInput->pLayout = pLayout;
507 
508     if (GetLocaleInfoW(LOWORD(pInput->pLocale->dwId),
509                        LOCALE_SABBREVLANGNAME | LOCALE_NOUSEROVERRIDE,
510                        szIndicator,
511                        ARRAYSIZE(szIndicator)))
512     {
513         size_t len = wcslen(szIndicator);
514 
515         if (len > 0)
516         {
517             szIndicator[len - 1] = 0;
518             pInput->pszIndicator = _wcsdup(szIndicator);
519         }
520     }
521 
522     return TRUE;
523 }
524 
525 
526 VOID
527 InputList_SetDefault(INPUT_LIST_NODE *pNode)
528 {
529     INPUT_LIST_NODE *pCurrent;
530 
531     if (pNode == NULL)
532         return;
533 
534     for (pCurrent = _InputList; pCurrent != NULL; pCurrent = pCurrent->pNext)
535     {
536         if (pCurrent == pNode)
537         {
538             pCurrent->wFlags |= INPUT_LIST_NODE_FLAG_DEFAULT;
539         }
540         else
541         {
542             pCurrent->wFlags &= ~INPUT_LIST_NODE_FLAG_DEFAULT;
543         }
544     }
545 }
546 
547 INPUT_LIST_NODE *
548 InputList_FindNextDefault(INPUT_LIST_NODE *pNode)
549 {
550     INPUT_LIST_NODE *pCurrent;
551 
552     for (pCurrent = pNode->pNext; pCurrent; pCurrent = pCurrent->pNext)
553     {
554         if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
555             continue;
556 
557         return pCurrent;
558     }
559 
560     for (pCurrent = pNode->pPrev; pCurrent; pCurrent = pCurrent->pPrev)
561     {
562         if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
563             continue;
564 
565         return pCurrent;
566     }
567 
568     return NULL;
569 }
570 
571 /*
572  * It marks the input method for deletion, but does not delete it directly.
573  * To apply the changes using InputList_Process()
574  */
575 BOOL
576 InputList_Remove(INPUT_LIST_NODE *pNode)
577 {
578     BOOL ret = FALSE;
579     BOOL bRemoveNode = FALSE;
580 
581     if (pNode == NULL)
582         return FALSE;
583 
584     if (pNode->wFlags & INPUT_LIST_NODE_FLAG_ADDED)
585     {
586         /*
587          * If the input method has been added to the list, but not yet written
588          * in the registry, then simply remove it from the list
589          */
590         bRemoveNode = TRUE;
591     }
592     else
593     {
594         pNode->wFlags |= INPUT_LIST_NODE_FLAG_DELETED;
595     }
596 
597     if (pNode->wFlags & INPUT_LIST_NODE_FLAG_DEFAULT)
598     {
599         INPUT_LIST_NODE *pCurrent = InputList_FindNextDefault(pNode);
600         if (pCurrent)
601             pCurrent->wFlags |= INPUT_LIST_NODE_FLAG_DEFAULT;
602 
603         pNode->wFlags &= ~INPUT_LIST_NODE_FLAG_DEFAULT;
604         ret = TRUE; /* default input is changed */
605     }
606 
607     if (bRemoveNode)
608     {
609         InputList_RemoveNode(pNode);
610     }
611 
612     return ret;
613 }
614 
615 BOOL
616 InputList_RemoveByLang(LANGID wLangId)
617 {
618     BOOL ret = FALSE;
619     INPUT_LIST_NODE *pCurrent;
620 
621 Retry:
622     for (pCurrent = _InputList; pCurrent; pCurrent = pCurrent->pNext)
623     {
624         if (pCurrent->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
625             continue;
626 
627         if (LOWORD(pCurrent->pLocale->dwId) == wLangId)
628         {
629             if (InputList_Remove(pCurrent))
630                 ret = TRUE; /* default input is changed */
631             goto Retry;
632         }
633     }
634 
635     return ret;
636 }
637 
638 VOID
639 InputList_Create(VOID)
640 {
641     INT iLayoutCount, iIndex;
642     WCHAR szIndicator[MAX_STR_LEN];
643     INPUT_LIST_NODE *pInput;
644     HKL *pLayoutList, hklDefault;
645 
646     SystemParametersInfoW(SPI_GETDEFAULTINPUTLANG, 0, &hklDefault, 0);
647 
648     iLayoutCount = GetKeyboardLayoutList(0, NULL);
649     pLayoutList = (HKL*) malloc(iLayoutCount * sizeof(HKL));
650 
651     if (!pLayoutList || GetKeyboardLayoutList(iLayoutCount, pLayoutList) <= 0)
652     {
653         free(pLayoutList);
654         return;
655     }
656 
657     for (iIndex = 0; iIndex < iLayoutCount; ++iIndex)
658     {
659         HKL hKL = pLayoutList[iIndex];
660         LOCALE_LIST_NODE *pLocale = LocaleList_GetByHkl(hKL);
661         LAYOUT_LIST_NODE *pLayout = LayoutList_GetByHkl(hKL);
662         if (!pLocale || !pLayout)
663             continue;
664 
665         pInput = InputList_AppendNode();
666         pInput->pLocale = pLocale;
667         pInput->pLayout = pLayout;
668         pInput->hkl     = hKL;
669 
670         if (pInput->hkl == hklDefault) /* Default HKL? */
671         {
672             pInput->wFlags |= INPUT_LIST_NODE_FLAG_DEFAULT;
673             hklDefault = NULL; /* No more default item */
674         }
675 
676         /* Get abbrev language name */
677         szIndicator[0] = 0;
678         if (GetLocaleInfoW(LOWORD(pInput->pLocale->dwId),
679                            LOCALE_SABBREVLANGNAME | LOCALE_NOUSEROVERRIDE,
680                            szIndicator,
681                            ARRAYSIZE(szIndicator)))
682         {
683             size_t len = wcslen(szIndicator);
684             if (len > 0)
685             {
686                 szIndicator[len - 1] = 0;
687                 pInput->pszIndicator = _wcsdup(szIndicator);
688             }
689         }
690     }
691 
692     free(pLayoutList);
693 }
694 
695 static INT InputList_Compare(INPUT_LIST_NODE *pNode1, INPUT_LIST_NODE *pNode2)
696 {
697     INT nCompare = _wcsicmp(pNode1->pszIndicator, pNode2->pszIndicator);
698     if (nCompare != 0)
699         return nCompare;
700 
701     return _wcsicmp(pNode1->pLayout->pszName, pNode2->pLayout->pszName);
702 }
703 
704 VOID InputList_Sort(VOID)
705 {
706     INPUT_LIST_NODE *pList = _InputList;
707     INPUT_LIST_NODE *pNext, *pPrev;
708     INPUT_LIST_NODE *pMinimum, *pNode;
709 
710     _InputList = NULL;
711 
712     while (pList)
713     {
714         /* Find the minimum node */
715         pMinimum = NULL;
716         for (pNode = pList; pNode; pNode = pNext)
717         {
718             pNext = pNode->pNext;
719 
720             if (pMinimum == NULL)
721             {
722                 pMinimum = pNode;
723             }
724             else if (InputList_Compare(pNode, pMinimum) < 0)
725             {
726                 pMinimum = pNode;
727             }
728         }
729 
730         // Remove pMinimum from pList
731         pNext = pMinimum->pNext;
732         pPrev = pMinimum->pPrev;
733         if (pNext)
734             pNext->pPrev = pPrev;
735         if (pPrev)
736             pPrev->pNext = pNext;
737         else
738             pList = pNext;
739 
740         // Append pMinimum to _InputList
741         if (!_InputList)
742         {
743             pMinimum->pPrev = pMinimum->pNext = NULL;
744             _InputList = pMinimum;
745         }
746         else
747         {
748             /* Find last node */
749             for (pNode = _InputList; pNode->pNext; pNode = pNode->pNext)
750             {
751                 NOTHING;
752             }
753 
754             /* Add to the end */
755             pNode->pNext = pMinimum;
756             pMinimum->pPrev = pNode;
757             pMinimum->pNext = NULL;
758         }
759     }
760 }
761 
762 INT
763 InputList_GetAliveCount(VOID)
764 {
765     INPUT_LIST_NODE *pNode;
766     INT nCount = 0;
767 
768     for (pNode = _InputList; pNode; pNode = pNode->pNext)
769     {
770         if (pNode->wFlags & INPUT_LIST_NODE_FLAG_DELETED)
771             continue;
772 
773         ++nCount;
774     }
775 
776     return nCount;
777 }
778 
779 INPUT_LIST_NODE*
780 InputList_GetFirst(VOID)
781 {
782     return _InputList;
783 }
784