- Update to trunk
[reactos.git] / 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 layerDescGetFn = EngFindImageProcAddress(*phModule, "KbdLayerDescriptor");
167
168 if(layerDescGetFn)
169 {
170 *pKbdTables = layerDescGetFn();
171 }
172 else
173 {
174 DPRINT1("Error: %wZ has no KbdLayerDescriptor()\n", &FullLayoutPath);
175 }
176
177 if(!layerDescGetFn || !*pKbdTables)
178 {
179 DPRINT1("Failed to load the keyboard layout.\n");
180 EngUnloadImage(*phModule);
181 return FALSE;
182 }
183 }
184 else
185 {
186 DPRINT1("Failed to load dll %wZ\n", &FullLayoutPath);
187 return FALSE;
188 }
189
190 return TRUE;
191 }
192
193 static PKBL UserLoadDllAndCreateKbl(DWORD LocaleId)
194 {
195 PKBL NewKbl;
196 ULONG hKl;
197 LANGID langid;
198
199 NewKbl = ExAllocatePoolWithTag(PagedPool, sizeof(KBL), TAG_KEYBOARD);
200
201 if(!NewKbl)
202 {
203 DPRINT1("%s: Can't allocate memory!\n", __FUNCTION__);
204 return NULL;
205 }
206
207 swprintf(NewKbl->Name, L"%08lx", LocaleId);
208
209 if(!UserLoadKbdDll(NewKbl->Name, &NewKbl->hModule, &NewKbl->KBTables))
210 {
211 DPRINT("%s: failed to load %x dll!\n", __FUNCTION__, LocaleId);
212 ExFreePoolWithTag(NewKbl, TAG_KEYBOARD);
213 return NULL;
214 }
215
216 /* Microsoft Office expects this value to be something specific
217 * for Japanese and Korean Windows with an IME the value is 0xe001
218 * We should probably check to see if an IME exists and if so then
219 * set this word properly.
220 */
221 langid = PRIMARYLANGID(LANGIDFROMLCID(LocaleId));
222 hKl = LocaleId;
223
224 if (langid == LANG_CHINESE || langid == LANG_JAPANESE || langid == LANG_KOREAN)
225 hKl |= 0xe001 << 16; /* FIXME */
226 else hKl |= hKl << 16;
227
228 NewKbl->hkl = (HKL)(ULONG_PTR) hKl;
229 NewKbl->klid = LocaleId;
230 NewKbl->Flags = 0;
231 NewKbl->RefCount = 0;
232
233 return NewKbl;
234 }
235
236 BOOL UserInitDefaultKeyboardLayout()
237 {
238 LCID LocaleId;
239 NTSTATUS Status;
240
241 Status = ZwQueryDefaultLocale(FALSE, &LocaleId);
242 if (!NT_SUCCESS(Status))
243 {
244 DPRINT1("Could not get default locale (%08lx).\n", Status);
245 }
246 else
247 {
248 DPRINT("DefaultLocale = %08lx\n", LocaleId);
249 }
250
251 if(!NT_SUCCESS(Status) || !(KBLList = UserLoadDllAndCreateKbl(LocaleId)))
252 {
253 DPRINT1("Trying to load US Keyboard Layout.\n");
254 LocaleId = 0x409;
255
256 if(!(KBLList = UserLoadDllAndCreateKbl(LocaleId)))
257 {
258 DPRINT1("Failed to load any Keyboard Layout\n");
259 return FALSE;
260 }
261 }
262
263 KBLList->Flags |= KBL_PRELOAD;
264
265 InitializeListHead(&KBLList->List);
266 return TRUE;
267 }
268
269 PKBL W32kGetDefaultKeyLayout(VOID)
270 {
271 const WCHAR szKeyboardLayoutPath[] = L"\\Keyboard Layout\\Preload";
272 const WCHAR szDefaultUserPath[] = L"\\REGISTRY\\USER\\.DEFAULT";
273
274 HANDLE KeyHandle;
275 LCID LayoutLocaleId = 0;
276 NTSTATUS Status;
277 OBJECT_ATTRIBUTES KeyAttributes;
278 PKBL pKbl;
279 UNICODE_STRING CurrentUserPath;
280 UNICODE_STRING FullKeyboardLayoutPath;
281 UNICODE_STRING LayoutValueName;
282 UNICODE_STRING LayoutLocaleIdString;
283 WCHAR wszBuffer[MAX_PATH];
284
285 // Get the path to HKEY_CURRENT_USER
286 Status = RtlFormatCurrentUserKeyPath(&CurrentUserPath);
287
288 if( NT_SUCCESS(Status) )
289 {
290 // FIXME: Is this 100% correct?
291 // We're called very early, so HKEY_CURRENT_USER might not be available yet. Check this first.
292 InitializeObjectAttributes(&KeyAttributes, &CurrentUserPath, OBJ_CASE_INSENSITIVE, NULL, NULL);
293 Status = ZwOpenKey(&KeyHandle, KEY_READ, &KeyAttributes);
294
295 if(Status == STATUS_OBJECT_NAME_NOT_FOUND)
296 {
297 // It is not available, so read it from HKEY_USERS\.DEFAULT
298 RtlCopyMemory(wszBuffer, szDefaultUserPath, sizeof(szDefaultUserPath));
299 }
300 else
301 {
302 // The path is available
303 ZwClose(KeyHandle);
304 RtlCopyMemory(wszBuffer, CurrentUserPath.Buffer, CurrentUserPath.MaximumLength);
305 }
306
307 // Build the full path
308 RtlInitUnicodeString(&FullKeyboardLayoutPath, wszBuffer);
309 FullKeyboardLayoutPath.MaximumLength = MAX_PATH;
310
311 Status = RtlAppendUnicodeToString(&FullKeyboardLayoutPath, szKeyboardLayoutPath);
312
313 if( NT_SUCCESS(Status) )
314 {
315 // Return the first keyboard layout listed there
316 RtlInitUnicodeString(&LayoutValueName, L"1");
317
318 Status = ReadRegistryValue(&FullKeyboardLayoutPath, &LayoutValueName, &LayoutLocaleIdString);
319
320 if( NT_SUCCESS(Status) )
321 {
322 RtlUnicodeStringToInteger(&LayoutLocaleIdString, 16, &LayoutLocaleId);
323 ExFreePoolWithTag(LayoutLocaleIdString.Buffer, TAG_STRING);
324 }
325 else
326 DPRINT1("ReadRegistryValue failed! (%08lx).\n", Status);
327 }
328 else
329 DPRINT1("RtlAppendUnicodeToString failed! (%08lx)\n", Status);
330
331 RtlFreeUnicodeString(&CurrentUserPath);
332 }
333 else
334 DPRINT1("RtlFormatCurrentUserKeyPath failed! (%08lx)\n", Status);
335
336 if(!LayoutLocaleId)
337 {
338 // This block is only reached in case of a failure, so use DPRINT1 here
339 DPRINT1("Assuming default locale for the keyboard layout (0x409 - US)\n");
340 LayoutLocaleId = 0x409;
341 }
342
343 pKbl = KBLList;
344 do
345 {
346 if(pKbl->klid == LayoutLocaleId)
347 {
348 return pKbl;
349 }
350
351 pKbl = (PKBL) pKbl->List.Flink;
352 } while(pKbl != KBLList);
353
354 DPRINT("Loading new default keyboard layout.\n");
355 pKbl = UserLoadDllAndCreateKbl(LayoutLocaleId);
356
357 if(!pKbl)
358 {
359 DPRINT("Failed to load %x!!! Returning any availableKL.\n", LayoutLocaleId);
360 return KBLList;
361 }
362
363 InsertTailList(&KBLList->List, &pKbl->List);
364 return pKbl;
365 }
366
367 PKBL UserHklToKbl(HKL hKl)
368 {
369 PKBL pKbl = KBLList;
370 do
371 {
372 if(pKbl->hkl == hKl) return pKbl;
373 pKbl = (PKBL) pKbl->List.Flink;
374 } while(pKbl != KBLList);
375
376 return NULL;
377 }
378
379 BOOL UserUnloadKbl(PKBL pKbl)
380 {
381 /* According to msdn, UnloadKeyboardLayout can fail
382 if the keyboard layout identifier was preloaded. */
383
384 if(pKbl->Flags & KBL_PRELOAD)
385 {
386 DPRINT1("Attempted to unload preloaded keyboard layout.\n");
387 return FALSE;
388 }
389
390 if(pKbl->RefCount > 0)
391 {
392 /* Layout is used by other threads.
393 Mark it as unloaded and don't do anything else. */
394 pKbl->Flags |= KBL_UNLOAD;
395 }
396 else
397 {
398 //Unload the layout
399 EngUnloadImage(pKbl->hModule);
400 RemoveEntryList(&pKbl->List);
401 ExFreePoolWithTag(pKbl, TAG_KEYBOARD);
402 }
403
404 return TRUE;
405 }
406
407 static PKBL co_UserActivateKbl(PTHREADINFO w32Thread, PKBL pKbl, UINT Flags)
408 {
409 PKBL Prev;
410
411 Prev = w32Thread->KeyboardLayout;
412 Prev->RefCount--;
413 w32Thread->KeyboardLayout = pKbl;
414 pKbl->RefCount++;
415
416 if(Flags & KLF_SETFORPROCESS)
417 {
418 //FIXME
419
420 }
421
422 if(Prev->Flags & KBL_UNLOAD && Prev->RefCount == 0)
423 {
424 UserUnloadKbl(Prev);
425 }
426
427 // Send WM_INPUTLANGCHANGE to thread's focus window
428 co_IntSendMessage(w32Thread->MessageQueue->FocusWindow,
429 WM_INPUTLANGCHANGE,
430 0, // FIXME: put charset here (what is this?)
431 (LPARAM)pKbl->hkl); //klid
432
433 return Prev;
434 }
435
436 /* EXPORTS *******************************************************************/
437
438 HKL FASTCALL
439 UserGetKeyboardLayout(
440 DWORD dwThreadId)
441 {
442 NTSTATUS Status;
443 PETHREAD Thread;
444 PTHREADINFO W32Thread;
445 HKL Ret;
446
447 if(!dwThreadId)
448 {
449 W32Thread = PsGetCurrentThreadWin32Thread();
450 return W32Thread->KeyboardLayout->hkl;
451 }
452
453 Status = PsLookupThreadByThreadId((HANDLE)(DWORD_PTR)dwThreadId, &Thread);
454 if(!NT_SUCCESS(Status))
455 {
456 SetLastWin32Error(ERROR_INVALID_PARAMETER);
457 return NULL;
458 }
459
460 W32Thread = PsGetThreadWin32Thread(Thread);
461 Ret = W32Thread->KeyboardLayout->hkl;
462 ObDereferenceObject(Thread);
463 return Ret;
464 }
465
466 UINT
467 APIENTRY
468 NtUserGetKeyboardLayoutList(
469 INT nItems,
470 HKL* pHklBuff)
471 {
472 UINT Ret = 0;
473 PKBL pKbl;
474
475 UserEnterShared();
476 pKbl = KBLList;
477
478 if(nItems == 0)
479 {
480 do
481 {
482 Ret++;
483 pKbl = (PKBL) pKbl->List.Flink;
484 } while(pKbl != KBLList);
485 }
486 else
487 {
488 _SEH2_TRY
489 {
490 ProbeForWrite(pHklBuff, nItems*sizeof(HKL), 4);
491
492 while(Ret < nItems)
493 {
494 if(!(pKbl->Flags & KBL_UNLOAD))
495 {
496 pHklBuff[Ret] = pKbl->hkl;
497 Ret++;
498 pKbl = (PKBL) pKbl->List.Flink;
499 if(pKbl == KBLList) break;
500 }
501 }
502
503 }
504 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
505 {
506 SetLastNtError(_SEH2_GetExceptionCode());
507 Ret = 0;
508 }
509 _SEH2_END;
510 }
511
512 UserLeave();
513 return Ret;
514 }
515
516 BOOL
517 APIENTRY
518 NtUserGetKeyboardLayoutName(
519 LPWSTR lpszName)
520 {
521 BOOL ret = FALSE;
522 PKBL pKbl;
523 PTHREADINFO pti;
524
525 UserEnterShared();
526
527 _SEH2_TRY
528 {
529 ProbeForWrite(lpszName, KL_NAMELENGTH*sizeof(WCHAR), 1);
530 pti = PsGetCurrentThreadWin32Thread();
531 pKbl = pti->KeyboardLayout;
532 RtlCopyMemory(lpszName, pKbl->Name, KL_NAMELENGTH*sizeof(WCHAR));
533 ret = TRUE;
534 }
535 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
536 {
537 SetLastNtError(_SEH2_GetExceptionCode());
538 ret = FALSE;
539 }
540 _SEH2_END;
541
542 UserLeave();
543 return ret;
544 }
545
546
547 HKL
548 APIENTRY
549 NtUserLoadKeyboardLayoutEx(
550 IN HANDLE Handle,
551 IN DWORD offTable,
552 IN PUNICODE_STRING puszKeyboardName,
553 IN HKL hKL,
554 IN PUNICODE_STRING puszKLID,
555 IN DWORD dwKLID,
556 IN UINT Flags)
557 {
558 HKL Ret = NULL;
559 PKBL pKbl = NULL, Cur;
560
561 UserEnterExclusive();
562
563 //Let's see if layout was already loaded.
564 Cur = KBLList;
565 do
566 {
567 if(Cur->klid == dwKLID)
568 {
569 pKbl = Cur;
570 pKbl->Flags &= ~KBL_UNLOAD;
571 break;
572 }
573
574 Cur = (PKBL) Cur->List.Flink;
575 } while(Cur != KBLList);
576
577 //It wasn't, so load it.
578 if(!pKbl)
579 {
580 pKbl = UserLoadDllAndCreateKbl(dwKLID);
581
582 if(!pKbl)
583 {
584 goto the_end;
585 }
586
587 InsertTailList(&KBLList->List, &pKbl->List);
588 }
589
590 if(Flags & KLF_REORDER) KBLList = pKbl;
591
592 if(Flags & KLF_ACTIVATE)
593 {
594 co_UserActivateKbl(PsGetCurrentThreadWin32Thread(), pKbl, Flags);
595 }
596
597 Ret = pKbl->hkl;
598
599 //FIXME: KLF_NOTELLSHELL
600 // KLF_REPLACELANG
601 // KLF_SUBSTITUTE_OK
602
603 the_end:
604 UserLeave();
605 return Ret;
606 }
607
608 HKL
609 APIENTRY
610 NtUserActivateKeyboardLayout(
611 HKL hKl,
612 ULONG Flags)
613 {
614 PKBL pKbl;
615 HKL Ret = NULL;
616 PTHREADINFO pWThread;
617
618 UserEnterExclusive();
619
620 pWThread = PsGetCurrentThreadWin32Thread();
621
622 if(pWThread->KeyboardLayout->hkl == hKl)
623 {
624 Ret = hKl;
625 goto the_end;
626 }
627
628 if(hKl == (HKL)HKL_NEXT)
629 {
630 pKbl = (PKBL)pWThread->KeyboardLayout->List.Flink;
631 }
632 else if(hKl == (HKL)HKL_PREV)
633 {
634 pKbl = (PKBL)pWThread->KeyboardLayout->List.Blink;
635 }
636 else pKbl = UserHklToKbl(hKl);
637
638 //FIXME: KLF_RESET, KLF_SHIFTLOCK
639
640 if(pKbl)
641 {
642 if(Flags & KLF_REORDER)
643 KBLList = pKbl;
644
645 if(pKbl == pWThread->KeyboardLayout)
646 {
647 Ret = pKbl->hkl;
648 }
649 else
650 {
651 pKbl = co_UserActivateKbl(pWThread, pKbl, Flags);
652 Ret = pKbl->hkl;
653 }
654 }
655 else
656 {
657 DPRINT1("%s: Invalid HKL %x!\n", __FUNCTION__, hKl);
658 }
659
660 the_end:
661 UserLeave();
662 return Ret;
663 }
664
665 BOOL
666 APIENTRY
667 NtUserUnloadKeyboardLayout(
668 HKL hKl)
669 {
670 PKBL pKbl;
671 BOOL Ret = FALSE;
672
673 UserEnterExclusive();
674
675 if((pKbl = UserHklToKbl(hKl)))
676 {
677 Ret = UserUnloadKbl(pKbl);
678 }
679 else
680 {
681 DPRINT1("%s: Invalid HKL %x!\n", __FUNCTION__, hKl);
682 }
683
684 UserLeave();
685 return Ret;
686 }
687
688 /* EOF */