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