Synchronize with trunk's revision r57652.
[reactos.git] / win32ss / user / ntuser / input.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS Win32k subsystem
4 * PURPOSE: General input functions
5 * FILE: subsystems/win32/win32k/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 PTHREADINFO ptiKeyboard;
17 PTHREADINFO ptiMouse;
18 PKTIMER MasterTimer = NULL;
19 PATTACHINFO gpai = NULL;
20 INT paiCount = 0;
21 HANDLE ghKeyboardDevice;
22
23 static DWORD LastInputTick = 0;
24 static HANDLE ghMouseDevice;
25
26 /* FUNCTIONS *****************************************************************/
27
28 /*
29 * IntLastInputTick
30 *
31 * Updates or gets last input tick count
32 */
33 static DWORD FASTCALL
34 IntLastInputTick(BOOL bUpdate)
35 {
36 if (bUpdate)
37 {
38 LARGE_INTEGER TickCount;
39 KeQueryTickCount(&TickCount);
40 LastInputTick = MsqCalculateMessageTime(&TickCount);
41 if (gpsi) gpsi->dwLastRITEventTickCount = LastInputTick;
42 }
43 return LastInputTick;
44 }
45
46 /*
47 * DoTheScreenSaver
48 *
49 * Check if scrensaver should be started and sends message to SAS window
50 */
51 VOID FASTCALL
52 DoTheScreenSaver(VOID)
53 {
54 LARGE_INTEGER TickCount;
55 DWORD Test, TO;
56
57 if (gspv.iScrSaverTimeout > 0) // Zero means Off.
58 {
59 KeQueryTickCount(&TickCount);
60 Test = MsqCalculateMessageTime(&TickCount);
61 Test = Test - LastInputTick;
62 TO = 1000 * gspv.iScrSaverTimeout;
63 if (Test > TO)
64 {
65 TRACE("Screensaver Message Start! Tick %d Timeout %d \n", Test, gspv.iScrSaverTimeout);
66
67 if (ppiScrnSaver) // We are or we are not the screensaver, prevent reentry...
68 {
69 if (!(ppiScrnSaver->W32PF_flags & W32PF_IDLESCREENSAVER))
70 {
71 ppiScrnSaver->W32PF_flags |= W32PF_IDLESCREENSAVER;
72 ERR("Screensaver is Idle\n");
73 }
74 }
75 else
76 {
77 PUSER_MESSAGE_QUEUE ForegroundQueue = IntGetFocusMessageQueue();
78 if (ForegroundQueue && ForegroundQueue->spwndActive)
79 UserPostMessage(hwndSAS, WM_LOGONNOTIFY, LN_START_SCREENSAVE, 1); // lParam 1 == Secure
80 else
81 UserPostMessage(hwndSAS, WM_LOGONNOTIFY, LN_START_SCREENSAVE, 0);
82 }
83 }
84 }
85 }
86
87 /*
88 * OpenInputDevice
89 *
90 * Opens input device for asynchronous access
91 */
92 static
93 NTSTATUS NTAPI
94 OpenInputDevice(PHANDLE pHandle, PFILE_OBJECT *ppObject, CONST WCHAR *pszDeviceName)
95 {
96 UNICODE_STRING DeviceName;
97 OBJECT_ATTRIBUTES ObjectAttributes;
98 NTSTATUS Status;
99 IO_STATUS_BLOCK Iosb;
100
101 RtlInitUnicodeString(&DeviceName, pszDeviceName);
102
103 InitializeObjectAttributes(&ObjectAttributes,
104 &DeviceName,
105 0,
106 NULL,
107 NULL);
108
109 Status = ZwOpenFile(pHandle,
110 FILE_ALL_ACCESS,
111 &ObjectAttributes,
112 &Iosb,
113 0,
114 0);
115 if (NT_SUCCESS(Status) && ppObject)
116 {
117 Status = ObReferenceObjectByHandle(*pHandle, SYNCHRONIZE, NULL, KernelMode, (PVOID*)ppObject, NULL);
118 ASSERT(NT_SUCCESS(Status));
119 }
120
121 return Status;
122 }
123
124 /*
125 * RawInputThreadMain
126 *
127 * Reads data from input devices and supports win32 timers
128 */
129 VOID NTAPI
130 RawInputThreadMain()
131 {
132 NTSTATUS MouStatus = STATUS_UNSUCCESSFUL, KbdStatus = STATUS_UNSUCCESSFUL, Status;
133 IO_STATUS_BLOCK MouIosb, KbdIosb;
134 PFILE_OBJECT pKbdDevice, pMouDevice;
135 LARGE_INTEGER ByteOffset;
136 //LARGE_INTEGER WaitTimeout;
137 PVOID WaitObjects[3], pSignaledObject = NULL;
138 ULONG cWaitObjects = 0, cMaxWaitObjects = 1;
139 MOUSE_INPUT_DATA MouseInput;
140 KEYBOARD_INPUT_DATA KeyInput;
141
142 ByteOffset.QuadPart = (LONGLONG)0;
143 //WaitTimeout.QuadPart = (LONGLONG)(-10000000);
144
145 ptiRawInput = GetW32ThreadInfo();
146 ptiRawInput->TIF_flags |= TIF_SYSTEMTHREAD;
147 ptiRawInput->pClientInfo->dwTIFlags = ptiRawInput->TIF_flags;
148
149 TRACE("Raw Input Thread 0x%x\n", ptiRawInput);
150
151 KeSetPriorityThread(&PsGetCurrentThread()->Tcb,
152 LOW_REALTIME_PRIORITY + 3);
153
154 UserEnterExclusive();
155 StartTheTimers();
156 UserLeave();
157
158 for(;;)
159 {
160 if (!ghMouseDevice)
161 {
162 /* Check if mouse device already exists */
163 Status = OpenInputDevice(&ghMouseDevice, &pMouDevice, L"\\Device\\PointerClass0" );
164 if (NT_SUCCESS(Status))
165 {
166 ++cMaxWaitObjects;
167 TRACE("Mouse connected!\n");
168 }
169 }
170 if (!ghKeyboardDevice)
171 {
172 /* Check if keyboard device already exists */
173 Status = OpenInputDevice(&ghKeyboardDevice, &pKbdDevice, L"\\Device\\KeyboardClass0");
174 if (NT_SUCCESS(Status))
175 {
176 ++cMaxWaitObjects;
177 TRACE("Keyboard connected!\n");
178 }
179 }
180
181 /* Reset WaitHandles array */
182 cWaitObjects = 0;
183 WaitObjects[cWaitObjects++] = MasterTimer;
184
185 if (ghMouseDevice)
186 {
187 /* Try to read from mouse if previous reading is not pending */
188 if (MouStatus != STATUS_PENDING)
189 {
190 MouStatus = ZwReadFile(ghMouseDevice,
191 NULL,
192 NULL,
193 NULL,
194 &MouIosb,
195 &MouseInput,
196 sizeof(MOUSE_INPUT_DATA),
197 &ByteOffset,
198 NULL);
199 }
200
201 if (MouStatus == STATUS_PENDING)
202 WaitObjects[cWaitObjects++] = &pMouDevice->Event;
203 }
204
205 if (ghKeyboardDevice)
206 {
207 /* Try to read from keyboard if previous reading is not pending */
208 if (KbdStatus != STATUS_PENDING)
209 {
210 KbdStatus = ZwReadFile(ghKeyboardDevice,
211 NULL,
212 NULL,
213 NULL,
214 &KbdIosb,
215 &KeyInput,
216 sizeof(KEYBOARD_INPUT_DATA),
217 &ByteOffset,
218 NULL);
219
220 }
221 if (KbdStatus == STATUS_PENDING)
222 WaitObjects[cWaitObjects++] = &pKbdDevice->Event;
223 }
224
225 /* If all objects are pending, wait for them */
226 if (cWaitObjects == cMaxWaitObjects)
227 {
228 Status = KeWaitForMultipleObjects(cWaitObjects,
229 WaitObjects,
230 WaitAny,
231 UserRequest,
232 KernelMode,
233 TRUE,
234 NULL,//&WaitTimeout,
235 NULL);
236
237 if ((Status >= STATUS_WAIT_0) &&
238 (Status < (STATUS_WAIT_0 + (LONG)cWaitObjects)))
239 {
240 /* Some device has finished reading */
241 pSignaledObject = WaitObjects[Status - STATUS_WAIT_0];
242
243 /* Check if it is mouse or keyboard and update status */
244 if (pSignaledObject == &pMouDevice->Event)
245 MouStatus = MouIosb.Status;
246 else if (pSignaledObject == &pKbdDevice->Event)
247 KbdStatus = KbdIosb.Status;
248 else if (pSignaledObject == MasterTimer)
249 {
250 ProcessTimers();
251 }
252 else ASSERT(FALSE);
253 }
254 }
255
256 /* Have we successed reading from mouse? */
257 if (NT_SUCCESS(MouStatus) && MouStatus != STATUS_PENDING)
258 {
259 TRACE("MouseEvent\n");
260
261 /* Set LastInputTick */
262 IntLastInputTick(TRUE);
263
264 /* Process data */
265 UserEnterExclusive();
266 UserProcessMouseInput(&MouseInput);
267 UserLeave();
268 }
269 else if (MouStatus != STATUS_PENDING)
270 ERR("Failed to read from mouse: %x.\n", MouStatus);
271
272 /* Have we successed reading from keyboard? */
273 if (NT_SUCCESS(KbdStatus) && KbdStatus != STATUS_PENDING)
274 {
275 TRACE("KeyboardEvent: %s %04x\n",
276 (KeyInput.Flags & KEY_BREAK) ? "up" : "down",
277 KeyInput.MakeCode);
278
279 /* Set LastInputTick */
280 IntLastInputTick(TRUE);
281
282 /* Process data */
283 UserEnterExclusive();
284 UserProcessKeyboardInput(&KeyInput);
285 UserLeave();
286 }
287 else if (KbdStatus != STATUS_PENDING)
288 ERR("Failed to read from keyboard: %x.\n", KbdStatus);
289 }
290 ERR("Raw Input Thread Exit!\n");
291 }
292
293 /*
294 * CreateSystemThreads
295 *
296 * Called form dedicated thread in CSRSS. RIT is started in context of this
297 * thread because it needs valid Win32 process with TEB initialized
298 */
299 DWORD NTAPI
300 CreateSystemThreads(UINT Type)
301 {
302 UserLeave();
303
304 switch (Type)
305 {
306 case 0: RawInputThreadMain(); break;
307 case 1: DesktopThreadMain(); break;
308 default: ERR("Wrong type: %x\n", Type);
309 }
310
311 UserEnterShared();
312
313 return 0;
314 }
315
316 /*
317 * InitInputImpl
318 *
319 * Inits input implementation
320 */
321 INIT_FUNCTION
322 NTSTATUS
323 NTAPI
324 InitInputImpl(VOID)
325 {
326 MasterTimer = ExAllocatePoolWithTag(NonPagedPool, sizeof(KTIMER), USERTAG_SYSTEM);
327 if (!MasterTimer)
328 {
329 ERR("Failed to allocate memory\n");
330 ASSERT(FALSE);
331 return STATUS_UNSUCCESSFUL;
332 }
333 KeInitializeTimer(MasterTimer);
334
335 return STATUS_SUCCESS;
336 }
337
338 BOOL FASTCALL
339 IntBlockInput(PTHREADINFO pti, BOOL BlockIt)
340 {
341 PTHREADINFO OldBlock;
342 ASSERT(pti);
343
344 if(!pti->rpdesk || ((pti->TIF_flags & TIF_INCLEANUP) && BlockIt))
345 {
346 /*
347 * Fail blocking if exiting the thread
348 */
349
350 return FALSE;
351 }
352
353 /*
354 * FIXME: Check access rights of the window station
355 * e.g. services running in the service window station cannot block input
356 */
357 if(!ThreadHasInputAccess(pti) ||
358 !IntIsActiveDesktop(pti->rpdesk))
359 {
360 EngSetLastError(ERROR_ACCESS_DENIED);
361 return FALSE;
362 }
363
364 ASSERT(pti->rpdesk);
365 OldBlock = pti->rpdesk->BlockInputThread;
366 if(OldBlock)
367 {
368 if(OldBlock != pti)
369 {
370 EngSetLastError(ERROR_ACCESS_DENIED);
371 return FALSE;
372 }
373 pti->rpdesk->BlockInputThread = (BlockIt ? pti : NULL);
374 return OldBlock == NULL;
375 }
376
377 pti->rpdesk->BlockInputThread = (BlockIt ? pti : NULL);
378 return OldBlock == NULL;
379 }
380
381 BOOL
382 APIENTRY
383 NtUserBlockInput(
384 BOOL BlockIt)
385 {
386 BOOL ret;
387
388 TRACE("Enter NtUserBlockInput\n");
389 UserEnterExclusive();
390
391 ret = IntBlockInput(PsGetCurrentThreadWin32Thread(), BlockIt);
392
393 UserLeave();
394 TRACE("Leave NtUserBlockInput, ret=%i\n", ret);
395
396 return ret;
397 }
398
399 PTHREADINFO FASTCALL
400 IsThreadAttach(PTHREADINFO ptiTo)
401 {
402 PATTACHINFO pai;
403
404 if (!gpai) return NULL;
405
406 pai = gpai;
407 do
408 {
409 if (pai->pti2 == ptiTo) break;
410 pai = pai->paiNext;
411 } while (pai);
412
413 if (!pai) return NULL;
414
415 // Return ptiFrom.
416 return pai->pti1;
417 }
418
419 NTSTATUS FASTCALL
420 UserAttachThreadInput(PTHREADINFO ptiFrom, PTHREADINFO ptiTo, BOOL fAttach)
421 {
422 MSG msg;
423 PATTACHINFO pai;
424
425 /* Can not be the same thread. */
426 if (ptiFrom == ptiTo) return STATUS_INVALID_PARAMETER;
427
428 /* Do not attach to system threads or between different desktops. */
429 if (ptiFrom->TIF_flags & TIF_DONTATTACHQUEUE ||
430 ptiTo->TIF_flags & TIF_DONTATTACHQUEUE ||
431 ptiFrom->rpdesk != ptiTo->rpdesk)
432 return STATUS_ACCESS_DENIED;
433
434 /* MSDN Note:
435 Keyboard and mouse events received by both threads are processed by the thread specified by the idAttachTo.
436 */
437
438 /* If Attach set, allocate and link. */
439 if (fAttach)
440 {
441 pai = ExAllocatePoolWithTag(PagedPool, sizeof(ATTACHINFO), USERTAG_ATTACHINFO);
442 if (!pai) return STATUS_NO_MEMORY;
443
444 pai->paiNext = gpai;
445 pai->pti1 = ptiFrom;
446 pai->pti2 = ptiTo;
447 gpai = pai;
448 paiCount++;
449 ERR("Attach Allocated! ptiFrom 0x%p ptiTo 0x%p paiCount %d\n",ptiFrom,ptiTo,paiCount);
450
451 if (ptiTo->MessageQueue == ptiFrom->MessageQueue)
452 {
453 ERR("Attach Threads are already associated!\n");
454 }
455
456 ptiTo->MessageQueue->iCursorLevel -= ptiFrom->iCursorLevel;
457
458 /* Keep the original queue in pqAttach (ie do not trash it in a second attachment) */
459 if (ptiFrom->pqAttach == NULL)
460 ptiFrom->pqAttach = ptiFrom->MessageQueue;
461 ptiFrom->MessageQueue = ptiTo->MessageQueue;
462
463 ptiFrom->MessageQueue->cThreads++;
464 ERR("ptiTo S Share count %d\n", ptiFrom->MessageQueue->cThreads);
465
466 // FIXME: conditions?
467 if (ptiFrom->pqAttach == gpqForeground)
468 {
469 ERR("ptiFrom is Foreground\n");
470 ptiFrom->MessageQueue->spwndActive = ptiFrom->pqAttach->spwndActive;
471 ptiFrom->MessageQueue->spwndFocus = ptiFrom->pqAttach->spwndFocus;
472 ptiFrom->MessageQueue->CursorObject = ptiFrom->pqAttach->CursorObject;
473 ptiFrom->MessageQueue->spwndCapture = ptiFrom->pqAttach->spwndCapture;
474 ptiFrom->MessageQueue->QF_flags ^= ((ptiFrom->MessageQueue->QF_flags ^ ptiFrom->pqAttach->QF_flags) & QF_CAPTURELOCKED);
475 ptiFrom->MessageQueue->CaretInfo = ptiFrom->pqAttach->CaretInfo;
476 }
477 else
478 {
479 ERR("ptiFrom NOT Foreground\n");
480 }
481 if (ptiTo->MessageQueue == gpqForeground)
482 {
483 ERR("ptiTo is Foreground\n");
484 }
485 else
486 {
487 ERR("ptiTo NOT Foreground\n");
488 }
489 }
490 else /* If clear, unlink and free it. */
491 {
492 BOOL Hit = FALSE;
493 PATTACHINFO *ppai;
494
495 if (!gpai) return STATUS_INVALID_PARAMETER;
496
497 /* Search list and free if found or return false. */
498 ppai = &gpai;
499 while (*ppai != NULL)
500 {
501 if ( (*ppai)->pti2 == ptiTo && (*ppai)->pti1 == ptiFrom )
502 {
503 pai = *ppai;
504 /* Remove it from the list */
505 *ppai = (*ppai)->paiNext;
506 ExFreePoolWithTag(pai, USERTAG_ATTACHINFO);
507 paiCount--;
508 Hit = TRUE;
509 break;
510 }
511 ppai = &((*ppai)->paiNext);
512 }
513
514 if (!Hit) return STATUS_INVALID_PARAMETER;
515
516 ASSERT(ptiFrom->pqAttach);
517
518 ERR("Attach Free! ptiFrom 0x%p ptiTo 0x%p paiCount %d\n",ptiFrom,ptiTo,paiCount);
519
520 /* Search list and check if the thread is attached one more time */
521 pai = gpai;
522 while(pai)
523 {
524 /* If the thread is attached again , we are done */
525 if (pai->pti1 == ptiFrom)
526 {
527 ptiFrom->MessageQueue->cThreads--;
528 ERR("ptiTo L Share count %d\n", ptiFrom->MessageQueue->cThreads);
529 /* Use the message queue of the last attachment */
530 ptiFrom->MessageQueue = pai->pti2->MessageQueue;
531 ptiFrom->MessageQueue->CursorObject = NULL;
532 ptiFrom->MessageQueue->spwndActive = NULL;
533 ptiFrom->MessageQueue->spwndFocus = NULL;
534 ptiFrom->MessageQueue->spwndCapture = NULL;
535 return STATUS_SUCCESS;
536 }
537 pai = pai->paiNext;
538 }
539
540 ptiFrom->MessageQueue->cThreads--;
541 ERR("ptiTo E Share count %d\n", ptiFrom->MessageQueue->cThreads);
542 ptiFrom->MessageQueue = ptiFrom->pqAttach;
543 // FIXME: conditions?
544 ptiFrom->MessageQueue->CursorObject = NULL;
545 ptiFrom->MessageQueue->spwndActive = NULL;
546 ptiFrom->MessageQueue->spwndFocus = NULL;
547 ptiFrom->MessageQueue->spwndCapture = NULL;
548 ptiFrom->pqAttach = NULL;
549 ptiTo->MessageQueue->iCursorLevel -= ptiFrom->iCursorLevel;
550 }
551 /* Note that key state, which can be ascertained by calls to the GetKeyState
552 or GetKeyboardState function, is reset after a call to AttachThreadInput.
553 ATM which one?
554 */
555 RtlCopyMemory(ptiTo->MessageQueue->afKeyState, gafAsyncKeyState, sizeof(gafAsyncKeyState));
556
557 /* Generate mouse move message */
558 msg.message = WM_MOUSEMOVE;
559 msg.wParam = UserGetMouseButtonsState();
560 msg.lParam = MAKELPARAM(gpsi->ptCursor.x, gpsi->ptCursor.y);
561 msg.pt = gpsi->ptCursor;
562 co_MsqInsertMouseMessage(&msg, 0, 0, TRUE);
563
564 return STATUS_SUCCESS;
565 }
566
567 /*
568 * NtUserSendInput
569 *
570 * Generates input events from software
571 */
572 UINT
573 APIENTRY
574 NtUserSendInput(
575 UINT nInputs,
576 LPINPUT pInput,
577 INT cbSize)
578 {
579 PTHREADINFO pti;
580 UINT uRet = 0;
581
582 TRACE("Enter NtUserSendInput\n");
583 UserEnterExclusive();
584
585 pti = PsGetCurrentThreadWin32Thread();
586 ASSERT(pti);
587
588 if (!pti->rpdesk)
589 {
590 goto cleanup;
591 }
592
593 if (!nInputs || !pInput || cbSize != sizeof(INPUT))
594 {
595 EngSetLastError(ERROR_INVALID_PARAMETER);
596 goto cleanup;
597 }
598
599 /*
600 * FIXME: Check access rights of the window station
601 * e.g. services running in the service window station cannot block input
602 */
603 if (!ThreadHasInputAccess(pti) ||
604 !IntIsActiveDesktop(pti->rpdesk))
605 {
606 EngSetLastError(ERROR_ACCESS_DENIED);
607 goto cleanup;
608 }
609
610 while (nInputs--)
611 {
612 INPUT SafeInput;
613 NTSTATUS Status;
614
615 Status = MmCopyFromCaller(&SafeInput, pInput++, sizeof(INPUT));
616 if (!NT_SUCCESS(Status))
617 {
618 SetLastNtError(Status);
619 goto cleanup;
620 }
621
622 switch (SafeInput.type)
623 {
624 case INPUT_MOUSE:
625 if (UserSendMouseInput(&SafeInput.mi, TRUE))
626 uRet++;
627 break;
628 case INPUT_KEYBOARD:
629 if (UserSendKeyboardInput(&SafeInput.ki, TRUE))
630 uRet++;
631 break;
632 case INPUT_HARDWARE:
633 FIXME("INPUT_HARDWARE not supported!");
634 break;
635 default:
636 ERR("SendInput(): Invalid input type: 0x%x\n", SafeInput.type);
637 break;
638 }
639 }
640
641 cleanup:
642 TRACE("Leave NtUserSendInput, ret=%i\n", uRet);
643 UserLeave();
644 return uRet;
645 }
646
647 /* EOF */