c0355e96ac8d4a1559c227f69e6bf614887c7f5f
[reactos.git] / reactos / win32ss / user / 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 PFAST_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, NULL, &Handle, TYPE_TIMER, 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), TYPE_TIMER);
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! %p Id: %u\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 %u!\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 MSG Msg;
390 PTHREADINFO pti;
391 BOOL Hit = FALSE;
392 PTIMER pTmr;
393
394 pti = PsGetCurrentThreadWin32Thread();
395
396 TimerEnterExclusive();
397 pLE = TimersListHead.Flink;
398 while(pLE != &TimersListHead)
399 {
400 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
401 if ( (pTmr->flags & TMRF_READY) &&
402 (pTmr->pti == pti) &&
403 ((pTmr->pWnd == Window) || (Window == NULL)) )
404 {
405 Msg.hwnd = (pTmr->pWnd) ? pTmr->pWnd->head.h : 0;
406 Msg.message = (pTmr->flags & TMRF_SYSTEM) ? WM_SYSTIMER : WM_TIMER;
407 Msg.wParam = (WPARAM) pTmr->nID;
408 Msg.lParam = (LPARAM) pTmr->pfn;
409
410 MsqPostMessage(pti, &Msg, FALSE, QS_TIMER, 0);
411 pTmr->flags &= ~TMRF_READY;
412 pti->cTimersReady++;
413 Hit = TRUE;
414 // Now move this entry to the end of the list so it will not be
415 // called again in the next msg loop.
416 if (pLE != &TimersListHead)
417 {
418 RemoveEntryList(&pTmr->ptmrList);
419 InsertTailList(&TimersListHead, &pTmr->ptmrList);
420 }
421 break;
422 }
423
424 pLE = pLE->Flink;
425 }
426
427 TimerLeave();
428
429 return Hit;
430 }
431
432 VOID
433 FASTCALL
434 ProcessTimers(VOID)
435 {
436 LARGE_INTEGER TickCount, DueTime;
437 LONG Time;
438 PLIST_ENTRY pLE;
439 PTIMER pTmr;
440 LONG TimerCount = 0;
441
442 TimerEnterExclusive();
443 pLE = TimersListHead.Flink;
444 KeQueryTickCount(&TickCount);
445 Time = MsqCalculateMessageTime(&TickCount);
446
447 DueTime.QuadPart = (LONGLONG)(-97656); // 1024hz .9765625 ms set to 10.0 ms
448
449 while(pLE != &TimersListHead)
450 {
451 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
452 TimerCount++;
453 if (pTmr->flags & TMRF_WAITING)
454 {
455 pLE = pTmr->ptmrList.Flink;
456 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
457 continue;
458 }
459
460 if (pTmr->flags & TMRF_INIT)
461 {
462 pTmr->flags &= ~TMRF_INIT; // Skip this run.
463 }
464 else
465 {
466 if (pTmr->cmsCountdown < 0)
467 {
468 ASSERT(pTmr->pti);
469 if ((!(pTmr->flags & TMRF_READY)) && (!(pTmr->pti->TIF_flags & TIF_INCLEANUP)))
470 {
471 if (pTmr->flags & TMRF_ONESHOT)
472 pTmr->flags |= TMRF_WAITING;
473
474 if (pTmr->flags & TMRF_RIT)
475 {
476 // Hard coded call here, inside raw input thread.
477 pTmr->pfn(NULL, WM_SYSTIMER, pTmr->nID, (LPARAM)pTmr);
478 }
479 else
480 {
481 pTmr->flags |= TMRF_READY; // Set timer ready to be ran.
482 // Set thread message queue for this timer.
483 if (pTmr->pti->MessageQueue)
484 { // Wakeup thread
485 ASSERT(pTmr->pti->pEventQueueServer != NULL);
486 KeSetEvent(pTmr->pti->pEventQueueServer, IO_NO_INCREMENT, FALSE);
487 }
488 }
489 }
490 pTmr->cmsCountdown = pTmr->cmsRate;
491 }
492 else
493 pTmr->cmsCountdown -= Time - TimeLast;
494 }
495
496 pLE = pLE->Flink;
497 }
498
499 // Restart the timer thread!
500 ASSERT(MasterTimer != NULL);
501 KeSetTimer(MasterTimer, DueTime, NULL);
502
503 TimeLast = Time;
504
505 TimerLeave();
506 TRACE("TimerCount = %d\n", TimerCount);
507 }
508
509 BOOL FASTCALL
510 DestroyTimersForWindow(PTHREADINFO pti, PWND Window)
511 {
512 PLIST_ENTRY pLE;
513 PTIMER pTmr;
514 BOOL TimersRemoved = FALSE;
515
516 if ((Window == NULL))
517 return FALSE;
518
519 TimerEnterExclusive();
520 pLE = TimersListHead.Flink;
521 while(pLE != &TimersListHead)
522 {
523 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
524 pLE = pLE->Flink; /* get next timer list entry before current timer is removed */
525 if ((pTmr) && (pTmr->pti == pti) && (pTmr->pWnd == Window))
526 {
527 TimersRemoved = RemoveTimer(pTmr);
528 }
529 }
530
531 TimerLeave();
532
533 return TimersRemoved;
534 }
535
536 BOOL FASTCALL
537 DestroyTimersForThread(PTHREADINFO pti)
538 {
539 PLIST_ENTRY pLE = TimersListHead.Flink;
540 PTIMER pTmr;
541 BOOL TimersRemoved = FALSE;
542
543 TimerEnterExclusive();
544
545 while(pLE != &TimersListHead)
546 {
547 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
548 pLE = pLE->Flink; /* get next timer list entry before current timer is removed */
549 if ((pTmr) && (pTmr->pti == pti))
550 {
551 TimersRemoved = RemoveTimer(pTmr);
552 }
553 }
554
555 TimerLeave();
556
557 return TimersRemoved;
558 }
559
560 BOOL FASTCALL
561 IntKillTimer(PWND Window, UINT_PTR IDEvent, BOOL SystemTimer)
562 {
563 PTIMER pTmr = NULL;
564 TRACE("IntKillTimer Window %p id %p systemtimer %s\n",
565 Window, IDEvent, SystemTimer ? "TRUE" : "FALSE");
566
567 TimerEnterExclusive();
568 pTmr = FindTimer(Window, IDEvent, SystemTimer ? TMRF_SYSTEM : 0);
569
570 if (pTmr)
571 {
572 RemoveTimer(pTmr);
573 }
574 TimerLeave();
575
576 return pTmr ? TRUE : FALSE;
577 }
578
579 INIT_FUNCTION
580 NTSTATUS
581 NTAPI
582 InitTimerImpl(VOID)
583 {
584 ULONG BitmapBytes;
585
586 /* Allocate FAST_MUTEX from non paged pool */
587 Mutex = ExAllocatePoolWithTag(NonPagedPool, sizeof(FAST_MUTEX), TAG_INTERNAL_SYNC);
588 if (!Mutex)
589 {
590 return STATUS_INSUFFICIENT_RESOURCES;
591 }
592
593 ExInitializeFastMutex(Mutex);
594
595 BitmapBytes = ROUND_UP(NUM_WINDOW_LESS_TIMERS, sizeof(ULONG) * 8) / 8;
596 WindowLessTimersBitMapBuffer = ExAllocatePoolWithTag(NonPagedPool, BitmapBytes, TAG_TIMERBMP);
597 if (WindowLessTimersBitMapBuffer == NULL)
598 {
599 return STATUS_UNSUCCESSFUL;
600 }
601
602 RtlInitializeBitMap(&WindowLessTimersBitMap,
603 WindowLessTimersBitMapBuffer,
604 BitmapBytes * 8);
605
606 /* Yes we need this, since ExAllocatePoolWithTag isn't supposed to zero out allocated memory */
607 RtlClearAllBits(&WindowLessTimersBitMap);
608
609 ExInitializeResourceLite(&TimerLock);
610 InitializeListHead(&TimersListHead);
611
612 return STATUS_SUCCESS;
613 }
614
615 UINT_PTR
616 APIENTRY
617 NtUserSetTimer
618 (
619 HWND hWnd,
620 UINT_PTR nIDEvent,
621 UINT uElapse,
622 TIMERPROC lpTimerFunc
623 )
624 {
625 PWND Window = NULL;
626 DECLARE_RETURN(UINT_PTR);
627
628 TRACE("Enter NtUserSetTimer\n");
629 UserEnterExclusive();
630 if (hWnd) Window = UserGetWindowObject(hWnd);
631 UserLeave();
632
633 RETURN(IntSetTimer(Window, nIDEvent, uElapse, lpTimerFunc, TMRF_TIFROMWND));
634
635 CLEANUP:
636 TRACE("Leave NtUserSetTimer, ret=%u\n", _ret_);
637
638 END_CLEANUP;
639 }
640
641
642 BOOL
643 APIENTRY
644 NtUserKillTimer
645 (
646 HWND hWnd,
647 UINT_PTR uIDEvent
648 )
649 {
650 PWND Window = NULL;
651 DECLARE_RETURN(BOOL);
652
653 TRACE("Enter NtUserKillTimer\n");
654 UserEnterExclusive();
655 if (hWnd) Window = UserGetWindowObject(hWnd);
656 UserLeave();
657
658 RETURN(IntKillTimer(Window, uIDEvent, FALSE));
659
660 CLEANUP:
661 TRACE("Leave NtUserKillTimer, ret=%i\n", _ret_);
662 END_CLEANUP;
663 }
664
665
666 UINT_PTR
667 APIENTRY
668 NtUserSetSystemTimer(
669 HWND hWnd,
670 UINT_PTR nIDEvent,
671 UINT uElapse,
672 TIMERPROC lpTimerFunc
673 )
674 {
675 DECLARE_RETURN(UINT_PTR);
676
677 TRACE("Enter NtUserSetSystemTimer\n");
678
679 RETURN(IntSetTimer(UserGetWindowObject(hWnd), nIDEvent, uElapse, NULL, TMRF_SYSTEM));
680
681 CLEANUP:
682 TRACE("Leave NtUserSetSystemTimer, ret=%u\n", _ret_);
683 END_CLEANUP;
684 }
685
686 BOOL
687 APIENTRY
688 NtUserValidateTimerCallback(
689 HWND hWnd,
690 WPARAM wParam,
691 LPARAM lParam)
692 {
693 BOOL Ret = FALSE;
694
695 UserEnterShared();
696
697 Ret = ValidateTimerCallback(PsGetCurrentThreadWin32Thread(), lParam);
698
699 UserLeave();
700 return Ret;
701 }
702
703 /* EOF */