[WIN32K]
[reactos.git] / reactos / subsystems / win32 / win32k / ntuser / kbdlayout.c
1 /*
2 * PROJECT: ReactOS Kernel
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 */
9
10 #include <win32k.h>
11 DBG_DEFAULT_CHANNEL(UserKbdLayout);
12
13 PKBL KBLList = NULL; // Keyboard layout list.
14
15 typedef PVOID (*KbdLayerDescriptor)(VOID);
16
17
18 /* PRIVATE FUNCTIONS ******************************************************/
19
20
21 /*
22 * Utility function to read a value from the registry more easily.
23 *
24 * IN PUNICODE_STRING KeyName -> Name of key to open
25 * IN PUNICODE_STRING ValueName -> Name of value to open
26 * OUT PUNICODE_STRING ReturnedValue -> String contained in registry
27 *
28 * Returns NTSTATUS
29 */
30
31 static NTSTATUS APIENTRY ReadRegistryValue( PUNICODE_STRING KeyName,
32 PUNICODE_STRING ValueName,
33 PUNICODE_STRING ReturnedValue )
34 {
35 NTSTATUS Status;
36 HANDLE KeyHandle;
37 OBJECT_ATTRIBUTES KeyAttributes;
38 PKEY_VALUE_PARTIAL_INFORMATION KeyValuePartialInfo;
39 ULONG Length = 0;
40 ULONG ResLength = 0;
41 PWCHAR ReturnBuffer;
42
43 InitializeObjectAttributes(&KeyAttributes, KeyName, OBJ_CASE_INSENSITIVE,
44 NULL, NULL);
45 Status = ZwOpenKey(&KeyHandle, KEY_READ, &KeyAttributes);
46 if( !NT_SUCCESS(Status) )
47 {
48 return Status;
49 }
50
51 Status = ZwQueryValueKey(KeyHandle, ValueName, KeyValuePartialInformation,
52 0,
53 0,
54 &ResLength);
55
56 if( Status != STATUS_BUFFER_TOO_SMALL )
57 {
58 NtClose(KeyHandle);
59 return Status;
60 }
61
62 ResLength += sizeof( *KeyValuePartialInfo );
63 KeyValuePartialInfo =
64 ExAllocatePoolWithTag(PagedPool, ResLength, TAG_STRING);
65 Length = ResLength;
66
67 if( !KeyValuePartialInfo )
68 {
69 NtClose(KeyHandle);
70 return STATUS_NO_MEMORY;
71 }
72
73 Status = ZwQueryValueKey(KeyHandle,
74 ValueName,
75 KeyValuePartialInformation,
76 (PVOID)KeyValuePartialInfo,
77 Length,
78 &ResLength);
79
80 if( !NT_SUCCESS(Status) )
81 {
82 NtClose(KeyHandle);
83 ExFreePoolWithTag(KeyValuePartialInfo, TAG_STRING);
84 return Status;
85 }
86
87 /* At this point, KeyValuePartialInfo->Data contains the key data */
88 ReturnBuffer = ExAllocatePoolWithTag(PagedPool,
89 KeyValuePartialInfo->DataLength,
90 TAG_STRING);
91
92 if(!ReturnBuffer)
93 {
94 NtClose(KeyHandle);
95 ExFreePoolWithTag(KeyValuePartialInfo, TAG_STRING);
96 return STATUS_NO_MEMORY;
97 }
98
99 RtlCopyMemory(ReturnBuffer,
100 KeyValuePartialInfo->Data,
101 KeyValuePartialInfo->DataLength);
102 RtlInitUnicodeString(ReturnedValue, ReturnBuffer);
103
104 ExFreePoolWithTag(KeyValuePartialInfo, TAG_STRING);
105 NtClose(KeyHandle);
106
107 return Status;
108 }
109
110 static BOOL UserLoadKbdDll(WCHAR *wsKLID,
111 HANDLE *phModule,
112 PKBDTABLES *pKbdTables)
113 {
114 NTSTATUS Status;
115 KbdLayerDescriptor layerDescGetFn;
116 ANSI_STRING kbdProcedureName;
117 UNICODE_STRING LayoutKeyName;
118 UNICODE_STRING LayoutValueName;
119 UNICODE_STRING LayoutFile;
120 UNICODE_STRING FullLayoutPath;
121 UNICODE_STRING klid;
122 WCHAR LayoutPathBuffer[MAX_PATH] = L"\\SystemRoot\\System32\\";
123 WCHAR KeyNameBuffer[MAX_PATH] = L"\\REGISTRY\\Machine\\SYSTEM\\"
124 L"CurrentControlSet\\Control\\Keyboard Layouts\\";
125
126 RtlInitUnicodeString(&klid, wsKLID);
127 RtlInitUnicodeString(&LayoutValueName,L"Layout File");
128 RtlInitUnicodeString(&LayoutKeyName, KeyNameBuffer);
129 LayoutKeyName.MaximumLength = sizeof(KeyNameBuffer);
130
131 RtlAppendUnicodeStringToString(&LayoutKeyName, &klid);
132 Status = ReadRegistryValue(&LayoutKeyName, &LayoutValueName, &LayoutFile);
133
134 if(!NT_SUCCESS(Status))
135 {
136 TRACE("Can't get layout filename for %wZ. (%08lx)\n", klid, Status);
137 return FALSE;
138 }
139
140 TRACE("Read registry and got %wZ\n", &LayoutFile);
141 RtlInitUnicodeString(&FullLayoutPath, LayoutPathBuffer);
142 FullLayoutPath.MaximumLength = sizeof(LayoutPathBuffer);
143 RtlAppendUnicodeStringToString(&FullLayoutPath, &LayoutFile);
144 TRACE("Loading Keyboard DLL %wZ\n", &FullLayoutPath);
145 ExFreePoolWithTag(LayoutFile.Buffer, TAG_STRING);
146
147 *phModule = EngLoadImage(FullLayoutPath.Buffer);
148
149 if(*phModule)
150 {
151 TRACE("Loaded %wZ\n", &FullLayoutPath);
152
153 RtlInitAnsiString( &kbdProcedureName, "KbdLayerDescriptor" );
154 layerDescGetFn = EngFindImageProcAddress(*phModule, "KbdLayerDescriptor");
155
156 if(layerDescGetFn)
157 {
158 *pKbdTables = layerDescGetFn();
159 }
160 else
161 {
162 ERR("Error: %wZ has no KbdLayerDescriptor()\n", &FullLayoutPath);
163 }
164
165 if(!layerDescGetFn || !*pKbdTables)
166 {
167 ERR("Failed to load the keyboard layout.\n");
168 EngUnloadImage(*phModule);
169 return FALSE;
170 }
171
172 #if 0 // Dump keyboard layout
173 {
174 unsigned i;
175 PVK_TO_BIT pVkToBit = (*pKbdTables)->pCharModifiers->pVkToBit;
176 PVK_TO_WCHAR_TABLE pVkToWchTbl = (*pKbdTables)->pVkToWcharTable;
177 PVSC_VK pVscVk = (*pKbdTables)->pVSCtoVK_E0;
178 DbgPrint("Kbd layout: fLocaleFlags %x bMaxVSCtoVK %x\n", (*pKbdTables)->fLocaleFlags, (*pKbdTables)->bMaxVSCtoVK);
179 DbgPrint("wMaxModBits %x\n", (*pKbdTables)->pCharModifiers->wMaxModBits);
180 while(pVkToBit->Vk)
181 {
182 DbgPrint("VkToBit %x -> %x\n", pVkToBit->Vk, pVkToBit->ModBits);
183 ++pVkToBit;
184 }
185 for(i = 0; i <= (*pKbdTables)->pCharModifiers->wMaxModBits; ++i)
186 DbgPrint("ModNumber %x -> %x\n", i, (*pKbdTables)->pCharModifiers->ModNumber[i]);
187 while(pVkToWchTbl->pVkToWchars)
188 {
189 PVK_TO_WCHARS1 pVkToWch = pVkToWchTbl->pVkToWchars;
190 DbgPrint("pVkToWchTbl nModifications %x cbSize %x\n", pVkToWchTbl->nModifications, pVkToWchTbl->cbSize);
191 while(pVkToWch->VirtualKey)
192 {
193 DbgPrint("pVkToWch VirtualKey %x Attributes %x wc { ", pVkToWch->VirtualKey, pVkToWch->Attributes);
194 for(i = 0; i < pVkToWchTbl->nModifications; ++i)
195 DbgPrint("%x ", pVkToWch->wch[i]);
196 DbgPrint("}\n");
197 pVkToWch = (PVK_TO_WCHARS1)(((PBYTE)pVkToWch) + pVkToWchTbl->cbSize);
198 }
199 ++pVkToWchTbl;
200 }
201 DbgPrint("pusVSCtoVK: { ");
202 for(i = 0; i < (*pKbdTables)->bMaxVSCtoVK; ++i)
203 DbgPrint("%x -> %x, ", i, (*pKbdTables)->pusVSCtoVK[i]);
204 DbgPrint("}\n");
205 DbgPrint("pVSCtoVK_E0: { ");
206 while(pVscVk->Vsc)
207 {
208 DbgPrint("%x -> %x, ", pVscVk->Vsc, pVscVk->Vk);
209 ++pVscVk;
210 }
211 DbgPrint("}\n");
212 pVscVk = (*pKbdTables)->pVSCtoVK_E1;
213 DbgPrint("pVSCtoVK_E1: { ");
214 while(pVscVk->Vsc)
215 {
216 DbgPrint("%x -> %x, ", pVscVk->Vsc, pVscVk->Vk);
217 ++pVscVk;
218 }
219 DbgPrint("}\n");
220 }
221 DbgBreakPoint();
222 #endif
223 }
224 else
225 {
226 ERR("Failed to load dll %wZ\n", &FullLayoutPath);
227 return FALSE;
228 }
229
230 return TRUE;
231 }
232
233 static PKBL UserLoadDllAndCreateKbl(DWORD LocaleId)
234 {
235 PKBL NewKbl;
236 ULONG hKl;
237 LANGID langid;
238
239 NewKbl = ExAllocatePoolWithTag(PagedPool, sizeof(KBL), USERTAG_KBDLAYOUT);
240
241 if(!NewKbl)
242 {
243 ERR("%s: Can't allocate memory!\n", __FUNCTION__);
244 return NULL;
245 }
246
247 swprintf(NewKbl->Name, L"%08lx", LocaleId);
248
249 if(!UserLoadKbdDll(NewKbl->Name, &NewKbl->hModule, &NewKbl->KBTables))
250 {
251 TRACE("%s: failed to load %x dll!\n", __FUNCTION__, LocaleId);
252 ExFreePoolWithTag(NewKbl, USERTAG_KBDLAYOUT);
253 return NULL;
254 }
255
256 /* Microsoft Office expects this value to be something specific
257 * for Japanese and Korean Windows with an IME the value is 0xe001
258 * We should probably check to see if an IME exists and if so then
259 * set this word properly.
260 */
261 langid = PRIMARYLANGID(LANGIDFROMLCID(LocaleId));
262 hKl = LocaleId;
263
264 if (langid == LANG_CHINESE || langid == LANG_JAPANESE || langid == LANG_KOREAN)
265 hKl |= 0xe001 << 16; /* FIXME */
266 else hKl |= hKl << 16;
267
268 NewKbl->hkl = (HKL)(ULONG_PTR) hKl;
269 NewKbl->klid = LocaleId;
270 NewKbl->Flags = 0;
271 NewKbl->RefCount = 0;
272
273 return NewKbl;
274 }
275
276 BOOL UserInitDefaultKeyboardLayout()
277 {
278 LCID LocaleId;
279 NTSTATUS Status;
280
281 Status = ZwQueryDefaultLocale(FALSE, &LocaleId);
282 if (!NT_SUCCESS(Status))
283 {
284 ERR("Could not get default locale (%08lx).\n", Status);
285 }
286 else
287 {
288 TRACE("DefaultLocale = %08lx\n", LocaleId);
289 }
290
291 if(!NT_SUCCESS(Status) || !(KBLList = UserLoadDllAndCreateKbl(LocaleId)))
292 {
293 ERR("Trying to load US Keyboard Layout.\n");
294 LocaleId = 0x409;
295
296 if(!(KBLList = UserLoadDllAndCreateKbl(LocaleId)))
297 {
298 ERR("Failed to load any Keyboard Layout\n");
299 return FALSE;
300 }
301 }
302
303 KBLList->Flags |= KBL_PRELOAD;
304
305 InitializeListHead(&KBLList->List);
306 return TRUE;
307 }
308
309 PKBL W32kGetDefaultKeyLayout(VOID)
310 {
311 const WCHAR szKeyboardLayoutPath[] = L"\\Keyboard Layout\\Preload";
312 const WCHAR szDefaultUserPath[] = L"\\REGISTRY\\USER\\.DEFAULT";
313
314 HANDLE KeyHandle;
315 LCID LayoutLocaleId = 0;
316 NTSTATUS Status;
317 OBJECT_ATTRIBUTES KeyAttributes;
318 PKBL pKbl;
319 UNICODE_STRING CurrentUserPath;
320 UNICODE_STRING FullKeyboardLayoutPath;
321 UNICODE_STRING LayoutValueName;
322 UNICODE_STRING LayoutLocaleIdString;
323 WCHAR wszBuffer[MAX_PATH];
324
325 // Get the path to HKEY_CURRENT_USER
326 Status = RtlFormatCurrentUserKeyPath(&CurrentUserPath);
327
328 if( NT_SUCCESS(Status) )
329 {
330 FullKeyboardLayoutPath.Buffer = wszBuffer;
331 FullKeyboardLayoutPath.MaximumLength = sizeof(wszBuffer);
332
333 // FIXME: Is this 100% correct?
334 // We're called very early, so HKEY_CURRENT_USER might not be available yet. Check this first.
335 InitializeObjectAttributes(&KeyAttributes, &CurrentUserPath, OBJ_CASE_INSENSITIVE, NULL, NULL);
336 Status = ZwOpenKey(&KeyHandle, KEY_READ, &KeyAttributes);
337
338 if(Status == STATUS_OBJECT_NAME_NOT_FOUND)
339 {
340 // It is not available, so read it from HKEY_USERS\.DEFAULT
341 FullKeyboardLayoutPath.Length = sizeof(szDefaultUserPath) - sizeof(UNICODE_NULL);
342 RtlCopyMemory(wszBuffer, szDefaultUserPath, sizeof(szDefaultUserPath));
343 }
344 else
345 {
346 // The path is available
347 ZwClose(KeyHandle);
348 RtlCopyUnicodeString(&FullKeyboardLayoutPath, &CurrentUserPath);
349 }
350
351 // Free CurrentUserPath - we dont need it anymore
352 RtlFreeUnicodeString(&CurrentUserPath);
353
354 Status = RtlAppendUnicodeToString(&FullKeyboardLayoutPath, szKeyboardLayoutPath);
355
356 if( NT_SUCCESS(Status) )
357 {
358 // Return the first keyboard layout listed there
359 RtlInitUnicodeString(&LayoutValueName, L"1");
360
361 Status = ReadRegistryValue(&FullKeyboardLayoutPath, &LayoutValueName, &LayoutLocaleIdString);
362
363 if( NT_SUCCESS(Status) )
364 {
365 RtlUnicodeStringToInteger(&LayoutLocaleIdString, 16, &LayoutLocaleId);
366 ExFreePoolWithTag(LayoutLocaleIdString.Buffer, TAG_STRING);
367 }
368 else
369 ERR("ReadRegistryValue failed! (%08lx).\n", Status);
370 }
371 else
372 ERR("RtlAppendUnicodeToString failed! (%08lx)\n", Status);
373 }
374 else
375 ERR("RtlFormatCurrentUserKeyPath failed! (%08lx)\n", Status);
376
377 if(!LayoutLocaleId)
378 {
379 ERR("Assuming default locale for the keyboard layout (0x409 - US)\n");
380 LayoutLocaleId = 0x409;
381 }
382
383 pKbl = KBLList;
384 do
385 {
386 if(pKbl->klid == LayoutLocaleId)
387 {
388 return pKbl;
389 }
390
391 pKbl = (PKBL) pKbl->List.Flink;
392 } while(pKbl != KBLList);
393
394 TRACE("Loading new default keyboard layout.\n");
395 pKbl = UserLoadDllAndCreateKbl(LayoutLocaleId);
396
397 if(!pKbl)
398 {
399 TRACE("Failed to load %x!!! Returning any availableKL.\n", LayoutLocaleId);
400 return KBLList;
401 }
402
403 InsertTailList(&KBLList->List, &pKbl->List);
404 return pKbl;
405 }
406
407 PKBL UserHklToKbl(HKL hKl)
408 {
409 PKBL pKbl = KBLList;
410 do
411 {
412 if(pKbl->hkl == hKl) return pKbl;
413 pKbl = (PKBL) pKbl->List.Flink;
414 } while(pKbl != KBLList);
415
416 return NULL;
417 }
418
419 BOOL UserUnloadKbl(PKBL pKbl)
420 {
421 /* According to msdn, UnloadKeyboardLayout can fail
422 if the keyboard layout identifier was preloaded. */
423
424 if(pKbl->Flags & KBL_PRELOAD)
425 {
426 ERR("Attempted to unload preloaded keyboard layout.\n");
427 return FALSE;
428 }
429
430 if(pKbl->RefCount > 0)
431 {
432 /* Layout is used by other threads.
433 Mark it as unloaded and don't do anything else. */
434 pKbl->Flags |= KBL_UNLOAD;
435 }
436 else
437 {
438 //Unload the layout
439 EngUnloadImage(pKbl->hModule);
440 RemoveEntryList(&pKbl->List);
441 ExFreePoolWithTag(pKbl, USERTAG_KBDLAYOUT);
442 }
443
444 return TRUE;
445 }
446
447 static PKBL co_UserActivateKbl(PTHREADINFO w32Thread, PKBL pKbl, UINT Flags)
448 {
449 PKBL Prev;
450
451 Prev = w32Thread->KeyboardLayout;
452 Prev->RefCount--;
453 w32Thread->KeyboardLayout = pKbl;
454 pKbl->RefCount++;
455
456 if(Flags & KLF_SETFORPROCESS)
457 {
458 //FIXME
459
460 }
461
462 if(Prev->Flags & KBL_UNLOAD && Prev->RefCount == 0)
463 {
464 UserUnloadKbl(Prev);
465 }
466
467 // Send WM_INPUTLANGCHANGE to thread's focus window
468 co_IntSendMessage(w32Thread->MessageQueue->FocusWindow,
469 WM_INPUTLANGCHANGE,
470 0, // FIXME: put charset here (what is this?)
471 (LPARAM)pKbl->hkl); //klid
472
473 return Prev;
474 }
475
476 /* EXPORTS *******************************************************************/
477
478 HKL FASTCALL
479 UserGetKeyboardLayout(
480 DWORD dwThreadId)
481 {
482 NTSTATUS Status;
483 PETHREAD Thread;
484 PTHREADINFO W32Thread;
485 HKL Ret;
486
487 if(!dwThreadId)
488 {
489 W32Thread = PsGetCurrentThreadWin32Thread();
490 return W32Thread->KeyboardLayout->hkl;
491 }
492
493 Status = PsLookupThreadByThreadId((HANDLE)(DWORD_PTR)dwThreadId, &Thread);
494 if(!NT_SUCCESS(Status))
495 {
496 EngSetLastError(ERROR_INVALID_PARAMETER);
497 return NULL;
498 }
499
500 W32Thread = PsGetThreadWin32Thread(Thread);
501 Ret = W32Thread->KeyboardLayout->hkl;
502 ObDereferenceObject(Thread);
503 return Ret;
504 }
505
506 UINT
507 APIENTRY
508 NtUserGetKeyboardLayoutList(
509 INT nItems,
510 HKL* pHklBuff)
511 {
512 UINT Ret = 0;
513 PKBL pKbl;
514
515 UserEnterShared();
516 pKbl = KBLList;
517
518 if(nItems == 0)
519 {
520 do
521 {
522 Ret++;
523 pKbl = (PKBL) pKbl->List.Flink;
524 } while(pKbl != KBLList);
525 }
526 else
527 {
528 _SEH2_TRY
529 {
530 ProbeForWrite(pHklBuff, nItems*sizeof(HKL), 4);
531
532 while(Ret < nItems)
533 {
534 if(!(pKbl->Flags & KBL_UNLOAD))
535 {
536 pHklBuff[Ret] = pKbl->hkl;
537 Ret++;
538 pKbl = (PKBL) pKbl->List.Flink;
539 if(pKbl == KBLList) break;
540 }
541 }
542
543 }
544 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
545 {
546 SetLastNtError(_SEH2_GetExceptionCode());
547 Ret = 0;
548 }
549 _SEH2_END;
550 }
551
552 UserLeave();
553 return Ret;
554 }
555
556 BOOL
557 APIENTRY
558 NtUserGetKeyboardLayoutName(
559 LPWSTR lpszName)
560 {
561 BOOL ret = FALSE;
562 PKBL pKbl;
563 PTHREADINFO pti;
564
565 UserEnterShared();
566
567 _SEH2_TRY
568 {
569 ProbeForWrite(lpszName, KL_NAMELENGTH*sizeof(WCHAR), 1);
570 pti = PsGetCurrentThreadWin32Thread();
571 pKbl = pti->KeyboardLayout;
572 RtlCopyMemory(lpszName, pKbl->Name, KL_NAMELENGTH*sizeof(WCHAR));
573 ret = TRUE;
574 }
575 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
576 {
577 SetLastNtError(_SEH2_GetExceptionCode());
578 ret = FALSE;
579 }
580 _SEH2_END;
581
582 UserLeave();
583 return ret;
584 }
585
586
587 HKL
588 APIENTRY
589 NtUserLoadKeyboardLayoutEx(
590 IN HANDLE Handle,
591 IN DWORD offTable,
592 IN PUNICODE_STRING puszKeyboardName,
593 IN HKL hKL,
594 IN PUNICODE_STRING puszKLID,
595 IN DWORD dwKLID,
596 IN UINT Flags)
597 {
598 HKL Ret = NULL;
599 PKBL pKbl = NULL, Cur;
600
601 UserEnterExclusive();
602
603 //Let's see if layout was already loaded.
604 Cur = KBLList;
605 do
606 {
607 if(Cur->klid == dwKLID)
608 {
609 pKbl = Cur;
610 pKbl->Flags &= ~KBL_UNLOAD;
611 break;
612 }
613
614 Cur = (PKBL) Cur->List.Flink;
615 } while(Cur != KBLList);
616
617 //It wasn't, so load it.
618 if(!pKbl)
619 {
620 pKbl = UserLoadDllAndCreateKbl(dwKLID);
621
622 if(!pKbl)
623 {
624 goto the_end;
625 }
626
627 InsertTailList(&KBLList->List, &pKbl->List);
628 }
629
630 if(Flags & KLF_REORDER) KBLList = pKbl;
631
632 if(Flags & KLF_ACTIVATE)
633 {
634 co_UserActivateKbl(PsGetCurrentThreadWin32Thread(), pKbl, Flags);
635 }
636
637 Ret = pKbl->hkl;
638
639 //FIXME: KLF_NOTELLSHELL
640 // KLF_REPLACELANG
641 // KLF_SUBSTITUTE_OK
642
643 the_end:
644 UserLeave();
645 return Ret;
646 }
647
648 HKL
649 APIENTRY
650 NtUserActivateKeyboardLayout(
651 HKL hKl,
652 ULONG Flags)
653 {
654 PKBL pKbl;
655 HKL Ret = NULL;
656 PTHREADINFO pWThread;
657
658 UserEnterExclusive();
659
660 pWThread = PsGetCurrentThreadWin32Thread();
661
662 if(pWThread->KeyboardLayout->hkl == hKl)
663 {
664 Ret = hKl;
665 goto the_end;
666 }
667
668 if(hKl == (HKL)HKL_NEXT)
669 {
670 pKbl = (PKBL)pWThread->KeyboardLayout->List.Flink;
671 }
672 else if(hKl == (HKL)HKL_PREV)
673 {
674 pKbl = (PKBL)pWThread->KeyboardLayout->List.Blink;
675 }
676 else pKbl = UserHklToKbl(hKl);
677
678 //FIXME: KLF_RESET, KLF_SHIFTLOCK
679
680 if(pKbl)
681 {
682 if(Flags & KLF_REORDER)
683 KBLList = pKbl;
684
685 if(pKbl == pWThread->KeyboardLayout)
686 {
687 Ret = pKbl->hkl;
688 }
689 else
690 {
691 pKbl = co_UserActivateKbl(pWThread, pKbl, Flags);
692 Ret = pKbl->hkl;
693 }
694 }
695 else
696 {
697 ERR("%s: Invalid HKL %x!\n", __FUNCTION__, hKl);
698 }
699
700 the_end:
701 UserLeave();
702 return Ret;
703 }
704
705 BOOL
706 APIENTRY
707 NtUserUnloadKeyboardLayout(
708 HKL hKl)
709 {
710 PKBL pKbl;
711 BOOL Ret = FALSE;
712
713 UserEnterExclusive();
714
715 if((pKbl = UserHklToKbl(hKl)))
716 {
717 Ret = UserUnloadKbl(pKbl);
718 }
719 else
720 {
721 ERR("%s: Invalid HKL %x!\n", __FUNCTION__, hKl);
722 }
723
724 UserLeave();
725 return Ret;
726 }
727
728 /* EOF */