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