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