xref: /reactos/win32ss/user/ntuser/kbdlayout.c (revision 565bf9e3)
1 /*
2  * PROJECT:         ReactOS Win32k subsystem
3  * LICENSE:         GPL - See COPYING in the top level directory
4  * FILE:            win32ss/user/ntuser/kbdlayout.c
5  * PURPOSE:         Keyboard layout management
6  * COPYRIGHT:       Copyright 2007 Saveliy Tretiakov
7  *                  Copyright 2008 Colin Finck
8  *                  Copyright 2011 Rafal Harabien
9  *                  Copyright 2022 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
10  */
11 
12 #include <win32k.h>
13 #include <ddk/immdev.h>
14 
15 // Was included only because of CP_ACP and required  the
16 // definition of SYSTEMTIME in ndk\rtltypes.h
17 //#include <winnls.h>
18 #define CP_ACP 0
19 
20 DBG_DEFAULT_CHANNEL(UserKbdLayout);
21 
22 PKL gspklBaseLayout = NULL; /* FIXME: Please move this to pWinSta->spklList */
23 PKBDFILE gpkfList = NULL;
24 DWORD gSystemFS = 0;
25 UINT gSystemCPCharSet = 0;
26 DWORD gLCIDSentToShell = 0;
27 
28 typedef PVOID (*PFN_KBDLAYERDESCRIPTOR)(VOID);
29 
30 /* PRIVATE FUNCTIONS ******************************************************/
31 
32 /*
33  * Retrieves a PKL by an input locale identifier (HKL).
34  * @implemented
35  * Win: HKLtoPKL
36  */
37 PKL FASTCALL IntHKLtoPKL(_Inout_ PTHREADINFO pti, _In_ HKL hKL)
38 {
39     PKL pFirstKL, pKL;
40 
41     pFirstKL = pti->KeyboardLayout;
42     if (!pFirstKL)
43         return NULL;
44 
45     pKL = pFirstKL;
46 
47     /* hKL can have special value HKL_NEXT or HKL_PREV */
48     if (hKL == (HKL)(ULONG_PTR)HKL_NEXT) /* Looking forward */
49     {
50         do
51         {
52             pKL = pKL->pklNext;
53             if (!(pKL->dwKL_Flags & KLF_UNLOAD))
54                 return pKL;
55         } while (pKL != pFirstKL);
56     }
57     else if (hKL == (HKL)(ULONG_PTR)HKL_PREV) /* Looking backward */
58     {
59         do
60         {
61             pKL = pKL->pklPrev;
62             if (!(pKL->dwKL_Flags & KLF_UNLOAD))
63                 return pKL;
64         } while (pKL != pFirstKL);
65     }
66     else if (HIWORD(hKL)) /* hKL is a full input locale identifier */
67     {
68         /* No KLF_UNLOAD check */
69         do
70         {
71             if (pKL->hkl == hKL)
72                 return pKL;
73 
74             pKL = pKL->pklNext;
75         } while (pKL != pFirstKL);
76     }
77     else  /* Language only specified */
78     {
79         /* No KLF_UNLOAD check */
80         do
81         {
82             if (LOWORD(pKL->hkl) == LOWORD(hKL)) /* Low word is language ID */
83                 return pKL;
84 
85             pKL = pKL->pklNext;
86         } while (pKL != pFirstKL);
87     }
88 
89     return NULL;
90 }
91 
92 /*
93  * A helper function for NtUserGetKeyboardLayoutList.
94  * @implemented
95  * Win: _GetKeyboardLayoutList
96  */
97 static UINT APIENTRY
98 IntGetKeyboardLayoutList(
99     _Inout_ PWINSTATION_OBJECT pWinSta,
100     _In_ ULONG nBuff,
101     _Out_ HKL *pHklBuff)
102 {
103     UINT ret = 0;
104     PKL pKL, pFirstKL;
105 
106     pFirstKL = gspklBaseLayout; /* FIXME: Use pWinSta->spklList instead */
107     if (!pWinSta || !pFirstKL)
108         return 0;
109 
110     pKL = pFirstKL;
111 
112     if (nBuff == 0)
113     {
114         /* Count the effective PKLs */
115         do
116         {
117             if (!(pKL->dwKL_Flags & KLF_UNLOAD))
118                 ++ret;
119             pKL = pKL->pklNext;
120         } while (pKL != pFirstKL);
121     }
122     else
123     {
124         /* Copy the effective HKLs to pHklBuff */
125         do
126         {
127             if (!(pKL->dwKL_Flags & KLF_UNLOAD))
128             {
129                 *pHklBuff = pKL->hkl;
130                 ++pHklBuff;
131                 ++ret;
132                 --nBuff;
133 
134                 if (nBuff == 0)
135                     break;
136             }
137             pKL = pKL->pklNext;
138         } while (pKL != pFirstKL);
139     }
140 
141     return ret;
142 }
143 
144 #if 0 && DBG
145 
146 static VOID
147 DumpKbdLayout(
148     IN PKBDTABLES pKbdTbl)
149 {
150     PVK_TO_BIT pVkToBit;
151     PVK_TO_WCHAR_TABLE pVkToWchTbl;
152     PVSC_VK pVscVk;
153     ULONG i;
154 
155     DbgPrint("Kbd layout: fLocaleFlags %x bMaxVSCtoVK %x\n",
156              pKbdTbl->fLocaleFlags, pKbdTbl->bMaxVSCtoVK);
157     DbgPrint("wMaxModBits %x\n",
158              pKbdTbl->pCharModifiers ? pKbdTbl->pCharModifiers->wMaxModBits
159                                      : 0);
160 
161     if (pKbdTbl->pCharModifiers)
162     {
163         pVkToBit = pKbdTbl->pCharModifiers->pVkToBit;
164         if (pVkToBit)
165         {
166             for (; pVkToBit->Vk; ++pVkToBit)
167             {
168                 DbgPrint("VkToBit %x -> %x\n", pVkToBit->Vk, pVkToBit->ModBits);
169             }
170         }
171 
172         for (i = 0; i <= pKbdTbl->pCharModifiers->wMaxModBits; ++i)
173         {
174             DbgPrint("ModNumber %x -> %x\n", i, pKbdTbl->pCharModifiers->ModNumber[i]);
175         }
176     }
177 
178     pVkToWchTbl = pKbdTbl->pVkToWcharTable;
179     if (pVkToWchTbl)
180     {
181         for (; pVkToWchTbl->pVkToWchars; ++pVkToWchTbl)
182         {
183             PVK_TO_WCHARS1 pVkToWch = pVkToWchTbl->pVkToWchars;
184 
185             DbgPrint("pVkToWchTbl nModifications %x cbSize %x\n",
186                      pVkToWchTbl->nModifications, pVkToWchTbl->cbSize);
187             if (pVkToWch)
188             {
189                 while (pVkToWch->VirtualKey)
190                 {
191                     DbgPrint("pVkToWch VirtualKey %x Attributes %x wc { ",
192                              pVkToWch->VirtualKey, pVkToWch->Attributes);
193                     for (i = 0; i < pVkToWchTbl->nModifications; ++i)
194                     {
195                         DbgPrint("%x ", pVkToWch->wch[i]);
196                     }
197                     DbgPrint("}\n");
198                     pVkToWch = (PVK_TO_WCHARS1)(((PBYTE)pVkToWch) + pVkToWchTbl->cbSize);
199                 }
200             }
201         }
202     }
203 
204 // TODO: DeadKeys, KeyNames, KeyNamesExt, KeyNamesDead
205 
206     DbgPrint("pusVSCtoVK: { ");
207     if (pKbdTbl->pusVSCtoVK)
208     {
209         for (i = 0; i < pKbdTbl->bMaxVSCtoVK; ++i)
210         {
211             DbgPrint("%x -> %x, ", i, pKbdTbl->pusVSCtoVK[i]);
212         }
213     }
214     DbgPrint("}\n");
215 
216     DbgPrint("pVSCtoVK_E0: { ");
217     pVscVk = pKbdTbl->pVSCtoVK_E0;
218     if (pVscVk)
219     {
220         for (; pVscVk->Vsc; ++pVscVk)
221         {
222             DbgPrint("%x -> %x, ", pVscVk->Vsc, pVscVk->Vk);
223         }
224     }
225     DbgPrint("}\n");
226 
227     DbgPrint("pVSCtoVK_E1: { ");
228     pVscVk = pKbdTbl->pVSCtoVK_E1;
229     if (pVscVk)
230     {
231         for (; pVscVk->Vsc; ++pVscVk)
232         {
233             DbgPrint("%x -> %x, ", pVscVk->Vsc, pVscVk->Vk);
234         }
235     }
236     DbgPrint("}\n");
237 
238 // TODO: Ligatures
239 }
240 
241 #endif // DBG
242 
243 
244 /*
245  * UserLoadKbdDll
246  *
247  * Loads keyboard layout DLL and gets address to KbdTables
248  */
249 static BOOL
250 UserLoadKbdDll(WCHAR *pwszLayoutPath,
251                HANDLE *phModule,
252                PKBDTABLES *pKbdTables)
253 {
254     PFN_KBDLAYERDESCRIPTOR pfnKbdLayerDescriptor;
255 
256     /* Load keyboard layout DLL */
257     TRACE("Loading Keyboard DLL %ws\n", pwszLayoutPath);
258     *phModule = EngLoadImage(pwszLayoutPath);
259     if (!(*phModule))
260     {
261         ERR("Failed to load dll %ws\n", pwszLayoutPath);
262         return FALSE;
263     }
264 
265     /* Find KbdLayerDescriptor function and get layout tables */
266     TRACE("Loaded %ws\n", pwszLayoutPath);
267     pfnKbdLayerDescriptor = EngFindImageProcAddress(*phModule, "KbdLayerDescriptor");
268 
269     /* FIXME: Windows reads file instead of executing!
270               It's not safe to kbdlayout DLL in kernel mode! */
271 
272     if (pfnKbdLayerDescriptor)
273         *pKbdTables = pfnKbdLayerDescriptor();
274     else
275         ERR("Error: %ws has no KbdLayerDescriptor()\n", pwszLayoutPath);
276 
277     if (!pfnKbdLayerDescriptor || !*pKbdTables)
278     {
279         ERR("Failed to load the keyboard layout.\n");
280         EngUnloadImage(*phModule);
281         return FALSE;
282     }
283 
284 #if 0 && DBG
285     /* Dump keyboard layout */
286     DumpKbdLayout(*pKbdTables);
287 #endif
288 
289     return TRUE;
290 }
291 
292 /*
293  * UserLoadKbdFile
294  *
295  * Loads keyboard layout DLL and creates KBDFILE object
296  */
297 static PKBDFILE
298 UserLoadKbdFile(PUNICODE_STRING pwszKLID)
299 {
300     PKBDFILE pkf, pRet = NULL;
301     NTSTATUS Status;
302     ULONG cbSize;
303     HKEY hKey = NULL;
304     WCHAR wszLayoutPath[MAX_PATH] = L"\\SystemRoot\\System32\\";
305     WCHAR wszLayoutRegKey[256] = L"\\REGISTRY\\Machine\\SYSTEM\\CurrentControlSet\\"
306                                  L"Control\\Keyboard Layouts\\";
307 
308     /* Create keyboard layout file object */
309     pkf = UserCreateObject(gHandleTable, NULL, NULL, NULL, TYPE_KBDFILE, sizeof(KBDFILE));
310     if (!pkf)
311     {
312         ERR("Failed to create object!\n");
313         return NULL;
314     }
315 
316     /* Set keyboard layout name */
317     swprintf(pkf->awchKF, L"%wZ", pwszKLID);
318 
319     /* Open layout registry key */
320     RtlStringCbCatW(wszLayoutRegKey, sizeof(wszLayoutRegKey), pkf->awchKF);
321     Status = RegOpenKey(wszLayoutRegKey, &hKey);
322     if (!NT_SUCCESS(Status))
323     {
324         ERR("Failed to open keyboard layouts registry key %ws (%lx)\n", wszLayoutRegKey, Status);
325         goto cleanup;
326     }
327 
328     /* Read filename of layout DLL */
329     cbSize = (ULONG)(sizeof(wszLayoutPath) - wcslen(wszLayoutPath)*sizeof(WCHAR));
330     Status = RegQueryValue(hKey,
331                            L"Layout File",
332                            REG_SZ,
333                            wszLayoutPath + wcslen(wszLayoutPath),
334                            &cbSize);
335 
336     if (!NT_SUCCESS(Status))
337     {
338         ERR("Can't get layout filename for %wZ (%lx)\n", pwszKLID, Status);
339         goto cleanup;
340     }
341 
342     /* Load keyboard file now */
343     if (!UserLoadKbdDll(wszLayoutPath, &pkf->hBase, &pkf->pKbdTbl))
344     {
345         ERR("Failed to load %ws dll!\n", wszLayoutPath);
346         goto cleanup;
347     }
348 
349     /* Update next field */
350     pkf->pkfNext = gpkfList;
351     gpkfList = pkf;
352 
353     /* Return keyboard file */
354     pRet = pkf;
355 
356 cleanup:
357     if (hKey)
358         ZwClose(hKey);
359     if (pkf)
360         UserDereferenceObject(pkf); // we dont need ptr anymore
361     if (!pRet)
362     {
363         /* We have failed - destroy created object */
364         if (pkf)
365             UserDeleteObject(pkf->head.h, TYPE_KBDFILE);
366     }
367 
368     return pRet;
369 }
370 
371 /*
372  * co_UserLoadKbdLayout
373  *
374  * Loads keyboard layout and creates KL object
375  */
376 static PKL
377 co_UserLoadKbdLayout(PUNICODE_STRING pustrKLID, HKL hKL)
378 {
379     LCID lCid;
380     CHARSETINFO cs;
381     PKL pKl;
382 
383     /* Create keyboard layout object */
384     pKl = UserCreateObject(gHandleTable, NULL, NULL, NULL, TYPE_KBDLAYOUT, sizeof(KL));
385     if (!pKl)
386     {
387         ERR("Failed to create object!\n");
388         return NULL;
389     }
390 
391     pKl->hkl = hKL;
392     pKl->spkf = UserLoadKbdFile(pustrKLID);
393 
394     /* Dereference keyboard layout */
395     UserDereferenceObject(pKl);
396 
397     /* If we failed, remove KL object */
398     if (!pKl->spkf)
399     {
400         ERR("UserLoadKbdFile(%wZ) failed!\n", pustrKLID);
401         UserDeleteObject(pKl->head.h, TYPE_KBDLAYOUT);
402         return NULL;
403     }
404 
405     // Up to Language Identifiers..
406     if (!NT_SUCCESS(RtlUnicodeStringToInteger(pustrKLID, 16, (PULONG)&lCid)))
407     {
408         ERR("RtlUnicodeStringToInteger failed for '%wZ'\n", pustrKLID);
409         UserDeleteObject(pKl->head.h, TYPE_KBDLAYOUT);
410         return NULL;
411     }
412 
413     TRACE("Language Identifiers %wZ LCID 0x%x\n", pustrKLID, lCid);
414     if (co_IntGetCharsetInfo(lCid, &cs))
415     {
416        pKl->iBaseCharset = cs.ciCharset;
417        pKl->dwFontSigs = cs.fs.fsCsb[0];
418        pKl->CodePage = (USHORT)cs.ciACP;
419        TRACE("Charset %u Font Sig %lu CodePage %u\n",
420              pKl->iBaseCharset, pKl->dwFontSigs, pKl->CodePage);
421     }
422     else
423     {
424        pKl->iBaseCharset = ANSI_CHARSET;
425        pKl->dwFontSigs = FS_LATIN1;
426        pKl->CodePage = CP_ACP;
427     }
428 
429     // Set initial system character set and font signature.
430     if (gSystemFS == 0)
431     {
432        gSystemCPCharSet = pKl->iBaseCharset;
433        gSystemFS = pKl->dwFontSigs;
434     }
435 
436     return pKl;
437 }
438 
439 /*
440  * UnloadKbdFile
441  *
442  * Destroys specified Keyboard File object
443  */
444 static
445 VOID
446 UnloadKbdFile(_In_ PKBDFILE pkf)
447 {
448     PKBDFILE *ppkfLink = &gpkfList;
449     NT_ASSERT(pkf != NULL);
450 
451     /* Find previous object */
452     while (*ppkfLink)
453     {
454         if (*ppkfLink == pkf)
455             break;
456 
457         ppkfLink = &(*ppkfLink)->pkfNext;
458     }
459 
460     if (*ppkfLink == pkf)
461         *ppkfLink = pkf->pkfNext;
462 
463     EngUnloadImage(pkf->hBase);
464     UserDeleteObject(pkf->head.h, TYPE_KBDFILE);
465 }
466 
467 /*
468  * UserUnloadKbl
469  *
470  * Unloads specified Keyboard Layout if possible
471  */
472 BOOL
473 UserUnloadKbl(PKL pKl)
474 {
475     /* According to msdn, UnloadKeyboardLayout can fail
476        if the keyboard layout identifier was preloaded. */
477     if (pKl == gspklBaseLayout)
478     {
479         if (pKl->pklNext == pKl->pklPrev)
480         {
481             /* There is only one layout */
482             return FALSE;
483         }
484 
485         /* Set next layout as default */
486         gspklBaseLayout = pKl->pklNext;
487     }
488 
489     if (pKl->head.cLockObj > 1)
490     {
491         /* Layout is used by other threads */
492         pKl->dwKL_Flags |= KLF_UNLOAD;
493         return FALSE;
494     }
495 
496     /* Unload the layout */
497     pKl->pklPrev->pklNext = pKl->pklNext;
498     pKl->pklNext->pklPrev = pKl->pklPrev;
499     UnloadKbdFile(pKl->spkf);
500     if (pKl->piiex)
501     {
502         ExFreePoolWithTag(pKl->piiex, USERTAG_IME);
503     }
504     UserDeleteObject(pKl->head.h, TYPE_KBDLAYOUT);
505     return TRUE;
506 }
507 
508 /*
509  * W32kGetDefaultKeyLayout
510  *
511  * Returns default layout for new threads
512  */
513 PKL
514 W32kGetDefaultKeyLayout(VOID)
515 {
516     PKL pKl = gspklBaseLayout;
517 
518     if (!pKl)
519         return NULL;
520 
521     /* Return not unloaded layout */
522     do
523     {
524         if (!(pKl->dwKL_Flags & KLF_UNLOAD))
525             return pKl;
526 
527         pKl = pKl->pklPrev; /* Confirmed on Win2k */
528     } while(pKl != gspklBaseLayout);
529 
530     /* We have not found proper KL */
531     return NULL;
532 }
533 
534 /*
535  * UserHklToKbl
536  *
537  * Gets KL object from hkl value
538  */
539 PKL
540 NTAPI
541 UserHklToKbl(HKL hKl)
542 {
543     PKL pKl = gspklBaseLayout;
544 
545     if (!gspklBaseLayout)
546         return NULL;
547 
548     do
549     {
550         if (pKl->hkl == hKl)
551             return pKl;
552 
553         pKl = pKl->pklNext;
554     } while (pKl != gspklBaseLayout);
555 
556     return NULL;
557 }
558 
559 // Win: ReorderKeyboardLayouts
560 VOID FASTCALL
561 IntReorderKeyboardLayouts(
562     _Inout_ PWINSTATION_OBJECT pWinSta,
563     _Inout_ PKL pNewKL)
564 {
565     PKL pOldKL = gspklBaseLayout;
566 
567     if ((pWinSta->Flags & WSS_NOIO) || pNewKL == pOldKL)
568         return;
569 
570     pNewKL->pklPrev->pklNext = pNewKL->pklNext;
571     pNewKL->pklNext->pklPrev = pNewKL->pklPrev;
572     pNewKL->pklNext = pOldKL;
573     pNewKL->pklPrev = pOldKL->pklPrev;
574     pOldKL->pklPrev->pklNext = pNewKL;
575     pOldKL->pklPrev = pNewKL;
576     gspklBaseLayout = pNewKL; /* Should we use UserAssignmentLock? */
577 }
578 
579 /*
580  * UserSetDefaultInputLang
581  *
582  * Sets default keyboard layout for system. Called from UserSystemParametersInfo.
583  */
584 BOOL
585 NTAPI
586 UserSetDefaultInputLang(HKL hKl)
587 {
588     PKL pKl;
589 
590     pKl = UserHklToKbl(hKl);
591     if (!pKl)
592         return FALSE;
593 
594     IntReorderKeyboardLayouts(IntGetProcessWindowStation(NULL), pKl);
595     return TRUE;
596 }
597 
598 /*
599  * co_UserActivateKbl
600  *
601  * Activates given layout in specified thread
602  */
603 static PKL
604 co_UserActivateKbl(PTHREADINFO pti, PKL pKl, UINT Flags)
605 {
606     PKL pklPrev;
607     PWND pWnd;
608 
609     pklPrev = pti->KeyboardLayout;
610 
611     UserAssignmentLock((PVOID*)&(pti->KeyboardLayout), pKl);
612     pti->pClientInfo->hKL = pKl->hkl;
613 
614     if (Flags & KLF_SETFORPROCESS)
615     {
616         FIXME("KLF_SETFORPROCESS\n");
617     }
618 
619     if (!(pWnd = pti->MessageQueue->spwndFocus))
620     {
621          pWnd = pti->MessageQueue->spwndActive;
622     }
623 
624     // Send WM_INPUTLANGCHANGE to thread's focus window
625     co_IntSendMessage( pWnd ? UserHMGetHandle(pWnd) : 0,
626                       WM_INPUTLANGCHANGE,
627                       (WPARAM)pKl->iBaseCharset, // FIXME: How to set it?
628                       (LPARAM)pKl->hkl); // hkl
629 
630     return pklPrev;
631 }
632 
633 VOID APIENTRY
634 IntImmActivateLayout(
635     _Inout_ PTHREADINFO pti,
636     _Inout_ PKL pKL)
637 {
638     PWND pImeWnd;
639     HWND hImeWnd;
640     USER_REFERENCE_ENTRY Ref;
641 
642     if (pti->KeyboardLayout == pKL)
643         return;
644 
645     pImeWnd = pti->spwndDefaultIme;
646     if (pImeWnd)
647     {
648         UserRefObjectCo(pImeWnd, &Ref);
649         hImeWnd = UserHMGetHandle(pImeWnd);
650         co_IntSendMessage(hImeWnd, WM_IME_SYSTEM, IMS_ACTIVATELAYOUT, (LPARAM)pKL->hkl);
651         UserDerefObjectCo(pImeWnd);
652     }
653     else
654     {
655         /* Remember old keyboard layout to switch back for Chinese IMEs */
656         pti->hklPrev = pti->KeyboardLayout->hkl;
657 
658         if (pti->spDefaultImc)
659         {
660             /* IME Activation is needed */
661             pti->pClientInfo->CI_flags |= CI_IMMACTIVATE;
662         }
663     }
664 
665     UserAssignmentLock((PVOID*)&(pti->KeyboardLayout), pKL);
666     pti->pClientInfo->hKL = pKL->hkl;
667     pti->pClientInfo->CodePage = pKL->CodePage;
668 }
669 
670 static VOID co_IntSetKeyboardLayoutForProcess(PPROCESSINFO ppi, PKL pKL)
671 {
672     PTHREADINFO ptiNode, ptiNext;
673     PCLIENTINFO pClientInfo;
674     BOOL bImmMode = IS_IMM_MODE();
675 
676     for (ptiNode = ppi->ptiList; ptiNode; ptiNode = ptiNext)
677     {
678         IntReferenceThreadInfo(ptiNode);
679         ptiNext = ptiNode->ptiSibling;
680 
681         /* Skip this thread if its keyboard layout is already the correct one, or if it's dying */
682         if (ptiNode->KeyboardLayout == pKL || (ptiNode->TIF_flags & TIF_INCLEANUP))
683         {
684             IntDereferenceThreadInfo(ptiNode);
685             continue;
686         }
687 
688         if (bImmMode)
689         {
690             IntImmActivateLayout(ptiNode, pKL);
691         }
692         else
693         {
694             UserAssignmentLock((PVOID*)&ptiNode->KeyboardLayout, pKL);
695             pClientInfo = ptiNode->pClientInfo;
696             pClientInfo->CodePage = pKL->CodePage;
697             pClientInfo->hKL = pKL->hkl;
698         }
699 
700         IntDereferenceThreadInfo(ptiNode);
701     }
702 }
703 
704 HKL APIENTRY
705 co_UserActivateKeyboardLayout(
706     _Inout_ PKL     pKL,
707     _In_    ULONG   uFlags,
708     _Inout_ PWND    pWnd)
709 {
710     HKL hOldKL = NULL;
711     PKL pOldKL = NULL;
712     PTHREADINFO pti = GetW32ThreadInfo();
713     PWND pTargetWnd, pImeWnd;
714     HWND hTargetWnd, hImeWnd;
715     USER_REFERENCE_ENTRY Ref1, Ref2;
716     PCLIENTINFO ClientInfo;
717     BOOL bSetForProcess = !!(uFlags & KLF_SETFORPROCESS);
718 
719     IntReferenceThreadInfo(pti);
720     ClientInfo = pti->pClientInfo;
721 
722     if (pti->KeyboardLayout)
723     {
724         pOldKL = pti->KeyboardLayout;
725         if (pOldKL)
726             hOldKL = pOldKL->hkl;
727     }
728 
729     if (uFlags & KLF_RESET)
730     {
731         FIXME("KLF_RESET\n");
732     }
733 
734     if (!bSetForProcess && pKL == pti->KeyboardLayout)
735     {
736         IntDereferenceThreadInfo(pti);
737         return hOldKL;
738     }
739 
740     pKL->wchDiacritic = 0;
741 
742     if (pOldKL)
743         UserRefObjectCo(pOldKL, &Ref1);
744 
745     if (pti->TIF_flags & TIF_CSRSSTHREAD)
746     {
747         UserAssignmentLock((PVOID*)&pti->KeyboardLayout, pKL);
748         ClientInfo->CodePage = pKL->CodePage;
749         ClientInfo->hKL = pKL->hkl;
750     }
751     else if (bSetForProcess)
752     {
753         co_IntSetKeyboardLayoutForProcess(pti->ppi, pKL);
754     }
755     else
756     {
757         if (IS_IMM_MODE())
758             IntImmActivateLayout(pti, pKL);
759         else
760             UserAssignmentLock((PVOID*)&pti->KeyboardLayout, pKL);
761 
762         ClientInfo->CodePage = pKL->CodePage;
763         ClientInfo->hKL = pKL->hkl;
764     }
765 
766     if (gptiForeground && (gptiForeground->ppi == pti->ppi))
767     {
768         /* Send shell message */
769         co_IntShellHookNotify(HSHELL_LANGUAGE, 0, (LPARAM)pKL->hkl);
770     }
771 
772     if (pti->MessageQueue)
773     {
774         /* Determine the target window */
775         pTargetWnd = pti->MessageQueue->spwndFocus;
776         if (!pTargetWnd)
777         {
778             pTargetWnd = pti->MessageQueue->spwndActive;
779             if (!pTargetWnd)
780                 pTargetWnd = pWnd;
781         }
782 
783         /* Send WM_INPUTLANGCHANGE message */
784         if (pTargetWnd)
785         {
786             UserRefObjectCo(pTargetWnd, &Ref2);
787             hTargetWnd = UserHMGetHandle(pTargetWnd);
788             co_IntSendMessage(hTargetWnd, WM_INPUTLANGCHANGE, pKL->iBaseCharset, (LPARAM)pKL->hkl);
789             UserDerefObjectCo(pTargetWnd);
790         }
791     }
792 
793     /* Send WM_IME_SYSTEM:IMS_SENDNOTIFICATION message if necessary */
794     if (pti && !(pti->TIF_flags & TIF_CSRSSTHREAD))
795     {
796         if (IS_IME_HKL(pKL->hkl) || IS_CICERO_MODE())
797         {
798             pImeWnd = pti->spwndDefaultIme;
799             if (pImeWnd)
800             {
801                 UserRefObjectCo(pImeWnd, &Ref2);
802                 hImeWnd = UserHMGetHandle(pImeWnd);
803                 co_IntSendMessage(hImeWnd, WM_IME_SYSTEM, IMS_SENDNOTIFICATION, bSetForProcess);
804                 UserDerefObjectCo(pImeWnd);
805             }
806         }
807     }
808 
809     if (pOldKL)
810         UserDerefObjectCo(pOldKL);
811 
812     IntDereferenceThreadInfo(pti);
813     return hOldKL;
814 }
815 
816 /* Win: xxxActivateKeyboardLayout */
817 HKL APIENTRY
818 co_IntActivateKeyboardLayout(
819     _Inout_ PWINSTATION_OBJECT pWinSta,
820     _In_ HKL hKL,
821     _In_ ULONG uFlags,
822     _Inout_ PWND pWnd)
823 {
824     PKL pKL;
825     PTHREADINFO pti = PsGetCurrentThreadWin32Thread();
826 
827     pKL = IntHKLtoPKL(pti, hKL);
828     if (!pKL)
829     {
830         ERR("Invalid HKL %p!\n", hKL);
831         return NULL;
832     }
833 
834     if (uFlags & KLF_REORDER)
835         IntReorderKeyboardLayouts(pWinSta, pKL);
836 
837     return co_UserActivateKeyboardLayout(pKL, uFlags, pWnd);
838 }
839 
840 // Win: xxxInternalUnloadKeyboardLayout
841 static BOOL APIENTRY
842 co_IntUnloadKeyboardLayoutEx(
843     _Inout_ PWINSTATION_OBJECT pWinSta,
844     _Inout_ PKL pKL,
845     _In_ DWORD dwFlags)
846 {
847     PKL pNextKL;
848     USER_REFERENCE_ENTRY Ref1, Ref2;
849     PTHREADINFO pti = gptiCurrent;
850 
851     if (pKL == gspklBaseLayout && !(dwFlags & 0x80000000))
852         return FALSE;
853 
854     UserRefObjectCo(pKL, &Ref1); /* Add reference */
855 
856     /* Regard as unloaded */
857     UserMarkObjectDestroy(pKL);
858     pKL->dwKL_Flags |= KLF_UNLOAD;
859 
860     if (!(dwFlags & 0x80000000) && pti->KeyboardLayout == pKL)
861     {
862         pNextKL = IntHKLtoPKL(pti, (HKL)(ULONG_PTR)HKL_NEXT);
863         if (pNextKL)
864         {
865             UserRefObjectCo(pNextKL, &Ref2); /* Add reference */
866             co_UserActivateKeyboardLayout(pNextKL, dwFlags, NULL);
867             UserDerefObjectCo(pNextKL); /* Release reference */
868         }
869     }
870 
871     if (gspklBaseLayout == pKL && pKL != pKL->pklNext)
872     {
873         /* Set next layout as default (FIXME: Use UserAssignmentLock?) */
874         gspklBaseLayout = pKL->pklNext;
875     }
876 
877     UserDerefObjectCo(pKL); /* Release reference */
878 
879     if (pti->pDeskInfo->fsHooks)
880     {
881         co_IntShellHookNotify(HSHELL_LANGUAGE, 0, 0);
882         gLCIDSentToShell = 0;
883     }
884 
885     return TRUE;
886 }
887 
888 // Win: xxxUnloadKeyboardLayout
889 static BOOL APIENTRY
890 IntUnloadKeyboardLayout(_Inout_ PWINSTATION_OBJECT pWinSta, _In_ HKL hKL)
891 {
892     PKL pKL = IntHKLtoPKL(gptiCurrent, hKL);
893     if (!pKL)
894     {
895         ERR("Invalid HKL %p!\n", hKL);
896         return FALSE;
897     }
898     return co_IntUnloadKeyboardLayoutEx(pWinSta, pKL, 0);
899 }
900 
901 PIMEINFOEX FASTCALL co_UserImmLoadLayout(_In_ HKL hKL)
902 {
903     PIMEINFOEX piiex;
904 
905     if (!IS_IME_HKL(hKL) && !IS_CICERO_MODE())
906         return NULL;
907 
908     piiex = ExAllocatePoolWithTag(PagedPool, sizeof(IMEINFOEX), USERTAG_IME);
909     if (!piiex)
910         return NULL;
911 
912     if (!co_ClientImmLoadLayout(hKL, piiex))
913     {
914         ExFreePoolWithTag(piiex, USERTAG_IME);
915         return NULL;
916     }
917 
918     return piiex;
919 }
920 
921 HKL APIENTRY
922 co_IntLoadKeyboardLayoutEx(
923     IN OUT PWINSTATION_OBJECT pWinSta,
924     IN HANDLE hSafeFile,
925     IN HKL hOldKL,
926     IN PUNICODE_STRING puszSafeKLID,
927     IN HKL hNewKL,
928     IN UINT Flags)
929 {
930     PKL pOldKL, pNewKL;
931 
932     UNREFERENCED_PARAMETER(hSafeFile);
933 
934     if (hNewKL == NULL || (pWinSta->Flags & WSS_NOIO))
935         return NULL;
936 
937     /* If hOldKL is specified, unload it and load new layput as default */
938     if (hOldKL && hOldKL != hNewKL)
939     {
940         pOldKL = UserHklToKbl(hOldKL);
941         if (pOldKL)
942             UserUnloadKbl(pOldKL);
943     }
944 
945     /* FIXME: It seems KLF_RESET is only supported for WINLOGON */
946 
947     /* Let's see if layout was already loaded. */
948     pNewKL = UserHklToKbl(hNewKL);
949     if (!pNewKL)
950     {
951         /* It wasn't, so load it. */
952         pNewKL = co_UserLoadKbdLayout(puszSafeKLID, hNewKL);
953         if (!pNewKL)
954             return NULL;
955 
956         if (gspklBaseLayout)
957         {
958             /* Find last not unloaded layout */
959             PKL pLastKL = gspklBaseLayout->pklPrev;
960             while (pLastKL != gspklBaseLayout && (pLastKL->dwKL_Flags & KLF_UNLOAD))
961                 pLastKL = pLastKL->pklPrev;
962 
963             /* Add new layout to the list */
964             pNewKL->pklNext = pLastKL->pklNext;
965             pNewKL->pklPrev = pLastKL;
966             pNewKL->pklNext->pklPrev = pNewKL;
967             pNewKL->pklPrev->pklNext = pNewKL;
968         }
969         else
970         {
971             /* This is the first layout */
972             pNewKL->pklNext = pNewKL;
973             pNewKL->pklPrev = pNewKL;
974             gspklBaseLayout = pNewKL;
975         }
976 
977         pNewKL->piiex = co_UserImmLoadLayout(hNewKL);
978     }
979 
980     /* If this layout was prepared to unload, undo it */
981     pNewKL->dwKL_Flags &= ~KLF_UNLOAD;
982 
983     /* Reorder if necessary */
984     if (Flags & KLF_REORDER)
985         IntReorderKeyboardLayouts(pWinSta, pNewKL);
986 
987     /* Activate this layout in current thread */
988     if (Flags & KLF_ACTIVATE)
989         co_UserActivateKbl(PsGetCurrentThreadWin32Thread(), pNewKL, Flags);
990 
991     /* Send shell message */
992     if (!(Flags & KLF_NOTELLSHELL))
993         co_IntShellHookNotify(HSHELL_LANGUAGE, 0, (LPARAM)hNewKL);
994 
995     /* FIXME: KLF_REPLACELANG */
996 
997     return hNewKL;
998 }
999 
1000 HANDLE FASTCALL IntVerifyKeyboardFileHandle(HANDLE hFile)
1001 {
1002     PFILE_OBJECT FileObject;
1003     NTSTATUS Status;
1004 
1005     if (hFile == INVALID_HANDLE_VALUE)
1006         return NULL;
1007 
1008     Status = ObReferenceObjectByHandle(hFile, FILE_READ_DATA, NULL, UserMode,
1009                                        (PVOID*)&FileObject, NULL);
1010     if (!NT_SUCCESS(Status))
1011     {
1012         ERR("0x%08X\n", Status);
1013         return NULL;
1014     }
1015 
1016     /* FIXME: Is the file in the system directory? */
1017 
1018     if (FileObject)
1019         ObDereferenceObject(FileObject);
1020 
1021     return hFile;
1022 }
1023 
1024 /* EXPORTS *******************************************************************/
1025 
1026 /*
1027  * UserGetKeyboardLayout
1028  *
1029  * Returns hkl of given thread keyboard layout
1030  */
1031 HKL FASTCALL
1032 UserGetKeyboardLayout(
1033     DWORD dwThreadId)
1034 {
1035     PTHREADINFO pti;
1036     PLIST_ENTRY ListEntry;
1037     PKL pKl;
1038 
1039     pti = PsGetCurrentThreadWin32Thread();
1040 
1041     if (!dwThreadId)
1042     {
1043         pKl = pti->KeyboardLayout;
1044         return pKl ? pKl->hkl : NULL;
1045     }
1046 
1047     ListEntry = pti->rpdesk->PtiList.Flink;
1048 
1049     //
1050     // Search the Desktop Thread list for related Desktop active Threads.
1051     //
1052     while(ListEntry != &pti->rpdesk->PtiList)
1053     {
1054         pti = CONTAINING_RECORD(ListEntry, THREADINFO, PtiLink);
1055 
1056         if (PsGetThreadId(pti->pEThread) == UlongToHandle(dwThreadId))
1057         {
1058            pKl = pti->KeyboardLayout;
1059            return pKl ? pKl->hkl : NULL;
1060         }
1061 
1062         ListEntry = ListEntry->Flink;
1063     }
1064 
1065     return NULL;
1066 }
1067 
1068 /*
1069  * NtUserGetKeyboardLayoutList
1070  *
1071  * Returns list of loaded keyboard layouts in system
1072  */
1073 UINT
1074 APIENTRY
1075 NtUserGetKeyboardLayoutList(
1076     ULONG nBuff,
1077     HKL *pHklBuff)
1078 {
1079     UINT ret = 0;
1080     PWINSTATION_OBJECT pWinSta;
1081 
1082     if (!pHklBuff)
1083         nBuff = 0;
1084 
1085     UserEnterShared();
1086 
1087     if (nBuff > MAXULONG / sizeof(HKL))
1088     {
1089         SetLastNtError(ERROR_INVALID_PARAMETER);
1090         goto Quit;
1091     }
1092 
1093     _SEH2_TRY
1094     {
1095         ProbeForWrite(pHklBuff, nBuff * sizeof(HKL), 1);
1096     }
1097     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
1098     {
1099         SetLastNtError(_SEH2_GetExceptionCode());
1100         goto Quit;
1101     }
1102     _SEH2_END;
1103 
1104     pWinSta = IntGetProcessWindowStation(NULL);
1105 
1106     _SEH2_TRY
1107     {
1108         ret = IntGetKeyboardLayoutList(pWinSta, nBuff, pHklBuff);
1109     }
1110     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
1111     {
1112         SetLastNtError(_SEH2_GetExceptionCode());
1113         goto Quit;
1114     }
1115     _SEH2_END;
1116 
1117 Quit:
1118     UserLeave();
1119     return ret;
1120 }
1121 
1122 /*
1123  * NtUserGetKeyboardLayoutName
1124  *
1125  * Returns KLID of current thread keyboard layout
1126  */
1127 BOOL
1128 APIENTRY
1129 NtUserGetKeyboardLayoutName(
1130     _Inout_ PUNICODE_STRING pustrName)
1131 {
1132     BOOL bRet = FALSE;
1133     PKL pKl;
1134     PTHREADINFO pti;
1135     UNICODE_STRING ustrNameSafe;
1136     NTSTATUS Status;
1137 
1138     UserEnterShared();
1139 
1140     pti = PsGetCurrentThreadWin32Thread();
1141     pKl = pti->KeyboardLayout;
1142 
1143     if (!pKl)
1144         goto cleanup;
1145 
1146     _SEH2_TRY
1147     {
1148         ProbeForWriteUnicodeString(pustrName);
1149         ustrNameSafe = *pustrName;
1150 
1151         ProbeForWrite(ustrNameSafe.Buffer, ustrNameSafe.MaximumLength, 1);
1152 
1153         if (IS_IME_HKL(pKl->hkl))
1154         {
1155             Status = RtlIntegerToUnicodeString((ULONG)(ULONG_PTR)pKl->hkl, 16, &ustrNameSafe);
1156         }
1157         else
1158         {
1159             if (ustrNameSafe.MaximumLength < KL_NAMELENGTH * sizeof(WCHAR))
1160             {
1161                 EngSetLastError(ERROR_INVALID_PARAMETER);
1162                 goto cleanup;
1163             }
1164 
1165             /* FIXME: Do not use awchKF */
1166             ustrNameSafe.Length = 0;
1167             Status = RtlAppendUnicodeToString(&ustrNameSafe, pKl->spkf->awchKF);
1168         }
1169 
1170         if (NT_SUCCESS(Status))
1171         {
1172             *pustrName = ustrNameSafe;
1173             bRet = TRUE;
1174         }
1175     }
1176     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
1177     {
1178         SetLastNtError(_SEH2_GetExceptionCode());
1179     }
1180     _SEH2_END;
1181 
1182 cleanup:
1183     UserLeave();
1184     return bRet;
1185 }
1186 
1187 /*
1188  * NtUserLoadKeyboardLayoutEx
1189  *
1190  * Loads keyboard layout with given locale id
1191  *
1192  * NOTE: We adopt a different design from Microsoft's one due to security reason.
1193  *       We don't use the 3rd parameter of NtUserLoadKeyboardLayoutEx.
1194  *       See https://bugtraq.securityfocus.com/detail/50056B96.6040306
1195  */
1196 HKL
1197 NTAPI
1198 NtUserLoadKeyboardLayoutEx(
1199     IN HANDLE hFile,
1200     IN DWORD offTable,
1201     IN PVOID pTables,
1202     IN HKL hOldKL,
1203     IN PUNICODE_STRING puszKLID,
1204     IN DWORD dwNewKL,
1205     IN UINT Flags)
1206 {
1207     HKL hRetKL;
1208     WCHAR Buffer[KL_NAMELENGTH];
1209     UNICODE_STRING uszSafeKLID;
1210     PWINSTATION_OBJECT pWinSta;
1211     HANDLE hSafeFile;
1212 
1213     UNREFERENCED_PARAMETER(offTable);
1214     UNREFERENCED_PARAMETER(pTables);
1215 
1216     if (Flags & ~(KLF_ACTIVATE|KLF_NOTELLSHELL|KLF_REORDER|KLF_REPLACELANG|
1217                   KLF_SUBSTITUTE_OK|KLF_SETFORPROCESS|KLF_UNLOADPREVIOUS|
1218                   KLF_RESET|KLF_SHIFTLOCK))
1219     {
1220         ERR("Invalid flags: %x\n", Flags);
1221         EngSetLastError(ERROR_INVALID_FLAGS);
1222         return NULL;
1223     }
1224 
1225     RtlInitEmptyUnicodeString(&uszSafeKLID, Buffer, sizeof(Buffer));
1226     _SEH2_TRY
1227     {
1228         ProbeForRead(puszKLID, sizeof(*puszKLID), 1);
1229         ProbeForRead(puszKLID->Buffer, sizeof(puszKLID->Length), 1);
1230         RtlCopyUnicodeString(&uszSafeKLID, puszKLID);
1231     }
1232     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
1233     {
1234         SetLastNtError(_SEH2_GetExceptionCode());
1235         _SEH2_YIELD(return NULL);
1236     }
1237     _SEH2_END;
1238 
1239     UserEnterExclusive();
1240 
1241     hSafeFile = (hFile ? IntVerifyKeyboardFileHandle(hFile) : NULL);
1242     pWinSta = IntGetProcessWindowStation(NULL);
1243     hRetKL = co_IntLoadKeyboardLayoutEx(pWinSta,
1244                                         hSafeFile,
1245                                         hOldKL,
1246                                         &uszSafeKLID,
1247                                         (HKL)(DWORD_PTR)dwNewKL,
1248                                         Flags);
1249     if (hSafeFile)
1250         ZwClose(hSafeFile);
1251 
1252     UserLeave();
1253     return hRetKL;
1254 }
1255 
1256 /*
1257  * NtUserActivateKeyboardLayout
1258  *
1259  * Activates specified layout for thread or process
1260  */
1261 HKL
1262 NTAPI
1263 NtUserActivateKeyboardLayout(
1264     HKL hKL,
1265     ULONG Flags)
1266 {
1267     PWINSTATION_OBJECT pWinSta;
1268     HKL hOldKL;
1269 
1270     UserEnterExclusive();
1271 
1272     /* FIXME */
1273 
1274     pWinSta = IntGetProcessWindowStation(NULL);
1275     hOldKL = co_IntActivateKeyboardLayout(pWinSta, hKL, Flags, NULL);
1276     UserLeave();
1277 
1278     return hOldKL;
1279 }
1280 
1281 /*
1282  * NtUserUnloadKeyboardLayout
1283  *
1284  * Unloads keyboard layout with specified hkl value
1285  */
1286 BOOL
1287 APIENTRY
1288 NtUserUnloadKeyboardLayout(
1289     HKL hKl)
1290 {
1291     BOOL ret;
1292     PWINSTATION_OBJECT pWinSta;
1293 
1294     UserEnterExclusive();
1295 
1296     pWinSta = IntGetProcessWindowStation(NULL);
1297     ret = IntUnloadKeyboardLayout(pWinSta, hKl);
1298 
1299     UserLeave();
1300     return ret;
1301 }
1302 
1303 /* EOF */
1304