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