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