- ProcessMouseInputData: Mouse pointer shown on window was slightly off. Fix calculat...
[reactos.git] / reactos / subsystems / win32 / win32k / ntuser / input.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 /* $Id$
20 *
21 * COPYRIGHT: See COPYING in the top level directory
22 * PROJECT: ReactOS kernel
23 * PURPOSE: Window classes
24 * FILE: subsys/win32k/ntuser/class.c
25 * PROGRAMER: Casper S. Hornstrup (chorns@users.sourceforge.net)
26 * REVISION HISTORY:
27 * 06-06-2001 CSH Created
28 */
29
30 /* INCLUDES ******************************************************************/
31
32 #include <w32k.h>
33 #include <ntddkbd.h>
34
35 #define NDEBUG
36 #include <debug.h>
37
38 extern BYTE gQueueKeyStateTable[];
39 extern NTSTATUS Win32kInitWin32Thread(PETHREAD Thread);
40
41 /* GLOBALS *******************************************************************/
42
43 PTHREADINFO ptiRawInput;
44 PKTIMER MasterTimer;
45
46 static HANDLE MouseDeviceHandle;
47 static HANDLE MouseThreadHandle;
48 static CLIENT_ID MouseThreadId;
49 static HANDLE KeyboardThreadHandle;
50 static CLIENT_ID KeyboardThreadId;
51 static HANDLE KeyboardDeviceHandle;
52 static HANDLE RawInputThreadHandle;
53 static CLIENT_ID RawInputThreadId;
54 static KEVENT InputThreadsStart;
55 static BOOLEAN InputThreadsRunning = FALSE;
56
57 /* FUNCTIONS *****************************************************************/
58 ULONG FASTCALL
59 IntSystemParametersInfo(UINT uiAction, UINT uiParam,PVOID pvParam, UINT fWinIni);
60 DWORD IntLastInputTick(BOOL LastInputTickSetGet);
61
62 #define ClearMouseInput(mi) \
63 mi.dx = 0; \
64 mi.dy = 0; \
65 mi.mouseData = 0; \
66 mi.dwFlags = 0;
67
68 #define SendMouseEvent(mi) \
69 if(mi.dx != 0 || mi.dy != 0) \
70 mi.dwFlags |= MOUSEEVENTF_MOVE; \
71 if(mi.dwFlags) \
72 IntMouseInput(&mi); \
73 ClearMouseInput(mi);
74
75
76 DWORD IntLastInputTick(BOOL LastInputTickSetGet)
77 {
78 static DWORD LastInputTick = 0;
79 if (LastInputTickSetGet == TRUE)
80 {
81 LARGE_INTEGER TickCount;
82 KeQueryTickCount(&TickCount);
83 LastInputTick = TickCount.u.LowPart * (KeQueryTimeIncrement() / 10000);
84 }
85 return LastInputTick;
86 }
87
88 BOOL
89 APIENTRY
90 NtUserGetLastInputInfo(PLASTINPUTINFO plii)
91 {
92 BOOL ret = TRUE;
93
94 UserEnterShared();
95
96 _SEH2_TRY
97 {
98 if (ProbeForReadUint(&plii->cbSize) != sizeof(LASTINPUTINFO))
99 {
100 SetLastWin32Error(ERROR_INVALID_PARAMETER);
101 ret = FALSE;
102 _SEH2_LEAVE;
103 }
104
105 ProbeForWrite(plii, sizeof(LASTINPUTINFO), sizeof(DWORD));
106
107 plii->dwTime = IntLastInputTick(FALSE);
108 }
109 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
110 {
111 SetLastNtError(_SEH2_GetExceptionCode());
112 ret = FALSE;
113 }
114 _SEH2_END;
115
116 UserLeave();
117
118 return ret;
119 }
120
121
122 VOID FASTCALL
123 ProcessMouseInputData(PMOUSE_INPUT_DATA Data, ULONG InputCount)
124 {
125 PMOUSE_INPUT_DATA mid;
126 MOUSEINPUT mi;
127 ULONG i;
128
129 ClearMouseInput(mi);
130 mi.time = 0;
131 mi.dwExtraInfo = 0;
132 for(i = 0; i < InputCount; i++)
133 {
134 mid = (Data + i);
135 mi.dx += mid->LastX;
136 mi.dy += mid->LastY;
137
138 /* Check if the mouse move is absolute */
139 if (mid->Flags == MOUSE_MOVE_ABSOLUTE)
140 {
141 /* Set flag and convert to screen location */
142 mi.dwFlags |= MOUSEEVENTF_ABSOLUTE;
143 mi.dx = mi.dx / (65535 / (UserGetSystemMetrics(SM_CXVIRTUALSCREEN) - 1));
144 mi.dy = mi.dy / (65535 / (UserGetSystemMetrics(SM_CYVIRTUALSCREEN) - 1));
145 }
146
147 if(mid->ButtonFlags)
148 {
149 if(mid->ButtonFlags & MOUSE_LEFT_BUTTON_DOWN)
150 {
151 mi.dwFlags |= MOUSEEVENTF_LEFTDOWN;
152 SendMouseEvent(mi);
153 }
154 if(mid->ButtonFlags & MOUSE_LEFT_BUTTON_UP)
155 {
156 mi.dwFlags |= MOUSEEVENTF_LEFTUP;
157 SendMouseEvent(mi);
158 }
159 if(mid->ButtonFlags & MOUSE_MIDDLE_BUTTON_DOWN)
160 {
161 mi.dwFlags |= MOUSEEVENTF_MIDDLEDOWN;
162 SendMouseEvent(mi);
163 }
164 if(mid->ButtonFlags & MOUSE_MIDDLE_BUTTON_UP)
165 {
166 mi.dwFlags |= MOUSEEVENTF_MIDDLEUP;
167 SendMouseEvent(mi);
168 }
169 if(mid->ButtonFlags & MOUSE_RIGHT_BUTTON_DOWN)
170 {
171 mi.dwFlags |= MOUSEEVENTF_RIGHTDOWN;
172 SendMouseEvent(mi);
173 }
174 if(mid->ButtonFlags & MOUSE_RIGHT_BUTTON_UP)
175 {
176 mi.dwFlags |= MOUSEEVENTF_RIGHTUP;
177 SendMouseEvent(mi);
178 }
179 if(mid->ButtonFlags & MOUSE_BUTTON_4_DOWN)
180 {
181 mi.mouseData |= XBUTTON1;
182 mi.dwFlags |= MOUSEEVENTF_XDOWN;
183 SendMouseEvent(mi);
184 }
185 if(mid->ButtonFlags & MOUSE_BUTTON_4_UP)
186 {
187 mi.mouseData |= XBUTTON1;
188 mi.dwFlags |= MOUSEEVENTF_XUP;
189 SendMouseEvent(mi);
190 }
191 if(mid->ButtonFlags & MOUSE_BUTTON_5_DOWN)
192 {
193 mi.mouseData |= XBUTTON2;
194 mi.dwFlags |= MOUSEEVENTF_XDOWN;
195 SendMouseEvent(mi);
196 }
197 if(mid->ButtonFlags & MOUSE_BUTTON_5_UP)
198 {
199 mi.mouseData |= XBUTTON2;
200 mi.dwFlags |= MOUSEEVENTF_XUP;
201 SendMouseEvent(mi);
202 }
203 if(mid->ButtonFlags & MOUSE_WHEEL)
204 {
205 mi.mouseData = mid->ButtonData;
206 mi.dwFlags |= MOUSEEVENTF_WHEEL;
207 SendMouseEvent(mi);
208 }
209 }
210 }
211
212 SendMouseEvent(mi);
213 }
214
215
216
217
218 VOID APIENTRY
219 MouseThreadMain(PVOID StartContext)
220 {
221 UNICODE_STRING MouseDeviceName = RTL_CONSTANT_STRING(L"\\Device\\PointerClass0");
222 OBJECT_ATTRIBUTES MouseObjectAttributes;
223 IO_STATUS_BLOCK Iosb;
224 NTSTATUS Status;
225 MOUSE_ATTRIBUTES MouseAttr;
226
227 InitializeObjectAttributes(&MouseObjectAttributes,
228 &MouseDeviceName,
229 0,
230 NULL,
231 NULL);
232 do
233 {
234 LARGE_INTEGER DueTime;
235 KEVENT Event;
236 DueTime.QuadPart = (LONGLONG)(-10000000);
237 KeInitializeEvent(&Event, NotificationEvent, FALSE);
238 Status = KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, &DueTime);
239 Status = NtOpenFile(&MouseDeviceHandle,
240 FILE_ALL_ACCESS,
241 &MouseObjectAttributes,
242 &Iosb,
243 0,
244 FILE_SYNCHRONOUS_IO_ALERT);
245 } while (!NT_SUCCESS(Status));
246
247 KeSetPriorityThread(&PsGetCurrentThread()->Tcb,
248 LOW_REALTIME_PRIORITY + 3);
249
250 for(;;)
251 {
252 /*
253 * Wait to start input.
254 */
255 DPRINT("Mouse Input Thread Waiting for start event\n");
256 Status = KeWaitForSingleObject(&InputThreadsStart,
257 0,
258 KernelMode,
259 TRUE,
260 NULL);
261 DPRINT("Mouse Input Thread Starting...\n");
262
263 /*FIXME: Does mouse attributes need to be used for anything */
264 Status = NtDeviceIoControlFile(MouseDeviceHandle,
265 NULL,
266 NULL,
267 NULL,
268 &Iosb,
269 IOCTL_MOUSE_QUERY_ATTRIBUTES,
270 &MouseAttr, sizeof(MOUSE_ATTRIBUTES),
271 NULL, 0);
272 if(!NT_SUCCESS(Status))
273 {
274 DPRINT("Failed to get mouse attributes\n");
275 }
276
277 /*
278 * Receive and process mouse input.
279 */
280 while(InputThreadsRunning)
281 {
282 MOUSE_INPUT_DATA MouseInput;
283 Status = NtReadFile(MouseDeviceHandle,
284 NULL,
285 NULL,
286 NULL,
287 &Iosb,
288 &MouseInput,
289 sizeof(MOUSE_INPUT_DATA),
290 NULL,
291 NULL);
292 if(Status == STATUS_ALERTED && !InputThreadsRunning)
293 {
294 break;
295 }
296 if(Status == STATUS_PENDING)
297 {
298 NtWaitForSingleObject(MouseDeviceHandle, FALSE, NULL);
299 Status = Iosb.Status;
300 }
301 if(!NT_SUCCESS(Status))
302 {
303 DPRINT1("Win32K: Failed to read from mouse.\n");
304 return; //(Status);
305 }
306 DPRINT("MouseEvent\n");
307 IntLastInputTick(TRUE);
308
309 UserEnterExclusive();
310
311 ProcessMouseInputData(&MouseInput, Iosb.Information / sizeof(MOUSE_INPUT_DATA));
312
313 UserLeave();
314 }
315 DPRINT("Mouse Input Thread Stopped...\n");
316 }
317 }
318
319 /* Returns a value that indicates if the key is a modifier key, and
320 * which one.
321 */
322 static UINT APIENTRY
323 IntKeyboardGetModifiers(KEYBOARD_INPUT_DATA *InputData)
324 {
325 if (InputData->Flags & KEY_E1)
326 return 0;
327
328 if (!(InputData->Flags & KEY_E0))
329 {
330 switch (InputData->MakeCode)
331 {
332 case 0x2a: /* left shift */
333 case 0x36: /* right shift */
334 return MOD_SHIFT;
335
336 case 0x1d: /* left control */
337 return MOD_CONTROL;
338
339 case 0x38: /* left alt */
340 return MOD_ALT;
341
342 default:
343 return 0;
344 }
345 }
346 else
347 {
348 switch (InputData->MakeCode)
349 {
350 case 0x1d: /* right control */
351 return MOD_CONTROL;
352
353 case 0x38: /* right alt */
354 return MOD_ALT;
355
356 case 0x5b: /* left gui (windows) */
357 case 0x5c: /* right gui (windows) */
358 return MOD_WIN;
359
360 default:
361 return 0;
362 }
363 }
364 }
365
366 /* Asks the keyboard driver to send a small table that shows which
367 * lights should connect with which scancodes
368 */
369 static NTSTATUS APIENTRY
370 IntKeyboardGetIndicatorTrans(HANDLE KeyboardDeviceHandle,
371 PKEYBOARD_INDICATOR_TRANSLATION *IndicatorTrans)
372 {
373 NTSTATUS Status;
374 DWORD Size = 0;
375 IO_STATUS_BLOCK Block;
376 PKEYBOARD_INDICATOR_TRANSLATION Ret;
377
378 Size = sizeof(KEYBOARD_INDICATOR_TRANSLATION);
379
380 Ret = ExAllocatePoolWithTag(PagedPool,
381 Size,
382 TAG_KEYBOARD);
383
384 while (Ret)
385 {
386 Status = NtDeviceIoControlFile(KeyboardDeviceHandle,
387 NULL,
388 NULL,
389 NULL,
390 &Block,
391 IOCTL_KEYBOARD_QUERY_INDICATOR_TRANSLATION,
392 NULL, 0,
393 Ret, Size);
394
395 if (Status != STATUS_BUFFER_TOO_SMALL)
396 break;
397
398 ExFreePoolWithTag(Ret, TAG_KEYBOARD);
399
400 Size += sizeof(KEYBOARD_INDICATOR_TRANSLATION);
401
402 Ret = ExAllocatePoolWithTag(PagedPool,
403 Size,
404 TAG_KEYBOARD);
405 }
406
407 if (!Ret)
408 return STATUS_INSUFFICIENT_RESOURCES;
409
410 if (Status != STATUS_SUCCESS)
411 {
412 ExFreePoolWithTag(Ret, TAG_KEYBOARD);
413 return Status;
414 }
415
416 *IndicatorTrans = Ret;
417 return Status;
418 }
419
420 /* Sends the keyboard commands to turn on/off the lights.
421 */
422 static NTSTATUS APIENTRY
423 IntKeyboardUpdateLeds(HANDLE KeyboardDeviceHandle,
424 PKEYBOARD_INPUT_DATA KeyInput,
425 PKEYBOARD_INDICATOR_TRANSLATION IndicatorTrans)
426 {
427 NTSTATUS Status;
428 UINT Count;
429 static KEYBOARD_INDICATOR_PARAMETERS Indicators;
430 IO_STATUS_BLOCK Block;
431
432 if (!IndicatorTrans)
433 return STATUS_NOT_SUPPORTED;
434
435 if (KeyInput->Flags & (KEY_E0 | KEY_E1 | KEY_BREAK))
436 return STATUS_SUCCESS;
437
438 for (Count = 0; Count < IndicatorTrans->NumberOfIndicatorKeys; Count++)
439 {
440 if (KeyInput->MakeCode == IndicatorTrans->IndicatorList[Count].MakeCode)
441 {
442 Indicators.LedFlags ^=
443 IndicatorTrans->IndicatorList[Count].IndicatorFlags;
444
445 /* Update the lights on the hardware */
446
447 Status = NtDeviceIoControlFile(KeyboardDeviceHandle,
448 NULL,
449 NULL,
450 NULL,
451 &Block,
452 IOCTL_KEYBOARD_SET_INDICATORS,
453 &Indicators, sizeof(Indicators),
454 NULL, 0);
455
456 return Status;
457 }
458 }
459
460 return STATUS_SUCCESS;
461 }
462
463 static VOID APIENTRY
464 IntKeyboardSendWinKeyMsg()
465 {
466 PWINDOW_OBJECT Window;
467 MSG Mesg;
468
469 if (!(Window = UserGetWindowObject(InputWindowStation->ShellWindow)))
470 {
471 DPRINT1("Couldn't find window to send Windows key message!\n");
472 return;
473 }
474
475 Mesg.hwnd = InputWindowStation->ShellWindow;
476 Mesg.message = WM_SYSCOMMAND;
477 Mesg.wParam = SC_TASKLIST;
478 Mesg.lParam = 0;
479
480 /* The QS_HOTKEY is just a guess */
481 MsqPostMessage(Window->MessageQueue, &Mesg, FALSE, QS_HOTKEY);
482 }
483
484 static VOID APIENTRY
485 co_IntKeyboardSendAltKeyMsg()
486 {
487 co_MsqPostKeyboardMessage(WM_SYSCOMMAND,SC_KEYMENU,0);
488 }
489
490 static VOID APIENTRY
491 KeyboardThreadMain(PVOID StartContext)
492 {
493 UNICODE_STRING KeyboardDeviceName = RTL_CONSTANT_STRING(L"\\Device\\KeyboardClass0");
494 OBJECT_ATTRIBUTES KeyboardObjectAttributes;
495 IO_STATUS_BLOCK Iosb;
496 NTSTATUS Status;
497 MSG msg;
498 PUSER_MESSAGE_QUEUE FocusQueue;
499 struct _ETHREAD *FocusThread;
500
501 PKEYBOARD_INDICATOR_TRANSLATION IndicatorTrans = NULL;
502 UINT ModifierState = 0;
503 USHORT LastMakeCode = 0;
504 USHORT LastFlags = 0;
505 UINT RepeatCount = 0;
506
507 InitializeObjectAttributes(&KeyboardObjectAttributes,
508 &KeyboardDeviceName,
509 0,
510 NULL,
511 NULL);
512 do
513 {
514 LARGE_INTEGER DueTime;
515 KEVENT Event;
516 DueTime.QuadPart = (LONGLONG)(-10000000);
517 KeInitializeEvent(&Event, NotificationEvent, FALSE);
518 Status = KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, &DueTime);
519 Status = NtOpenFile(&KeyboardDeviceHandle,
520 FILE_ALL_ACCESS,
521 &KeyboardObjectAttributes,
522 &Iosb,
523 0,
524 FILE_SYNCHRONOUS_IO_ALERT);
525 } while (!NT_SUCCESS(Status));
526
527 /* Not sure if converting this thread to a win32 thread is such
528 a great idea. Since we're posting keyboard messages to the focus
529 window message queue, we'll be (indirectly) doing sendmessage
530 stuff from this thread (for WH_KEYBOARD_LL processing), which
531 means we need our own message queue. If keyboard messages were
532 instead queued to the system message queue, the thread removing
533 the message from the system message queue would be responsible
534 for WH_KEYBOARD_LL processing and we wouldn't need this thread
535 to be a win32 thread. */
536 Status = Win32kInitWin32Thread(PsGetCurrentThread());
537 if (!NT_SUCCESS(Status))
538 {
539 DPRINT1("Win32K: Failed making keyboard thread a win32 thread.\n");
540 return; //(Status);
541 }
542
543 KeSetPriorityThread(&PsGetCurrentThread()->Tcb,
544 LOW_REALTIME_PRIORITY + 3);
545
546 IntKeyboardGetIndicatorTrans(KeyboardDeviceHandle,
547 &IndicatorTrans);
548
549 for (;;)
550 {
551 /*
552 * Wait to start input.
553 */
554 DPRINT( "Keyboard Input Thread Waiting for start event\n" );
555 Status = KeWaitForSingleObject(&InputThreadsStart,
556 0,
557 KernelMode,
558 TRUE,
559 NULL);
560
561 DPRINT( "Keyboard Input Thread Starting...\n" );
562 /*
563 * Receive and process keyboard input.
564 */
565 while (InputThreadsRunning)
566 {
567 BOOLEAN NumKeys = 1;
568 BOOLEAN bLeftAlt;
569 KEYBOARD_INPUT_DATA KeyInput;
570 KEYBOARD_INPUT_DATA NextKeyInput;
571 LPARAM lParam = 0;
572 UINT fsModifiers, fsNextModifiers;
573 struct _ETHREAD *Thread;
574 HWND hWnd;
575 int id;
576
577 DPRINT("KeyInput @ %08x\n", &KeyInput);
578
579 Status = NtReadFile (KeyboardDeviceHandle,
580 NULL,
581 NULL,
582 NULL,
583 &Iosb,
584 &KeyInput,
585 sizeof(KEYBOARD_INPUT_DATA),
586 NULL,
587 NULL);
588
589 if(Status == STATUS_ALERTED && !InputThreadsRunning)
590 {
591 break;
592 }
593 if(Status == STATUS_PENDING)
594 {
595 NtWaitForSingleObject(KeyboardDeviceHandle, FALSE, NULL);
596 Status = Iosb.Status;
597 }
598 if(!NT_SUCCESS(Status))
599 {
600 DPRINT1("Win32K: Failed to read from mouse.\n");
601 return; //(Status);
602 }
603
604 DPRINT("KeyRaw: %s %04x\n",
605 (KeyInput.Flags & KEY_BREAK) ? "up" : "down",
606 KeyInput.MakeCode );
607
608 if (Status == STATUS_ALERTED && !InputThreadsRunning)
609 break;
610
611 if (!NT_SUCCESS(Status))
612 {
613 DPRINT1("Win32K: Failed to read from keyboard.\n");
614 return; //(Status);
615 }
616
617 /* Set LastInputTick */
618 IntLastInputTick(TRUE);
619
620 /* Update modifier state */
621 fsModifiers = IntKeyboardGetModifiers(&KeyInput);
622
623 if (fsModifiers)
624 {
625 if (KeyInput.Flags & KEY_BREAK)
626 {
627 ModifierState &= ~fsModifiers;
628 if(fsModifiers == MOD_ALT)
629 {
630 if(KeyInput.Flags & KEY_E0)
631 {
632 gQueueKeyStateTable[VK_RMENU] = 0;
633 }
634 else
635 {
636 gQueueKeyStateTable[VK_LMENU] = 0;
637 }
638 if (gQueueKeyStateTable[VK_RMENU] == 0 &&
639 gQueueKeyStateTable[VK_LMENU] == 0)
640 {
641 gQueueKeyStateTable[VK_MENU] = 0;
642 }
643 }
644 }
645 else
646 {
647 ModifierState |= fsModifiers;
648
649 if (ModifierState == fsModifiers &&
650 (fsModifiers == MOD_ALT || fsModifiers == MOD_WIN))
651 {
652 /* First send out special notifications
653 * (For alt, the message that turns on accelerator
654 * display, not sure what for win. Both TODO though.)
655 */
656 bLeftAlt = FALSE;
657 if(fsModifiers == MOD_ALT)
658 {
659 if(KeyInput.Flags & KEY_E0)
660 {
661 gQueueKeyStateTable[VK_RMENU] = 0x80;
662 }
663 else
664 {
665 gQueueKeyStateTable[VK_LMENU] = 0x80;
666 bLeftAlt = TRUE;
667 }
668
669 gQueueKeyStateTable[VK_MENU] = 0x80;
670 }
671
672 /* Read the next key before sending this one */
673 do
674 {
675 Status = NtReadFile (KeyboardDeviceHandle,
676 NULL,
677 NULL,
678 NULL,
679 &Iosb,
680 &NextKeyInput,
681 sizeof(KEYBOARD_INPUT_DATA),
682 NULL,
683 NULL);
684 DPRINT("KeyRaw: %s %04x\n",
685 (NextKeyInput.Flags & KEY_BREAK) ? "up":"down",
686 NextKeyInput.MakeCode );
687
688 if (Status == STATUS_ALERTED && !InputThreadsRunning)
689 goto KeyboardEscape;
690
691 }
692 while ((!(NextKeyInput.Flags & KEY_BREAK)) &&
693 NextKeyInput.MakeCode == KeyInput.MakeCode);
694 /* ^ Ignore repeats, they'll be KEY_MAKE and the same
695 * code. I'm not caring about the counting, not sure
696 * if that matters. I think not.
697 */
698
699 /* If the ModifierState is now empty again, send a
700 * special notification and eat both keypresses
701 */
702
703 fsNextModifiers = IntKeyboardGetModifiers(&NextKeyInput);
704
705 if (fsNextModifiers)
706 ModifierState ^= fsNextModifiers;
707
708 if (ModifierState == 0)
709 {
710 if (fsModifiers == MOD_WIN)
711 IntKeyboardSendWinKeyMsg();
712 else if (fsModifiers == MOD_ALT)
713 {
714 gQueueKeyStateTable[VK_MENU] = 0;
715 if(bLeftAlt)
716 {
717 gQueueKeyStateTable[VK_LMENU] = 0;
718 }
719 else
720 {
721 gQueueKeyStateTable[VK_RMENU] = 0;
722 }
723 co_IntKeyboardSendAltKeyMsg();
724 }
725 continue;
726 }
727
728 NumKeys = 2;
729 }
730 }
731 }
732
733 for (;NumKeys;memcpy(&KeyInput, &NextKeyInput, sizeof(KeyInput)),
734 NumKeys--)
735 {
736 lParam = 0;
737
738 IntKeyboardUpdateLeds(KeyboardDeviceHandle,
739 &KeyInput,
740 IndicatorTrans);
741
742 /* While we are working, we set up lParam. The format is:
743 * 0-15: The number of times this key has autorepeated
744 * 16-23: The keyboard scancode
745 * 24: Set if it's and extended key (I assume KEY_E0 | KEY_E1)
746 * Note that E1 is only used for PAUSE (E1-1D-45) and
747 * E0-45 happens not to be anything.
748 * 29: Alt is pressed ('Context code')
749 * 30: Previous state, if the key was down before this message
750 * This is a cheap way to ignore autorepeat keys
751 * 31: 1 if the key is being pressed
752 */
753
754 /* If it's a KEY_MAKE (which is 0, so test using !KEY_BREAK)
755 * and it's the same key as the last one, increase the repeat
756 * count.
757 */
758
759 if (!(KeyInput.Flags & KEY_BREAK))
760 {
761 if (((KeyInput.Flags & (KEY_E0 | KEY_E1)) == LastFlags) &&
762 (KeyInput.MakeCode == LastMakeCode))
763 {
764 RepeatCount++;
765 lParam |= (1 << 30);
766 }
767 else
768 {
769 RepeatCount = 0;
770 LastFlags = KeyInput.Flags & (KEY_E0 | KEY_E1);
771 LastMakeCode = KeyInput.MakeCode;
772 }
773 }
774 else
775 {
776 LastFlags = 0;
777 LastMakeCode = 0; /* Should never match */
778 lParam |= (1 << 30) | (1 << 31);
779 }
780
781 lParam |= RepeatCount;
782
783 lParam |= (KeyInput.MakeCode & 0xff) << 16;
784
785 if (KeyInput.Flags & KEY_E0)
786 lParam |= (1 << 24);
787
788 if (ModifierState & MOD_ALT)
789 {
790 lParam |= (1 << 29);
791
792 if (!(KeyInput.Flags & KEY_BREAK))
793 msg.message = WM_SYSKEYDOWN;
794 else
795 msg.message = WM_SYSKEYUP;
796 }
797 else
798 {
799 if (!(KeyInput.Flags & KEY_BREAK))
800 msg.message = WM_KEYDOWN;
801 else
802 msg.message = WM_KEYUP;
803 }
804
805 /* Find the target thread whose locale is in effect */
806 FocusQueue = IntGetFocusMessageQueue();
807
808 /* This might cause us to lose hot keys, which are important
809 * (ctrl-alt-del secure attention sequence). Not sure if it
810 * can happen though.
811 */
812 if (!FocusQueue)
813 continue;
814
815 msg.lParam = lParam;
816 msg.hwnd = FocusQueue->FocusWindow;
817
818 FocusThread = FocusQueue->Thread;
819
820 if (!(FocusThread && FocusThread->Tcb.Win32Thread &&
821 ((PTHREADINFO)FocusThread->Tcb.Win32Thread)->KeyboardLayout))
822 continue;
823
824 /* This function uses lParam to fill wParam according to the
825 * keyboard layout in use.
826 */
827 W32kKeyProcessMessage(&msg,
828 ((PTHREADINFO)FocusThread->Tcb.Win32Thread)->KeyboardLayout->KBTables,
829 KeyInput.Flags & KEY_E0 ? 0xE0 :
830 (KeyInput.Flags & KEY_E1 ? 0xE1 : 0));
831
832 if (GetHotKey(ModifierState,
833 msg.wParam,
834 &Thread,
835 &hWnd,
836 &id))
837 {
838 if (!(KeyInput.Flags & KEY_BREAK))
839 {
840 DPRINT("Hot key pressed (hWnd %lx, id %d)\n", hWnd, id);
841 MsqPostHotKeyMessage (Thread,
842 hWnd,
843 (WPARAM)id,
844 MAKELPARAM((WORD)ModifierState,
845 (WORD)msg.wParam));
846 }
847 continue; /* Eat key up motion too */
848 }
849
850 /*
851 * Post a keyboard message.
852 */
853 co_MsqPostKeyboardMessage(msg.message,msg.wParam,msg.lParam);
854 }
855 }
856
857 KeyboardEscape:
858 DPRINT( "KeyboardInput Thread Stopped...\n" );
859 }
860 }
861
862
863 static PVOID Objects[2];
864 /*
865 Raw Input Thread.
866 Since this relies on InputThreadsStart, just fake it.
867 */
868 static VOID APIENTRY
869 RawInputThreadMain(PVOID StartContext)
870 {
871 NTSTATUS Status;
872 LARGE_INTEGER DueTime;
873
874 DueTime.QuadPart = (LONGLONG)(-10000000);
875
876 do
877 {
878 KEVENT Event;
879 KeInitializeEvent(&Event, NotificationEvent, FALSE);
880 Status = KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, &DueTime);
881 } while (!NT_SUCCESS(Status));
882
883
884 Objects[0] = &InputThreadsStart;
885
886 MasterTimer = ExAllocatePoolWithTag(NonPagedPool, sizeof(KTIMER), TAG_INPUT);
887 if (!MasterTimer)
888 {
889 DPRINT1("Win32K: Failed making Raw Input thread a win32 thread.\n");
890 return;
891 }
892 KeInitializeTimer(MasterTimer);
893 Objects[1] = MasterTimer;
894
895 // This thread requires win32k!
896 Status = Win32kInitWin32Thread(PsGetCurrentThread());
897 if (!NT_SUCCESS(Status))
898 {
899 DPRINT1("Win32K: Failed making Raw Input thread a win32 thread.\n");
900 return; //(Status);
901 }
902
903 ptiRawInput = PsGetCurrentThreadWin32Thread();
904 DPRINT1("\nRaw Input Thread 0x%x \n", ptiRawInput);
905
906
907 KeSetPriorityThread(&PsGetCurrentThread()->Tcb,
908 LOW_REALTIME_PRIORITY + 3);
909
910 UserEnterExclusive();
911 StartTheTimers();
912 UserLeave();
913
914 //
915 // ATM, we just have one job to handle, merge the other two later.
916 //
917 for(;;)
918 {
919 DPRINT( "Raw Input Thread Waiting for start event\n" );
920
921 Status = KeWaitForMultipleObjects( 2,
922 Objects,
923 WaitAll, //WaitAny,
924 WrUserRequest,
925 KernelMode,
926 TRUE,
927 NULL,
928 NULL);
929 DPRINT( "Raw Input Thread Starting...\n" );
930
931 ProcessTimers();
932 }
933 DPRINT1("Raw Input Thread Exit!\n");
934 }
935
936 NTSTATUS FASTCALL
937 InitInputImpl(VOID)
938 {
939 NTSTATUS Status;
940
941 KeInitializeEvent(&InputThreadsStart, NotificationEvent, FALSE);
942
943 /* Initialize the default keyboard layout */
944 if(!UserInitDefaultKeyboardLayout())
945 {
946 DPRINT1("Failed to initialize default keyboard layout!\n");
947 }
948
949 Status = PsCreateSystemThread(&RawInputThreadHandle,
950 THREAD_ALL_ACCESS,
951 NULL,
952 NULL,
953 &RawInputThreadId,
954 RawInputThreadMain,
955 NULL);
956 if (!NT_SUCCESS(Status))
957 {
958 DPRINT1("Win32K: Failed to create raw thread.\n");
959 }
960
961 Status = PsCreateSystemThread(&KeyboardThreadHandle,
962 THREAD_ALL_ACCESS,
963 NULL,
964 NULL,
965 &KeyboardThreadId,
966 KeyboardThreadMain,
967 NULL);
968 if (!NT_SUCCESS(Status))
969 {
970 DPRINT1("Win32K: Failed to create keyboard thread.\n");
971 }
972
973 Status = PsCreateSystemThread(&MouseThreadHandle,
974 THREAD_ALL_ACCESS,
975 NULL,
976 NULL,
977 &MouseThreadId,
978 MouseThreadMain,
979 NULL);
980 if (!NT_SUCCESS(Status))
981 {
982 DPRINT1("Win32K: Failed to create mouse thread.\n");
983 }
984
985 InputThreadsRunning = TRUE;
986 KeSetEvent(&InputThreadsStart, IO_NO_INCREMENT, FALSE);
987
988 return STATUS_SUCCESS;
989 }
990
991 NTSTATUS FASTCALL
992 CleanupInputImp(VOID)
993 {
994 return(STATUS_SUCCESS);
995 }
996
997 BOOL
998 APIENTRY
999 NtUserDragDetect(
1000 HWND hWnd,
1001 POINT pt) // Just like the User call.
1002 {
1003 UNIMPLEMENTED
1004 return 0;
1005 }
1006
1007 BOOL FASTCALL
1008 IntBlockInput(PTHREADINFO W32Thread, BOOL BlockIt)
1009 {
1010 PTHREADINFO OldBlock;
1011 ASSERT(W32Thread);
1012
1013 if(!W32Thread->Desktop || (W32Thread->IsExiting && BlockIt))
1014 {
1015 /*
1016 * fail blocking if exiting the thread
1017 */
1018
1019 return FALSE;
1020 }
1021
1022 /*
1023 * FIXME - check access rights of the window station
1024 * e.g. services running in the service window station cannot block input
1025 */
1026 if(!ThreadHasInputAccess(W32Thread) ||
1027 !IntIsActiveDesktop(W32Thread->Desktop))
1028 {
1029 SetLastWin32Error(ERROR_ACCESS_DENIED);
1030 return FALSE;
1031 }
1032
1033 ASSERT(W32Thread->Desktop);
1034 OldBlock = W32Thread->Desktop->BlockInputThread;
1035 if(OldBlock)
1036 {
1037 if(OldBlock != W32Thread)
1038 {
1039 SetLastWin32Error(ERROR_ACCESS_DENIED);
1040 return FALSE;
1041 }
1042 W32Thread->Desktop->BlockInputThread = (BlockIt ? W32Thread : NULL);
1043 return OldBlock == NULL;
1044 }
1045
1046 W32Thread->Desktop->BlockInputThread = (BlockIt ? W32Thread : NULL);
1047 return OldBlock == NULL;
1048 }
1049
1050 BOOL
1051 APIENTRY
1052 NtUserBlockInput(
1053 BOOL BlockIt)
1054 {
1055 DECLARE_RETURN(BOOLEAN);
1056
1057 DPRINT("Enter NtUserBlockInput\n");
1058 UserEnterExclusive();
1059
1060 RETURN( IntBlockInput(PsGetCurrentThreadWin32Thread(), BlockIt));
1061
1062 CLEANUP:
1063 DPRINT("Leave NtUserBlockInput, ret=%i\n",_ret_);
1064 UserLeave();
1065 END_CLEANUP;
1066 }
1067
1068 BOOL FASTCALL
1069 IntSwapMouseButton(PWINSTATION_OBJECT WinStaObject, BOOL Swap)
1070 {
1071 PSYSTEM_CURSORINFO CurInfo;
1072 BOOL res;
1073
1074 CurInfo = IntGetSysCursorInfo(WinStaObject);
1075 res = CurInfo->SwapButtons;
1076 CurInfo->SwapButtons = Swap;
1077 return res;
1078 }
1079
1080 BOOL FASTCALL
1081 IntMouseInput(MOUSEINPUT *mi)
1082 {
1083 const UINT SwapBtnMsg[2][2] =
1084 {
1085 {
1086 WM_LBUTTONDOWN, WM_RBUTTONDOWN
1087 },
1088 {WM_LBUTTONUP, WM_RBUTTONUP}
1089 };
1090 const WPARAM SwapBtn[2] =
1091 {
1092 MK_LBUTTON, MK_RBUTTON
1093 };
1094 POINT MousePos = {0}, OrgPos;
1095 PSYSTEM_CURSORINFO CurInfo;
1096 PWINSTATION_OBJECT WinSta;
1097 BOOL DoMove, SwapButtons;
1098 MSG Msg;
1099 SURFACE *psurf;
1100 SURFOBJ *pso;
1101 PDC dc;
1102 PWINDOW_OBJECT DesktopWindow;
1103
1104 #if 1
1105
1106 HDC hDC;
1107
1108 /* FIXME - get the screen dc from the window station or desktop */
1109 if(!(hDC = IntGetScreenDC()))
1110 {
1111 return FALSE;
1112 }
1113 #endif
1114
1115 ASSERT(mi);
1116 #if 0
1117
1118 WinSta = PsGetCurrentProcessWin32Process()->WindowStation;
1119 #else
1120 /* FIXME - ugly hack but as long as we're using this dumb callback from the
1121 mouse class driver, we can't access the window station from the calling
1122 process */
1123 WinSta = InputWindowStation;
1124 #endif
1125
1126 ASSERT(WinSta);
1127
1128 CurInfo = IntGetSysCursorInfo(WinSta);
1129
1130 if(!mi->time)
1131 {
1132 LARGE_INTEGER LargeTickCount;
1133 KeQueryTickCount(&LargeTickCount);
1134 mi->time = MsqCalculateMessageTime(&LargeTickCount);
1135 }
1136
1137 SwapButtons = CurInfo->SwapButtons;
1138 DoMove = FALSE;
1139
1140 IntGetCursorLocation(WinSta, &MousePos);
1141 OrgPos.x = MousePos.x;
1142 OrgPos.y = MousePos.y;
1143
1144 if(mi->dwFlags & MOUSEEVENTF_MOVE)
1145 {
1146 if(mi->dwFlags & MOUSEEVENTF_ABSOLUTE)
1147 {
1148 MousePos.x = mi->dx;
1149 MousePos.y = mi->dy;
1150 }
1151 else
1152 {
1153 MousePos.x += mi->dx;
1154 MousePos.y += mi->dy;
1155 }
1156
1157 DesktopWindow = IntGetWindowObject(WinSta->ActiveDesktop->DesktopWindow);
1158
1159 if (DesktopWindow)
1160 {
1161 if(MousePos.x >= DesktopWindow->Wnd->ClientRect.right)
1162 MousePos.x = DesktopWindow->Wnd->ClientRect.right - 1;
1163 if(MousePos.y >= DesktopWindow->Wnd->ClientRect.bottom)
1164 MousePos.y = DesktopWindow->Wnd->ClientRect.bottom - 1;
1165 UserDereferenceObject(DesktopWindow);
1166 }
1167
1168 if(MousePos.x < 0)
1169 MousePos.x = 0;
1170 if(MousePos.y < 0)
1171 MousePos.y = 0;
1172
1173 if(CurInfo->CursorClipInfo.IsClipped)
1174 {
1175 /* The mouse cursor needs to be clipped */
1176
1177 if(MousePos.x >= (LONG)CurInfo->CursorClipInfo.Right)
1178 MousePos.x = (LONG)CurInfo->CursorClipInfo.Right;
1179 if(MousePos.x < (LONG)CurInfo->CursorClipInfo.Left)
1180 MousePos.x = (LONG)CurInfo->CursorClipInfo.Left;
1181 if(MousePos.y >= (LONG)CurInfo->CursorClipInfo.Bottom)
1182 MousePos.y = (LONG)CurInfo->CursorClipInfo.Bottom;
1183 if(MousePos.y < (LONG)CurInfo->CursorClipInfo.Top)
1184 MousePos.y = (LONG)CurInfo->CursorClipInfo.Top;
1185 }
1186
1187 DoMove = (MousePos.x != OrgPos.x || MousePos.y != OrgPos.y);
1188 }
1189
1190 if (DoMove)
1191 {
1192 dc = DC_LockDc(hDC);
1193 if (dc)
1194 {
1195 psurf = dc->dclevel.pSurface;
1196 if (psurf)
1197 {
1198 pso = &psurf->SurfObj;
1199
1200 if (CurInfo->ShowingCursor)
1201 {
1202 IntEngMovePointer(pso, MousePos.x, MousePos.y, &(GDIDEV(pso)->Pointer.Exclude));
1203 }
1204 /* Only now, update the info in the PDEVOBJ, so EngMovePointer can
1205 * use the old values to move the pointer image */
1206 gpsi->ptCursor.x = MousePos.x;
1207 gpsi->ptCursor.y = MousePos.y;
1208 }
1209
1210 DC_UnlockDc(dc);
1211 }
1212 }
1213
1214 /*
1215 * Insert the messages into the system queue
1216 */
1217
1218 Msg.wParam = CurInfo->ButtonsDown;
1219 Msg.lParam = MAKELPARAM(MousePos.x, MousePos.y);
1220 Msg.pt = MousePos;
1221
1222 if (gQueueKeyStateTable[VK_SHIFT] & 0xc0)
1223 {
1224 Msg.wParam |= MK_SHIFT;
1225 }
1226
1227 if (gQueueKeyStateTable[VK_CONTROL] & 0xc0)
1228 {
1229 Msg.wParam |= MK_CONTROL;
1230 }
1231
1232 if(DoMove)
1233 {
1234 Msg.message = WM_MOUSEMOVE;
1235 MsqInsertSystemMessage(&Msg);
1236 }
1237
1238 Msg.message = 0;
1239 if(mi->dwFlags & MOUSEEVENTF_LEFTDOWN)
1240 {
1241 gQueueKeyStateTable[VK_LBUTTON] |= 0xc0;
1242 Msg.message = SwapBtnMsg[0][SwapButtons];
1243 CurInfo->ButtonsDown |= SwapBtn[SwapButtons];
1244 MsqInsertSystemMessage(&Msg);
1245 }
1246 else if(mi->dwFlags & MOUSEEVENTF_LEFTUP)
1247 {
1248 gQueueKeyStateTable[VK_LBUTTON] &= ~0x80;
1249 Msg.message = SwapBtnMsg[1][SwapButtons];
1250 CurInfo->ButtonsDown &= ~SwapBtn[SwapButtons];
1251 MsqInsertSystemMessage(&Msg);
1252 }
1253 if(mi->dwFlags & MOUSEEVENTF_MIDDLEDOWN)
1254 {
1255 gQueueKeyStateTable[VK_MBUTTON] |= 0xc0;
1256 Msg.message = WM_MBUTTONDOWN;
1257 CurInfo->ButtonsDown |= MK_MBUTTON;
1258 MsqInsertSystemMessage(&Msg);
1259 }
1260 else if(mi->dwFlags & MOUSEEVENTF_MIDDLEUP)
1261 {
1262 gQueueKeyStateTable[VK_MBUTTON] &= ~0x80;
1263 Msg.message = WM_MBUTTONUP;
1264 CurInfo->ButtonsDown &= ~MK_MBUTTON;
1265 MsqInsertSystemMessage(&Msg);
1266 }
1267 if(mi->dwFlags & MOUSEEVENTF_RIGHTDOWN)
1268 {
1269 gQueueKeyStateTable[VK_RBUTTON] |= 0xc0;
1270 Msg.message = SwapBtnMsg[0][!SwapButtons];
1271 CurInfo->ButtonsDown |= SwapBtn[!SwapButtons];
1272 MsqInsertSystemMessage(&Msg);
1273 }
1274 else if(mi->dwFlags & MOUSEEVENTF_RIGHTUP)
1275 {
1276 gQueueKeyStateTable[VK_RBUTTON] &= ~0x80;
1277 Msg.message = SwapBtnMsg[1][!SwapButtons];
1278 CurInfo->ButtonsDown &= ~SwapBtn[!SwapButtons];
1279 MsqInsertSystemMessage(&Msg);
1280 }
1281
1282 if((mi->dwFlags & (MOUSEEVENTF_XDOWN | MOUSEEVENTF_XUP)) &&
1283 (mi->dwFlags & MOUSEEVENTF_WHEEL))
1284 {
1285 /* fail because both types of events use the mouseData field */
1286 return FALSE;
1287 }
1288
1289 if(mi->dwFlags & MOUSEEVENTF_XDOWN)
1290 {
1291 Msg.message = WM_XBUTTONDOWN;
1292 if(mi->mouseData & XBUTTON1)
1293 {
1294 gQueueKeyStateTable[VK_XBUTTON1] |= 0xc0;
1295 Msg.wParam = MAKEWPARAM(CurInfo->ButtonsDown, XBUTTON1);
1296 CurInfo->ButtonsDown |= XBUTTON1;
1297 MsqInsertSystemMessage(&Msg);
1298 }
1299 if(mi->mouseData & XBUTTON2)
1300 {
1301 gQueueKeyStateTable[VK_XBUTTON2] |= 0xc0;
1302 Msg.wParam = MAKEWPARAM(CurInfo->ButtonsDown, XBUTTON2);
1303 CurInfo->ButtonsDown |= XBUTTON2;
1304 MsqInsertSystemMessage(&Msg);
1305 }
1306 }
1307 else if(mi->dwFlags & MOUSEEVENTF_XUP)
1308 {
1309 Msg.message = WM_XBUTTONUP;
1310 if(mi->mouseData & XBUTTON1)
1311 {
1312 gQueueKeyStateTable[VK_XBUTTON1] &= ~0x80;
1313 Msg.wParam = MAKEWPARAM(CurInfo->ButtonsDown, XBUTTON1);
1314 CurInfo->ButtonsDown &= ~XBUTTON1;
1315 MsqInsertSystemMessage(&Msg);
1316 }
1317 if(mi->mouseData & XBUTTON2)
1318 {
1319 gQueueKeyStateTable[VK_XBUTTON2] &= ~0x80;
1320 Msg.wParam = MAKEWPARAM(CurInfo->ButtonsDown, XBUTTON2);
1321 CurInfo->ButtonsDown &= ~XBUTTON2;
1322 MsqInsertSystemMessage(&Msg);
1323 }
1324 }
1325 if(mi->dwFlags & MOUSEEVENTF_WHEEL)
1326 {
1327 Msg.message = WM_MOUSEWHEEL;
1328 Msg.wParam = MAKEWPARAM(CurInfo->ButtonsDown, mi->mouseData);
1329 MsqInsertSystemMessage(&Msg);
1330 }
1331
1332 return TRUE;
1333 }
1334
1335 BOOL FASTCALL
1336 IntKeyboardInput(KEYBDINPUT *ki)
1337 {
1338 return FALSE;
1339 }
1340
1341 UINT
1342 APIENTRY
1343 NtUserSendInput(
1344 UINT nInputs,
1345 LPINPUT pInput,
1346 INT cbSize)
1347 {
1348 PTHREADINFO W32Thread;
1349 UINT cnt;
1350 DECLARE_RETURN(UINT);
1351
1352 DPRINT("Enter NtUserSendInput\n");
1353 UserEnterExclusive();
1354
1355 W32Thread = PsGetCurrentThreadWin32Thread();
1356 ASSERT(W32Thread);
1357
1358 if(!W32Thread->Desktop)
1359 {
1360 RETURN( 0);
1361 }
1362
1363 if(!nInputs || !pInput || (cbSize != sizeof(INPUT)))
1364 {
1365 SetLastWin32Error(ERROR_INVALID_PARAMETER);
1366 RETURN( 0);
1367 }
1368
1369 /*
1370 * FIXME - check access rights of the window station
1371 * e.g. services running in the service window station cannot block input
1372 */
1373 if(!ThreadHasInputAccess(W32Thread) ||
1374 !IntIsActiveDesktop(W32Thread->Desktop))
1375 {
1376 SetLastWin32Error(ERROR_ACCESS_DENIED);
1377 RETURN( 0);
1378 }
1379
1380 cnt = 0;
1381 while(nInputs--)
1382 {
1383 INPUT SafeInput;
1384 NTSTATUS Status;
1385
1386 Status = MmCopyFromCaller(&SafeInput, pInput++, sizeof(INPUT));
1387 if(!NT_SUCCESS(Status))
1388 {
1389 SetLastNtError(Status);
1390 RETURN( cnt);
1391 }
1392
1393 switch(SafeInput.type)
1394 {
1395 case INPUT_MOUSE:
1396 if(IntMouseInput(&SafeInput.mi))
1397 {
1398 cnt++;
1399 }
1400 break;
1401 case INPUT_KEYBOARD:
1402 if(IntKeyboardInput(&SafeInput.ki))
1403 {
1404 cnt++;
1405 }
1406 break;
1407 case INPUT_HARDWARE:
1408 break;
1409 #ifndef NDEBUG
1410
1411 default:
1412 DPRINT1("SendInput(): Invalid input type: 0x%x\n", SafeInput.type);
1413 break;
1414 #endif
1415
1416 }
1417 }
1418
1419 RETURN( cnt);
1420
1421 CLEANUP:
1422 DPRINT("Leave NtUserSendInput, ret=%i\n",_ret_);
1423 UserLeave();
1424 END_CLEANUP;
1425 }
1426
1427 /* EOF */