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
11 DBG_DEFAULT_CHANNEL(UserKbdLayout
);
13 PKBL KBLList
= NULL
; // Keyboard layout list.
15 typedef PVOID (*KbdLayerDescriptor
)(VOID
);
18 /* PRIVATE FUNCTIONS ******************************************************/
22 * Utility function to read a value from the registry more easily.
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
31 static NTSTATUS APIENTRY
ReadRegistryValue( PUNICODE_STRING KeyName
,
32 PUNICODE_STRING ValueName
,
33 PUNICODE_STRING ReturnedValue
)
37 OBJECT_ATTRIBUTES KeyAttributes
;
38 PKEY_VALUE_PARTIAL_INFORMATION KeyValuePartialInfo
;
43 InitializeObjectAttributes(&KeyAttributes
, KeyName
, OBJ_CASE_INSENSITIVE
,
45 Status
= ZwOpenKey(&KeyHandle
, KEY_READ
, &KeyAttributes
);
46 if (!NT_SUCCESS(Status
))
51 Status
= ZwQueryValueKey(KeyHandle
, ValueName
, KeyValuePartialInformation
,
56 if (Status
!= STATUS_BUFFER_TOO_SMALL
)
62 ResLength
+= sizeof(*KeyValuePartialInfo
);
64 ExAllocatePoolWithTag(PagedPool
, ResLength
, TAG_STRING
);
67 if (!KeyValuePartialInfo
)
70 return STATUS_NO_MEMORY
;
73 Status
= ZwQueryValueKey(KeyHandle
,
75 KeyValuePartialInformation
,
76 (PVOID
)KeyValuePartialInfo
,
80 if (!NT_SUCCESS(Status
))
83 ExFreePoolWithTag(KeyValuePartialInfo
, TAG_STRING
);
87 /* At this point, KeyValuePartialInfo->Data contains the key data */
88 ReturnBuffer
= ExAllocatePoolWithTag(PagedPool
,
89 KeyValuePartialInfo
->DataLength
,
95 ExFreePoolWithTag(KeyValuePartialInfo
, TAG_STRING
);
96 return STATUS_NO_MEMORY
;
99 RtlCopyMemory(ReturnBuffer
,
100 KeyValuePartialInfo
->Data
,
101 KeyValuePartialInfo
->DataLength
);
102 RtlInitUnicodeString(ReturnedValue
, ReturnBuffer
);
104 ExFreePoolWithTag(KeyValuePartialInfo
, TAG_STRING
);
110 static BOOL
UserLoadKbdDll(WCHAR
*wsKLID
,
112 PKBDTABLES
*pKbdTables
)
115 KbdLayerDescriptor layerDescGetFn
;
116 ANSI_STRING kbdProcedureName
;
117 UNICODE_STRING LayoutKeyName
;
118 UNICODE_STRING LayoutValueName
;
119 UNICODE_STRING LayoutFile
;
120 UNICODE_STRING FullLayoutPath
;
122 WCHAR LayoutPathBuffer
[MAX_PATH
] = L
"\\SystemRoot\\System32\\";
123 WCHAR KeyNameBuffer
[MAX_PATH
] = L
"\\REGISTRY\\Machine\\SYSTEM\\"
124 L
"CurrentControlSet\\Control\\Keyboard Layouts\\";
126 RtlInitUnicodeString(&klid
, wsKLID
);
127 RtlInitUnicodeString(&LayoutValueName
, L
"Layout File");
128 RtlInitUnicodeString(&LayoutKeyName
, KeyNameBuffer
);
129 LayoutKeyName
.MaximumLength
= sizeof(KeyNameBuffer
);
131 RtlAppendUnicodeStringToString(&LayoutKeyName
, &klid
);
132 Status
= ReadRegistryValue(&LayoutKeyName
, &LayoutValueName
, &LayoutFile
);
134 if (!NT_SUCCESS(Status
))
136 TRACE("Can't get layout filename for %wZ. (%08lx)\n", klid
, Status
);
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
);
147 *phModule
= EngLoadImage(FullLayoutPath
.Buffer
);
151 TRACE("Loaded %wZ\n", &FullLayoutPath
);
153 RtlInitAnsiString( &kbdProcedureName
, "KbdLayerDescriptor" );
154 layerDescGetFn
= EngFindImageProcAddress(*phModule
, "KbdLayerDescriptor");
158 *pKbdTables
= layerDescGetFn();
162 ERR("Error: %wZ has no KbdLayerDescriptor()\n", &FullLayoutPath
);
165 if (!layerDescGetFn
|| !*pKbdTables
)
167 ERR("Failed to load the keyboard layout.\n");
168 EngUnloadImage(*phModule
);
172 #if 0 // Dump keyboard layout
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
);
182 DbgPrint("VkToBit %x -> %x\n", pVkToBit
->Vk
, pVkToBit
->ModBits
);
185 for (i
= 0; i
<= (*pKbdTables
)->pCharModifiers
->wMaxModBits
; ++i
)
186 DbgPrint("ModNumber %x -> %x\n", i
, (*pKbdTables
)->pCharModifiers
->ModNumber
[i
]);
187 while (pVkToWchTbl
->pVkToWchars
)
189 PVK_TO_WCHARS1 pVkToWch
= pVkToWchTbl
->pVkToWchars
;
190 DbgPrint("pVkToWchTbl nModifications %x cbSize %x\n", pVkToWchTbl
->nModifications
, pVkToWchTbl
->cbSize
);
191 while (pVkToWch
->VirtualKey
)
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
]);
197 pVkToWch
= (PVK_TO_WCHARS1
)(((PBYTE
)pVkToWch
) + pVkToWchTbl
->cbSize
);
201 DbgPrint("pusVSCtoVK: { ");
202 for (i
= 0; i
< (*pKbdTables
)->bMaxVSCtoVK
; ++i
)
203 DbgPrint("%x -> %x, ", i
, (*pKbdTables
)->pusVSCtoVK
[i
]);
205 DbgPrint("pVSCtoVK_E0: { ");
208 DbgPrint("%x -> %x, ", pVscVk
->Vsc
, pVscVk
->Vk
);
212 pVscVk
= (*pKbdTables
)->pVSCtoVK_E1
;
213 DbgPrint("pVSCtoVK_E1: { ");
216 DbgPrint("%x -> %x, ", pVscVk
->Vsc
, pVscVk
->Vk
);
226 ERR("Failed to load dll %wZ\n", &FullLayoutPath
);
233 static PKBL
UserLoadDllAndCreateKbl(DWORD LocaleId
)
239 NewKbl
= ExAllocatePoolWithTag(PagedPool
, sizeof(KBL
), USERTAG_KBDLAYOUT
);
243 ERR("%s: Can't allocate memory!\n", __FUNCTION__
);
247 swprintf(NewKbl
->Name
, L
"%08lx", LocaleId
);
249 if (!UserLoadKbdDll(NewKbl
->Name
, &NewKbl
->hModule
, &NewKbl
->KBTables
))
251 TRACE("%s: failed to load %x dll!\n", __FUNCTION__
, LocaleId
);
252 ExFreePoolWithTag(NewKbl
, USERTAG_KBDLAYOUT
);
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.
261 langid
= PRIMARYLANGID(LANGIDFROMLCID(LocaleId
));
264 if (langid
== LANG_CHINESE
|| langid
== LANG_JAPANESE
|| langid
== LANG_KOREAN
)
265 hKl
|= 0xe001 << 16; /* FIXME */
266 else hKl
|= hKl
<< 16;
268 NewKbl
->hkl
= (HKL
)(ULONG_PTR
) hKl
;
269 NewKbl
->klid
= LocaleId
;
271 NewKbl
->RefCount
= 0;
276 BOOL
UserInitDefaultKeyboardLayout()
281 Status
= ZwQueryDefaultLocale(FALSE
, &LocaleId
);
282 if (!NT_SUCCESS(Status
))
284 ERR("Could not get default locale (%08lx).\n", Status
);
288 TRACE("DefaultLocale = %08lx\n", LocaleId
);
291 if (!NT_SUCCESS(Status
) || !(KBLList
= UserLoadDllAndCreateKbl(LocaleId
)))
293 ERR("Trying to load US Keyboard Layout.\n");
296 if (!(KBLList
= UserLoadDllAndCreateKbl(LocaleId
)))
298 ERR("Failed to load any Keyboard Layout\n");
303 KBLList
->Flags
|= KBL_PRELOAD
;
305 InitializeListHead(&KBLList
->List
);
309 PKBL
W32kGetDefaultKeyLayout(VOID
)
311 const WCHAR szKeyboardLayoutPath
[] = L
"\\Keyboard Layout\\Preload";
312 const WCHAR szDefaultUserPath
[] = L
"\\REGISTRY\\USER\\.DEFAULT";
315 LCID LayoutLocaleId
= 0;
317 OBJECT_ATTRIBUTES KeyAttributes
;
319 UNICODE_STRING CurrentUserPath
;
320 UNICODE_STRING FullKeyboardLayoutPath
;
321 UNICODE_STRING LayoutValueName
;
322 UNICODE_STRING LayoutLocaleIdString
;
323 WCHAR wszBuffer
[MAX_PATH
];
325 // Get the path to HKEY_CURRENT_USER
326 Status
= RtlFormatCurrentUserKeyPath(&CurrentUserPath
);
328 if (NT_SUCCESS(Status
))
330 FullKeyboardLayoutPath
.Buffer
= wszBuffer
;
331 FullKeyboardLayoutPath
.MaximumLength
= sizeof(wszBuffer
);
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
);
338 if (Status
== STATUS_OBJECT_NAME_NOT_FOUND
)
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
));
346 // The path is available
348 RtlCopyUnicodeString(&FullKeyboardLayoutPath
, &CurrentUserPath
);
351 // Free CurrentUserPath - we dont need it anymore
352 RtlFreeUnicodeString(&CurrentUserPath
);
354 Status
= RtlAppendUnicodeToString(&FullKeyboardLayoutPath
, szKeyboardLayoutPath
);
356 if (NT_SUCCESS(Status
))
358 // Return the first keyboard layout listed there
359 RtlInitUnicodeString(&LayoutValueName
, L
"1");
361 Status
= ReadRegistryValue(&FullKeyboardLayoutPath
, &LayoutValueName
, &LayoutLocaleIdString
);
363 if (NT_SUCCESS(Status
))
365 RtlUnicodeStringToInteger(&LayoutLocaleIdString
, 16, &LayoutLocaleId
);
366 ExFreePoolWithTag(LayoutLocaleIdString
.Buffer
, TAG_STRING
);
369 ERR("ReadRegistryValue failed! (%08lx).\n", Status
);
372 ERR("RtlAppendUnicodeToString failed! (%08lx)\n", Status
);
375 ERR("RtlFormatCurrentUserKeyPath failed! (%08lx)\n", Status
);
379 ERR("Assuming default locale for the keyboard layout (0x409 - US)\n");
380 LayoutLocaleId
= 0x409;
386 if (pKbl
->klid
== LayoutLocaleId
)
391 pKbl
= (PKBL
) pKbl
->List
.Flink
;
392 } while (pKbl
!= KBLList
);
394 TRACE("Loading new default keyboard layout.\n");
395 pKbl
= UserLoadDllAndCreateKbl(LayoutLocaleId
);
399 TRACE("Failed to load %x!!! Returning any availableKL.\n", LayoutLocaleId
);
403 InsertTailList(&KBLList
->List
, &pKbl
->List
);
407 PKBL
UserHklToKbl(HKL hKl
)
412 if (pKbl
->hkl
== hKl
) return pKbl
;
413 pKbl
= (PKBL
) pKbl
->List
.Flink
;
414 } while (pKbl
!= KBLList
);
419 BOOL
UserUnloadKbl(PKBL pKbl
)
421 /* According to msdn, UnloadKeyboardLayout can fail
422 if the keyboard layout identifier was preloaded. */
424 if (pKbl
->Flags
& KBL_PRELOAD
)
426 ERR("Attempted to unload preloaded keyboard layout.\n");
430 if (pKbl
->RefCount
> 0)
432 /* Layout is used by other threads.
433 Mark it as unloaded and don't do anything else. */
434 pKbl
->Flags
|= KBL_UNLOAD
;
439 EngUnloadImage(pKbl
->hModule
);
440 RemoveEntryList(&pKbl
->List
);
441 ExFreePoolWithTag(pKbl
, USERTAG_KBDLAYOUT
);
447 static PKBL
co_UserActivateKbl(PTHREADINFO w32Thread
, PKBL pKbl
, UINT Flags
)
451 Prev
= w32Thread
->KeyboardLayout
;
453 w32Thread
->KeyboardLayout
= pKbl
;
456 if (Flags
& KLF_SETFORPROCESS
)
462 if (Prev
->Flags
& KBL_UNLOAD
&& Prev
->RefCount
== 0)
467 // Send WM_INPUTLANGCHANGE to thread's focus window
468 co_IntSendMessage(w32Thread
->MessageQueue
->FocusWindow
,
470 0, // FIXME: put charset here (what is this?)
471 (LPARAM
)pKbl
->hkl
); //klid
476 /* EXPORTS *******************************************************************/
479 UserGetKeyboardLayout(
484 PTHREADINFO W32Thread
;
489 W32Thread
= PsGetCurrentThreadWin32Thread();
490 return W32Thread
->KeyboardLayout
->hkl
;
493 Status
= PsLookupThreadByThreadId((HANDLE
)(DWORD_PTR
)dwThreadId
, &Thread
);
494 if (!NT_SUCCESS(Status
))
496 EngSetLastError(ERROR_INVALID_PARAMETER
);
500 W32Thread
= PsGetThreadWin32Thread(Thread
);
501 Ret
= W32Thread
->KeyboardLayout
->hkl
;
502 ObDereferenceObject(Thread
);
508 NtUserGetKeyboardLayoutList(
523 pKbl
= (PKBL
) pKbl
->List
.Flink
;
524 } while (pKbl
!= KBLList
);
530 ProbeForWrite(pHklBuff
, nItems
*sizeof(HKL
), 4);
534 if (!(pKbl
->Flags
& KBL_UNLOAD
))
536 pHklBuff
[Ret
] = pKbl
->hkl
;
538 pKbl
= (PKBL
) pKbl
->List
.Flink
;
539 if (pKbl
== KBLList
) break;
544 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
546 SetLastNtError(_SEH2_GetExceptionCode());
558 NtUserGetKeyboardLayoutName(
569 ProbeForWrite(lpszName
, KL_NAMELENGTH
*sizeof(WCHAR
), 1);
570 pti
= PsGetCurrentThreadWin32Thread();
571 pKbl
= pti
->KeyboardLayout
;
572 RtlCopyMemory(lpszName
, pKbl
->Name
, KL_NAMELENGTH
*sizeof(WCHAR
));
575 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
577 SetLastNtError(_SEH2_GetExceptionCode());
589 NtUserLoadKeyboardLayoutEx(
592 IN PUNICODE_STRING puszKeyboardName
,
594 IN PUNICODE_STRING puszKLID
,
599 PKBL pKbl
= NULL
, Cur
;
601 UserEnterExclusive();
603 //Let's see if layout was already loaded.
607 if (Cur
->klid
== dwKLID
)
610 pKbl
->Flags
&= ~KBL_UNLOAD
;
614 Cur
= (PKBL
) Cur
->List
.Flink
;
615 } while (Cur
!= KBLList
);
617 //It wasn't, so load it.
620 pKbl
= UserLoadDllAndCreateKbl(dwKLID
);
627 InsertTailList(&KBLList
->List
, &pKbl
->List
);
630 if (Flags
& KLF_REORDER
) KBLList
= pKbl
;
632 if (Flags
& KLF_ACTIVATE
)
634 co_UserActivateKbl(PsGetCurrentThreadWin32Thread(), pKbl
, Flags
);
639 //FIXME: KLF_NOTELLSHELL
650 NtUserActivateKeyboardLayout(
656 PTHREADINFO pWThread
;
658 UserEnterExclusive();
660 pWThread
= PsGetCurrentThreadWin32Thread();
662 if (pWThread
->KeyboardLayout
->hkl
== hKl
)
668 if (hKl
== (HKL
)HKL_NEXT
)
670 pKbl
= (PKBL
)pWThread
->KeyboardLayout
->List
.Flink
;
672 else if (hKl
== (HKL
)HKL_PREV
)
674 pKbl
= (PKBL
)pWThread
->KeyboardLayout
->List
.Blink
;
676 else pKbl
= UserHklToKbl(hKl
);
678 //FIXME: KLF_RESET, KLF_SHIFTLOCK
682 if (Flags
& KLF_REORDER
)
685 if (pKbl
== pWThread
->KeyboardLayout
)
691 pKbl
= co_UserActivateKbl(pWThread
, pKbl
, Flags
);
697 ERR("Invalid HKL %x!\n", hKl
);
707 NtUserUnloadKeyboardLayout(
713 UserEnterExclusive();
715 if ((pKbl
= UserHklToKbl(hKl
)))
717 Ret
= UserUnloadKbl(pKbl
);
721 ERR("Invalid HKL %x!\n", hKl
);