e781aca29db0ced524f5486aa5c88dae173131b4
[reactos.git] / reactos / subsystems / win32 / win32k / ntuser / timer.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * PURPOSE: Window timers messages
5 * FILE: subsystems/win32/win32k/ntuser/timer.c
6 * PROGRAMER: Gunnar
7 * Thomas Weidenmueller (w3seek@users.sourceforge.net)
8 * Michael Martin (michael.martin@reactos.org)
9 */
10
11 #include <win32k.h>
12 DBG_DEFAULT_CHANNEL(UserTimer);
13
14 /* GLOBALS *******************************************************************/
15
16 static LIST_ENTRY TimersListHead;
17 static LONG TimeLast = 0;
18
19 #define MAX_ELAPSE_TIME 0x7FFFFFFF
20
21 /* Windows 2000 has room for 32768 window-less timers */
22 #define NUM_WINDOW_LESS_TIMERS 32768
23
24 static FAST_MUTEX Mutex;
25 static RTL_BITMAP WindowLessTimersBitMap;
26 static PVOID WindowLessTimersBitMapBuffer;
27 static ULONG HintIndex = 1;
28
29 ERESOURCE TimerLock;
30
31 #define IntLockWindowlessTimerBitmap() \
32 ExEnterCriticalRegionAndAcquireFastMutexUnsafe(&Mutex)
33
34 #define IntUnlockWindowlessTimerBitmap() \
35 ExReleaseFastMutexUnsafeAndLeaveCriticalRegion(&Mutex)
36
37 #define TimerEnterExclusive() \
38 { \
39 KeEnterCriticalRegion(); \
40 ExAcquireResourceExclusiveLite(&TimerLock, TRUE); \
41 }
42
43 #define TimerLeave() \
44 { \
45 ExReleaseResourceLite(&TimerLock); \
46 KeLeaveCriticalRegion(); \
47 }
48
49
50 /* FUNCTIONS *****************************************************************/
51 static
52 PTIMER
53 FASTCALL
54 CreateTimer(VOID)
55 {
56 HANDLE Handle;
57 PTIMER Ret = NULL;
58
59 Ret = UserCreateObject(gHandleTable, NULL, &Handle, otTimer, sizeof(TIMER));
60 if (Ret)
61 {
62 Ret->head.h = Handle;
63 InsertTailList(&TimersListHead, &Ret->ptmrList);
64 }
65
66 return Ret;
67 }
68
69 static
70 BOOL
71 FASTCALL
72 RemoveTimer(PTIMER pTmr)
73 {
74 BOOL Ret = FALSE;
75 if (pTmr)
76 {
77 /* Set the flag, it will be removed when ready */
78 RemoveEntryList(&pTmr->ptmrList);
79 if ((pTmr->pWnd == NULL) && (!(pTmr->flags & TMRF_SYSTEM))) // System timers are reusable.
80 {
81 UINT_PTR IDEvent;
82
83 IDEvent = NUM_WINDOW_LESS_TIMERS - pTmr->nID;
84 IntLockWindowlessTimerBitmap();
85 RtlClearBit(&WindowLessTimersBitMap, IDEvent);
86 IntUnlockWindowlessTimerBitmap();
87 }
88 UserDereferenceObject(pTmr);
89 Ret = UserDeleteObject( UserHMGetHandle(pTmr), otTimer);
90 }
91 if (!Ret) ERR("Warning: Unable to delete timer\n");
92
93 return Ret;
94 }
95
96 PTIMER
97 FASTCALL
98 FindTimer(PWND Window,
99 UINT_PTR nID,
100 UINT flags)
101 {
102 PLIST_ENTRY pLE;
103 PTIMER pTmr, RetTmr = NULL;
104
105 TimerEnterExclusive();
106 pLE = TimersListHead.Flink;
107 while (pLE != &TimersListHead)
108 {
109 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
110
111 if ( pTmr->nID == nID &&
112 pTmr->pWnd == Window &&
113 (pTmr->flags & (TMRF_SYSTEM|TMRF_RIT)) == (flags & (TMRF_SYSTEM|TMRF_RIT)))
114 {
115 RetTmr = pTmr;
116 break;
117 }
118
119 pLE = pLE->Flink;
120 }
121 TimerLeave();
122
123 return RetTmr;
124 }
125
126 PTIMER
127 FASTCALL
128 FindSystemTimer(PMSG pMsg)
129 {
130 PLIST_ENTRY pLE;
131 PTIMER pTmr = NULL;
132
133 TimerEnterExclusive();
134 pLE = TimersListHead.Flink;
135 while (pLE != &TimersListHead)
136 {
137 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
138
139 if ( pMsg->lParam == (LPARAM)pTmr->pfn &&
140 (pTmr->flags & TMRF_SYSTEM) )
141 break;
142
143 pLE = pLE->Flink;
144 }
145 TimerLeave();
146
147 return pTmr;
148 }
149
150 BOOL
151 FASTCALL
152 ValidateTimerCallback(PTHREADINFO pti,
153 LPARAM lParam)
154 {
155 PLIST_ENTRY pLE;
156 BOOL Ret = FALSE;
157 PTIMER pTmr;
158
159 TimerEnterExclusive();
160 pLE = TimersListHead.Flink;
161 while (pLE != &TimersListHead)
162 {
163 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
164 if ( (lParam == (LPARAM)pTmr->pfn) &&
165 !(pTmr->flags & (TMRF_SYSTEM|TMRF_RIT)) &&
166 (pTmr->pti->ppi == pti->ppi) )
167 {
168 Ret = TRUE;
169 break;
170 }
171 pLE = pLE->Flink;
172 }
173 TimerLeave();
174
175 return Ret;
176 }
177
178 UINT_PTR FASTCALL
179 IntSetTimer( PWND Window,
180 UINT_PTR IDEvent,
181 UINT Elapse,
182 TIMERPROC TimerFunc,
183 INT Type)
184 {
185 PTIMER pTmr;
186 UINT Ret = IDEvent;
187 LARGE_INTEGER DueTime;
188 DueTime.QuadPart = (LONGLONG)(-97656); // 1024hz .9765625 ms set to 10.0 ms
189
190 #if 0
191 /* Windows NT/2k/XP behaviour */
192 if (Elapse > MAX_ELAPSE_TIME)
193 {
194 TRACE("Adjusting uElapse\n");
195 Elapse = 1;
196 }
197 #else
198 /* Windows XP SP2 and Windows Server 2003 behaviour */
199 if (Elapse > MAX_ELAPSE_TIME)
200 {
201 TRACE("Adjusting uElapse\n");
202 Elapse = MAX_ELAPSE_TIME;
203 }
204 #endif
205
206 /* Windows 2k/XP and Windows Server 2003 SP1 behaviour */
207 if (Elapse < 10)
208 {
209 TRACE("Adjusting uElapse\n");
210 Elapse = 10; // 1024hz .9765625 ms, set to 10.0 ms (+/-)1 ms
211 }
212
213 /* Passing an IDEvent of 0 and the SetTimer returns 1.
214 It will create the timer with an ID of 0 */
215 if ((Window) && (IDEvent == 0))
216 Ret = 1;
217
218 pTmr = FindTimer(Window, IDEvent, Type);
219
220 if ((!pTmr) && (Window == NULL) && (!(Type & TMRF_SYSTEM)))
221 {
222 IntLockWindowlessTimerBitmap();
223
224 IDEvent = RtlFindClearBitsAndSet(&WindowLessTimersBitMap, 1, HintIndex);
225
226 if (IDEvent == (UINT_PTR) -1)
227 {
228 IntUnlockWindowlessTimerBitmap();
229 ERR("Unable to find a free window-less timer id\n");
230 EngSetLastError(ERROR_NO_SYSTEM_RESOURCES);
231 ASSERT(FALSE);
232 return 0;
233 }
234
235 IDEvent = NUM_WINDOW_LESS_TIMERS - IDEvent;
236 Ret = IDEvent;
237
238 IntUnlockWindowlessTimerBitmap();
239 }
240
241 if (!pTmr)
242 {
243 pTmr = CreateTimer();
244 if (!pTmr) return 0;
245
246 if (Window && (Type & TMRF_TIFROMWND))
247 pTmr->pti = Window->head.pti->pEThread->Tcb.Win32Thread;
248 else
249 {
250 if (Type & TMRF_RIT)
251 pTmr->pti = ptiRawInput;
252 else
253 pTmr->pti = PsGetCurrentThreadWin32Thread();
254 }
255
256 pTmr->pWnd = Window;
257 pTmr->cmsCountdown = Elapse;
258 pTmr->cmsRate = Elapse;
259 pTmr->pfn = TimerFunc;
260 pTmr->nID = IDEvent;
261 pTmr->flags = Type|TMRF_INIT;
262 }
263 else
264 {
265 pTmr->cmsCountdown = Elapse;
266 pTmr->cmsRate = Elapse;
267 }
268
269 ASSERT(MasterTimer != NULL);
270 // Start the timer thread!
271 if (TimersListHead.Flink == TimersListHead.Blink) // There is only one timer
272 KeSetTimer(MasterTimer, DueTime, NULL);
273
274 return Ret;
275 }
276
277 //
278 // Process win32k system timers.
279 //
280 VOID
281 CALLBACK
282 SystemTimerProc(HWND hwnd,
283 UINT uMsg,
284 UINT_PTR idEvent,
285 DWORD dwTime)
286 {
287 PDESKTOP pDesk;
288 PWND pWnd = NULL;
289
290 if (hwnd)
291 {
292 pWnd = UserGetWindowObject(hwnd);
293 if (!pWnd)
294 {
295 ERR( "System Timer Proc has invalid window handle! 0x%x Id: %d\n", hwnd, idEvent);
296 return;
297 }
298 }
299 else
300 {
301 TRACE( "Windowless Timer Running!\n" );
302 return;
303 }
304
305 switch (idEvent)
306 {
307 /*
308 Used in NtUserTrackMouseEvent.
309 */
310 case ID_EVENT_SYSTIMER_MOUSEHOVER:
311 {
312 POINT Point;
313 UINT Msg;
314 WPARAM wParam;
315
316 pDesk = pWnd->head.rpdesk;
317 if ( pDesk->dwDTFlags & DF_TME_HOVER &&
318 pWnd == pDesk->spwndTrack )
319 {
320 Point = pWnd->head.pti->MessageQueue->MouseMoveMsg.pt;
321 if ( RECTL_bPointInRect(&pDesk->rcMouseHover, Point.x, Point.y) )
322 {
323 if (pDesk->htEx == HTCLIENT) // In a client area.
324 {
325 wParam = UserGetMouseButtonsState();
326 Msg = WM_MOUSEHOVER;
327
328 if (pWnd->ExStyle & WS_EX_LAYOUTRTL)
329 {
330 Point.x = pWnd->rcClient.right - Point.x - 1;
331 }
332 else
333 Point.x -= pWnd->rcClient.left;
334 Point.y -= pWnd->rcClient.top;
335 }
336 else
337 {
338 wParam = pDesk->htEx; // Need to support all HTXYZ hits.
339 Msg = WM_NCMOUSEHOVER;
340 }
341 TRACE("Generating WM_NCMOUSEHOVER\n");
342 UserPostMessage(hwnd, Msg, wParam, MAKELPARAM(Point.x, Point.y));
343 pDesk->dwDTFlags &= ~DF_TME_HOVER;
344 break; // Kill this timer.
345 }
346 }
347 }
348 return; // Not this window so just return.
349
350 default:
351 ERR( "System Timer Proc invalid id %d!\n", idEvent );
352 break;
353 }
354 IntKillTimer(pWnd, idEvent, TRUE);
355 }
356
357 VOID
358 FASTCALL
359 StartTheTimers(VOID)
360 {
361 // Need to start gdi syncro timers then start timer with Hang App proc
362 // that calles Idle process so the screen savers will know to run......
363 IntSetTimer(NULL, 0, 1000, HungAppSysTimerProc, TMRF_RIT);
364 // Test Timers
365 // IntSetTimer(NULL, 0, 1000, SystemTimerProc, TMRF_RIT);
366 }
367
368 UINT_PTR
369 FASTCALL
370 SystemTimerSet( PWND Window,
371 UINT_PTR nIDEvent,
372 UINT uElapse,
373 TIMERPROC lpTimerFunc)
374 {
375 if (Window && Window->head.pti->pEThread->ThreadsProcess != PsGetCurrentProcess())
376 {
377 EngSetLastError(ERROR_ACCESS_DENIED);
378 TRACE("SysemTimerSet: Access Denied!\n");
379 return 0;
380 }
381 return IntSetTimer( Window, nIDEvent, uElapse, lpTimerFunc, TMRF_SYSTEM);
382 }
383
384 BOOL
385 FASTCALL
386 PostTimerMessages(PWND Window)
387 {
388 PLIST_ENTRY pLE;
389 PUSER_MESSAGE_QUEUE ThreadQueue;
390 MSG Msg;
391 PTHREADINFO pti;
392 BOOL Hit = FALSE;
393 PTIMER pTmr;
394
395 pti = PsGetCurrentThreadWin32Thread();
396 ThreadQueue = pti->MessageQueue;
397
398 TimerEnterExclusive();
399 pLE = TimersListHead.Flink;
400 while(pLE != &TimersListHead)
401 {
402 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
403 if ( (pTmr->flags & TMRF_READY) &&
404 (pTmr->pti == pti) &&
405 ((pTmr->pWnd == Window) || (Window == NULL)) )
406 {
407 Msg.hwnd = (pTmr->pWnd) ? pTmr->pWnd->head.h : 0;
408 Msg.message = (pTmr->flags & TMRF_SYSTEM) ? WM_SYSTIMER : WM_TIMER;
409 Msg.wParam = (WPARAM) pTmr->nID;
410 Msg.lParam = (LPARAM) pTmr->pfn;
411
412 MsqPostMessage(ThreadQueue, &Msg, FALSE, QS_TIMER);
413 pTmr->flags &= ~TMRF_READY;
414 pti->cTimersReady++;
415 Hit = TRUE;
416 // Now move this entry to the end of the list so it will not be
417 // called again in the next msg loop.
418 if (pLE != &TimersListHead)
419 {
420 RemoveEntryList(&pTmr->ptmrList);
421 InsertTailList(&TimersListHead, &pTmr->ptmrList);
422 }
423 break;
424 }
425
426 pLE = pLE->Flink;
427 }
428
429 TimerLeave();
430
431 return Hit;
432 }
433
434 VOID
435 FASTCALL
436 ProcessTimers(VOID)
437 {
438 LARGE_INTEGER TickCount, DueTime;
439 LONG Time;
440 PLIST_ENTRY pLE;
441 PTIMER pTmr;
442 LONG TimerCount = 0;
443
444 TimerEnterExclusive();
445 pLE = TimersListHead.Flink;
446 KeQueryTickCount(&TickCount);
447 Time = MsqCalculateMessageTime(&TickCount);
448
449 DueTime.QuadPart = (LONGLONG)(-97656); // 1024hz .9765625 ms set to 10.0 ms
450
451 while(pLE != &TimersListHead)
452 {
453 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
454 TimerCount++;
455 if (pTmr->flags & TMRF_WAITING)
456 {
457 pLE = pTmr->ptmrList.Flink;
458 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
459 continue;
460 }
461
462 if (pTmr->flags & TMRF_INIT)
463 {
464 pTmr->flags &= ~TMRF_INIT; // Skip this run.
465 }
466 else
467 {
468 if (pTmr->cmsCountdown < 0)
469 {
470 ASSERT(pTmr->pti);
471 if ((!(pTmr->flags & TMRF_READY)) && (!(pTmr->pti->TIF_flags & TIF_INCLEANUP)))
472 {
473 if (pTmr->flags & TMRF_ONESHOT)
474 pTmr->flags |= TMRF_WAITING;
475
476 if (pTmr->flags & TMRF_RIT)
477 {
478 // Hard coded call here, inside raw input thread.
479 pTmr->pfn(NULL, WM_SYSTIMER, pTmr->nID, (LPARAM)pTmr);
480 }
481 else
482 {
483 pTmr->flags |= TMRF_READY; // Set timer ready to be ran.
484 // Set thread message queue for this timer.
485 if (pTmr->pti->MessageQueue)
486 { // Wakeup thread
487 ASSERT(pTmr->pti->MessageQueue->NewMessages != NULL);
488 KeSetEvent(pTmr->pti->MessageQueue->NewMessages, IO_NO_INCREMENT, FALSE);
489 }
490 }
491 }
492 pTmr->cmsCountdown = pTmr->cmsRate;
493 }
494 else
495 pTmr->cmsCountdown -= Time - TimeLast;
496 }
497
498 pLE = pLE->Flink;
499 }
500
501 // Restart the timer thread!
502 ASSERT(MasterTimer != NULL);
503 KeSetTimer(MasterTimer, DueTime, NULL);
504
505 TimeLast = Time;
506
507 TimerLeave();
508 TRACE("TimerCount = %d\n", TimerCount);
509 }
510
511 BOOL FASTCALL
512 DestroyTimersForWindow(PTHREADINFO pti, PWND Window)
513 {
514 PLIST_ENTRY pLE;
515 PTIMER pTmr;
516 BOOL TimersRemoved = FALSE;
517
518 if ((Window == NULL))
519 return FALSE;
520
521 TimerEnterExclusive();
522 pLE = TimersListHead.Flink;
523 while(pLE != &TimersListHead)
524 {
525 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
526 pLE = pLE->Flink; /* get next timer list entry before current timer is removed */
527 if ((pTmr) && (pTmr->pti == pti) && (pTmr->pWnd == Window))
528 {
529 TimersRemoved = RemoveTimer(pTmr);
530 }
531 }
532
533 TimerLeave();
534
535 return TimersRemoved;
536 }
537
538 BOOL FASTCALL
539 DestroyTimersForThread(PTHREADINFO pti)
540 {
541 PLIST_ENTRY pLE = TimersListHead.Flink;
542 PTIMER pTmr;
543 BOOL TimersRemoved = FALSE;
544
545 TimerEnterExclusive();
546
547 while(pLE != &TimersListHead)
548 {
549 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
550 pLE = pLE->Flink; /* get next timer list entry before current timer is removed */
551 if ((pTmr) && (pTmr->pti == pti))
552 {
553 TimersRemoved = RemoveTimer(pTmr);
554 }
555 }
556
557 TimerLeave();
558
559 return TimersRemoved;
560 }
561
562 BOOL FASTCALL
563 IntKillTimer(PWND Window, UINT_PTR IDEvent, BOOL SystemTimer)
564 {
565 PTIMER pTmr = NULL;
566 TRACE("IntKillTimer Window %x id %p systemtimer %s\n",
567 Window, IDEvent, SystemTimer ? "TRUE" : "FALSE");
568
569 TimerEnterExclusive();
570 pTmr = FindTimer(Window, IDEvent, SystemTimer ? TMRF_SYSTEM : 0);
571
572 if (pTmr)
573 {
574 RemoveTimer(pTmr);
575 }
576 TimerLeave();
577
578 return pTmr ? TRUE : FALSE;
579 }
580
581 INIT_FUNCTION
582 NTSTATUS
583 NTAPI
584 InitTimerImpl(VOID)
585 {
586 ULONG BitmapBytes;
587
588 ExInitializeFastMutex(&Mutex);
589
590 BitmapBytes = ROUND_UP(NUM_WINDOW_LESS_TIMERS, sizeof(ULONG) * 8) / 8;
591 WindowLessTimersBitMapBuffer = ExAllocatePoolWithTag(NonPagedPool, BitmapBytes, TAG_TIMERBMP);
592 if (WindowLessTimersBitMapBuffer == NULL)
593 {
594 return STATUS_UNSUCCESSFUL;
595 }
596
597 RtlInitializeBitMap(&WindowLessTimersBitMap,
598 WindowLessTimersBitMapBuffer,
599 BitmapBytes * 8);
600
601 /* Yes we need this, since ExAllocatePoolWithTag isn't supposed to zero out allocated memory */
602 RtlClearAllBits(&WindowLessTimersBitMap);
603
604 ExInitializeResourceLite(&TimerLock);
605 InitializeListHead(&TimersListHead);
606
607 return STATUS_SUCCESS;
608 }
609
610 UINT_PTR
611 APIENTRY
612 NtUserSetTimer
613 (
614 HWND hWnd,
615 UINT_PTR nIDEvent,
616 UINT uElapse,
617 TIMERPROC lpTimerFunc
618 )
619 {
620 PWND Window;
621 DECLARE_RETURN(UINT_PTR);
622
623 TRACE("Enter NtUserSetTimer\n");
624 UserEnterExclusive();
625 Window = UserGetWindowObject(hWnd);
626 UserLeave();
627
628 RETURN(IntSetTimer(Window, nIDEvent, uElapse, lpTimerFunc, TMRF_TIFROMWND));
629
630 CLEANUP:
631 TRACE("Leave NtUserSetTimer, ret=%i\n", _ret_);
632
633 END_CLEANUP;
634 }
635
636
637 BOOL
638 APIENTRY
639 NtUserKillTimer
640 (
641 HWND hWnd,
642 UINT_PTR uIDEvent
643 )
644 {
645 PWND Window;
646 DECLARE_RETURN(BOOL);
647
648 TRACE("Enter NtUserKillTimer\n");
649 UserEnterExclusive();
650 Window = UserGetWindowObject(hWnd);
651 UserLeave();
652
653 RETURN(IntKillTimer(Window, uIDEvent, FALSE));
654
655 CLEANUP:
656 TRACE("Leave NtUserKillTimer, ret=%i\n", _ret_);
657 END_CLEANUP;
658 }
659
660
661 UINT_PTR
662 APIENTRY
663 NtUserSetSystemTimer(
664 HWND hWnd,
665 UINT_PTR nIDEvent,
666 UINT uElapse,
667 TIMERPROC lpTimerFunc
668 )
669 {
670 DECLARE_RETURN(UINT_PTR);
671
672 TRACE("Enter NtUserSetSystemTimer\n");
673
674 RETURN(IntSetTimer(UserGetWindowObject(hWnd), nIDEvent, uElapse, NULL, TMRF_SYSTEM));
675
676 CLEANUP:
677 TRACE("Leave NtUserSetSystemTimer, ret=%i\n", _ret_);
678 END_CLEANUP;
679 }
680
681 BOOL
682 APIENTRY
683 NtUserValidateTimerCallback(
684 HWND hWnd,
685 WPARAM wParam,
686 LPARAM lParam)
687 {
688 BOOL Ret = FALSE;
689
690 UserEnterShared();
691
692 Ret = ValidateTimerCallback(PsGetCurrentThreadWin32Thread(), lParam);
693
694 UserLeave();
695 return Ret;
696 }
697
698 /* EOF */