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