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)
11 DBG_DEFAULT_CHANNEL(UserInput
);
13 /* GLOBALS *******************************************************************/
15 PTHREADINFO ptiRawInput
;
16 PKTIMER MasterTimer
= NULL
;
17 PATTACHINFO gpai
= NULL
;
19 HANDLE ghKeyboardDevice
= NULL
;
21 static DWORD LastInputTick
= 0;
22 static HANDLE ghMouseDevice
;
24 /* FUNCTIONS *****************************************************************/
29 * Updates or gets last input tick count
32 IntLastInputTick(BOOL bUpdate
)
36 LastInputTick
= EngGetTickCount32();
37 if (gpsi
) gpsi
->dwLastRITEventTickCount
= LastInputTick
;
45 * Check if screensaver should be started and sends message to SAS window
48 DoTheScreenSaver(VOID
)
52 if (gspv
.iScrSaverTimeout
> 0) // Zero means Off.
54 Test
= EngGetTickCount32();
55 Test
= Test
- LastInputTick
;
56 TO
= 1000 * gspv
.iScrSaverTimeout
;
59 TRACE("Screensaver Message Start! Tick %lu Timeout %d \n", Test
, gspv
.iScrSaverTimeout
);
61 if (ppiScrnSaver
) // We are or we are not the screensaver, prevent reentry...
63 if (!(ppiScrnSaver
->W32PF_flags
& W32PF_IDLESCREENSAVER
))
65 ppiScrnSaver
->W32PF_flags
|= W32PF_IDLESCREENSAVER
;
66 ERR("Screensaver is Idle\n");
71 PUSER_MESSAGE_QUEUE ForegroundQueue
= IntGetFocusMessageQueue();
72 if (ForegroundQueue
&& ForegroundQueue
->spwndActive
)
73 UserPostMessage(hwndSAS
, WM_LOGONNOTIFY
, LN_START_SCREENSAVE
, 1); // lParam 1 == Secure
75 UserPostMessage(hwndSAS
, WM_LOGONNOTIFY
, LN_START_SCREENSAVE
, 0);
84 * Opens input device for asynchronous access
88 OpenInputDevice(PHANDLE pHandle
, PFILE_OBJECT
*ppObject
, CONST WCHAR
*pszDeviceName
)
90 UNICODE_STRING DeviceName
;
91 OBJECT_ATTRIBUTES ObjectAttributes
;
95 RtlInitUnicodeString(&DeviceName
, pszDeviceName
);
97 InitializeObjectAttributes(&ObjectAttributes
,
103 Status
= ZwOpenFile(pHandle
,
109 if (NT_SUCCESS(Status
) && ppObject
)
111 Status
= ObReferenceObjectByHandle(*pHandle
, SYNCHRONIZE
, NULL
, KernelMode
, (PVOID
*)ppObject
, NULL
);
112 ASSERT(NT_SUCCESS(Status
));
121 * Reads data from input devices and supports win32 timers
124 RawInputThreadMain(VOID
)
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
;
139 ByteOffset
.QuadPart
= (LONGLONG
)0;
140 //WaitTimeout.QuadPart = (LONGLONG)(-10000000);
142 ptiRawInput
= GetW32ThreadInfo();
143 ptiRawInput
->TIF_flags
|= TIF_SYSTEMTHREAD
;
144 ptiRawInput
->pClientInfo
->dwTIFlags
= ptiRawInput
->TIF_flags
;
146 TRACE("Raw Input Thread %p\n", ptiRawInput
);
148 KeSetPriorityThread(&PsGetCurrentThread()->Tcb
,
149 LOW_REALTIME_PRIORITY
+ 3);
151 Status
= ObOpenObjectByPointer(InputWindowStation
,
155 ExWindowStationObjectType
,
158 if (NT_SUCCESS(Status
))
160 UserSetProcessWindowStation(hWinSta
);
165 /* Failed to open the interactive winsta! What now? */
168 UserEnterExclusive();
172 NT_ASSERT(ghMouseDevice
== NULL
);
173 NT_ASSERT(ghKeyboardDevice
== NULL
);
175 PoRequestShutdownEvent(&ShutdownEvent
);
180 /* Check if mouse device already exists */
181 Status
= OpenInputDevice(&ghMouseDevice
, &pMouDevice
, L
"\\Device\\PointerClass0" );
182 if (NT_SUCCESS(Status
))
185 TRACE("Mouse connected!\n");
188 if (!ghKeyboardDevice
)
190 /* Check if keyboard device already exists */
191 Status
= OpenInputDevice(&ghKeyboardDevice
, &pKbdDevice
, L
"\\Device\\KeyboardClass0");
192 if (NT_SUCCESS(Status
))
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.
212 /* Reset WaitHandles array */
214 WaitObjects
[cWaitObjects
++] = ShutdownEvent
;
215 WaitObjects
[cWaitObjects
++] = MasterTimer
;
219 /* Try to read from mouse if previous reading is not pending */
220 if (MouStatus
!= STATUS_PENDING
)
222 MouStatus
= ZwReadFile(ghMouseDevice
,
228 sizeof(MOUSE_INPUT_DATA
),
233 if (MouStatus
== STATUS_PENDING
)
234 WaitObjects
[cWaitObjects
++] = &pMouDevice
->Event
;
237 if (ghKeyboardDevice
)
239 /* Try to read from keyboard if previous reading is not pending */
240 if (KbdStatus
!= STATUS_PENDING
)
242 KbdStatus
= ZwReadFile(ghKeyboardDevice
,
248 sizeof(KEYBOARD_INPUT_DATA
),
253 if (KbdStatus
== STATUS_PENDING
)
254 WaitObjects
[cWaitObjects
++] = &pKbdDevice
->Event
;
257 /* If all objects are pending, wait for them */
258 if (cWaitObjects
== cMaxWaitObjects
)
260 Status
= KeWaitForMultipleObjects(cWaitObjects
,
269 if ((Status
>= STATUS_WAIT_0
) &&
270 (Status
< (STATUS_WAIT_0
+ (LONG
)cWaitObjects
)))
272 /* Some device has finished reading */
273 pSignaledObject
= WaitObjects
[Status
- STATUS_WAIT_0
];
275 /* Check if it is mouse or keyboard and update status */
276 if ((MouStatus
== STATUS_PENDING
) &&
277 (pSignaledObject
== &pMouDevice
->Event
))
279 MouStatus
= MouIosb
.Status
;
281 else if ((KbdStatus
== STATUS_PENDING
) &&
282 (pSignaledObject
== &pKbdDevice
->Event
))
284 KbdStatus
= KbdIosb
.Status
;
286 else if (pSignaledObject
== MasterTimer
)
290 else if (pSignaledObject
== ShutdownEvent
)
298 /* Have we successed reading from mouse? */
299 if (NT_SUCCESS(MouStatus
) && MouStatus
!= STATUS_PENDING
)
301 TRACE("MouseEvent\n");
303 /* Set LastInputTick */
304 IntLastInputTick(TRUE
);
307 UserEnterExclusive();
308 UserProcessMouseInput(&MouseInput
);
311 else if (MouStatus
!= STATUS_PENDING
)
312 ERR("Failed to read from mouse: %x.\n", MouStatus
);
314 /* Have we successed reading from keyboard? */
315 if (NT_SUCCESS(KbdStatus
) && KbdStatus
!= STATUS_PENDING
)
317 TRACE("KeyboardEvent: %s %04x\n",
318 (KeyInput
.Flags
& KEY_BREAK
) ? "up" : "down",
321 /* Set LastInputTick */
322 IntLastInputTick(TRUE
);
325 UserEnterExclusive();
326 UserProcessKeyboardInput(&KeyInput
);
329 else if (KbdStatus
!= STATUS_PENDING
)
330 ERR("Failed to read from keyboard: %x.\n", KbdStatus
);
335 (void)ZwCancelIoFile(ghMouseDevice
, &MouIosb
);
336 ObCloseHandle(ghMouseDevice
, KernelMode
);
337 ObDereferenceObject(pMouDevice
);
338 ghMouseDevice
= NULL
;
341 if (ghKeyboardDevice
)
343 (void)ZwCancelIoFile(ghKeyboardDevice
, &KbdIosb
);
344 ObCloseHandle(ghKeyboardDevice
, KernelMode
);
345 ObDereferenceObject(pKbdDevice
);
346 ghKeyboardDevice
= NULL
;
349 ERR("Raw Input Thread Exit!\n");
355 * Inits input implementation
362 MasterTimer
= ExAllocatePoolWithTag(NonPagedPool
, sizeof(KTIMER
), USERTAG_SYSTEM
);
365 ERR("Failed to allocate memory\n");
367 return STATUS_UNSUCCESSFUL
;
369 KeInitializeTimer(MasterTimer
);
371 return STATUS_SUCCESS
;
375 IntBlockInput(PTHREADINFO pti
, BOOL BlockIt
)
377 PTHREADINFO OldBlock
;
380 if(!pti
->rpdesk
|| ((pti
->TIF_flags
& TIF_INCLEANUP
) && BlockIt
))
383 * Fail blocking if exiting the thread
390 * FIXME: Check access rights of the window station
391 * e.g. services running in the service window station cannot block input
393 if(!ThreadHasInputAccess(pti
) ||
394 !IntIsActiveDesktop(pti
->rpdesk
))
396 EngSetLastError(ERROR_ACCESS_DENIED
);
401 OldBlock
= pti
->rpdesk
->BlockInputThread
;
406 EngSetLastError(ERROR_ACCESS_DENIED
);
409 pti
->rpdesk
->BlockInputThread
= (BlockIt
? pti
: NULL
);
410 return OldBlock
== NULL
;
413 pti
->rpdesk
->BlockInputThread
= (BlockIt
? pti
: NULL
);
414 return OldBlock
== NULL
;
424 TRACE("Enter NtUserBlockInput\n");
425 UserEnterExclusive();
427 ret
= IntBlockInput(PsGetCurrentThreadWin32Thread(), BlockIt
);
430 TRACE("Leave NtUserBlockInput, ret=%i\n", ret
);
437 IsRemoveAttachThread(PTHREADINFO pti
)
442 PTHREADINFO ptiFrom
= NULL
, ptiTo
= NULL
;
446 if (!gpai
) return TRUE
;
448 pai
= gpai
; // Bottom of the list.
452 if (pai
->pti2
== pti
)
458 if (pai
->pti1
== pti
)
468 if (!pai
&& !ptiFrom
&& !ptiTo
) break;
470 Status
= UserAttachThreadInput(ptiFrom
, ptiTo
, FALSE
);
471 if (!NT_SUCCESS(Status
)) Ret
= FALSE
;
478 // Win: zzzAttachThreadInput
480 UserAttachThreadInput(PTHREADINFO ptiFrom
, PTHREADINFO ptiTo
, BOOL fAttach
)
484 PCURICON_OBJECT CurIcon
;
486 /* Can not be the same thread. */
487 if (ptiFrom
== ptiTo
) return STATUS_INVALID_PARAMETER
;
489 /* Do not attach to system threads or between different desktops. */
490 if (ptiFrom
->TIF_flags
& TIF_DONTATTACHQUEUE
||
491 ptiTo
->TIF_flags
& TIF_DONTATTACHQUEUE
||
492 ptiFrom
->rpdesk
!= ptiTo
->rpdesk
)
493 return STATUS_ACCESS_DENIED
;
496 Keyboard and mouse events received by both threads are processed by the thread specified by the idAttachTo.
499 /* If Attach set, allocate and link. */
502 pai
= ExAllocatePoolWithTag(PagedPool
, sizeof(ATTACHINFO
), USERTAG_ATTACHINFO
);
503 if (!pai
) return STATUS_NO_MEMORY
;
510 ERR("Attach Allocated! ptiFrom 0x%p ptiTo 0x%p paiCount %d\n",ptiFrom
,ptiTo
,paiCount
);
512 if (ptiTo
->MessageQueue
!= ptiFrom
->MessageQueue
)
515 ptiTo
->MessageQueue
->iCursorLevel
-= ptiFrom
->iCursorLevel
;
517 if (ptiFrom
->MessageQueue
== gpqForeground
)
519 ERR("ptiFrom is Foreground\n");
520 ptiTo
->MessageQueue
->spwndActive
= ptiFrom
->MessageQueue
->spwndActive
;
521 ptiTo
->MessageQueue
->spwndFocus
= ptiFrom
->MessageQueue
->spwndFocus
;
522 ptiTo
->MessageQueue
->spwndCapture
= ptiFrom
->MessageQueue
->spwndCapture
;
523 ptiTo
->MessageQueue
->QF_flags
^= ((ptiTo
->MessageQueue
->QF_flags
^ ptiFrom
->MessageQueue
->QF_flags
) & QF_CAPTURELOCKED
);
524 RtlCopyMemory(&ptiTo
->MessageQueue
->CaretInfo
,
525 &ptiFrom
->MessageQueue
->CaretInfo
,
526 sizeof(ptiTo
->MessageQueue
->CaretInfo
));
527 IntSetFocusMessageQueue(NULL
);
528 IntSetFocusMessageQueue(ptiTo
->MessageQueue
);
529 gptiForeground
= ptiTo
;
533 ERR("ptiFrom NOT Foreground\n");
534 if ( ptiTo
->MessageQueue
->spwndActive
== 0 )
535 ptiTo
->MessageQueue
->spwndActive
= ptiFrom
->MessageQueue
->spwndActive
;
536 if ( ptiTo
->MessageQueue
->spwndFocus
== 0 )
537 ptiTo
->MessageQueue
->spwndFocus
= ptiFrom
->MessageQueue
->spwndFocus
;
540 CurIcon
= ptiFrom
->MessageQueue
->CursorObject
;
542 MsqDestroyMessageQueue(ptiFrom
);
546 // Could be global. Keep it above the water line!
547 UserReferenceObject(CurIcon
);
550 if (CurIcon
&& UserObjectInDestroy(UserHMGetHandle(CurIcon
)))
552 UserDereferenceObject(CurIcon
);
556 ptiFrom
->MessageQueue
= ptiTo
->MessageQueue
;
558 // Pass cursor From if To is null. Pass test_SetCursor parent_id == current pti ID.
559 if (CurIcon
&& ptiTo
->MessageQueue
->CursorObject
== NULL
)
561 ERR("ptiTo receiving ptiFrom Cursor\n");
562 ptiTo
->MessageQueue
->CursorObject
= CurIcon
;
565 ptiFrom
->MessageQueue
->cThreads
++;
566 ERR("ptiTo S Share count %u\n", ptiFrom
->MessageQueue
->cThreads
);
568 IntReferenceMessageQueue(ptiTo
->MessageQueue
);
572 ERR("Attach Threads are already associated!\n");
575 else /* If clear, unlink and free it. */
580 if (!gpai
) return STATUS_INVALID_PARAMETER
;
582 /* Search list and free if found or return false. */
584 while (*ppai
!= NULL
)
586 if ( (*ppai
)->pti2
== ptiTo
&& (*ppai
)->pti1
== ptiFrom
)
589 /* Remove it from the list */
590 *ppai
= (*ppai
)->paiNext
;
591 ExFreePoolWithTag(pai
, USERTAG_ATTACHINFO
);
596 ppai
= &((*ppai
)->paiNext
);
599 if (!Hit
) return STATUS_INVALID_PARAMETER
;
601 ERR("Attach Free! ptiFrom 0x%p ptiTo 0x%p paiCount %d\n",ptiFrom
,ptiTo
,paiCount
);
603 if (ptiTo
->MessageQueue
== ptiFrom
->MessageQueue
)
605 PWND spwndActive
= ptiTo
->MessageQueue
->spwndActive
;
606 PWND spwndFocus
= ptiTo
->MessageQueue
->spwndFocus
;
608 if (gptiForeground
== ptiFrom
)
610 ERR("ptiTo is now pti FG.\n");
611 // MessageQueue foreground is set so switch threads.
612 gptiForeground
= ptiTo
;
614 ptiTo
->MessageQueue
->cThreads
--;
615 ERR("ptiTo E Share count %u\n", ptiTo
->MessageQueue
->cThreads
);
616 ASSERT(ptiTo
->MessageQueue
->cThreads
>= 1);
618 IntDereferenceMessageQueue(ptiTo
->MessageQueue
);
620 ptiFrom
->MessageQueue
= MsqCreateMessageQueue(ptiFrom
);
624 if (spwndActive
->head
.pti
== ptiFrom
)
626 ptiFrom
->MessageQueue
->spwndActive
= spwndActive
;
627 ptiTo
->MessageQueue
->spwndActive
= 0;
632 if (spwndFocus
->head
.pti
== ptiFrom
)
634 ptiFrom
->MessageQueue
->spwndFocus
= spwndFocus
;
635 ptiTo
->MessageQueue
->spwndFocus
= 0;
638 ptiTo
->MessageQueue
->iCursorLevel
-= ptiFrom
->iCursorLevel
;
642 ERR("Detaching Threads are not associated!\n");
645 /* Note that key state, which can be ascertained by calls to the GetKeyState
646 or GetKeyboardState function, is reset after a call to AttachThreadInput.
649 RtlCopyMemory(ptiTo
->MessageQueue
->afKeyState
, gafAsyncKeyState
, sizeof(gafAsyncKeyState
));
651 ptiTo
->MessageQueue
->msgDblClk
.message
= 0;
653 /* Generate mouse move message */
654 msg
.message
= WM_MOUSEMOVE
;
655 msg
.wParam
= UserGetMouseButtonsState();
656 msg
.lParam
= MAKELPARAM(gpsi
->ptCursor
.x
, gpsi
->ptCursor
.y
);
657 msg
.pt
= gpsi
->ptCursor
;
658 co_MsqInsertMouseMessage(&msg
, 0, 0, TRUE
);
660 return STATUS_SUCCESS
;
665 NtUserAttachThreadInput(
671 PTHREADINFO pti
, ptiTo
;
674 UserEnterExclusive();
675 TRACE("Enter NtUserAttachThreadInput %s\n",(fAttach
? "TRUE" : "FALSE" ));
677 pti
= IntTID2PTI(UlongToHandle(idAttach
));
678 ptiTo
= IntTID2PTI(UlongToHandle(idAttachTo
));
680 if ( !pti
|| !ptiTo
)
682 TRACE("AttachThreadInput pti or ptiTo NULL.\n");
683 EngSetLastError(ERROR_INVALID_PARAMETER
);
687 Status
= UserAttachThreadInput( pti
, ptiTo
, fAttach
);
688 if (!NT_SUCCESS(Status
))
690 TRACE("AttachThreadInput Error Status 0x%x. \n",Status
);
691 EngSetLastError(RtlNtStatusToDosError(Status
));
696 TRACE("Leave NtUserAttachThreadInput, ret=%d\n",Ret
);
704 * Generates input events from software
716 TRACE("Enter NtUserSendInput\n");
717 UserEnterExclusive();
719 pti
= PsGetCurrentThreadWin32Thread();
727 if (!nInputs
|| !pInput
|| cbSize
!= sizeof(INPUT
))
729 EngSetLastError(ERROR_INVALID_PARAMETER
);
734 * FIXME: Check access rights of the window station
735 * e.g. services running in the service window station cannot block input
737 if (!ThreadHasInputAccess(pti
) ||
738 !IntIsActiveDesktop(pti
->rpdesk
))
740 EngSetLastError(ERROR_ACCESS_DENIED
);
749 Status
= MmCopyFromCaller(&SafeInput
, pInput
++, sizeof(INPUT
));
750 if (!NT_SUCCESS(Status
))
752 SetLastNtError(Status
);
756 switch (SafeInput
.type
)
759 if (UserSendMouseInput(&SafeInput
.mi
, TRUE
))
763 if (UserSendKeyboardInput(&SafeInput
.ki
, TRUE
))
767 FIXME("INPUT_HARDWARE not supported!\n");
770 ERR("SendInput(): Invalid input type: 0x%x\n", SafeInput
.type
);
776 TRACE("Leave NtUserSendInput, ret=%u\n", uRet
);