5ff0cdcb5aaf2c6fc079be251662b0813756cd35
[reactos.git] / reactos / subsys / win32k / ntuser / keyboard.c
1 /*
2 * ReactOS W32 Subsystem
3 * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003 ReactOS Team
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 /*
20 * COPYRIGHT: See COPYING in the top level directory
21 * PROJECT: ReactOS kernel
22 * PURPOSE: Messages
23 * FILE: subsys/win32k/ntuser/keyboard.c
24 * PROGRAMER: Casper S. Hornstrup (chorns@users.sourceforge.net)
25 * REVISION HISTORY:
26 * 06-06-2001 CSH Created
27 */
28
29 /* INCLUDES ******************************************************************/
30
31 #include <w32k.h>
32
33 #define NDEBUG
34 #include <debug.h>
35
36 /* Directory to load key layouts from */
37 #define SYSTEMROOT_DIR L"\\SystemRoot\\System32\\"
38 /* Lock modifiers */
39 #define CAPITAL_BIT 0x80000000
40 #define NUMLOCK_BIT 0x40000000
41 #define MOD_BITS_MASK 0x3fffffff
42 #define MOD_KCTRL 0x02
43 /* Key States */
44 #define KS_DOWN_MASK 0xc0
45 #define KS_DOWN_BIT 0x80
46 #define KS_LOCK_BIT 0x01
47 /* lParam bits */
48 #define LP_EXT_BIT (1<<24)
49 /* From kbdxx.c -- Key changes with numlock */
50 #define KNUMP 0x400
51
52
53 BYTE gQueueKeyStateTable[256];
54
55
56 /* FUNCTIONS *****************************************************************/
57
58 /* Initialization -- Right now, just zero the key state and init the lock */
59 NTSTATUS FASTCALL InitKeyboardImpl(VOID)
60 {
61 RtlZeroMemory(&gQueueKeyStateTable,0x100);
62 return STATUS_SUCCESS;
63 }
64
65 /*** Statics used by TranslateMessage ***/
66
67 /*** Shift state code was out of hand, sorry. --- arty */
68
69 static UINT DontDistinguishShifts( UINT ret )
70 {
71 if( ret == VK_LSHIFT || ret == VK_RSHIFT )
72 ret = VK_LSHIFT;
73 if( ret == VK_LCONTROL || ret == VK_RCONTROL )
74 ret = VK_LCONTROL;
75 if( ret == VK_LMENU || ret == VK_RMENU )
76 ret = VK_LMENU;
77 return ret;
78 }
79
80 static VOID STDCALL SetKeyState(DWORD key, DWORD vk, DWORD ext, BOOL down)
81 {
82 ASSERT(vk <= 0xff);
83
84 /* Special handling for toggles like numpad and caps lock */
85 if (vk == VK_CAPITAL || vk == VK_NUMLOCK)
86 {
87 if (down)
88 gQueueKeyStateTable[vk] ^= KS_LOCK_BIT;
89 }
90
91 if (ext && vk == VK_LSHIFT)
92 vk = VK_RSHIFT;
93 if (ext && vk == VK_LCONTROL)
94 vk = VK_RCONTROL;
95 if (ext && vk == VK_LMENU)
96 vk = VK_RMENU;
97
98 if (down)
99 gQueueKeyStateTable[vk] |= KS_DOWN_BIT;
100 else
101 gQueueKeyStateTable[vk] &= ~KS_DOWN_MASK;
102
103 if (vk == VK_LSHIFT || vk == VK_RSHIFT)
104 {
105 if ((gQueueKeyStateTable[VK_LSHIFT] & KS_DOWN_BIT) ||
106 (gQueueKeyStateTable[VK_RSHIFT] & KS_DOWN_BIT))
107 {
108 gQueueKeyStateTable[VK_SHIFT] |= KS_DOWN_BIT;
109 }
110 else
111 {
112 gQueueKeyStateTable[VK_SHIFT] &= ~KS_DOWN_MASK;
113 }
114 }
115
116 if (vk == VK_LCONTROL || vk == VK_RCONTROL)
117 {
118 if ((gQueueKeyStateTable[VK_LCONTROL] & KS_DOWN_BIT) ||
119 (gQueueKeyStateTable[VK_RCONTROL] & KS_DOWN_BIT))
120 {
121 gQueueKeyStateTable[VK_CONTROL] |= KS_DOWN_BIT;
122 }
123 else
124 {
125 gQueueKeyStateTable[VK_CONTROL] &= ~KS_DOWN_MASK;
126 }
127 }
128
129 if (vk == VK_LMENU || vk == VK_RMENU)
130 {
131 if ((gQueueKeyStateTable[VK_LMENU] & KS_DOWN_BIT) ||
132 (gQueueKeyStateTable[VK_RMENU] & KS_DOWN_BIT))
133 {
134 gQueueKeyStateTable[VK_MENU] |= KS_DOWN_BIT;
135 }
136 else
137 {
138 gQueueKeyStateTable[VK_MENU] &= ~KS_DOWN_MASK;
139 }
140 }
141 }
142
143 VOID DumpKeyState( PBYTE KeyState )
144 {
145 int i;
146
147 DbgPrint( "KeyState { " );
148 for( i = 0; i < 0x100; i++ )
149 {
150 if( KeyState[i] )
151 DbgPrint( "%02x(%02x) ", i, KeyState[i] );
152 }
153 DbgPrint( "};\n" );
154 }
155
156 static BYTE KeysSet( PKBDTABLES pkKT, PBYTE KeyState,
157 int FakeModLeft, int FakeModRight )
158 {
159 if( !KeyState || !pkKT )
160 return 0;
161
162 /* Search special codes first */
163 if( FakeModLeft && KeyState[FakeModLeft] )
164 return KeyState[FakeModLeft];
165 else if( FakeModRight && KeyState[FakeModRight] )
166 return KeyState[FakeModRight];
167
168 return 0;
169 }
170
171 /* Search the keyboard layout modifiers table for the shift bit. I don't
172 * want to count on the shift bit not moving, because it can be specified
173 * in the layout */
174
175 static DWORD FASTCALL GetShiftBit( PKBDTABLES pkKT, DWORD Vk )
176 {
177 int i;
178
179 for( i = 0; pkKT->pCharModifiers->pVkToBit[i].Vk; i++ )
180 if( pkKT->pCharModifiers->pVkToBit[i].Vk == Vk )
181 return pkKT->pCharModifiers->pVkToBit[i].ModBits;
182
183 return 0;
184 }
185
186 static DWORD ModBits( PKBDTABLES pkKT, PBYTE KeyState )
187 {
188 DWORD ModBits = 0;
189
190 if( !KeyState )
191 return 0;
192
193 /* DumpKeyState( KeyState ); */
194
195 if (KeysSet( pkKT, KeyState, VK_LSHIFT, VK_RSHIFT ) &
196 KS_DOWN_BIT)
197 ModBits |= GetShiftBit( pkKT, VK_SHIFT );
198
199 if (KeysSet( pkKT, KeyState, VK_LCONTROL, VK_RCONTROL ) &
200 KS_DOWN_BIT )
201 ModBits |= GetShiftBit( pkKT, VK_CONTROL );
202
203 if (KeysSet( pkKT, KeyState, VK_LMENU, VK_RMENU ) &
204 KS_DOWN_BIT )
205 ModBits |= GetShiftBit( pkKT, VK_MENU );
206
207 /* Handle Alt+Gr */
208 if (KeysSet( pkKT, KeyState, VK_RMENU, 0 ) &
209 KS_DOWN_BIT )
210 ModBits |= GetShiftBit( pkKT, VK_CONTROL );
211
212 /* Deal with VK_CAPITAL */
213 if (KeysSet( pkKT, KeyState, VK_CAPITAL, 0 ) & KS_LOCK_BIT)
214 {
215 ModBits |= CAPITAL_BIT;
216 }
217
218 /* Deal with VK_NUMLOCK */
219 if (KeysSet( pkKT, KeyState, VK_NUMLOCK, 0 ) & KS_LOCK_BIT)
220 {
221 ModBits |= NUMLOCK_BIT;
222 }
223
224 DPRINT( "Current Mod Bits: %x\n", ModBits );
225
226 return ModBits;
227 }
228
229 static BOOL TryToTranslateChar(WORD wVirtKey,
230 DWORD ModBits,
231 PBOOL pbDead,
232 PBOOL pbLigature,
233 PWCHAR pwcTranslatedChar,
234 PKBDTABLES keyLayout )
235 {
236 PVK_TO_WCHAR_TABLE vtwTbl;
237 PVK_TO_WCHARS10 vkPtr;
238 size_t size_this_entry;
239 int nMod;
240 DWORD CapsMod = 0, CapsState = 0;
241
242 CapsState = ModBits & ~MOD_BITS_MASK;
243 ModBits = ModBits & MOD_BITS_MASK;
244
245 DPRINT ( "TryToTranslate: %04x %x\n", wVirtKey, ModBits );
246
247 if (ModBits > keyLayout->pCharModifiers->wMaxModBits)
248 {
249 return FALSE;
250 }
251 for (nMod = 0; keyLayout->pVkToWcharTable[nMod].nModifications; nMod++)
252 {
253 vtwTbl = &keyLayout->pVkToWcharTable[nMod];
254 size_this_entry = vtwTbl->cbSize;
255 vkPtr = (PVK_TO_WCHARS10)((BYTE *)vtwTbl->pVkToWchars);
256 while(vkPtr->VirtualKey)
257 {
258 if( wVirtKey == (vkPtr->VirtualKey & 0xff) )
259 {
260 CapsMod = keyLayout->pCharModifiers->ModNumber
261 [ModBits ^
262 ((CapsState & CAPITAL_BIT) ? vkPtr->Attributes : 0)];
263
264 if( CapsMod > keyLayout->pVkToWcharTable[nMod].nModifications )
265 {
266 DWORD MaxBit = 1;
267 while( MaxBit <
268 keyLayout->pVkToWcharTable[nMod].nModifications )
269 MaxBit <<= 1;
270
271 CapsMod &= MaxBit - 1; /* Guarantee that CapsMod lies
272 in bounds. */
273 }
274
275 *pbDead = vkPtr->wch[CapsMod] == WCH_DEAD;
276 *pbLigature = vkPtr->wch[CapsMod] == WCH_LGTR;
277 *pwcTranslatedChar = vkPtr->wch[CapsMod];
278
279 DPRINT("%d %04x: CapsMod %08x CapsState %08x Char %04x\n",
280 nMod, wVirtKey,
281 CapsMod, CapsState, *pwcTranslatedChar);
282
283 if( *pbDead )
284 {
285 vkPtr = (PVK_TO_WCHARS10)(((BYTE *)vkPtr) + size_this_entry);
286 if( vkPtr->VirtualKey != 0xff )
287 {
288 DPRINT( "Found dead key with no trailer in the table.\n" );
289 DPRINT( "VK: %04x, ADDR: %08x\n", wVirtKey, (int)vkPtr );
290 return FALSE;
291 }
292 *pwcTranslatedChar = vkPtr->wch[CapsMod];
293 }
294 return TRUE;
295 }
296 vkPtr = (PVK_TO_WCHARS10)(((BYTE *)vkPtr) + size_this_entry);
297 }
298 }
299 return FALSE;
300 }
301
302 static
303 int STDCALL
304 ToUnicodeInner(UINT wVirtKey,
305 UINT wScanCode,
306 PBYTE lpKeyState,
307 LPWSTR pwszBuff,
308 int cchBuff,
309 UINT wFlags,
310 PKBDTABLES pkKT)
311 {
312 WCHAR wcTranslatedChar;
313 BOOL bDead;
314 BOOL bLigature;
315
316 if( !pkKT )
317 return 0;
318
319 if( TryToTranslateChar( wVirtKey,
320 ModBits( pkKT, lpKeyState ),
321 &bDead,
322 &bLigature,
323 &wcTranslatedChar,
324 pkKT ) )
325 {
326 if( bLigature )
327 {
328 DPRINT("Not handling ligature (yet)\n" );
329 return 0;
330 }
331
332 if( cchBuff > 0 )
333 pwszBuff[0] = wcTranslatedChar;
334
335 return bDead ? -1 : 1;
336 }
337
338 return 0;
339 }
340
341
342 DWORD FASTCALL UserGetKeyState(DWORD key)
343 {
344 DWORD ret = 0;
345
346 if( key < 0x100 )
347 {
348 ret = ((DWORD)(gQueueKeyStateTable[key] & KS_DOWN_BIT) << 8 ) |
349 (gQueueKeyStateTable[key] & KS_LOCK_BIT);
350 }
351
352 return ret;
353 }
354
355
356 DWORD
357 STDCALL
358 NtUserGetKeyState(
359 DWORD key)
360 {
361 DECLARE_RETURN(DWORD);
362
363 DPRINT("Enter NtUserGetKeyState\n");
364 UserEnterExclusive();
365
366 RETURN(UserGetKeyState(key));
367
368 CLEANUP:
369 DPRINT("Leave NtUserGetKeyState, ret=%i\n",_ret_);
370 UserLeave();
371 END_CLEANUP;
372 }
373
374
375
376 DWORD FASTCALL UserGetAsyncKeyState(DWORD key)
377 {
378 DWORD ret = 0;
379
380 if( key < 0x100 )
381 {
382 ret = ((DWORD)(gQueueKeyStateTable[key] & KS_DOWN_BIT) << 8 ) |
383 (gQueueKeyStateTable[key] & KS_LOCK_BIT);
384 }
385
386 return ret;
387 }
388
389
390
391 DWORD
392 STDCALL
393 NtUserGetAsyncKeyState(
394 DWORD key)
395 {
396 DECLARE_RETURN(DWORD);
397
398 DPRINT("Enter NtUserGetAsyncKeyState\n");
399 UserEnterExclusive();
400
401 RETURN(UserGetAsyncKeyState(key));
402
403 CLEANUP:
404 DPRINT("Leave NtUserGetAsyncKeyState, ret=%i\n",_ret_);
405 UserLeave();
406 END_CLEANUP;
407 }
408
409
410
411 int STDCALL ToUnicodeEx( UINT wVirtKey,
412 UINT wScanCode,
413 PBYTE lpKeyState,
414 LPWSTR pwszBuff,
415 int cchBuff,
416 UINT wFlags,
417 HKL dwhkl )
418 {
419 int ToUnicodeResult = 0;
420
421 if (0 == (lpKeyState[wVirtKey] & KS_DOWN_BIT))
422 {
423 ToUnicodeResult = 0;
424 }
425 else
426 {
427 ToUnicodeResult = ToUnicodeInner( wVirtKey,
428 wScanCode,
429 lpKeyState,
430 pwszBuff,
431 cchBuff,
432 wFlags,
433 PsGetWin32Thread() ?
434 PsGetWin32Thread()->KeyboardLayout : 0 );
435 }
436
437 return ToUnicodeResult;
438 }
439
440 int STDCALL ToUnicode( UINT wVirtKey,
441 UINT wScanCode,
442 PBYTE lpKeyState,
443 LPWSTR pwszBuff,
444 int cchBuff,
445 UINT wFlags )
446 {
447 return ToUnicodeEx( wVirtKey,
448 wScanCode,
449 gQueueKeyStateTable,
450 pwszBuff,
451 cchBuff,
452 wFlags,
453 0 );
454 }
455
456 /*
457 * Utility to copy and append two unicode strings.
458 *
459 * IN OUT PUNICODE_STRING ResultFirst -> First string and result
460 * IN PUNICODE_STRING Second -> Second string to append
461 * IN BOOL Deallocate -> TRUE: Deallocate First string before
462 * overwriting.
463 *
464 * Returns NTSTATUS.
465 */
466
467 NTSTATUS NTAPI AppendUnicodeString(PUNICODE_STRING ResultFirst,
468 PUNICODE_STRING Second,
469 BOOL Deallocate)
470 {
471 NTSTATUS Status;
472 PWSTR new_string =
473 ExAllocatePoolWithTag(PagedPool,
474 (ResultFirst->Length + Second->Length + sizeof(WCHAR)),
475 TAG_STRING);
476 if( !new_string )
477 {
478 return STATUS_NO_MEMORY;
479 }
480 memcpy( new_string, ResultFirst->Buffer,
481 ResultFirst->Length );
482 memcpy( new_string + ResultFirst->Length / sizeof(WCHAR),
483 Second->Buffer,
484 Second->Length );
485 if( Deallocate )
486 RtlFreeUnicodeString(ResultFirst);
487 ResultFirst->Length += Second->Length;
488 ResultFirst->MaximumLength = ResultFirst->Length;
489 new_string[ResultFirst->Length / sizeof(WCHAR)] = 0;
490 Status = RtlCreateUnicodeString(ResultFirst,new_string) ?
491 STATUS_SUCCESS : STATUS_NO_MEMORY;
492 ExFreePool(new_string);
493 return Status;
494 }
495
496 /*
497 * Utility function to read a value from the registry more easily.
498 *
499 * IN PUNICODE_STRING KeyName -> Name of key to open
500 * IN PUNICODE_STRING ValueName -> Name of value to open
501 * OUT PUNICODE_STRING ReturnedValue -> String contained in registry
502 *
503 * Returns NTSTATUS
504 */
505
506 static NTSTATUS NTAPI ReadRegistryValue( PUNICODE_STRING KeyName,
507 PUNICODE_STRING ValueName,
508 PUNICODE_STRING ReturnedValue )
509 {
510 NTSTATUS Status;
511 HANDLE KeyHandle;
512 OBJECT_ATTRIBUTES KeyAttributes;
513 PKEY_VALUE_PARTIAL_INFORMATION KeyValuePartialInfo;
514 ULONG Length = 0;
515 ULONG ResLength = 0;
516 UNICODE_STRING Temp;
517
518 InitializeObjectAttributes(&KeyAttributes, KeyName, OBJ_CASE_INSENSITIVE,
519 NULL, NULL);
520 Status = ZwOpenKey(&KeyHandle, KEY_ALL_ACCESS, &KeyAttributes);
521 if( !NT_SUCCESS(Status) )
522 {
523 return Status;
524 }
525
526 Status = ZwQueryValueKey(KeyHandle, ValueName, KeyValuePartialInformation,
527 0,
528 0,
529 &ResLength);
530
531 if( Status != STATUS_BUFFER_TOO_SMALL )
532 {
533 NtClose(KeyHandle);
534 return Status;
535 }
536
537 ResLength += sizeof( *KeyValuePartialInfo );
538 KeyValuePartialInfo =
539 ExAllocatePoolWithTag(PagedPool, ResLength, TAG_STRING);
540 Length = ResLength;
541
542 if( !KeyValuePartialInfo )
543 {
544 NtClose(KeyHandle);
545 return STATUS_NO_MEMORY;
546 }
547
548 Status = ZwQueryValueKey(KeyHandle, ValueName, KeyValuePartialInformation,
549 (PVOID)KeyValuePartialInfo,
550 Length,
551 &ResLength);
552
553 if( !NT_SUCCESS(Status) )
554 {
555 NtClose(KeyHandle);
556 ExFreePool(KeyValuePartialInfo);
557 return Status;
558 }
559
560 Temp.Length = Temp.MaximumLength = KeyValuePartialInfo->DataLength;
561 Temp.Buffer = (PWCHAR)KeyValuePartialInfo->Data;
562
563 /* At this point, KeyValuePartialInfo->Data contains the key data */
564 RtlInitUnicodeString(ReturnedValue,L"");
565 AppendUnicodeString(ReturnedValue,&Temp,FALSE);
566
567 ExFreePool(KeyValuePartialInfo);
568 NtClose(KeyHandle);
569
570 return Status;
571 }
572
573 typedef PVOID (*KbdLayerDescriptor)(VOID);
574 NTSTATUS STDCALL LdrGetProcedureAddress(PVOID module,
575 PANSI_STRING import_name,
576 DWORD flags,
577 PVOID *func_addr);
578
579 void InitKbdLayout( PVOID *pkKeyboardLayout )
580 {
581 WCHAR LocaleBuffer[16];
582 UNICODE_STRING LayoutKeyName;
583 UNICODE_STRING LayoutValueName;
584 UNICODE_STRING DefaultLocale;
585 UNICODE_STRING LayoutFile;
586 UNICODE_STRING FullLayoutPath;
587 LCID LocaleId;
588 PWCHAR KeyboardLayoutWSTR;
589 HMODULE kbModule = 0;
590 NTSTATUS Status;
591 ANSI_STRING kbdProcedureName;
592 KbdLayerDescriptor layerDescGetFn;
593
594 #define XX_STATUS(x) if (!NT_SUCCESS(Status = (x))) continue;
595
596 do
597 {
598 Status = ZwQueryDefaultLocale(FALSE, &LocaleId);
599 if (!NT_SUCCESS(Status))
600 {
601 DPRINT1("Could not get default locale (%08lx).\n", Status);
602 }
603 else
604 {
605 DPRINT("DefaultLocale = %lx\n", LocaleId);
606 swprintf(LocaleBuffer, L"%08lx", LocaleId);
607 DPRINT("DefaultLocale = %S\n", LocaleBuffer);
608 RtlInitUnicodeString(&DefaultLocale, LocaleBuffer);
609
610 RtlInitUnicodeString(&LayoutKeyName,
611 L"\\REGISTRY\\Machine\\SYSTEM\\CurrentControlSet"
612 L"\\Control\\KeyboardLayouts\\");
613
614 AppendUnicodeString(&LayoutKeyName,&DefaultLocale,FALSE);
615
616 RtlInitUnicodeString(&LayoutValueName,L"Layout File");
617
618 Status = ReadRegistryValue(&LayoutKeyName,&LayoutValueName,&LayoutFile);
619 RtlInitUnicodeString(&FullLayoutPath,SYSTEMROOT_DIR);
620
621 if( !NT_SUCCESS(Status) )
622 {
623 DPRINT1("Got default locale but not layout file. (%08lx)\n",
624 Status);
625 }
626 else
627 {
628 DPRINT("Read registry and got %wZ\n", &LayoutFile);
629
630 RtlFreeUnicodeString(&LayoutKeyName);
631
632 AppendUnicodeString(&FullLayoutPath,&LayoutFile,FALSE);
633
634 DPRINT("Loading Keyboard DLL %wZ\n", &FullLayoutPath);
635
636 RtlFreeUnicodeString(&LayoutFile);
637
638 KeyboardLayoutWSTR =
639 ExAllocatePoolWithTag(PagedPool,
640 FullLayoutPath.Length + sizeof(WCHAR),
641 TAG_STRING);
642
643 if( !KeyboardLayoutWSTR )
644 {
645 DPRINT1("Couldn't allocate a string for the keyboard layout name.\n");
646 RtlFreeUnicodeString(&FullLayoutPath);
647 return;
648 }
649 memcpy(KeyboardLayoutWSTR,FullLayoutPath.Buffer,
650 FullLayoutPath.Length + sizeof(WCHAR));
651 KeyboardLayoutWSTR[FullLayoutPath.Length / sizeof(WCHAR)] = 0;
652
653 kbModule = EngLoadImage(KeyboardLayoutWSTR);
654 DPRINT( "Load Keyboard Layout: %S\n", KeyboardLayoutWSTR );
655
656 if( !kbModule )
657 DPRINT1( "Load Keyboard Layout: No %wZ\n", &FullLayoutPath );
658
659 RtlFreeUnicodeString(&FullLayoutPath);
660 }
661 }
662
663 if( !kbModule )
664 {
665 DPRINT1("Trying to load US Keyboard Layout\n");
666 kbModule = EngLoadImage(L"\\SystemRoot\\system32\\kbdus.dll");
667
668 if (!kbModule)
669 {
670 DPRINT1("Failed to load any Keyboard Layout\n");
671 return;
672 }
673 }
674
675 RtlInitAnsiString( &kbdProcedureName, "KbdLayerDescriptor" );
676
677 LdrGetProcedureAddress((PVOID)kbModule,
678 &kbdProcedureName,
679 0,
680 (PVOID*)&layerDescGetFn);
681
682 if( layerDescGetFn )
683 {
684 *pkKeyboardLayout = layerDescGetFn();
685 }
686 }
687 while (FALSE);
688
689 if( !*pkKeyboardLayout )
690 {
691 DPRINT1("Failed to load the keyboard layout.\n");
692 }
693
694 #undef XX_STATUS
695 }
696
697 PKBDTABLES W32kGetDefaultKeyLayout()
698 {
699 PKBDTABLES pkKeyboardLayout = 0;
700 InitKbdLayout( (PVOID) &pkKeyboardLayout );
701 return pkKeyboardLayout;
702 }
703
704 BOOL FASTCALL
705 IntTranslateKbdMessage(LPMSG lpMsg,
706 HKL dwhkl)
707 {
708 static INT dead_char = 0;
709 LONG UState = 0;
710 WCHAR wp[2] = { 0 };
711 MSG NewMsg = { 0 };
712 PKBDTABLES keyLayout;
713 BOOL Result = FALSE;
714 DWORD ScanCode = 0;
715
716
717 keyLayout = PsGetWin32Thread()->KeyboardLayout;
718 if( !keyLayout )
719 return FALSE;
720
721 if (lpMsg->message != WM_KEYDOWN && lpMsg->message != WM_SYSKEYDOWN)
722 return FALSE;
723
724 ScanCode = (lpMsg->lParam >> 16) & 0xff;
725
726 /* All messages have to contain the cursor point. */
727 IntGetCursorLocation(PsGetWin32Thread()->Desktop->WindowStation,
728 &NewMsg.pt);
729
730 UState = ToUnicodeInner(lpMsg->wParam, HIWORD(lpMsg->lParam) & 0xff,
731 gQueueKeyStateTable, wp, 2, 0,
732 keyLayout );
733
734 if (UState == 1)
735 {
736 NewMsg.message = (lpMsg->message == WM_KEYDOWN) ? WM_CHAR : WM_SYSCHAR;
737 if (dead_char)
738 {
739 ULONG i;
740 WCHAR first, second;
741 DPRINT("PREVIOUS DEAD CHAR: %c\n", dead_char);
742
743 for( i = 0; keyLayout->pDeadKey[i].dwBoth; i++ )
744 {
745 first = keyLayout->pDeadKey[i].dwBoth >> 16;
746 second = keyLayout->pDeadKey[i].dwBoth;
747 if (first == dead_char && second == wp[0])
748 {
749 wp[0] = keyLayout->pDeadKey[i].wchComposed;
750 dead_char = 0;
751 break;
752 }
753 }
754
755 DPRINT("FINAL CHAR: %c\n", wp[0]);
756 }
757
758 if (dead_char)
759 {
760 NewMsg.hwnd = lpMsg->hwnd;
761 NewMsg.wParam = dead_char;
762 NewMsg.lParam = lpMsg->lParam;
763 dead_char = 0;
764 MsqPostMessage(PsGetWin32Thread()->MessageQueue, &NewMsg, FALSE, QS_KEY);
765 }
766
767 NewMsg.hwnd = lpMsg->hwnd;
768 NewMsg.wParam = wp[0];
769 NewMsg.lParam = lpMsg->lParam;
770 DPRINT( "CHAR='%c' %04x %08x\n", wp[0], wp[0], lpMsg->lParam );
771 MsqPostMessage(PsGetWin32Thread()->MessageQueue, &NewMsg, FALSE, QS_KEY);
772 Result = TRUE;
773 }
774 else if (UState == -1)
775 {
776 NewMsg.message =
777 (lpMsg->message == WM_KEYDOWN) ? WM_DEADCHAR : WM_SYSDEADCHAR;
778 NewMsg.hwnd = lpMsg->hwnd;
779 NewMsg.wParam = wp[0];
780 NewMsg.lParam = lpMsg->lParam;
781 dead_char = wp[0];
782 MsqPostMessage(PsGetWin32Thread()->MessageQueue, &NewMsg, FALSE, QS_KEY);
783 Result = TRUE;
784 }
785
786 return Result;
787 }
788
789 DWORD
790 STDCALL
791 NtUserGetKeyboardState(
792 LPBYTE lpKeyState)
793 {
794 BOOL Result = TRUE;
795 DECLARE_RETURN(DWORD);
796
797 DPRINT("Enter NtUserGetKeyboardState\n");
798 UserEnterShared();
799
800 if (lpKeyState)
801 {
802 if(!NT_SUCCESS(MmCopyToCaller(lpKeyState, gQueueKeyStateTable, 256)))
803 Result = FALSE;
804 }
805
806 RETURN(Result);
807
808 CLEANUP:
809 DPRINT("Leave NtUserGetKeyboardState, ret=%i\n",_ret_);
810 UserLeave();
811 END_CLEANUP;
812 }
813
814 DWORD
815 STDCALL
816 NtUserSetKeyboardState(LPBYTE lpKeyState)
817 {
818 BOOL Result = TRUE;
819 DECLARE_RETURN(DWORD);
820
821 DPRINT("Enter NtUserSetKeyboardState\n");
822 UserEnterExclusive();
823
824 if (lpKeyState)
825 {
826 if(! NT_SUCCESS(MmCopyFromCaller(gQueueKeyStateTable, lpKeyState, 256)))
827 Result = FALSE;
828 }
829
830 RETURN(Result);
831
832 CLEANUP:
833 DPRINT("Leave NtUserSetKeyboardState, ret=%i\n",_ret_);
834 UserLeave();
835 END_CLEANUP;
836 }
837
838 static UINT VkToScan( UINT Code, BOOL ExtCode, PKBDTABLES pkKT )
839 {
840 int i;
841
842 for( i = 0; i < pkKT->bMaxVSCtoVK; i++ )
843 {
844 if( pkKT->pusVSCtoVK[i] == Code )
845 {
846 return i;
847 }
848 }
849
850 return 0;
851 }
852
853 UINT ScanToVk( UINT Code, BOOL ExtKey, PKBDTABLES pkKT )
854 {
855 if( !pkKT )
856 {
857 DPRINT("ScanToVk: No layout\n");
858 return 0;
859 }
860
861 if( ExtKey )
862 {
863 int i;
864
865 for( i = 0; pkKT->pVSCtoVK_E0[i].Vsc; i++ )
866 {
867 if( pkKT->pVSCtoVK_E0[i].Vsc == Code )
868 return pkKT->pVSCtoVK_E0[i].Vk & 0xff;
869 }
870 for( i = 0; pkKT->pVSCtoVK_E1[i].Vsc; i++ )
871 {
872 if( pkKT->pVSCtoVK_E1[i].Vsc == Code )
873 return pkKT->pVSCtoVK_E1[i].Vk & 0xff;
874 }
875
876 return 0;
877 }
878 else
879 {
880 if( Code >= pkKT->bMaxVSCtoVK )
881 {
882 return 0;
883 }
884 return pkKT->pusVSCtoVK[Code] & 0xff;
885 }
886 }
887
888 /*
889 * Map a virtual key code, or virtual scan code, to a scan code, key code,
890 * or unshifted unicode character.
891 *
892 * Code: See Below
893 * Type:
894 * 0 -- Code is a virtual key code that is converted into a virtual scan code
895 * that does not distinguish between left and right shift keys.
896 * 1 -- Code is a virtual scan code that is converted into a virtual key code
897 * that does not distinguish between left and right shift keys.
898 * 2 -- Code is a virtual key code that is converted into an unshifted unicode
899 * character.
900 * 3 -- Code is a virtual scan code that is converted into a virtual key code
901 * that distinguishes left and right shift keys.
902 * KeyLayout: Keyboard layout handle (currently, unused)
903 *
904 * @implemented
905 */
906
907 static UINT IntMapVirtualKeyEx( UINT Code, UINT Type, PKBDTABLES keyLayout )
908 {
909 UINT ret = 0;
910
911 switch( Type )
912 {
913 case 0:
914 if( Code == VK_RSHIFT )
915 Code = VK_LSHIFT;
916 if( Code == VK_RMENU )
917 Code = VK_LMENU;
918 if( Code == VK_RCONTROL )
919 Code = VK_LCONTROL;
920 ret = VkToScan( Code, FALSE, keyLayout );
921 break;
922
923 case 1:
924 ret =
925 DontDistinguishShifts
926 (IntMapVirtualKeyEx( Code, 3, keyLayout ) );
927 break;
928
929 case 2:
930 {
931 WCHAR wp[2];
932
933 ret = VkToScan( Code, FALSE, keyLayout );
934 ToUnicodeInner( Code, ret, 0, wp, 2, 0, keyLayout );
935 ret = wp[0];
936 }
937 break;
938
939 case 3:
940
941 ret = ScanToVk( Code, FALSE, keyLayout );
942 break;
943 }
944
945 return ret;
946 }
947
948 UINT
949 STDCALL
950 NtUserMapVirtualKeyEx( UINT Code, UINT Type, DWORD keyboardId, HKL dwhkl )
951 {
952 PKBDTABLES keyLayout;
953 DECLARE_RETURN(UINT);
954
955 DPRINT("Enter NtUserMapVirtualKeyEx\n");
956 UserEnterExclusive();
957
958 keyLayout = PsGetWin32Thread() ? PsGetWin32Thread()->KeyboardLayout : 0;
959
960 if( !keyLayout )
961 RETURN(0);
962
963 RETURN(IntMapVirtualKeyEx( Code, Type, keyLayout ));
964
965 CLEANUP:
966 DPRINT("Leave NtUserMapVirtualKeyEx, ret=%i\n",_ret_);
967 UserLeave();
968 END_CLEANUP;
969 }
970
971
972 int
973 STDCALL
974 NtUserToUnicodeEx(
975 UINT wVirtKey,
976 UINT wScanCode,
977 PBYTE lpKeyState,
978 LPWSTR pwszBuff,
979 int cchBuff,
980 UINT wFlags,
981 HKL dwhkl )
982 {
983 BYTE KeyStateBuf[0x100];
984 PWCHAR OutPwszBuff = 0;
985 int ret = 0;
986 DECLARE_RETURN(int);
987
988 DPRINT("Enter NtUserSetKeyboardState\n");
989 UserEnterShared();//faxme: this syscall doesnt seem to need any locking...
990
991
992 if( !NT_SUCCESS(MmCopyFromCaller(KeyStateBuf,
993 lpKeyState,
994 sizeof(KeyStateBuf))) )
995 {
996 DPRINT1( "Couldn't copy key state from caller.\n" );
997 RETURN(0);
998 }
999 OutPwszBuff = ExAllocatePoolWithTag(NonPagedPool,sizeof(WCHAR) * cchBuff, TAG_STRING);
1000 if( !OutPwszBuff )
1001 {
1002 DPRINT1( "ExAllocatePool(%d) failed\n", sizeof(WCHAR) * cchBuff);
1003 RETURN(0);
1004 }
1005 RtlZeroMemory( OutPwszBuff, sizeof( WCHAR ) * cchBuff );
1006
1007 ret = ToUnicodeEx( wVirtKey,
1008 wScanCode,
1009 KeyStateBuf,
1010 OutPwszBuff,
1011 cchBuff,
1012 wFlags,
1013 dwhkl );
1014
1015 MmCopyToCaller(pwszBuff,OutPwszBuff,sizeof(WCHAR)*cchBuff);
1016 ExFreePool(OutPwszBuff);
1017
1018 RETURN(ret);
1019
1020 CLEANUP:
1021 DPRINT("Leave NtUserSetKeyboardState, ret=%i\n",_ret_);
1022 UserLeave();
1023 END_CLEANUP;
1024 }
1025
1026 static int W32kSimpleToupper( int ch )
1027 {
1028 if( ch >= 'a' && ch <= 'z' )
1029 ch = ch - 'a' + 'A';
1030 return ch;
1031 }
1032
1033 DWORD
1034 STDCALL
1035 NtUserGetKeyNameText( LONG lParam, LPWSTR lpString, int nSize )
1036 {
1037 int i;
1038 DWORD ret = 0;
1039 UINT CareVk = 0;
1040 UINT VkCode = 0;
1041 UINT ScanCode = (lParam >> 16) & 0xff;
1042 BOOL ExtKey = lParam & (1<<24) ? TRUE : FALSE;
1043 PKBDTABLES keyLayout;
1044 DECLARE_RETURN(DWORD);
1045
1046 DPRINT("Enter NtUserGetKeyNameText\n");
1047 UserEnterShared();
1048
1049 keyLayout = PsGetWin32Thread() ?
1050 PsGetWin32Thread()->KeyboardLayout : 0;
1051
1052 if( !keyLayout || nSize < 1 )
1053 RETURN(0);
1054
1055 if( lParam & (1<<25) )
1056 {
1057 CareVk = VkCode = ScanToVk( ScanCode, ExtKey, keyLayout );
1058 if( VkCode == VK_LSHIFT || VkCode == VK_RSHIFT )
1059 VkCode = VK_LSHIFT;
1060 if( VkCode == VK_LCONTROL || VkCode == VK_RCONTROL )
1061 VkCode = VK_LCONTROL;
1062 if( VkCode == VK_LMENU || VkCode == VK_RMENU )
1063 VkCode = VK_LMENU;
1064 }
1065 else
1066 {
1067 VkCode = ScanToVk( ScanCode, ExtKey, keyLayout );
1068 }
1069
1070 VSC_LPWSTR *KeyNames = 0;
1071
1072 if( CareVk != VkCode )
1073 ScanCode = VkToScan( VkCode, ExtKey, keyLayout );
1074
1075 if( ExtKey )
1076 KeyNames = keyLayout->pKeyNamesExt;
1077 else
1078 KeyNames = keyLayout->pKeyNames;
1079
1080 for( i = 0; KeyNames[i].pwsz; i++ )
1081 {
1082 if( KeyNames[i].vsc == ScanCode )
1083 {
1084 UINT StrLen = wcslen(KeyNames[i].pwsz);
1085 UINT StrMax = StrLen > (nSize - 1) ? (nSize - 1) : StrLen;
1086 WCHAR null_wc = 0;
1087 if( NT_SUCCESS( MmCopyToCaller( lpString,
1088 KeyNames[i].pwsz,
1089 StrMax * sizeof(WCHAR) ) ) &&
1090 NT_SUCCESS( MmCopyToCaller( lpString + StrMax,
1091 &null_wc,
1092 sizeof( WCHAR ) ) ) )
1093 {
1094 ret = StrMax;
1095 break;
1096 }
1097 }
1098 }
1099
1100 if( ret == 0 )
1101 {
1102 WCHAR UCName[2];
1103
1104 UCName[0] = W32kSimpleToupper(IntMapVirtualKeyEx( VkCode, 2, keyLayout ));
1105 UCName[1] = 0;
1106 ret = 1;
1107
1108 if( !NT_SUCCESS(MmCopyToCaller( lpString, UCName, 2 * sizeof(WCHAR) )) )
1109 RETURN(0);
1110 }
1111
1112 RETURN(ret);
1113
1114 CLEANUP:
1115 DPRINT("Leave NtUserGetKeyNameText, ret=%i\n",_ret_);
1116 UserLeave();
1117 END_CLEANUP;
1118 }
1119
1120 /*
1121 * Filter this message according to the current key layout, setting wParam
1122 * appropriately.
1123 */
1124
1125 VOID FASTCALL
1126 W32kKeyProcessMessage(LPMSG Msg,
1127 PKBDTABLES KeyboardLayout,
1128 BYTE Prefix)
1129 {
1130 DWORD ScanCode = 0, ModifierBits = 0;
1131 DWORD i = 0;
1132 DWORD BaseMapping = 0;
1133 DWORD RawVk = 0;
1134 static WORD NumpadConversion[][2] =
1135 { { VK_DELETE, VK_DECIMAL },
1136 { VK_INSERT, VK_NUMPAD0 },
1137 { VK_END, VK_NUMPAD1 },
1138 { VK_DOWN, VK_NUMPAD2 },
1139 { VK_NEXT, VK_NUMPAD3 },
1140 { VK_LEFT, VK_NUMPAD4 },
1141 { VK_CLEAR, VK_NUMPAD5 },
1142 { VK_RIGHT, VK_NUMPAD6 },
1143 { VK_HOME, VK_NUMPAD7 },
1144 { VK_UP, VK_NUMPAD8 },
1145 { VK_PRIOR, VK_NUMPAD9 },
1146 { 0,0 } };
1147 PVSC_VK VscVkTable = NULL;
1148
1149 if( !KeyboardLayout || !Msg ||
1150 (Msg->message != WM_KEYDOWN && Msg->message != WM_SYSKEYDOWN &&
1151 Msg->message != WM_KEYUP && Msg->message != WM_SYSKEYUP) )
1152 {
1153 return;
1154 }
1155
1156 /* arty -- handle numpad -- On real windows, the actual key produced
1157 * by the messaging layer is different based on the state of numlock. */
1158 ModifierBits = ModBits(KeyboardLayout,gQueueKeyStateTable);
1159
1160 /* Get the raw scan code, so we can look up whether the key is a numpad
1161 * key
1162 *
1163 * Shift and the LP_EXT_BIT cancel. */
1164 ScanCode = (Msg->lParam >> 16) & 0xff;
1165 BaseMapping = Msg->wParam =
1166 IntMapVirtualKeyEx( ScanCode, 1, KeyboardLayout );
1167 if( Prefix == 0 )
1168 {
1169 if( ScanCode >= KeyboardLayout->bMaxVSCtoVK )
1170 RawVk = 0xff;
1171 else
1172 RawVk = KeyboardLayout->pusVSCtoVK[ScanCode];
1173 }
1174 else
1175 {
1176 if( Prefix == 0xE0 )
1177 {
1178 /* ignore shift codes */
1179 if( ScanCode == 0x2A || ScanCode == 0x36 )
1180 {
1181 return;
1182 }
1183 VscVkTable = KeyboardLayout->pVSCtoVK_E0;
1184 }
1185 else if( Prefix == 0xE1 )
1186 {
1187 VscVkTable = KeyboardLayout->pVSCtoVK_E1;
1188 }
1189
1190 RawVk = 0xff;
1191 while (VscVkTable->Vsc)
1192 {
1193 if( VscVkTable->Vsc == ScanCode )
1194 {
1195 RawVk = VscVkTable->Vk;
1196 }
1197 VscVkTable++;
1198 }
1199 }
1200
1201 if ((ModifierBits & NUMLOCK_BIT) &&
1202 !(ModifierBits & GetShiftBit(KeyboardLayout, VK_SHIFT)) &&
1203 (RawVk & KNUMP) &&
1204 !(Msg->lParam & LP_EXT_BIT))
1205 {
1206 /* The key in question is a numpad key. Search for a translation. */
1207 for (i = 0; NumpadConversion[i][0]; i++)
1208 {
1209 if ((BaseMapping & 0xff) == NumpadConversion[i][0]) /* RawVk? */
1210 {
1211 Msg->wParam = NumpadConversion[i][1];
1212 break;
1213 }
1214 }
1215 }
1216
1217 DPRINT("Key: [%04x -> %04x]\n", BaseMapping, Msg->wParam);
1218
1219 /* Now that we have the VK, we can set the keymap appropriately
1220 * This is a better place for this code, as it's guaranteed to be
1221 * run, unlike translate message. */
1222 if (Msg->message == WM_KEYDOWN || Msg->message == WM_SYSKEYDOWN)
1223 {
1224 SetKeyState( ScanCode, Msg->wParam, Msg->lParam & LP_EXT_BIT,
1225 TRUE ); /* Strike key */
1226 }
1227 else if (Msg->message == WM_KEYUP || Msg->message == WM_SYSKEYUP)
1228 {
1229 SetKeyState( ScanCode, Msg->wParam, Msg->lParam & LP_EXT_BIT,
1230 FALSE ); /* Release key */
1231 }
1232
1233 /* We need to unset SYSKEYDOWN if the ALT key is an ALT+Gr */
1234 if( gQueueKeyStateTable[VK_RMENU] & KS_DOWN_BIT )
1235 {
1236 if( Msg->message == WM_SYSKEYDOWN )
1237 Msg->message = WM_KEYDOWN;
1238 else
1239 Msg->message = WM_KEYUP;
1240 }
1241
1242 }
1243
1244 DWORD
1245 STDCALL
1246 NtUserGetKeyboardLayoutList(
1247 DWORD Items,
1248 DWORD pHklBuff)
1249 {
1250 UNIMPLEMENTED
1251
1252 return 0;
1253 }
1254
1255 DWORD
1256 STDCALL
1257 NtUserGetKeyboardLayoutName(
1258 DWORD lpszName)
1259 {
1260 UNIMPLEMENTED
1261
1262 return 0;
1263 }
1264
1265
1266
1267 HKL FASTCALL
1268 UserGetKeyboardLayout(
1269 DWORD dwThreadId)
1270 {
1271 NTSTATUS Status;
1272 PETHREAD Thread;
1273 PW32THREAD W32Thread;
1274 PKBDTABLES layout;
1275
1276 if (!dwThreadId)
1277 W32Thread = PsGetWin32Thread();
1278 else
1279 {
1280 Status = PsLookupThreadByThreadId((HANDLE)dwThreadId, &Thread);//fixme: deref thread
1281 if(!NT_SUCCESS(Status))
1282 {
1283 SetLastWin32Error(ERROR_INVALID_PARAMETER);
1284 return 0;
1285 }
1286 W32Thread = Thread->Tcb.Win32Thread;
1287 }
1288 layout = W32Thread->KeyboardLayout;
1289 if(!layout)
1290 return 0;
1291 return (HKL)layout;
1292 }
1293
1294
1295 HKL
1296 STDCALL
1297 NtUserGetKeyboardLayout(
1298 DWORD dwThreadId)
1299 {
1300 DECLARE_RETURN(HKL);
1301
1302 UserEnterShared();
1303 DPRINT("Enter NtUserGetKeyboardLayout\n");
1304
1305 RETURN( UserGetKeyboardLayout(dwThreadId));
1306
1307 CLEANUP:
1308 DPRINT("Leave NtUserGetKeyboardLayout, ret=%i\n",_ret_);
1309 UserLeave();
1310 END_CLEANUP;
1311 }
1312
1313
1314 DWORD FASTCALL
1315 UserGetKeyboardType(
1316 DWORD TypeFlag)
1317 {
1318 switch(TypeFlag)
1319 {
1320 case 0: /* Keyboard type */
1321 return 4; /* AT-101 */
1322 case 1: /* Keyboard Subtype */
1323 return 0; /* There are no defined subtypes */
1324 case 2: /* Number of F-keys */
1325 return 12; /* We're doing an 101 for now, so return 12 F-keys */
1326 default:
1327 DPRINT1("Unknown type!\n");
1328 return 0; /* The book says 0 here, so 0 */
1329 }
1330 }
1331
1332 DWORD
1333 STDCALL
1334 NtUserGetKeyboardType(
1335 DWORD TypeFlag)
1336 {
1337 DECLARE_RETURN(DWORD);
1338
1339 DPRINT("Enter NtUserGetKeyboardType\n");
1340 UserEnterShared();
1341
1342 RETURN( UserGetKeyboardType(TypeFlag));
1343
1344 CLEANUP:
1345 DPRINT("Leave NtUserGetKeyboardType, ret=%i\n",_ret_);
1346 UserLeave();
1347 END_CLEANUP;
1348 }
1349
1350
1351 /*
1352 Based on TryToTranslateChar, instead of processing VirtualKey match,
1353 look for wChar match.
1354 */
1355 DWORD
1356 STDCALL
1357 NtUserVkKeyScanEx(
1358 DWORD wChar,
1359 DWORD KeyboardLayout,
1360 DWORD Unknown2)
1361 {
1362 /* FAXME: currently, this routine doesnt seem to need any locking */
1363
1364 PKBDTABLES KeyLayout;
1365 PVK_TO_WCHAR_TABLE vtwTbl;
1366 PVK_TO_WCHARS10 vkPtr;
1367 size_t size_this_entry;
1368 int nMod;
1369 DWORD CapsMod = 0, CapsState = 0;
1370
1371 if(!KeyboardLayout)
1372 return -1;
1373 KeyLayout = (PKBDTABLES) KeyboardLayout;
1374
1375 for (nMod = 0; KeyLayout->pVkToWcharTable[nMod].nModifications; nMod++)
1376 {
1377 vtwTbl = &KeyLayout->pVkToWcharTable[nMod];
1378 size_this_entry = vtwTbl->cbSize;
1379 vkPtr = (PVK_TO_WCHARS10)((BYTE *)vtwTbl->pVkToWchars);
1380
1381 while(vkPtr->VirtualKey)
1382 {
1383 /*
1384 0x01 Shift key
1385 0x02 Ctrl key
1386 0x04 Alt key
1387 Should have only 8 valid possibilities. Including zero.
1388 */
1389 for(CapsState = 0; CapsState < vtwTbl->nModifications; CapsState++)
1390 {
1391 if(vkPtr->wch[CapsState] == wChar)
1392 {
1393 CapsMod = KeyLayout->pCharModifiers->ModNumber[CapsState];
1394 DPRINT("nMod %d wC %04x: CapsMod %08x CapsState %08x MaxModBits %08x\n",
1395 nMod, wChar, CapsMod, CapsState, KeyLayout->pCharModifiers->wMaxModBits);
1396 return ((CapsMod << 8)|(vkPtr->VirtualKey & 0xff));
1397 }
1398 }
1399 vkPtr = (PVK_TO_WCHARS10)(((BYTE *)vkPtr) + size_this_entry);
1400 }
1401 }
1402 return -1;
1403 }
1404
1405
1406 /* EOF */