[WIN32K]
[reactos.git] / reactos / 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 return 0;
479 pKl = gspklBaseLayout;
480
481 if (nBuff == 0)
482 {
483 do
484 {
485 uRet++;
486 pKl = pKl->pklNext;
487 } while (pKl != gspklBaseLayout);
488 }
489 else
490 {
491 _SEH2_TRY
492 {
493 ProbeForWrite(pHklBuff, nBuff*sizeof(HKL), 4);
494
495 while (uRet < nBuff)
496 {
497 pHklBuff[uRet] = pKl->hkl;
498 uRet++;
499 pKl = pKl->pklNext;
500 if (pKl == gspklBaseLayout)
501 break;
502 }
503 }
504 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
505 {
506 SetLastNtError(_SEH2_GetExceptionCode());
507 uRet = 0;
508 }
509 _SEH2_END;
510 }
511
512 UserLeave();
513 return uRet;
514 }
515
516 /*
517 * NtUserGetKeyboardLayoutName
518 *
519 * Returns KLID of current thread keyboard layout
520 */
521 BOOL
522 APIENTRY
523 NtUserGetKeyboardLayoutName(
524 LPWSTR pwszName)
525 {
526 BOOL bRet = FALSE;
527 PKL pKl;
528 PTHREADINFO pti;
529
530 UserEnterShared();
531
532 pti = PsGetCurrentThreadWin32Thread();
533 pKl = pti->KeyboardLayout;
534
535 if (!pKl)
536 goto cleanup;
537
538 _SEH2_TRY
539 {
540 ProbeForWrite(pwszName, KL_NAMELENGTH*sizeof(WCHAR), 1);
541 wcscpy(pwszName, pKl->spkf->awchKF);
542 bRet = TRUE;
543 }
544 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
545 {
546 SetLastNtError(_SEH2_GetExceptionCode());
547 }
548 _SEH2_END;
549
550 cleanup:
551 UserLeave();
552 return bRet;
553 }
554
555 /*
556 * NtUserLoadKeyboardLayoutEx
557 *
558 * Loads keyboard layout with given locale id
559 */
560 HKL
561 APIENTRY
562 NtUserLoadKeyboardLayoutEx(
563 IN HANDLE Handle, // hFile (See downloads.securityfocus.com/vulnerabilities/exploits/43774.c)
564 IN DWORD offTable, // Offset to KbdTables
565 IN PUNICODE_STRING puszKeyboardName, // Not used?
566 IN HKL hklUnload,
567 IN PUNICODE_STRING pustrKLID,
568 IN DWORD hkl,
569 IN UINT Flags)
570 {
571 HKL hklRet = NULL;
572 PKL pKl = NULL, pklLast;
573 WCHAR Buffer[9];
574 UNICODE_STRING ustrSafeKLID;
575
576 if (Flags & ~(KLF_ACTIVATE|KLF_NOTELLSHELL|KLF_REORDER|KLF_REPLACELANG|
577 KLF_SUBSTITUTE_OK|KLF_SETFORPROCESS|KLF_UNLOADPREVIOUS|
578 KLF_RESET|KLF_SETFORPROCESS|KLF_SHIFTLOCK))
579 {
580 ERR("Invalid flags: %x\n", Flags);
581 EngSetLastError(ERROR_INVALID_FLAGS);
582 return NULL;
583 }
584
585 /* FIXME: It seems KLF_RESET is only supported for WINLOGON */
586
587 RtlInitEmptyUnicodeString(&ustrSafeKLID, Buffer, sizeof(Buffer));
588 _SEH2_TRY
589 {
590 ProbeForRead(pustrKLID, sizeof(*pustrKLID), 1);
591 ProbeForRead(pustrKLID->Buffer, sizeof(pustrKLID->Length), 1);
592 RtlCopyUnicodeString(&ustrSafeKLID, pustrKLID);
593 }
594 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
595 {
596 SetLastNtError(_SEH2_GetExceptionCode());
597 _SEH2_YIELD(return NULL);
598 }
599 _SEH2_END;
600
601 UserEnterExclusive();
602
603 /* If hklUnload is specified, unload it and load new layput as default */
604 if (hklUnload && hklUnload != (HKL)hkl)
605 {
606 pKl = UserHklToKbl(hklUnload);
607 if (pKl)
608 UserUnloadKbl(pKl);
609 }
610
611 /* Let's see if layout was already loaded. */
612 pKl = UserHklToKbl((HKL)hkl);
613 if (!pKl)
614 {
615 /* It wasn't, so load it. */
616 pKl = UserLoadKbdLayout(&ustrSafeKLID, (HKL)hkl);
617 if (!pKl)
618 goto cleanup;
619
620 if (gspklBaseLayout)
621 {
622 /* Find last not unloaded layout */
623 pklLast = gspklBaseLayout->pklPrev;
624 while (pklLast != gspklBaseLayout && pklLast->dwKL_Flags & KLF_UNLOAD)
625 pklLast = pklLast->pklPrev;
626
627 /* Add new layout to the list */
628 pKl->pklNext = pklLast->pklNext;
629 pKl->pklPrev = pklLast;
630 pKl->pklNext->pklPrev = pKl;
631 pKl->pklPrev->pklNext = pKl;
632 }
633 else
634 {
635 /* This is the first layout */
636 pKl->pklNext = pKl;
637 pKl->pklPrev = pKl;
638 gspklBaseLayout = pKl;
639 }
640 }
641
642 /* If this layout was prepared to unload, undo it */
643 pKl->dwKL_Flags &= ~KLF_UNLOAD;
644
645 /* Activate this layout in current thread */
646 if (Flags & KLF_ACTIVATE)
647 co_UserActivateKbl(PsGetCurrentThreadWin32Thread(), pKl, Flags);
648
649 /* Send shell message */
650 if (!(Flags & KLF_NOTELLSHELL))
651 co_IntShellHookNotify(HSHELL_LANGUAGE, 0, (LPARAM)hkl);
652
653 /* Return hkl on success */
654 hklRet = (HKL)hkl;
655
656 /* FIXME: KLF_REPLACELANG
657 KLF_REORDER */
658
659 cleanup:
660 UserLeave();
661 return hklRet;
662 }
663
664 /*
665 * NtUserActivateKeyboardLayout
666 *
667 * Activates specified layout for thread or process
668 */
669 HKL
670 APIENTRY
671 NtUserActivateKeyboardLayout(
672 HKL hKl,
673 ULONG Flags)
674 {
675 PKL pKl = NULL;
676 HKL hkl = NULL;
677 PTHREADINFO pti;
678
679 UserEnterExclusive();
680
681 pti = PsGetCurrentThreadWin32Thread();
682
683 /* hKl can have special value HKL_NEXT or HKL_PREV */
684 if (hKl == (HKL)HKL_NEXT)
685 {
686 /* Get next keyboard layout starting with current */
687 if (pti->KeyboardLayout)
688 pKl = pti->KeyboardLayout->pklNext;
689 }
690 else if (hKl == (HKL)HKL_PREV)
691 {
692 /* Get previous keyboard layout starting with current */
693 if (pti->KeyboardLayout)
694 pKl = pti->KeyboardLayout->pklNext;
695 }
696 else
697 pKl = UserHklToKbl(hKl);
698
699 if (!pKl)
700 {
701 ERR("Invalid HKL %p!\n", hKl);
702 goto cleanup;
703 }
704
705 hkl = pKl->hkl;
706
707 /* FIXME: KLF_RESET
708 KLF_SHIFTLOCK */
709
710 if (Flags & KLF_REORDER)
711 gspklBaseLayout = pKl;
712
713 if (pKl != pti->KeyboardLayout)
714 {
715 /* Activate layout for current thread */
716 pKl = co_UserActivateKbl(pti, pKl, Flags);
717
718 /* Send shell message */
719 if (!(Flags & KLF_NOTELLSHELL))
720 co_IntShellHookNotify(HSHELL_LANGUAGE, 0, (LPARAM)hkl);
721 }
722
723 cleanup:
724 UserLeave();
725 return hkl;
726 }
727
728 /*
729 * NtUserUnloadKeyboardLayout
730 *
731 * Unloads keyboard layout with specified hkl value
732 */
733 BOOL
734 APIENTRY
735 NtUserUnloadKeyboardLayout(
736 HKL hKl)
737 {
738 PKL pKl;
739 BOOL bRet = FALSE;
740
741 UserEnterExclusive();
742
743 pKl = UserHklToKbl(hKl);
744 if (pKl)
745 bRet = UserUnloadKbl(pKl);
746 else
747 ERR("Invalid HKL %p!\n", hKl);
748
749 UserLeave();
750 return bRet;
751 }
752
753 /* EOF */