3 * PROJECT: ReactOS Kernel
4 * LICENSE: GPL - See COPYING in the top level directory
5 * FILE: subsystems/win32/win32k/ntuser/kbdlayout.c
6 * PURPOSE: Keyboard layout management
7 * COPYRIGHT: Copyright 2007 Saveliy Tretiakov
8 * Copyright 2008 Colin Finck
13 /* INCLUDES ******************************************************************/
17 DBG_DEFAULT_CHANNEL(UserKbdLayout
);
19 PKBL KBLList
= NULL
; // Keyboard layout list.
21 typedef PVOID (*KbdLayerDescriptor
)(VOID
);
22 NTSTATUS APIENTRY
LdrGetProcedureAddress(PVOID module
,
23 PANSI_STRING import_name
,
29 /* PRIVATE FUNCTIONS ******************************************************/
33 * Utility function to read a value from the registry more easily.
35 * IN PUNICODE_STRING KeyName -> Name of key to open
36 * IN PUNICODE_STRING ValueName -> Name of value to open
37 * OUT PUNICODE_STRING ReturnedValue -> String contained in registry
42 static NTSTATUS APIENTRY
ReadRegistryValue( PUNICODE_STRING KeyName
,
43 PUNICODE_STRING ValueName
,
44 PUNICODE_STRING ReturnedValue
)
48 OBJECT_ATTRIBUTES KeyAttributes
;
49 PKEY_VALUE_PARTIAL_INFORMATION KeyValuePartialInfo
;
54 InitializeObjectAttributes(&KeyAttributes
, KeyName
, OBJ_CASE_INSENSITIVE
,
56 Status
= ZwOpenKey(&KeyHandle
, KEY_READ
, &KeyAttributes
);
57 if( !NT_SUCCESS(Status
) )
62 Status
= ZwQueryValueKey(KeyHandle
, ValueName
, KeyValuePartialInformation
,
67 if( Status
!= STATUS_BUFFER_TOO_SMALL
)
73 ResLength
+= sizeof( *KeyValuePartialInfo
);
75 ExAllocatePoolWithTag(PagedPool
, ResLength
, TAG_STRING
);
78 if( !KeyValuePartialInfo
)
81 return STATUS_NO_MEMORY
;
84 Status
= ZwQueryValueKey(KeyHandle
,
86 KeyValuePartialInformation
,
87 (PVOID
)KeyValuePartialInfo
,
91 if( !NT_SUCCESS(Status
) )
94 ExFreePoolWithTag(KeyValuePartialInfo
, TAG_STRING
);
98 /* At this point, KeyValuePartialInfo->Data contains the key data */
99 ReturnBuffer
= ExAllocatePoolWithTag(PagedPool
,
100 KeyValuePartialInfo
->DataLength
,
106 ExFreePoolWithTag(KeyValuePartialInfo
, TAG_STRING
);
107 return STATUS_NO_MEMORY
;
110 RtlCopyMemory(ReturnBuffer
,
111 KeyValuePartialInfo
->Data
,
112 KeyValuePartialInfo
->DataLength
);
113 RtlInitUnicodeString(ReturnedValue
, ReturnBuffer
);
115 ExFreePoolWithTag(KeyValuePartialInfo
, TAG_STRING
);
121 static BOOL
UserLoadKbdDll(WCHAR
*wsKLID
,
123 PKBDTABLES
*pKbdTables
)
126 KbdLayerDescriptor layerDescGetFn
;
127 ANSI_STRING kbdProcedureName
;
128 UNICODE_STRING LayoutKeyName
;
129 UNICODE_STRING LayoutValueName
;
130 UNICODE_STRING LayoutFile
;
131 UNICODE_STRING FullLayoutPath
;
133 WCHAR LayoutPathBuffer
[MAX_PATH
] = L
"\\SystemRoot\\System32\\";
134 WCHAR KeyNameBuffer
[MAX_PATH
] = L
"\\REGISTRY\\Machine\\SYSTEM\\"
135 L
"CurrentControlSet\\Control\\Keyboard Layouts\\";
137 RtlInitUnicodeString(&klid
, wsKLID
);
138 RtlInitUnicodeString(&LayoutValueName
,L
"Layout File");
139 RtlInitUnicodeString(&LayoutKeyName
, KeyNameBuffer
);
140 LayoutKeyName
.MaximumLength
= sizeof(KeyNameBuffer
);
142 RtlAppendUnicodeStringToString(&LayoutKeyName
, &klid
);
143 Status
= ReadRegistryValue(&LayoutKeyName
, &LayoutValueName
, &LayoutFile
);
145 if(!NT_SUCCESS(Status
))
147 TRACE("Can't get layout filename for %wZ. (%08lx)\n", klid
, Status
);
151 TRACE("Read registry and got %wZ\n", &LayoutFile
);
152 RtlInitUnicodeString(&FullLayoutPath
, LayoutPathBuffer
);
153 FullLayoutPath
.MaximumLength
= sizeof(LayoutPathBuffer
);
154 RtlAppendUnicodeStringToString(&FullLayoutPath
, &LayoutFile
);
155 TRACE("Loading Keyboard DLL %wZ\n", &FullLayoutPath
);
156 ExFreePoolWithTag(LayoutFile
.Buffer
, TAG_STRING
);
158 *phModule
= EngLoadImage(FullLayoutPath
.Buffer
);
162 TRACE("Loaded %wZ\n", &FullLayoutPath
);
164 RtlInitAnsiString( &kbdProcedureName
, "KbdLayerDescriptor" );
165 layerDescGetFn
= EngFindImageProcAddress(*phModule
, "KbdLayerDescriptor");
169 *pKbdTables
= layerDescGetFn();
173 ERR("Error: %wZ has no KbdLayerDescriptor()\n", &FullLayoutPath
);
176 if(!layerDescGetFn
|| !*pKbdTables
)
178 ERR("Failed to load the keyboard layout.\n");
179 EngUnloadImage(*phModule
);
185 ERR("Failed to load dll %wZ\n", &FullLayoutPath
);
192 static PKBL
UserLoadDllAndCreateKbl(DWORD LocaleId
)
198 NewKbl
= ExAllocatePoolWithTag(PagedPool
, sizeof(KBL
), USERTAG_KBDLAYOUT
);
202 ERR("%s: Can't allocate memory!\n", __FUNCTION__
);
206 swprintf(NewKbl
->Name
, L
"%08lx", LocaleId
);
208 if(!UserLoadKbdDll(NewKbl
->Name
, &NewKbl
->hModule
, &NewKbl
->KBTables
))
210 TRACE("%s: failed to load %x dll!\n", __FUNCTION__
, LocaleId
);
211 ExFreePoolWithTag(NewKbl
, USERTAG_KBDLAYOUT
);
215 /* Microsoft Office expects this value to be something specific
216 * for Japanese and Korean Windows with an IME the value is 0xe001
217 * We should probably check to see if an IME exists and if so then
218 * set this word properly.
220 langid
= PRIMARYLANGID(LANGIDFROMLCID(LocaleId
));
223 if (langid
== LANG_CHINESE
|| langid
== LANG_JAPANESE
|| langid
== LANG_KOREAN
)
224 hKl
|= 0xe001 << 16; /* FIXME */
225 else hKl
|= hKl
<< 16;
227 NewKbl
->hkl
= (HKL
)(ULONG_PTR
) hKl
;
228 NewKbl
->klid
= LocaleId
;
230 NewKbl
->RefCount
= 0;
235 BOOL
UserInitDefaultKeyboardLayout()
240 Status
= ZwQueryDefaultLocale(FALSE
, &LocaleId
);
241 if (!NT_SUCCESS(Status
))
243 ERR("Could not get default locale (%08lx).\n", Status
);
247 TRACE("DefaultLocale = %08lx\n", LocaleId
);
250 if(!NT_SUCCESS(Status
) || !(KBLList
= UserLoadDllAndCreateKbl(LocaleId
)))
252 ERR("Trying to load US Keyboard Layout.\n");
255 if(!(KBLList
= UserLoadDllAndCreateKbl(LocaleId
)))
257 ERR("Failed to load any Keyboard Layout\n");
262 KBLList
->Flags
|= KBL_PRELOAD
;
264 InitializeListHead(&KBLList
->List
);
268 PKBL
W32kGetDefaultKeyLayout(VOID
)
270 const WCHAR szKeyboardLayoutPath
[] = L
"\\Keyboard Layout\\Preload";
271 const WCHAR szDefaultUserPath
[] = L
"\\REGISTRY\\USER\\.DEFAULT";
274 LCID LayoutLocaleId
= 0;
276 OBJECT_ATTRIBUTES KeyAttributes
;
278 UNICODE_STRING CurrentUserPath
;
279 UNICODE_STRING FullKeyboardLayoutPath
;
280 UNICODE_STRING LayoutValueName
;
281 UNICODE_STRING LayoutLocaleIdString
;
282 WCHAR wszBuffer
[MAX_PATH
];
284 // Get the path to HKEY_CURRENT_USER
285 Status
= RtlFormatCurrentUserKeyPath(&CurrentUserPath
);
287 if( NT_SUCCESS(Status
) )
289 FullKeyboardLayoutPath
.Buffer
= wszBuffer
;
290 FullKeyboardLayoutPath
.MaximumLength
= sizeof(wszBuffer
);
292 // FIXME: Is this 100% correct?
293 // We're called very early, so HKEY_CURRENT_USER might not be available yet. Check this first.
294 InitializeObjectAttributes(&KeyAttributes
, &CurrentUserPath
, OBJ_CASE_INSENSITIVE
, NULL
, NULL
);
295 Status
= ZwOpenKey(&KeyHandle
, KEY_READ
, &KeyAttributes
);
297 if(Status
== STATUS_OBJECT_NAME_NOT_FOUND
)
299 // It is not available, so read it from HKEY_USERS\.DEFAULT
300 FullKeyboardLayoutPath
.Length
= sizeof(szDefaultUserPath
) - sizeof(UNICODE_NULL
);
301 RtlCopyMemory(wszBuffer
, szDefaultUserPath
, sizeof(szDefaultUserPath
));
305 // The path is available
307 RtlCopyUnicodeString(&FullKeyboardLayoutPath
, &CurrentUserPath
);
310 // Free CurrentUserPath - we dont need it anymore
311 RtlFreeUnicodeString(&CurrentUserPath
);
313 Status
= RtlAppendUnicodeToString(&FullKeyboardLayoutPath
, szKeyboardLayoutPath
);
315 if( NT_SUCCESS(Status
) )
317 // Return the first keyboard layout listed there
318 RtlInitUnicodeString(&LayoutValueName
, L
"1");
320 Status
= ReadRegistryValue(&FullKeyboardLayoutPath
, &LayoutValueName
, &LayoutLocaleIdString
);
322 if( NT_SUCCESS(Status
) )
324 RtlUnicodeStringToInteger(&LayoutLocaleIdString
, 16, &LayoutLocaleId
);
325 ExFreePoolWithTag(LayoutLocaleIdString
.Buffer
, TAG_STRING
);
328 ERR("ReadRegistryValue failed! (%08lx).\n", Status
);
331 ERR("RtlAppendUnicodeToString failed! (%08lx)\n", Status
);
334 ERR("RtlFormatCurrentUserKeyPath failed! (%08lx)\n", Status
);
338 ERR("Assuming default locale for the keyboard layout (0x409 - US)\n");
339 LayoutLocaleId
= 0x409;
345 if(pKbl
->klid
== LayoutLocaleId
)
350 pKbl
= (PKBL
) pKbl
->List
.Flink
;
351 } while(pKbl
!= KBLList
);
353 TRACE("Loading new default keyboard layout.\n");
354 pKbl
= UserLoadDllAndCreateKbl(LayoutLocaleId
);
358 TRACE("Failed to load %x!!! Returning any availableKL.\n", LayoutLocaleId
);
362 InsertTailList(&KBLList
->List
, &pKbl
->List
);
366 PKBL
UserHklToKbl(HKL hKl
)
371 if(pKbl
->hkl
== hKl
) return pKbl
;
372 pKbl
= (PKBL
) pKbl
->List
.Flink
;
373 } while(pKbl
!= KBLList
);
378 BOOL
UserUnloadKbl(PKBL pKbl
)
380 /* According to msdn, UnloadKeyboardLayout can fail
381 if the keyboard layout identifier was preloaded. */
383 if(pKbl
->Flags
& KBL_PRELOAD
)
385 ERR("Attempted to unload preloaded keyboard layout.\n");
389 if(pKbl
->RefCount
> 0)
391 /* Layout is used by other threads.
392 Mark it as unloaded and don't do anything else. */
393 pKbl
->Flags
|= KBL_UNLOAD
;
398 EngUnloadImage(pKbl
->hModule
);
399 RemoveEntryList(&pKbl
->List
);
400 ExFreePoolWithTag(pKbl
, USERTAG_KBDLAYOUT
);
406 static PKBL
co_UserActivateKbl(PTHREADINFO w32Thread
, PKBL pKbl
, UINT Flags
)
410 Prev
= w32Thread
->KeyboardLayout
;
412 w32Thread
->KeyboardLayout
= pKbl
;
415 if(Flags
& KLF_SETFORPROCESS
)
421 if(Prev
->Flags
& KBL_UNLOAD
&& Prev
->RefCount
== 0)
426 // Send WM_INPUTLANGCHANGE to thread's focus window
427 co_IntSendMessage(w32Thread
->MessageQueue
->FocusWindow
,
429 0, // FIXME: put charset here (what is this?)
430 (LPARAM
)pKbl
->hkl
); //klid
435 /* EXPORTS *******************************************************************/
438 UserGetKeyboardLayout(
443 PTHREADINFO W32Thread
;
448 W32Thread
= PsGetCurrentThreadWin32Thread();
449 return W32Thread
->KeyboardLayout
->hkl
;
452 Status
= PsLookupThreadByThreadId((HANDLE
)(DWORD_PTR
)dwThreadId
, &Thread
);
453 if(!NT_SUCCESS(Status
))
455 EngSetLastError(ERROR_INVALID_PARAMETER
);
459 W32Thread
= PsGetThreadWin32Thread(Thread
);
460 Ret
= W32Thread
->KeyboardLayout
->hkl
;
461 ObDereferenceObject(Thread
);
467 NtUserGetKeyboardLayoutList(
482 pKbl
= (PKBL
) pKbl
->List
.Flink
;
483 } while(pKbl
!= KBLList
);
489 ProbeForWrite(pHklBuff
, nItems
*sizeof(HKL
), 4);
493 if(!(pKbl
->Flags
& KBL_UNLOAD
))
495 pHklBuff
[Ret
] = pKbl
->hkl
;
497 pKbl
= (PKBL
) pKbl
->List
.Flink
;
498 if(pKbl
== KBLList
) break;
503 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
505 SetLastNtError(_SEH2_GetExceptionCode());
517 NtUserGetKeyboardLayoutName(
528 ProbeForWrite(lpszName
, KL_NAMELENGTH
*sizeof(WCHAR
), 1);
529 pti
= PsGetCurrentThreadWin32Thread();
530 pKbl
= pti
->KeyboardLayout
;
531 RtlCopyMemory(lpszName
, pKbl
->Name
, KL_NAMELENGTH
*sizeof(WCHAR
));
534 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
536 SetLastNtError(_SEH2_GetExceptionCode());
548 NtUserLoadKeyboardLayoutEx(
551 IN PUNICODE_STRING puszKeyboardName
,
553 IN PUNICODE_STRING puszKLID
,
558 PKBL pKbl
= NULL
, Cur
;
560 UserEnterExclusive();
562 //Let's see if layout was already loaded.
566 if(Cur
->klid
== dwKLID
)
569 pKbl
->Flags
&= ~KBL_UNLOAD
;
573 Cur
= (PKBL
) Cur
->List
.Flink
;
574 } while(Cur
!= KBLList
);
576 //It wasn't, so load it.
579 pKbl
= UserLoadDllAndCreateKbl(dwKLID
);
586 InsertTailList(&KBLList
->List
, &pKbl
->List
);
589 if(Flags
& KLF_REORDER
) KBLList
= pKbl
;
591 if(Flags
& KLF_ACTIVATE
)
593 co_UserActivateKbl(PsGetCurrentThreadWin32Thread(), pKbl
, Flags
);
598 //FIXME: KLF_NOTELLSHELL
609 NtUserActivateKeyboardLayout(
615 PTHREADINFO pWThread
;
617 UserEnterExclusive();
619 pWThread
= PsGetCurrentThreadWin32Thread();
621 if(pWThread
->KeyboardLayout
->hkl
== hKl
)
627 if(hKl
== (HKL
)HKL_NEXT
)
629 pKbl
= (PKBL
)pWThread
->KeyboardLayout
->List
.Flink
;
631 else if(hKl
== (HKL
)HKL_PREV
)
633 pKbl
= (PKBL
)pWThread
->KeyboardLayout
->List
.Blink
;
635 else pKbl
= UserHklToKbl(hKl
);
637 //FIXME: KLF_RESET, KLF_SHIFTLOCK
641 if(Flags
& KLF_REORDER
)
644 if(pKbl
== pWThread
->KeyboardLayout
)
650 pKbl
= co_UserActivateKbl(pWThread
, pKbl
, Flags
);
656 ERR("%s: Invalid HKL %x!\n", __FUNCTION__
, hKl
);
666 NtUserUnloadKeyboardLayout(
672 UserEnterExclusive();
674 if((pKbl
= UserHklToKbl(hKl
)))
676 Ret
= UserUnloadKbl(pKbl
);
680 ERR("%s: Invalid HKL %x!\n", __FUNCTION__
, hKl
);