xref: /reactos/win32ss/user/ntuser/input.c (revision 5417742f)
1 /*
2  * COPYRIGHT:        See COPYING in the top level directory
3  * PROJECT:          ReactOS Win32k subsystem
4  * PURPOSE:          General input functions
5  * FILE:             win32ss/user/ntuser/input.c
6  * PROGRAMERS:       Casper S. Hornstrup (chorns@users.sourceforge.net)
7  *                   Rafal Harabien (rafalh@reactos.org)
8  */
9 
10 #include <win32k.h>
11 DBG_DEFAULT_CHANNEL(UserInput);
12 
13 /* GLOBALS *******************************************************************/
14 
15 PTHREADINFO ptiRawInput;
16 PKTIMER MasterTimer = NULL;
17 PATTACHINFO gpai = NULL;
18 INT paiCount = 0;
19 HANDLE ghKeyboardDevice = NULL;
20 
21 static DWORD LastInputTick = 0;
22 static HANDLE ghMouseDevice;
23 
24 /* FUNCTIONS *****************************************************************/
25 
26 /*
27  * IntLastInputTick
28  *
29  * Updates or gets last input tick count
30  */
31 static DWORD FASTCALL
32 IntLastInputTick(BOOL bUpdate)
33 {
34     if (bUpdate)
35     {
36         LARGE_INTEGER TickCount;
37         KeQueryTickCount(&TickCount);
38         LastInputTick = MsqCalculateMessageTime(&TickCount);
39         if (gpsi) gpsi->dwLastRITEventTickCount = LastInputTick;
40     }
41     return LastInputTick;
42 }
43 
44 /*
45  * DoTheScreenSaver
46  *
47  * Check if scrensaver should be started and sends message to SAS window
48  */
49 VOID FASTCALL
50 DoTheScreenSaver(VOID)
51 {
52     LARGE_INTEGER TickCount;
53     DWORD Test, TO;
54 
55     if (gspv.iScrSaverTimeout > 0) // Zero means Off.
56     {
57         KeQueryTickCount(&TickCount);
58         Test = MsqCalculateMessageTime(&TickCount);
59         Test = Test - LastInputTick;
60         TO = 1000 * gspv.iScrSaverTimeout;
61         if (Test > TO)
62         {
63             TRACE("Screensaver Message Start! Tick %lu Timeout %d \n", Test, gspv.iScrSaverTimeout);
64 
65             if (ppiScrnSaver) // We are or we are not the screensaver, prevent reentry...
66             {
67                 if (!(ppiScrnSaver->W32PF_flags & W32PF_IDLESCREENSAVER))
68                 {
69                     ppiScrnSaver->W32PF_flags |= W32PF_IDLESCREENSAVER;
70                     ERR("Screensaver is Idle\n");
71                 }
72             }
73             else
74             {
75                 PUSER_MESSAGE_QUEUE ForegroundQueue = IntGetFocusMessageQueue();
76                 if (ForegroundQueue && ForegroundQueue->spwndActive)
77                     UserPostMessage(hwndSAS, WM_LOGONNOTIFY, LN_START_SCREENSAVE, 1); // lParam 1 == Secure
78                 else
79                     UserPostMessage(hwndSAS, WM_LOGONNOTIFY, LN_START_SCREENSAVE, 0);
80             }
81         }
82     }
83 }
84 
85 /*
86  * OpenInputDevice
87  *
88  * Opens input device for asynchronous access
89  */
90 static
91 NTSTATUS NTAPI
92 OpenInputDevice(PHANDLE pHandle, PFILE_OBJECT *ppObject, CONST WCHAR *pszDeviceName)
93 {
94     UNICODE_STRING DeviceName;
95     OBJECT_ATTRIBUTES ObjectAttributes;
96     NTSTATUS Status;
97     IO_STATUS_BLOCK Iosb;
98 
99     RtlInitUnicodeString(&DeviceName, pszDeviceName);
100 
101     InitializeObjectAttributes(&ObjectAttributes,
102                                &DeviceName,
103                                OBJ_KERNEL_HANDLE,
104                                NULL,
105                                NULL);
106 
107     Status = ZwOpenFile(pHandle,
108                         FILE_ALL_ACCESS,
109                         &ObjectAttributes,
110                         &Iosb,
111                         0,
112                         0);
113     if (NT_SUCCESS(Status) && ppObject)
114     {
115         Status = ObReferenceObjectByHandle(*pHandle, SYNCHRONIZE, NULL, KernelMode, (PVOID*)ppObject, NULL);
116         ASSERT(NT_SUCCESS(Status));
117     }
118 
119     return Status;
120 }
121 
122 /*
123  * RawInputThreadMain
124  *
125  * Reads data from input devices and supports win32 timers
126  */
127 VOID NTAPI
128 RawInputThreadMain(VOID)
129 {
130     NTSTATUS MouStatus = STATUS_UNSUCCESSFUL, KbdStatus = STATUS_UNSUCCESSFUL, Status;
131     IO_STATUS_BLOCK MouIosb, KbdIosb;
132     PFILE_OBJECT pKbdDevice = NULL, pMouDevice = NULL;
133     LARGE_INTEGER ByteOffset;
134     //LARGE_INTEGER WaitTimeout;
135     PVOID WaitObjects[4], pSignaledObject = NULL;
136     KWAIT_BLOCK WaitBlockArray[RTL_NUMBER_OF(WaitObjects)];
137     ULONG cWaitObjects = 0, cMaxWaitObjects = 2;
138     MOUSE_INPUT_DATA MouseInput;
139     KEYBOARD_INPUT_DATA KeyInput;
140     PVOID ShutdownEvent;
141     HWINSTA hWinSta;
142 
143     ByteOffset.QuadPart = (LONGLONG)0;
144     //WaitTimeout.QuadPart = (LONGLONG)(-10000000);
145 
146     ptiRawInput = GetW32ThreadInfo();
147     ptiRawInput->TIF_flags |= TIF_SYSTEMTHREAD;
148     ptiRawInput->pClientInfo->dwTIFlags = ptiRawInput->TIF_flags;
149 
150     TRACE("Raw Input Thread %p\n", ptiRawInput);
151 
152     KeSetPriorityThread(&PsGetCurrentThread()->Tcb,
153                         LOW_REALTIME_PRIORITY + 3);
154 
155     Status = ObOpenObjectByPointer(InputWindowStation,
156                                    0,
157                                    NULL,
158                                    MAXIMUM_ALLOWED,
159                                    ExWindowStationObjectType,
160                                    UserMode,
161                                    (PHANDLE)&hWinSta);
162     if (NT_SUCCESS(Status))
163     {
164         UserSetProcessWindowStation(hWinSta);
165     }
166     else
167     {
168         ASSERT(FALSE);
169         /* Failed to open the interactive winsta! What now? */
170     }
171 
172     UserEnterExclusive();
173     StartTheTimers();
174     UserLeave();
175 
176     NT_ASSERT(ghMouseDevice == NULL);
177     NT_ASSERT(ghKeyboardDevice == NULL);
178 
179     PoRequestShutdownEvent(&ShutdownEvent);
180     for (;;)
181     {
182         if (!ghMouseDevice)
183         {
184             /* Check if mouse device already exists */
185             Status = OpenInputDevice(&ghMouseDevice, &pMouDevice, L"\\Device\\PointerClass0" );
186             if (NT_SUCCESS(Status))
187             {
188                 ++cMaxWaitObjects;
189                 TRACE("Mouse connected!\n");
190             }
191         }
192         if (!ghKeyboardDevice)
193         {
194             /* Check if keyboard device already exists */
195             Status = OpenInputDevice(&ghKeyboardDevice, &pKbdDevice, L"\\Device\\KeyboardClass0");
196             if (NT_SUCCESS(Status))
197             {
198                 ++cMaxWaitObjects;
199                 TRACE("Keyboard connected!\n");
200                 // Get and load keyboard attributes.
201                 UserInitKeyboard(ghKeyboardDevice);
202                 UserEnterExclusive();
203                 // Register the Window hotkey.
204                 UserRegisterHotKey(PWND_BOTTOM, IDHK_WINKEY, MOD_WIN, 0);
205                 // Register the Window Snap hotkey.
206                 UserRegisterHotKey(PWND_BOTTOM, IDHK_SNAP_LEFT, MOD_WIN, VK_LEFT);
207                 UserRegisterHotKey(PWND_BOTTOM, IDHK_SNAP_RIGHT, MOD_WIN, VK_RIGHT);
208                 UserRegisterHotKey(PWND_BOTTOM, IDHK_SNAP_UP, MOD_WIN, VK_UP);
209                 UserRegisterHotKey(PWND_BOTTOM, IDHK_SNAP_DOWN, MOD_WIN, VK_DOWN);
210                 // Register the debug hotkeys.
211                 StartDebugHotKeys();
212                 UserLeave();
213             }
214         }
215 
216         /* Reset WaitHandles array */
217         cWaitObjects = 0;
218         WaitObjects[cWaitObjects++] = ShutdownEvent;
219         WaitObjects[cWaitObjects++] = MasterTimer;
220 
221         if (ghMouseDevice)
222         {
223             /* Try to read from mouse if previous reading is not pending */
224             if (MouStatus != STATUS_PENDING)
225             {
226                 MouStatus = ZwReadFile(ghMouseDevice,
227                                        NULL,
228                                        NULL,
229                                        NULL,
230                                        &MouIosb,
231                                        &MouseInput,
232                                        sizeof(MOUSE_INPUT_DATA),
233                                        &ByteOffset,
234                                        NULL);
235             }
236 
237             if (MouStatus == STATUS_PENDING)
238                 WaitObjects[cWaitObjects++] = &pMouDevice->Event;
239         }
240 
241         if (ghKeyboardDevice)
242         {
243             /* Try to read from keyboard if previous reading is not pending */
244             if (KbdStatus != STATUS_PENDING)
245             {
246                 KbdStatus = ZwReadFile(ghKeyboardDevice,
247                                        NULL,
248                                        NULL,
249                                        NULL,
250                                        &KbdIosb,
251                                        &KeyInput,
252                                        sizeof(KEYBOARD_INPUT_DATA),
253                                        &ByteOffset,
254                                        NULL);
255 
256             }
257             if (KbdStatus == STATUS_PENDING)
258                 WaitObjects[cWaitObjects++] = &pKbdDevice->Event;
259         }
260 
261         /* If all objects are pending, wait for them */
262         if (cWaitObjects == cMaxWaitObjects)
263         {
264             Status = KeWaitForMultipleObjects(cWaitObjects,
265                                               WaitObjects,
266                                               WaitAny,
267                                               UserRequest,
268                                               KernelMode,
269                                               TRUE,
270                                               NULL,//&WaitTimeout,
271                                               WaitBlockArray);
272 
273             if ((Status >= STATUS_WAIT_0) &&
274                 (Status < (STATUS_WAIT_0 + (LONG)cWaitObjects)))
275             {
276                 /* Some device has finished reading */
277                 pSignaledObject = WaitObjects[Status - STATUS_WAIT_0];
278 
279                 /* Check if it is mouse or keyboard and update status */
280                 if ((MouStatus == STATUS_PENDING) &&
281                     (pSignaledObject == &pMouDevice->Event))
282                 {
283                     MouStatus = MouIosb.Status;
284                 }
285                 else if ((KbdStatus == STATUS_PENDING) &&
286                          (pSignaledObject == &pKbdDevice->Event))
287                 {
288                     KbdStatus = KbdIosb.Status;
289                 }
290                 else if (pSignaledObject == MasterTimer)
291                 {
292                     ProcessTimers();
293                 }
294                 else if (pSignaledObject == ShutdownEvent)
295                 {
296                     break;
297                 }
298                 else ASSERT(FALSE);
299             }
300         }
301 
302         /* Have we successed reading from mouse? */
303         if (NT_SUCCESS(MouStatus) && MouStatus != STATUS_PENDING)
304         {
305             TRACE("MouseEvent\n");
306 
307             /* Set LastInputTick */
308             IntLastInputTick(TRUE);
309 
310             /* Process data */
311             UserEnterExclusive();
312             UserProcessMouseInput(&MouseInput);
313             UserLeave();
314         }
315         else if (MouStatus != STATUS_PENDING)
316             ERR("Failed to read from mouse: %x.\n", MouStatus);
317 
318         /* Have we successed reading from keyboard? */
319         if (NT_SUCCESS(KbdStatus) && KbdStatus != STATUS_PENDING)
320         {
321             TRACE("KeyboardEvent: %s %04x\n",
322                   (KeyInput.Flags & KEY_BREAK) ? "up" : "down",
323                   KeyInput.MakeCode);
324 
325             /* Set LastInputTick */
326             IntLastInputTick(TRUE);
327 
328             /* Process data */
329             UserEnterExclusive();
330             UserProcessKeyboardInput(&KeyInput);
331             UserLeave();
332         }
333         else if (KbdStatus != STATUS_PENDING)
334             ERR("Failed to read from keyboard: %x.\n", KbdStatus);
335     }
336 
337     if (ghMouseDevice)
338     {
339         (void)ZwCancelIoFile(ghMouseDevice, &MouIosb);
340         ObCloseHandle(ghMouseDevice, KernelMode);
341         ObDereferenceObject(pMouDevice);
342         ghMouseDevice = NULL;
343     }
344 
345     if (ghKeyboardDevice)
346     {
347         (void)ZwCancelIoFile(ghKeyboardDevice, &KbdIosb);
348         ObCloseHandle(ghKeyboardDevice, KernelMode);
349         ObDereferenceObject(pKbdDevice);
350         ghKeyboardDevice = NULL;
351     }
352 
353     ERR("Raw Input Thread Exit!\n");
354 }
355 
356 /*
357  * InitInputImpl
358  *
359  * Inits input implementation
360  */
361 INIT_FUNCTION
362 NTSTATUS
363 NTAPI
364 InitInputImpl(VOID)
365 {
366     MasterTimer = ExAllocatePoolWithTag(NonPagedPool, sizeof(KTIMER), USERTAG_SYSTEM);
367     if (!MasterTimer)
368     {
369         ERR("Failed to allocate memory\n");
370         ASSERT(FALSE);
371         return STATUS_UNSUCCESSFUL;
372     }
373     KeInitializeTimer(MasterTimer);
374 
375     return STATUS_SUCCESS;
376 }
377 
378 BOOL FASTCALL
379 IntBlockInput(PTHREADINFO pti, BOOL BlockIt)
380 {
381     PTHREADINFO OldBlock;
382     ASSERT(pti);
383 
384     if(!pti->rpdesk || ((pti->TIF_flags & TIF_INCLEANUP) && BlockIt))
385     {
386         /*
387          * Fail blocking if exiting the thread
388          */
389 
390         return FALSE;
391     }
392 
393     /*
394      * FIXME: Check access rights of the window station
395      *        e.g. services running in the service window station cannot block input
396      */
397     if(!ThreadHasInputAccess(pti) ||
398        !IntIsActiveDesktop(pti->rpdesk))
399     {
400         EngSetLastError(ERROR_ACCESS_DENIED);
401         return FALSE;
402     }
403 
404     ASSERT(pti->rpdesk);
405     OldBlock = pti->rpdesk->BlockInputThread;
406     if(OldBlock)
407     {
408         if(OldBlock != pti)
409         {
410             EngSetLastError(ERROR_ACCESS_DENIED);
411             return FALSE;
412         }
413         pti->rpdesk->BlockInputThread = (BlockIt ? pti : NULL);
414         return OldBlock == NULL;
415     }
416 
417     pti->rpdesk->BlockInputThread = (BlockIt ? pti : NULL);
418     return OldBlock == NULL;
419 }
420 
421 BOOL
422 APIENTRY
423 NtUserBlockInput(
424     BOOL BlockIt)
425 {
426     BOOL ret;
427 
428     TRACE("Enter NtUserBlockInput\n");
429     UserEnterExclusive();
430 
431     ret = IntBlockInput(PsGetCurrentThreadWin32Thread(), BlockIt);
432 
433     UserLeave();
434     TRACE("Leave NtUserBlockInput, ret=%i\n", ret);
435 
436     return ret;
437 }
438 
439 BOOL
440 FASTCALL
441 IsRemoveAttachThread(PTHREADINFO pti)
442 {
443     NTSTATUS Status;
444     PATTACHINFO pai;
445     BOOL Ret = TRUE;
446     PTHREADINFO ptiFrom = NULL, ptiTo = NULL;
447 
448     do
449     {
450        if (!gpai) return TRUE;
451 
452        pai = gpai; // Bottom of the list.
453 
454        do
455        {
456           if (pai->pti2 == pti)
457           {
458              ptiFrom = pai->pti1;
459              ptiTo = pti;
460              break;
461           }
462           if (pai->pti1 == pti)
463           {
464              ptiFrom = pti;
465              ptiTo = pai->pti2;
466              break;
467           }
468           pai = pai->paiNext;
469 
470        } while (pai);
471 
472        if (!pai && !ptiFrom && !ptiTo) break;
473 
474        Status = UserAttachThreadInput(ptiFrom, ptiTo, FALSE);
475        if (!NT_SUCCESS(Status)) Ret = FALSE;
476 
477     } while (Ret);
478 
479     return Ret;
480 }
481 
482 NTSTATUS FASTCALL
483 UserAttachThreadInput(PTHREADINFO ptiFrom, PTHREADINFO ptiTo, BOOL fAttach)
484 {
485     MSG msg;
486     PATTACHINFO pai;
487     PCURICON_OBJECT CurIcon;
488 
489     /* Can not be the same thread. */
490     if (ptiFrom == ptiTo) return STATUS_INVALID_PARAMETER;
491 
492     /* Do not attach to system threads or between different desktops. */
493     if (ptiFrom->TIF_flags & TIF_DONTATTACHQUEUE ||
494         ptiTo->TIF_flags & TIF_DONTATTACHQUEUE ||
495         ptiFrom->rpdesk != ptiTo->rpdesk)
496         return STATUS_ACCESS_DENIED;
497 
498     /* MSDN Note:
499        Keyboard and mouse events received by both threads are processed by the thread specified by the idAttachTo.
500      */
501 
502     /* If Attach set, allocate and link. */
503     if (fAttach)
504     {
505         pai = ExAllocatePoolWithTag(PagedPool, sizeof(ATTACHINFO), USERTAG_ATTACHINFO);
506         if (!pai) return STATUS_NO_MEMORY;
507 
508         pai->paiNext = gpai;
509         pai->pti1 = ptiFrom;
510         pai->pti2 = ptiTo;
511         gpai = pai;
512         paiCount++;
513         ERR("Attach Allocated! ptiFrom 0x%p  ptiTo 0x%p paiCount %d\n",ptiFrom,ptiTo,paiCount);
514 
515         if (ptiTo->MessageQueue != ptiFrom->MessageQueue)
516         {
517 
518            ptiTo->MessageQueue->iCursorLevel -= ptiFrom->iCursorLevel;
519 
520            if (ptiFrom->MessageQueue == gpqForeground)
521            {
522               ERR("ptiFrom is Foreground\n");
523               ptiTo->MessageQueue->spwndActive  = ptiFrom->MessageQueue->spwndActive;
524               ptiTo->MessageQueue->spwndFocus   = ptiFrom->MessageQueue->spwndFocus;
525               ptiTo->MessageQueue->spwndCapture = ptiFrom->MessageQueue->spwndCapture;
526               ptiTo->MessageQueue->QF_flags    ^= ((ptiTo->MessageQueue->QF_flags ^ ptiFrom->MessageQueue->QF_flags) & QF_CAPTURELOCKED);
527               RtlCopyMemory(&ptiTo->MessageQueue->CaretInfo,
528                             &ptiFrom->MessageQueue->CaretInfo,
529                             sizeof(ptiTo->MessageQueue->CaretInfo));
530               IntSetFocusMessageQueue(NULL);
531               IntSetFocusMessageQueue(ptiTo->MessageQueue);
532               gptiForeground = ptiTo;
533            }
534            else
535            {
536               ERR("ptiFrom NOT Foreground\n");
537               if ( ptiTo->MessageQueue->spwndActive == 0 )
538                   ptiTo->MessageQueue->spwndActive = ptiFrom->MessageQueue->spwndActive;
539               if ( ptiTo->MessageQueue->spwndFocus == 0 )
540                   ptiTo->MessageQueue->spwndFocus  = ptiFrom->MessageQueue->spwndFocus;
541            }
542 
543            CurIcon = ptiFrom->MessageQueue->CursorObject;
544 
545            MsqDestroyMessageQueue(ptiFrom);
546 
547            if (CurIcon)
548            {
549               // Could be global. Keep it above the water line!
550               UserReferenceObject(CurIcon);
551            }
552 
553            if (CurIcon && UserObjectInDestroy(UserHMGetHandle(CurIcon)))
554            {
555               UserDereferenceObject(CurIcon);
556               CurIcon = NULL;
557            }
558 
559            ptiFrom->MessageQueue = ptiTo->MessageQueue;
560 
561            // Pass cursor From if To is null. Pass test_SetCursor parent_id == current pti ID.
562            if (CurIcon && ptiTo->MessageQueue->CursorObject == NULL)
563            {
564               ERR("ptiTo receiving ptiFrom Cursor\n");
565               ptiTo->MessageQueue->CursorObject = CurIcon;
566            }
567 
568            ptiFrom->MessageQueue->cThreads++;
569            ERR("ptiTo S Share count %u\n", ptiFrom->MessageQueue->cThreads);
570 
571            IntReferenceMessageQueue(ptiTo->MessageQueue);
572         }
573         else
574         {
575            ERR("Attach Threads are already associated!\n");
576         }
577     }
578     else /* If clear, unlink and free it. */
579     {
580         BOOL Hit = FALSE;
581         PATTACHINFO *ppai;
582 
583         if (!gpai) return STATUS_INVALID_PARAMETER;
584 
585         /* Search list and free if found or return false. */
586         ppai = &gpai;
587         while (*ppai != NULL)
588         {
589            if ( (*ppai)->pti2 == ptiTo && (*ppai)->pti1 == ptiFrom )
590            {
591               pai = *ppai;
592               /* Remove it from the list */
593               *ppai = (*ppai)->paiNext;
594               ExFreePoolWithTag(pai, USERTAG_ATTACHINFO);
595               paiCount--;
596               Hit = TRUE;
597               break;
598            }
599            ppai = &((*ppai)->paiNext);
600         }
601 
602         if (!Hit) return STATUS_INVALID_PARAMETER;
603 
604         ERR("Attach Free! ptiFrom 0x%p  ptiTo 0x%p paiCount %d\n",ptiFrom,ptiTo,paiCount);
605 
606         if (ptiTo->MessageQueue == ptiFrom->MessageQueue)
607         {
608            PWND spwndActive = ptiTo->MessageQueue->spwndActive;
609            PWND spwndFocus  = ptiTo->MessageQueue->spwndFocus;
610 
611            if (gptiForeground == ptiFrom)
612            {
613               ERR("ptiTo is now pti FG.\n");
614               // MessageQueue foreground is set so switch threads.
615               gptiForeground = ptiTo;
616            }
617            ptiTo->MessageQueue->cThreads--;
618            ERR("ptiTo E Share count %u\n", ptiTo->MessageQueue->cThreads);
619            ASSERT(ptiTo->MessageQueue->cThreads >= 1);
620 
621            IntDereferenceMessageQueue(ptiTo->MessageQueue);
622 
623            ptiFrom->MessageQueue = MsqCreateMessageQueue(ptiFrom);
624 
625            if (spwndActive)
626            {
627               if (spwndActive->head.pti == ptiFrom)
628               {
629                  ptiFrom->MessageQueue->spwndActive = spwndActive;
630                  ptiTo->MessageQueue->spwndActive = 0;
631               }
632            }
633            if (spwndFocus)
634            {
635               if (spwndFocus->head.pti == ptiFrom)
636               {
637                  ptiFrom->MessageQueue->spwndFocus = spwndFocus;
638                  ptiTo->MessageQueue->spwndFocus = 0;
639               }
640            }
641            ptiTo->MessageQueue->iCursorLevel -= ptiFrom->iCursorLevel;
642         }
643         else
644         {
645            ERR("Detaching Threads are not associated!\n");
646         }
647     }
648     /* Note that key state, which can be ascertained by calls to the GetKeyState
649        or GetKeyboardState function, is reset after a call to AttachThreadInput.
650        ATM which one?
651      */
652     RtlCopyMemory(ptiTo->MessageQueue->afKeyState, gafAsyncKeyState, sizeof(gafAsyncKeyState));
653 
654     ptiTo->MessageQueue->msgDblClk.message = 0;
655 
656     /* Generate mouse move message */
657     msg.message = WM_MOUSEMOVE;
658     msg.wParam = UserGetMouseButtonsState();
659     msg.lParam = MAKELPARAM(gpsi->ptCursor.x, gpsi->ptCursor.y);
660     msg.pt = gpsi->ptCursor;
661     co_MsqInsertMouseMessage(&msg, 0, 0, TRUE);
662 
663     return STATUS_SUCCESS;
664 }
665 
666 BOOL
667 APIENTRY
668 NtUserAttachThreadInput(
669     IN DWORD idAttach,
670     IN DWORD idAttachTo,
671     IN BOOL fAttach)
672 {
673   NTSTATUS Status;
674   PTHREADINFO pti, ptiTo;
675   BOOL Ret = FALSE;
676 
677   UserEnterExclusive();
678   TRACE("Enter NtUserAttachThreadInput %s\n",(fAttach ? "TRUE" : "FALSE" ));
679 
680   pti = IntTID2PTI(UlongToHandle(idAttach));
681   ptiTo = IntTID2PTI(UlongToHandle(idAttachTo));
682 
683   if ( !pti || !ptiTo )
684   {
685      TRACE("AttachThreadInput pti or ptiTo NULL.\n");
686      EngSetLastError(ERROR_INVALID_PARAMETER);
687      goto Exit;
688   }
689 
690   Status = UserAttachThreadInput( pti, ptiTo, fAttach);
691   if (!NT_SUCCESS(Status))
692   {
693      TRACE("AttachThreadInput Error Status 0x%x. \n",Status);
694      EngSetLastError(RtlNtStatusToDosError(Status));
695   }
696   else Ret = TRUE;
697 
698 Exit:
699   TRACE("Leave NtUserAttachThreadInput, ret=%d\n",Ret);
700   UserLeave();
701   return Ret;
702 }
703 
704 /*
705  * NtUserSendInput
706  *
707  * Generates input events from software
708  */
709 UINT
710 APIENTRY
711 NtUserSendInput(
712     UINT nInputs,
713     LPINPUT pInput,
714     INT cbSize)
715 {
716     PTHREADINFO pti;
717     UINT uRet = 0;
718 
719     TRACE("Enter NtUserSendInput\n");
720     UserEnterExclusive();
721 
722     pti = PsGetCurrentThreadWin32Thread();
723     ASSERT(pti);
724 
725     if (!pti->rpdesk)
726     {
727         goto cleanup;
728     }
729 
730     if (!nInputs || !pInput || cbSize != sizeof(INPUT))
731     {
732         EngSetLastError(ERROR_INVALID_PARAMETER);
733         goto cleanup;
734     }
735 
736     /*
737      * FIXME: Check access rights of the window station
738      *        e.g. services running in the service window station cannot block input
739      */
740     if (!ThreadHasInputAccess(pti) ||
741         !IntIsActiveDesktop(pti->rpdesk))
742     {
743         EngSetLastError(ERROR_ACCESS_DENIED);
744         goto cleanup;
745     }
746 
747     while (nInputs--)
748     {
749         INPUT SafeInput;
750         NTSTATUS Status;
751 
752         Status = MmCopyFromCaller(&SafeInput, pInput++, sizeof(INPUT));
753         if (!NT_SUCCESS(Status))
754         {
755             SetLastNtError(Status);
756             goto cleanup;
757         }
758 
759         switch (SafeInput.type)
760         {
761             case INPUT_MOUSE:
762                 if (UserSendMouseInput(&SafeInput.mi, TRUE))
763                     uRet++;
764                 break;
765             case INPUT_KEYBOARD:
766                 if (UserSendKeyboardInput(&SafeInput.ki, TRUE))
767                     uRet++;
768                 break;
769             case INPUT_HARDWARE:
770                 FIXME("INPUT_HARDWARE not supported!");
771                 break;
772             default:
773                 ERR("SendInput(): Invalid input type: 0x%x\n", SafeInput.type);
774                 break;
775         }
776     }
777 
778 cleanup:
779     TRACE("Leave NtUserSendInput, ret=%u\n", uRet);
780     UserLeave();
781     return uRet;
782 }
783 
784 /* EOF */
785