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