xref: /reactos/win32ss/user/ntuser/kbdlayout.c (revision 5ddbd373)
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  */
10 
11 #include <win32k.h>
12 
13 #include <winnls.h>
14 
15 DBG_DEFAULT_CHANNEL(UserKbdLayout);
16 
17 PKL gspklBaseLayout = NULL;
18 PKBDFILE gpkfList = NULL;
19 DWORD gSystemFS = 0;
20 UINT gSystemCPCharSet = 0;
21 
22 typedef PVOID (*PFN_KBDLAYERDESCRIPTOR)(VOID);
23 
24 
25 /* PRIVATE FUNCTIONS ******************************************************/
26 
27 /*
28  * UserLoadKbdDll
29  *
30  * Loads keyboard layout DLL and gets address to KbdTables
31  */
32 static BOOL
33 UserLoadKbdDll(WCHAR *pwszLayoutPath,
34                HANDLE *phModule,
35                PKBDTABLES *pKbdTables)
36 {
37     PFN_KBDLAYERDESCRIPTOR pfnKbdLayerDescriptor;
38 
39     /* Load keyboard layout DLL */
40     TRACE("Loading Keyboard DLL %ws\n", pwszLayoutPath);
41     *phModule = EngLoadImage(pwszLayoutPath);
42     if (!(*phModule))
43     {
44         ERR("Failed to load dll %ws\n", pwszLayoutPath);
45         return FALSE;
46     }
47 
48     /* Find KbdLayerDescriptor function and get layout tables */
49     TRACE("Loaded %ws\n", pwszLayoutPath);
50     pfnKbdLayerDescriptor = EngFindImageProcAddress(*phModule, "KbdLayerDescriptor");
51 
52     /* FIXME: Windows reads file instead of executing!
53               It's not safe to kbdlayout DLL in kernel mode! */
54 
55     if (pfnKbdLayerDescriptor)
56         *pKbdTables = pfnKbdLayerDescriptor();
57     else
58         ERR("Error: %ws has no KbdLayerDescriptor()\n", pwszLayoutPath);
59 
60     if (!pfnKbdLayerDescriptor || !*pKbdTables)
61     {
62         ERR("Failed to load the keyboard layout.\n");
63         EngUnloadImage(*phModule);
64         return FALSE;
65     }
66 
67 #if 0 /* Dump keyboard layout */
68     {
69         unsigned i;
70         PVK_TO_BIT pVkToBit = (*pKbdTables)->pCharModifiers->pVkToBit;
71         PVK_TO_WCHAR_TABLE pVkToWchTbl = (*pKbdTables)->pVkToWcharTable;
72         PVSC_VK pVscVk = (*pKbdTables)->pVSCtoVK_E0;
73         DbgPrint("Kbd layout: fLocaleFlags %x bMaxVSCtoVK %x\n", (*pKbdTables)->fLocaleFlags, (*pKbdTables)->bMaxVSCtoVK);
74         DbgPrint("wMaxModBits %x\n", (*pKbdTables)->pCharModifiers->wMaxModBits);
75         while (pVkToBit->Vk)
76         {
77             DbgPrint("VkToBit %x -> %x\n", pVkToBit->Vk, pVkToBit->ModBits);
78             ++pVkToBit;
79         }
80         for (i = 0; i <= (*pKbdTables)->pCharModifiers->wMaxModBits; ++i)
81             DbgPrint("ModNumber %x -> %x\n", i, (*pKbdTables)->pCharModifiers->ModNumber[i]);
82         while (pVkToWchTbl->pVkToWchars)
83         {
84             PVK_TO_WCHARS1 pVkToWch = pVkToWchTbl->pVkToWchars;
85             DbgPrint("pVkToWchTbl nModifications %x cbSize %x\n", pVkToWchTbl->nModifications, pVkToWchTbl->cbSize);
86             while (pVkToWch->VirtualKey)
87             {
88                 DbgPrint("pVkToWch VirtualKey %x Attributes %x wc { ", pVkToWch->VirtualKey, pVkToWch->Attributes);
89                 for (i = 0; i < pVkToWchTbl->nModifications; ++i)
90                     DbgPrint("%x ", pVkToWch->wch[i]);
91                 DbgPrint("}\n");
92                 pVkToWch = (PVK_TO_WCHARS1)(((PBYTE)pVkToWch) + pVkToWchTbl->cbSize);
93             }
94             ++pVkToWchTbl;
95         }
96         DbgPrint("pusVSCtoVK: { ");
97         for (i = 0; i < (*pKbdTables)->bMaxVSCtoVK; ++i)
98         DbgPrint("%x -> %x, ", i, (*pKbdTables)->pusVSCtoVK[i]);
99         DbgPrint("}\n");
100         DbgPrint("pVSCtoVK_E0: { ");
101         while (pVscVk->Vsc)
102         {
103             DbgPrint("%x -> %x, ", pVscVk->Vsc, pVscVk->Vk);
104             ++pVscVk;
105         }
106         DbgPrint("}\n");
107         pVscVk = (*pKbdTables)->pVSCtoVK_E1;
108         DbgPrint("pVSCtoVK_E1: { ");
109         while (pVscVk->Vsc)
110         {
111             DbgPrint("%x -> %x, ", pVscVk->Vsc, pVscVk->Vk);
112             ++pVscVk;
113         }
114         DbgPrint("}\n");
115         DbgBreakPoint();
116     }
117 #endif
118 
119     return TRUE;
120 }
121 
122 /*
123  * UserLoadKbdFile
124  *
125  * Loads keyboard layout DLL and creates KBDFILE object
126  */
127 static PKBDFILE
128 UserLoadKbdFile(PUNICODE_STRING pwszKLID)
129 {
130     PKBDFILE pkf, pRet = NULL;
131     NTSTATUS Status;
132     ULONG cbSize;
133     HKEY hKey = NULL;
134     WCHAR wszLayoutPath[MAX_PATH] = L"\\SystemRoot\\System32\\";
135     WCHAR wszLayoutRegKey[256] = L"\\REGISTRY\\Machine\\SYSTEM\\CurrentControlSet\\"
136                                  L"Control\\Keyboard Layouts\\";
137 
138     /* Create keyboard layout file object */
139     pkf = UserCreateObject(gHandleTable, NULL, NULL, NULL, TYPE_KBDFILE, sizeof(KBDFILE));
140     if (!pkf)
141     {
142         ERR("Failed to create object!\n");
143         return NULL;
144     }
145 
146     /* Set keyboard layout name */
147     swprintf(pkf->awchKF, L"%wZ", pwszKLID);
148 
149     /* Open layout registry key */
150     RtlStringCbCatW(wszLayoutRegKey, sizeof(wszLayoutRegKey), pkf->awchKF);
151     Status = RegOpenKey(wszLayoutRegKey, &hKey);
152     if (!NT_SUCCESS(Status))
153     {
154         ERR("Failed to open keyboard layouts registry key %ws (%lx)\n", wszLayoutRegKey, Status);
155         goto cleanup;
156     }
157 
158     /* Read filename of layout DLL */
159     cbSize = (ULONG)(sizeof(wszLayoutPath) - wcslen(wszLayoutPath)*sizeof(WCHAR));
160     Status = RegQueryValue(hKey,
161                            L"Layout File",
162                            REG_SZ,
163                            wszLayoutPath + wcslen(wszLayoutPath),
164                            &cbSize);
165 
166     if (!NT_SUCCESS(Status))
167     {
168         ERR("Can't get layout filename for %wZ (%lx)\n", pwszKLID, Status);
169         goto cleanup;
170     }
171 
172     /* Load keyboard file now */
173     if (!UserLoadKbdDll(wszLayoutPath, &pkf->hBase, &pkf->pKbdTbl))
174     {
175         ERR("Failed to load %ws dll!\n", wszLayoutPath);
176         goto cleanup;
177     }
178 
179     /* Update next field */
180     pkf->pkfNext = gpkfList;
181     gpkfList = pkf;
182 
183     /* Return keyboard file */
184     pRet = pkf;
185 
186 cleanup:
187     if (hKey)
188         ZwClose(hKey);
189     if (pkf)
190         UserDereferenceObject(pkf); // we dont need ptr anymore
191     if (!pRet)
192     {
193         /* We have failed - destroy created object */
194         if (pkf)
195             UserDeleteObject(pkf->head.h, TYPE_KBDFILE);
196     }
197 
198     return pRet;
199 }
200 
201 /*
202  * UserLoadKbdLayout
203  *
204  * Loads keyboard layout and creates KL object
205  */
206 static PKL
207 UserLoadKbdLayout(PUNICODE_STRING pustrKLID, HKL hKL)
208 {
209     LCID lCid;
210     CHARSETINFO cs;
211     PKL pKl;
212 
213     /* Create keyboard layout object */
214     pKl = UserCreateObject(gHandleTable, NULL, NULL, NULL, TYPE_KBDLAYOUT, sizeof(KL));
215     if (!pKl)
216     {
217         ERR("Failed to create object!\n");
218         return NULL;
219     }
220 
221     pKl->hkl = hKL;
222     pKl->spkf = UserLoadKbdFile(pustrKLID);
223 
224     /* Dereference keyboard layout */
225     UserDereferenceObject(pKl);
226 
227     /* If we failed, remove KL object */
228     if (!pKl->spkf)
229     {
230         ERR("UserLoadKbdFile(%wZ) failed!\n", pustrKLID);
231         UserDeleteObject(pKl->head.h, TYPE_KBDLAYOUT);
232         return NULL;
233     }
234 
235     // Up to Language Identifiers..
236     if (!NT_SUCCESS(RtlUnicodeStringToInteger(pustrKLID, 16, (PULONG)&lCid)))
237     {
238         ERR("RtlUnicodeStringToInteger failed for '%wZ'\n", pustrKLID);
239         UserDeleteObject(pKl->head.h, TYPE_KBDLAYOUT);
240         return NULL;
241     }
242 
243     TRACE("Language Identifiers %wZ LCID 0x%x\n", pustrKLID, lCid);
244     if (co_IntGetCharsetInfo(lCid, &cs))
245     {
246        pKl->iBaseCharset = cs.ciCharset;
247        pKl->dwFontSigs = cs.fs.fsCsb[0];
248        pKl->CodePage = (USHORT)cs.ciACP;
249        TRACE("Charset %u Font Sig %lu CodePage %u\n",
250              pKl->iBaseCharset, pKl->dwFontSigs, pKl->CodePage);
251     }
252     else
253     {
254        pKl->iBaseCharset = ANSI_CHARSET;
255        pKl->dwFontSigs = FS_LATIN1;
256        pKl->CodePage = CP_ACP;
257     }
258 
259     // Set initial system character set and font signature.
260     if (gSystemFS == 0)
261     {
262        gSystemCPCharSet = pKl->iBaseCharset;
263        gSystemFS = pKl->dwFontSigs;
264     }
265 
266     return pKl;
267 }
268 
269 /*
270  * UnloadKbdFile
271  *
272  * Destroys specified Keyboard File object
273  */
274 static
275 VOID
276 UnloadKbdFile(_In_ PKBDFILE pkf)
277 {
278     PKBDFILE *ppkfLink = &gpkfList;
279     NT_ASSERT(pkf != NULL);
280 
281     /* Find previous object */
282     while (*ppkfLink)
283     {
284         if (*ppkfLink == pkf)
285             break;
286 
287         ppkfLink = &(*ppkfLink)->pkfNext;
288     }
289 
290     if (*ppkfLink == pkf)
291         *ppkfLink = pkf->pkfNext;
292 
293     EngUnloadImage(pkf->hBase);
294     UserDeleteObject(pkf->head.h, TYPE_KBDFILE);
295 }
296 
297 /*
298  * UserUnloadKbl
299  *
300  * Unloads specified Keyboard Layout if possible
301  */
302 BOOL
303 UserUnloadKbl(PKL pKl)
304 {
305     /* According to msdn, UnloadKeyboardLayout can fail
306        if the keyboard layout identifier was preloaded. */
307     if (pKl == gspklBaseLayout)
308     {
309         if (pKl->pklNext == pKl->pklPrev)
310         {
311             /* There is only one layout */
312             return FALSE;
313         }
314 
315         /* Set next layout as default */
316         gspklBaseLayout = pKl->pklNext;
317     }
318 
319     if (pKl->head.cLockObj > 1)
320     {
321         /* Layout is used by other threads */
322         pKl->dwKL_Flags |= KLF_UNLOAD;
323         return FALSE;
324     }
325 
326     /* Unload the layout */
327     pKl->pklPrev->pklNext = pKl->pklNext;
328     pKl->pklNext->pklPrev = pKl->pklPrev;
329     UnloadKbdFile(pKl->spkf);
330     UserDeleteObject(pKl->head.h, TYPE_KBDLAYOUT);
331     return TRUE;
332 }
333 
334 /*
335  * W32kGetDefaultKeyLayout
336  *
337  * Returns default layout for new threads
338  */
339 PKL
340 W32kGetDefaultKeyLayout(VOID)
341 {
342     PKL pKl = gspklBaseLayout;
343 
344     if (!pKl)
345         return NULL;
346 
347     /* Return not unloaded layout */
348     do
349     {
350         if (!(pKl->dwKL_Flags & KLF_UNLOAD))
351             return pKl;
352 
353         pKl = pKl->pklPrev; /* Confirmed on Win2k */
354     } while(pKl != gspklBaseLayout);
355 
356     /* We have not found proper KL */
357     return NULL;
358 }
359 
360 /*
361  * UserHklToKbl
362  *
363  * Gets KL object from hkl value
364  */
365 PKL
366 NTAPI
367 UserHklToKbl(HKL hKl)
368 {
369     PKL pKl = gspklBaseLayout;
370 
371     if (!gspklBaseLayout)
372         return NULL;
373 
374     do
375     {
376         if (pKl->hkl == hKl)
377             return pKl;
378 
379         pKl = pKl->pklNext;
380     } while (pKl != gspklBaseLayout);
381 
382     return NULL;
383 }
384 
385 /*
386  * UserSetDefaultInputLang
387  *
388  * Sets default kyboard layout for system. Called from UserSystemParametersInfo.
389  */
390 BOOL
391 NTAPI
392 UserSetDefaultInputLang(HKL hKl)
393 {
394     PKL pKl;
395 
396     pKl = UserHklToKbl(hKl);
397     if (!pKl)
398         return FALSE;
399 
400     gspklBaseLayout = pKl;
401     return TRUE;
402 }
403 
404 /*
405  * co_UserActivateKbl
406  *
407  * Activates given layout in specified thread
408  */
409 static PKL
410 co_UserActivateKbl(PTHREADINFO pti, PKL pKl, UINT Flags)
411 {
412     PKL pklPrev;
413     PWND pWnd;
414 
415     pklPrev = pti->KeyboardLayout;
416     if (pklPrev)
417         UserDereferenceObject(pklPrev);
418 
419     pti->KeyboardLayout = pKl;
420     pti->pClientInfo->hKL = pKl->hkl;
421     UserReferenceObject(pKl);
422 
423     if (Flags & KLF_SETFORPROCESS)
424     {
425         // FIXME
426     }
427 
428     if (!(pWnd = pti->MessageQueue->spwndFocus))
429     {
430          pWnd = pti->MessageQueue->spwndActive;
431     }
432 
433     // Send WM_INPUTLANGCHANGE to thread's focus window
434     co_IntSendMessage( pWnd ? UserHMGetHandle(pWnd) : 0,
435                       WM_INPUTLANGCHANGE,
436                       (WPARAM)pKl->iBaseCharset, // FIXME: How to set it?
437                       (LPARAM)pKl->hkl); // hkl
438 
439     return pklPrev;
440 }
441 
442 /* EXPORTS *******************************************************************/
443 
444 /*
445  * UserGetKeyboardLayout
446  *
447  * Returns hkl of given thread keyboard layout
448  */
449 HKL FASTCALL
450 UserGetKeyboardLayout(
451     DWORD dwThreadId)
452 {
453     PTHREADINFO pti;
454     PLIST_ENTRY ListEntry;
455     PKL pKl;
456 
457     pti = PsGetCurrentThreadWin32Thread();
458 
459     if (!dwThreadId)
460     {
461         pKl = pti->KeyboardLayout;
462         return pKl ? pKl->hkl : NULL;
463     }
464 
465     ListEntry = pti->rpdesk->PtiList.Flink;
466 
467     //
468     // Search the Desktop Thread list for related Desktop active Threads.
469     //
470     while(ListEntry != &pti->rpdesk->PtiList)
471     {
472         pti = CONTAINING_RECORD(ListEntry, THREADINFO, PtiLink);
473 
474         if (PsGetThreadId(pti->pEThread) == UlongToHandle(dwThreadId))
475         {
476            pKl = pti->KeyboardLayout;
477            return pKl ? pKl->hkl : NULL;
478         }
479 
480         ListEntry = ListEntry->Flink;
481     }
482 
483     return NULL;
484 }
485 
486 /*
487  * NtUserGetKeyboardLayoutList
488  *
489  * Returns list of loaded keyboard layouts in system
490  */
491 UINT
492 APIENTRY
493 NtUserGetKeyboardLayoutList(
494     ULONG nBuff,
495     HKL *pHklBuff)
496 {
497     UINT uRet = 0;
498     PKL pKl;
499 
500     if (!pHklBuff)
501         nBuff = 0;
502 
503     UserEnterShared();
504 
505     if (!gspklBaseLayout)
506     {
507         UserLeave();
508         return 0;
509     }
510     pKl = gspklBaseLayout;
511 
512     if (nBuff == 0)
513     {
514         do
515         {
516             uRet++;
517             pKl = pKl->pklNext;
518         } while (pKl != gspklBaseLayout);
519     }
520     else
521     {
522         _SEH2_TRY
523         {
524             ProbeForWrite(pHklBuff, nBuff*sizeof(HKL), 4);
525 
526             while (uRet < nBuff)
527             {
528                 pHklBuff[uRet] = pKl->hkl;
529                 uRet++;
530                 pKl = pKl->pklNext;
531                 if (pKl == gspklBaseLayout)
532                     break;
533             }
534         }
535         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
536         {
537             SetLastNtError(_SEH2_GetExceptionCode());
538             uRet = 0;
539         }
540         _SEH2_END;
541     }
542 
543     UserLeave();
544     return uRet;
545 }
546 
547 /*
548  * NtUserGetKeyboardLayoutName
549  *
550  * Returns KLID of current thread keyboard layout
551  */
552 BOOL
553 APIENTRY
554 NtUserGetKeyboardLayoutName(
555     LPWSTR pwszName)
556 {
557     BOOL bRet = FALSE;
558     PKL pKl;
559     PTHREADINFO pti;
560 
561     UserEnterShared();
562 
563     pti = PsGetCurrentThreadWin32Thread();
564     pKl = pti->KeyboardLayout;
565 
566     if (!pKl)
567         goto cleanup;
568 
569     _SEH2_TRY
570     {
571         ProbeForWrite(pwszName, KL_NAMELENGTH*sizeof(WCHAR), 1);
572         wcscpy(pwszName, pKl->spkf->awchKF);
573         bRet = TRUE;
574     }
575     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
576     {
577         SetLastNtError(_SEH2_GetExceptionCode());
578     }
579     _SEH2_END;
580 
581 cleanup:
582     UserLeave();
583     return bRet;
584 }
585 
586 /*
587  * NtUserLoadKeyboardLayoutEx
588  *
589  * Loads keyboard layout with given locale id
590  */
591 HKL
592 APIENTRY
593 NtUserLoadKeyboardLayoutEx(
594     IN HANDLE Handle, // hFile (See downloads.securityfocus.com/vulnerabilities/exploits/43774.c)
595     IN DWORD offTable, // Offset to KbdTables
596     IN PUNICODE_STRING puszKeyboardName, // Not used?
597     IN HKL hklUnload,
598     IN PUNICODE_STRING pustrKLID,
599     IN DWORD hkl,
600     IN UINT Flags)
601 {
602     HKL hklRet = NULL;
603     PKL pKl = NULL, pklLast;
604     WCHAR Buffer[9];
605     UNICODE_STRING ustrSafeKLID;
606 
607     if (Flags & ~(KLF_ACTIVATE|KLF_NOTELLSHELL|KLF_REORDER|KLF_REPLACELANG|
608                   KLF_SUBSTITUTE_OK|KLF_SETFORPROCESS|KLF_UNLOADPREVIOUS|
609                   KLF_RESET|KLF_SHIFTLOCK))
610     {
611         ERR("Invalid flags: %x\n", Flags);
612         EngSetLastError(ERROR_INVALID_FLAGS);
613         return NULL;
614     }
615 
616     /* FIXME: It seems KLF_RESET is only supported for WINLOGON */
617 
618     RtlInitEmptyUnicodeString(&ustrSafeKLID, Buffer, sizeof(Buffer));
619     _SEH2_TRY
620     {
621         ProbeForRead(pustrKLID, sizeof(*pustrKLID), 1);
622         ProbeForRead(pustrKLID->Buffer, sizeof(pustrKLID->Length), 1);
623         RtlCopyUnicodeString(&ustrSafeKLID, pustrKLID);
624     }
625     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
626     {
627         SetLastNtError(_SEH2_GetExceptionCode());
628         _SEH2_YIELD(return NULL);
629     }
630     _SEH2_END;
631 
632     UserEnterExclusive();
633 
634     /* If hklUnload is specified, unload it and load new layput as default */
635     if (hklUnload && (hklUnload != UlongToHandle(hkl)))
636     {
637         pKl = UserHklToKbl(hklUnload);
638         if (pKl)
639             UserUnloadKbl(pKl);
640     }
641 
642     /* Let's see if layout was already loaded. */
643     pKl = UserHklToKbl(UlongToHandle(hkl));
644     if (!pKl)
645     {
646         /* It wasn't, so load it. */
647         pKl = UserLoadKbdLayout(&ustrSafeKLID, UlongToHandle(hkl));
648         if (!pKl)
649             goto cleanup;
650 
651         if (gspklBaseLayout)
652         {
653             /* Find last not unloaded layout */
654             pklLast = gspklBaseLayout->pklPrev;
655             while (pklLast != gspklBaseLayout && pklLast->dwKL_Flags & KLF_UNLOAD)
656                 pklLast = pklLast->pklPrev;
657 
658             /* Add new layout to the list */
659             pKl->pklNext = pklLast->pklNext;
660             pKl->pklPrev = pklLast;
661             pKl->pklNext->pklPrev = pKl;
662             pKl->pklPrev->pklNext = pKl;
663         }
664         else
665         {
666             /* This is the first layout */
667             pKl->pklNext = pKl;
668             pKl->pklPrev = pKl;
669             gspklBaseLayout = pKl;
670         }
671     }
672 
673     /* If this layout was prepared to unload, undo it */
674     pKl->dwKL_Flags &= ~KLF_UNLOAD;
675 
676     /* Activate this layout in current thread */
677     if (Flags & KLF_ACTIVATE)
678         co_UserActivateKbl(PsGetCurrentThreadWin32Thread(), pKl, Flags);
679 
680     /* Send shell message */
681     if (!(Flags & KLF_NOTELLSHELL))
682         co_IntShellHookNotify(HSHELL_LANGUAGE, 0, (LPARAM)hkl);
683 
684     /* Return hkl on success */
685     hklRet = UlongToHandle(hkl);
686 
687     /* FIXME: KLF_REPLACELANG
688               KLF_REORDER */
689 
690 cleanup:
691     UserLeave();
692     return hklRet;
693 }
694 
695 /*
696  * NtUserActivateKeyboardLayout
697  *
698  * Activates specified layout for thread or process
699  */
700 HKL
701 APIENTRY
702 NtUserActivateKeyboardLayout(
703     HKL hKl,
704     ULONG Flags)
705 {
706     PKL pKl = NULL;
707     HKL hkl = NULL;
708     PTHREADINFO pti;
709 
710     UserEnterExclusive();
711 
712     pti = PsGetCurrentThreadWin32Thread();
713 
714     /* hKl can have special value HKL_NEXT or HKL_PREV */
715     if (hKl == (HKL)HKL_NEXT)
716     {
717         /* Get next keyboard layout starting with current */
718         if (pti->KeyboardLayout)
719             pKl = pti->KeyboardLayout->pklNext;
720     }
721     else if (hKl == (HKL)HKL_PREV)
722     {
723         /* Get previous keyboard layout starting with current */
724         if (pti->KeyboardLayout)
725             pKl = pti->KeyboardLayout->pklPrev;
726     }
727     else
728         pKl = UserHklToKbl(hKl);
729 
730     if (!pKl)
731     {
732         ERR("Invalid HKL %p!\n", hKl);
733         goto cleanup;
734     }
735 
736     hkl = pKl->hkl;
737 
738     /* FIXME: KLF_RESET
739               KLF_SHIFTLOCK */
740 
741     if (Flags & KLF_REORDER)
742         gspklBaseLayout = pKl;
743 
744     if (pKl != pti->KeyboardLayout)
745     {
746         /* Activate layout for current thread */
747         pKl = co_UserActivateKbl(pti, pKl, Flags);
748 
749         /* Send shell message */
750         if (!(Flags & KLF_NOTELLSHELL))
751             co_IntShellHookNotify(HSHELL_LANGUAGE, 0, (LPARAM)hkl);
752     }
753 
754 cleanup:
755     UserLeave();
756     return hkl;
757 }
758 
759 /*
760  * NtUserUnloadKeyboardLayout
761  *
762  * Unloads keyboard layout with specified hkl value
763  */
764 BOOL
765 APIENTRY
766 NtUserUnloadKeyboardLayout(
767     HKL hKl)
768 {
769     PKL pKl;
770     BOOL bRet = FALSE;
771 
772     UserEnterExclusive();
773 
774     pKl = UserHklToKbl(hKl);
775     if (pKl)
776         bRet = UserUnloadKbl(pKl);
777     else
778         ERR("Invalid HKL %p!\n", hKl);
779 
780     UserLeave();
781     return bRet;
782 }
783 
784 /* EOF */
785