[WIN32K]
[reactos.git] / reactos / 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 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 0,
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()
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[3], pSignaledObject = NULL;
136 ULONG cWaitObjects = 0, cMaxWaitObjects = 1;
137 MOUSE_INPUT_DATA MouseInput;
138 KEYBOARD_INPUT_DATA KeyInput;
139
140 ByteOffset.QuadPart = (LONGLONG)0;
141 //WaitTimeout.QuadPart = (LONGLONG)(-10000000);
142
143 ptiRawInput = GetW32ThreadInfo();
144 ptiRawInput->TIF_flags |= TIF_SYSTEMTHREAD;
145 ptiRawInput->pClientInfo->dwTIFlags = ptiRawInput->TIF_flags;
146
147 TRACE("Raw Input Thread %p\n", ptiRawInput);
148
149 KeSetPriorityThread(&PsGetCurrentThread()->Tcb,
150 LOW_REALTIME_PRIORITY + 3);
151
152 UserEnterExclusive();
153 StartTheTimers();
154 UserLeave();
155
156 for(;;)
157 {
158 if (!ghMouseDevice)
159 {
160 /* Check if mouse device already exists */
161 Status = OpenInputDevice(&ghMouseDevice, &pMouDevice, L"\\Device\\PointerClass0" );
162 if (NT_SUCCESS(Status))
163 {
164 ++cMaxWaitObjects;
165 TRACE("Mouse connected!\n");
166 }
167 }
168 if (!ghKeyboardDevice)
169 {
170 /* Check if keyboard device already exists */
171 Status = OpenInputDevice(&ghKeyboardDevice, &pKbdDevice, L"\\Device\\KeyboardClass0");
172 if (NT_SUCCESS(Status))
173 {
174 ++cMaxWaitObjects;
175 TRACE("Keyboard connected!\n");
176 // Get and load keyboard attributes.
177 UserInitKeyboard(ghKeyboardDevice);
178 UserEnterExclusive();
179 // Register the Window hotkey.
180 UserRegisterHotKey(PWND_BOTTOM, IDHK_WINKEY, MOD_WIN, 0);
181 // Register the debug hotkeys.
182 StartDebugHotKeys();
183 UserLeave();
184 }
185 }
186
187 /* Reset WaitHandles array */
188 cWaitObjects = 0;
189 WaitObjects[cWaitObjects++] = MasterTimer;
190
191 if (ghMouseDevice)
192 {
193 /* Try to read from mouse if previous reading is not pending */
194 if (MouStatus != STATUS_PENDING)
195 {
196 MouStatus = ZwReadFile(ghMouseDevice,
197 NULL,
198 NULL,
199 NULL,
200 &MouIosb,
201 &MouseInput,
202 sizeof(MOUSE_INPUT_DATA),
203 &ByteOffset,
204 NULL);
205 }
206
207 if (MouStatus == STATUS_PENDING)
208 WaitObjects[cWaitObjects++] = &pMouDevice->Event;
209 }
210
211 if (ghKeyboardDevice)
212 {
213 /* Try to read from keyboard if previous reading is not pending */
214 if (KbdStatus != STATUS_PENDING)
215 {
216 KbdStatus = ZwReadFile(ghKeyboardDevice,
217 NULL,
218 NULL,
219 NULL,
220 &KbdIosb,
221 &KeyInput,
222 sizeof(KEYBOARD_INPUT_DATA),
223 &ByteOffset,
224 NULL);
225
226 }
227 if (KbdStatus == STATUS_PENDING)
228 WaitObjects[cWaitObjects++] = &pKbdDevice->Event;
229 }
230
231 /* If all objects are pending, wait for them */
232 if (cWaitObjects == cMaxWaitObjects)
233 {
234 Status = KeWaitForMultipleObjects(cWaitObjects,
235 WaitObjects,
236 WaitAny,
237 UserRequest,
238 KernelMode,
239 TRUE,
240 NULL,//&WaitTimeout,
241 NULL);
242
243 if ((Status >= STATUS_WAIT_0) &&
244 (Status < (STATUS_WAIT_0 + (LONG)cWaitObjects)))
245 {
246 /* Some device has finished reading */
247 pSignaledObject = WaitObjects[Status - STATUS_WAIT_0];
248
249 /* Check if it is mouse or keyboard and update status */
250 if (pSignaledObject == &pMouDevice->Event)
251 MouStatus = MouIosb.Status;
252 else if (pSignaledObject == &pKbdDevice->Event)
253 KbdStatus = KbdIosb.Status;
254 else if (pSignaledObject == MasterTimer)
255 {
256 ProcessTimers();
257 }
258 else ASSERT(FALSE);
259 }
260 }
261
262 /* Have we successed reading from mouse? */
263 if (NT_SUCCESS(MouStatus) && MouStatus != STATUS_PENDING)
264 {
265 TRACE("MouseEvent\n");
266
267 /* Set LastInputTick */
268 IntLastInputTick(TRUE);
269
270 /* Process data */
271 UserEnterExclusive();
272 UserProcessMouseInput(&MouseInput);
273 UserLeave();
274 }
275 else if (MouStatus != STATUS_PENDING)
276 ERR("Failed to read from mouse: %x.\n", MouStatus);
277
278 /* Have we successed reading from keyboard? */
279 if (NT_SUCCESS(KbdStatus) && KbdStatus != STATUS_PENDING)
280 {
281 TRACE("KeyboardEvent: %s %04x\n",
282 (KeyInput.Flags & KEY_BREAK) ? "up" : "down",
283 KeyInput.MakeCode);
284
285 /* Set LastInputTick */
286 IntLastInputTick(TRUE);
287
288 /* Process data */
289 UserEnterExclusive();
290 UserProcessKeyboardInput(&KeyInput);
291 UserLeave();
292 }
293 else if (KbdStatus != STATUS_PENDING)
294 ERR("Failed to read from keyboard: %x.\n", KbdStatus);
295 }
296 ERR("Raw Input Thread Exit!\n");
297 }
298
299 /*
300 * CreateSystemThreads
301 *
302 * Called form dedicated thread in CSRSS. RIT is started in context of this
303 * thread because it needs valid Win32 process with TEB initialized
304 */
305 DWORD NTAPI
306 CreateSystemThreads(UINT Type)
307 {
308 UserLeave();
309
310 switch (Type)
311 {
312 case 0: RawInputThreadMain(); break;
313 case 1: DesktopThreadMain(); break;
314 default: ERR("Wrong type: %x\n", Type);
315 }
316
317 UserEnterShared();
318
319 return 0;
320 }
321
322 /*
323 * InitInputImpl
324 *
325 * Inits input implementation
326 */
327 INIT_FUNCTION
328 NTSTATUS
329 NTAPI
330 InitInputImpl(VOID)
331 {
332 MasterTimer = ExAllocatePoolWithTag(NonPagedPool, sizeof(KTIMER), USERTAG_SYSTEM);
333 if (!MasterTimer)
334 {
335 ERR("Failed to allocate memory\n");
336 ASSERT(FALSE);
337 return STATUS_UNSUCCESSFUL;
338 }
339 KeInitializeTimer(MasterTimer);
340
341 return STATUS_SUCCESS;
342 }
343
344 BOOL FASTCALL
345 IntBlockInput(PTHREADINFO pti, BOOL BlockIt)
346 {
347 PTHREADINFO OldBlock;
348 ASSERT(pti);
349
350 if(!pti->rpdesk || ((pti->TIF_flags & TIF_INCLEANUP) && BlockIt))
351 {
352 /*
353 * Fail blocking if exiting the thread
354 */
355
356 return FALSE;
357 }
358
359 /*
360 * FIXME: Check access rights of the window station
361 * e.g. services running in the service window station cannot block input
362 */
363 if(!ThreadHasInputAccess(pti) ||
364 !IntIsActiveDesktop(pti->rpdesk))
365 {
366 EngSetLastError(ERROR_ACCESS_DENIED);
367 return FALSE;
368 }
369
370 ASSERT(pti->rpdesk);
371 OldBlock = pti->rpdesk->BlockInputThread;
372 if(OldBlock)
373 {
374 if(OldBlock != pti)
375 {
376 EngSetLastError(ERROR_ACCESS_DENIED);
377 return FALSE;
378 }
379 pti->rpdesk->BlockInputThread = (BlockIt ? pti : NULL);
380 return OldBlock == NULL;
381 }
382
383 pti->rpdesk->BlockInputThread = (BlockIt ? pti : NULL);
384 return OldBlock == NULL;
385 }
386
387 BOOL
388 APIENTRY
389 NtUserBlockInput(
390 BOOL BlockIt)
391 {
392 BOOL ret;
393
394 TRACE("Enter NtUserBlockInput\n");
395 UserEnterExclusive();
396
397 ret = IntBlockInput(PsGetCurrentThreadWin32Thread(), BlockIt);
398
399 UserLeave();
400 TRACE("Leave NtUserBlockInput, ret=%i\n", ret);
401
402 return ret;
403 }
404
405 BOOL
406 FASTCALL
407 IsRemoveAttachThread(PTHREADINFO pti)
408 {
409 NTSTATUS Status;
410 PATTACHINFO pai;
411 BOOL Ret = TRUE;
412 PTHREADINFO ptiFrom = NULL, ptiTo = NULL;
413
414 do
415 {
416 if (!gpai) return TRUE;
417
418 pai = gpai; // Bottom of the list.
419
420 do
421 {
422 if (pai->pti2 == pti)
423 {
424 ptiFrom = pai->pti1;
425 ptiTo = pti;
426 break;
427 }
428 if (pai->pti1 == pti)
429 {
430 ptiFrom = pti;
431 ptiTo = pai->pti2;
432 break;
433 }
434 pai = pai->paiNext;
435
436 } while (pai);
437
438 if (!pai && !ptiFrom && !ptiTo) break;
439
440 Status = UserAttachThreadInput(ptiFrom, ptiTo, FALSE);
441 if (!NT_SUCCESS(Status)) Ret = FALSE;
442
443 } while (Ret);
444
445 return Ret;
446 }
447
448 NTSTATUS FASTCALL
449 UserAttachThreadInput(PTHREADINFO ptiFrom, PTHREADINFO ptiTo, BOOL fAttach)
450 {
451 MSG msg;
452 PATTACHINFO pai;
453
454 /* Can not be the same thread. */
455 if (ptiFrom == ptiTo) return STATUS_INVALID_PARAMETER;
456
457 /* Do not attach to system threads or between different desktops. */
458 if (ptiFrom->TIF_flags & TIF_DONTATTACHQUEUE ||
459 ptiTo->TIF_flags & TIF_DONTATTACHQUEUE ||
460 ptiFrom->rpdesk != ptiTo->rpdesk)
461 return STATUS_ACCESS_DENIED;
462
463 /* MSDN Note:
464 Keyboard and mouse events received by both threads are processed by the thread specified by the idAttachTo.
465 */
466
467 /* If Attach set, allocate and link. */
468 if (fAttach)
469 {
470 pai = ExAllocatePoolWithTag(PagedPool, sizeof(ATTACHINFO), USERTAG_ATTACHINFO);
471 if (!pai) return STATUS_NO_MEMORY;
472
473 pai->paiNext = gpai;
474 pai->pti1 = ptiFrom;
475 pai->pti2 = ptiTo;
476 gpai = pai;
477 paiCount++;
478 ERR("Attach Allocated! ptiFrom 0x%p ptiTo 0x%p paiCount %d\n",ptiFrom,ptiTo,paiCount);
479
480 if (ptiTo->MessageQueue != ptiFrom->MessageQueue)
481 {
482
483 ptiTo->MessageQueue->iCursorLevel -= ptiFrom->iCursorLevel;
484
485 // FIXME: conditions?
486 if (ptiTo->MessageQueue == gpqForeground)
487 {
488 ERR("ptiTo is Foreground\n");
489 }
490 else
491 {
492 ERR("ptiTo NOT Foreground\n");
493 }
494
495 if (ptiFrom->MessageQueue == gpqForeground)
496 {
497 ERR("ptiFrom is Foreground\n");
498 ptiTo->MessageQueue->spwndActive = ptiFrom->MessageQueue->spwndActive;
499 ptiTo->MessageQueue->spwndFocus = ptiFrom->MessageQueue->spwndFocus;
500 ptiTo->MessageQueue->CursorObject = ptiFrom->MessageQueue->CursorObject;
501 ptiTo->MessageQueue->spwndCapture = ptiFrom->MessageQueue->spwndCapture;
502 ptiTo->MessageQueue->QF_flags ^= ((ptiTo->MessageQueue->QF_flags ^ ptiFrom->MessageQueue->QF_flags) & QF_CAPTURELOCKED);
503 ptiTo->MessageQueue->CaretInfo = ptiFrom->MessageQueue->CaretInfo;
504 IntSetFocusMessageQueue(NULL);
505 IntSetFocusMessageQueue(ptiTo->MessageQueue);
506 gptiForeground = ptiTo;
507 }
508 else
509 {
510 ERR("ptiFrom NOT Foreground\n");
511 }
512
513 MsqDestroyMessageQueue(ptiFrom);
514
515 ptiFrom->MessageQueue = ptiTo->MessageQueue;
516
517 ptiFrom->MessageQueue->cThreads++;
518 ERR("ptiTo S Share count %d\n", ptiFrom->MessageQueue->cThreads);
519
520 IntReferenceMessageQueue(ptiTo->MessageQueue);
521 }
522 else
523 {
524 ERR("Attach Threads are already associated!\n");
525 }
526 }
527 else /* If clear, unlink and free it. */
528 {
529 BOOL Hit = FALSE;
530 PATTACHINFO *ppai;
531
532 if (!gpai) return STATUS_INVALID_PARAMETER;
533
534 /* Search list and free if found or return false. */
535 ppai = &gpai;
536 while (*ppai != NULL)
537 {
538 if ( (*ppai)->pti2 == ptiTo && (*ppai)->pti1 == ptiFrom )
539 {
540 pai = *ppai;
541 /* Remove it from the list */
542 *ppai = (*ppai)->paiNext;
543 ExFreePoolWithTag(pai, USERTAG_ATTACHINFO);
544 paiCount--;
545 Hit = TRUE;
546 break;
547 }
548 ppai = &((*ppai)->paiNext);
549 }
550
551 if (!Hit) return STATUS_INVALID_PARAMETER;
552
553 ERR("Attach Free! ptiFrom 0x%p ptiTo 0x%p paiCount %d\n",ptiFrom,ptiTo,paiCount);
554
555 if (ptiTo->MessageQueue == ptiFrom->MessageQueue)
556 {
557 if (gptiForeground == ptiFrom)
558 {
559 ERR("ptiTo is now pti FG.\n");
560 // MessageQueue foreground is set so switch threads.
561 gptiForeground = ptiTo;
562 }
563 ptiTo->MessageQueue->cThreads--;
564 ERR("ptiTo E Share count %d\n", ptiTo->MessageQueue->cThreads);
565 ASSERT(ptiTo->MessageQueue->cThreads >= 1);
566
567 IntDereferenceMessageQueue(ptiTo->MessageQueue);
568
569 ptiFrom->MessageQueue = MsqCreateMessageQueue(ptiFrom);
570
571 ptiTo->MessageQueue->iCursorLevel -= ptiFrom->iCursorLevel;
572 }
573 else
574 {
575 ERR("Detaching Threads are not associated!\n");
576 }
577 }
578 /* Note that key state, which can be ascertained by calls to the GetKeyState
579 or GetKeyboardState function, is reset after a call to AttachThreadInput.
580 ATM which one?
581 */
582 RtlCopyMemory(ptiTo->MessageQueue->afKeyState, gafAsyncKeyState, sizeof(gafAsyncKeyState));
583
584 /* Generate mouse move message */
585 msg.message = WM_MOUSEMOVE;
586 msg.wParam = UserGetMouseButtonsState();
587 msg.lParam = MAKELPARAM(gpsi->ptCursor.x, gpsi->ptCursor.y);
588 msg.pt = gpsi->ptCursor;
589 co_MsqInsertMouseMessage(&msg, 0, 0, TRUE);
590
591 return STATUS_SUCCESS;
592 }
593
594 BOOL
595 APIENTRY
596 NtUserAttachThreadInput(
597 IN DWORD idAttach,
598 IN DWORD idAttachTo,
599 IN BOOL fAttach)
600 {
601 NTSTATUS Status;
602 PTHREADINFO pti, ptiTo;
603 BOOL Ret = FALSE;
604
605 UserEnterExclusive();
606 ERR("Enter NtUserAttachThreadInput %s\n",(fAttach ? "TRUE" : "FALSE" ));
607
608 pti = IntTID2PTI((HANDLE)idAttach);
609 ptiTo = IntTID2PTI((HANDLE)idAttachTo);
610
611 if ( !pti || !ptiTo )
612 {
613 ERR("AttachThreadInput pti or ptiTo NULL.\n");
614 EngSetLastError(ERROR_INVALID_PARAMETER);
615 goto Exit;
616 }
617
618 Status = UserAttachThreadInput( pti, ptiTo, fAttach);
619 if (!NT_SUCCESS(Status))
620 {
621 ERR("AttachThreadInput Error Status 0x%x. \n",Status);
622 EngSetLastError(RtlNtStatusToDosError(Status));
623 }
624 else Ret = TRUE;
625
626 Exit:
627 ERR("Leave NtUserAttachThreadInput, ret=%d\n",Ret);
628 UserLeave();
629 return Ret;
630 }
631
632 /*
633 * NtUserSendInput
634 *
635 * Generates input events from software
636 */
637 UINT
638 APIENTRY
639 NtUserSendInput(
640 UINT nInputs,
641 LPINPUT pInput,
642 INT cbSize)
643 {
644 PTHREADINFO pti;
645 UINT uRet = 0;
646
647 TRACE("Enter NtUserSendInput\n");
648 UserEnterExclusive();
649
650 pti = PsGetCurrentThreadWin32Thread();
651 ASSERT(pti);
652
653 if (!pti->rpdesk)
654 {
655 goto cleanup;
656 }
657
658 if (!nInputs || !pInput || cbSize != sizeof(INPUT))
659 {
660 EngSetLastError(ERROR_INVALID_PARAMETER);
661 goto cleanup;
662 }
663
664 /*
665 * FIXME: Check access rights of the window station
666 * e.g. services running in the service window station cannot block input
667 */
668 if (!ThreadHasInputAccess(pti) ||
669 !IntIsActiveDesktop(pti->rpdesk))
670 {
671 EngSetLastError(ERROR_ACCESS_DENIED);
672 goto cleanup;
673 }
674
675 while (nInputs--)
676 {
677 INPUT SafeInput;
678 NTSTATUS Status;
679
680 Status = MmCopyFromCaller(&SafeInput, pInput++, sizeof(INPUT));
681 if (!NT_SUCCESS(Status))
682 {
683 SetLastNtError(Status);
684 goto cleanup;
685 }
686
687 switch (SafeInput.type)
688 {
689 case INPUT_MOUSE:
690 if (UserSendMouseInput(&SafeInput.mi, TRUE))
691 uRet++;
692 break;
693 case INPUT_KEYBOARD:
694 if (UserSendKeyboardInput(&SafeInput.ki, TRUE))
695 uRet++;
696 break;
697 case INPUT_HARDWARE:
698 FIXME("INPUT_HARDWARE not supported!");
699 break;
700 default:
701 ERR("SendInput(): Invalid input type: 0x%x\n", SafeInput.type);
702 break;
703 }
704 }
705
706 cleanup:
707 TRACE("Leave NtUserSendInput, ret=%u\n", uRet);
708 UserLeave();
709 return uRet;
710 }
711
712 /* EOF */