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