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