[WIN32K]
[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 * 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 RemoveEntryList(&pTmr->ptmrList);
72 UserDeleteObject( UserHMGetHandle(pTmr), otTimer);
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;
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 pTmr = (PTIMER)1; // We are here to remove the timer.
100 }
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 pTmr;
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 // Rename it to IntSetTimer after move.
166 UINT_PTR FASTCALL
167 InternalSetTimer( PWINDOW_OBJECT Window,
168 UINT_PTR IDEvent,
169 UINT Elapse,
170 TIMERPROC TimerFunc,
171 INT Type)
172 {
173 PTIMER pTmr;
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 pTmr = FindTimer(Window, IDEvent, Type, FALSE);
201 if (!pTmr)
202 {
203 pTmr = CreateTimer();
204 if (!pTmr) return 0;
205
206 if (Window && (Type & TMRF_TIFROMWND))
207 pTmr->pti = Window->pti->pEThread->Tcb.Win32Thread;
208 else
209 {
210 if (Type & TMRF_RIT)
211 pTmr->pti = ptiRawInput;
212 else
213 pTmr->pti = PsGetCurrentThreadWin32Thread();
214 }
215 pTmr->pWnd = Window;
216 pTmr->cmsCountdown = Elapse;
217 pTmr->cmsRate = Elapse;
218 pTmr->flags = Type|TMRF_INIT; // Set timer to Init mode.
219 pTmr->pfn = TimerFunc;
220 pTmr->nID = IDEvent;
221
222 InsertTailList(&FirstpTmr->ptmrList, &pTmr->ptmrList);
223 }
224
225 // Start the timer thread!
226 KeSetTimer(MasterTimer, DueTime, NULL);
227
228 if (!pTmr->nID) return 1;
229 return pTmr->nID;
230 }
231
232 //
233 // Process win32k system timers.
234 //
235 VOID
236 CALLBACK
237 SystemTimerProc(HWND hwnd,
238 UINT uMsg,
239 UINT_PTR idEvent,
240 DWORD dwTime)
241 {
242 DPRINT( "Timer Running!\n" );
243 }
244
245 VOID
246 FASTCALL
247 StartTheTimers(VOID)
248 {
249 // Need to start gdi syncro timers then start timer with Hang App proc
250 // that calles Idle process so the screen savers will know to run......
251 InternalSetTimer(NULL, 0, 1000, SystemTimerProc, TMRF_RIT);
252 }
253
254 UINT_PTR
255 FASTCALL
256 SystemTimerSet( PWINDOW_OBJECT Window,
257 UINT_PTR nIDEvent,
258 UINT uElapse,
259 TIMERPROC lpTimerFunc)
260 {
261 if (Window && Window->pti->pEThread->ThreadsProcess != PsGetCurrentProcess())
262 {
263 SetLastWin32Error(ERROR_ACCESS_DENIED);
264 return 0;
265 }
266 return InternalSetTimer( Window, nIDEvent, uElapse, lpTimerFunc, TMRF_SYSTEM);
267 }
268
269 BOOL
270 FASTCALL
271 PostTimerMessages(PWINDOW_OBJECT Window)
272 {
273 PLIST_ENTRY pLE;
274 PUSER_MESSAGE_QUEUE ThreadQueue;
275 MSG Msg;
276 PTHREADINFO pti;
277 BOOL Hit = FALSE;
278 PTIMER pTmr = FirstpTmr;
279
280 if (!pTmr) return FALSE;
281
282 ASSERT(Window);
283
284 if (Window && ((ULONG_PTR)Window != 1))
285 {
286 if (!Window->Wnd) return FALSE;
287 }
288
289 pti = PsGetCurrentThreadWin32Thread();
290 ThreadQueue = pti->MessageQueue;
291
292 KeEnterCriticalRegion();
293 do
294 {
295 if ( (pTmr->flags & TMRF_READY) &&
296 (pTmr->pti == pti) &&
297 (pTmr->pWnd == Window))
298 {
299 ASSERT((ULONG_PTR)Window != 1);
300 Msg.hwnd = Window->hSelf;
301 Msg.message = (pTmr->flags & TMRF_SYSTEM) ? WM_SYSTIMER : WM_TIMER;
302 Msg.wParam = (WPARAM) pTmr->nID;
303 Msg.lParam = (LPARAM) pTmr->pfn;
304 MsqPostMessage(ThreadQueue, &Msg, FALSE, QS_POSTMESSAGE);
305
306 pTmr->flags &= ~TMRF_READY;
307 ThreadQueue->WakeMask = ~QS_TIMER;
308 Hit = TRUE;
309 }
310
311 pLE = pTmr->ptmrList.Flink;
312 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
313 } while (pTmr != FirstpTmr);
314 KeLeaveCriticalRegion();
315
316 return Hit;
317 }
318
319 VOID
320 FASTCALL
321 ProcessTimers(VOID)
322 {
323 LARGE_INTEGER TickCount, DueTime;
324 LONG Time;
325 PLIST_ENTRY pLE;
326 PTIMER pTmr = FirstpTmr;
327
328 if (!pTmr) return;
329
330 UserEnterExclusive();
331
332 KeQueryTickCount(&TickCount);
333 Time = MsqCalculateMessageTime(&TickCount);
334
335 DueTime.QuadPart = (LONGLONG)(-10000000);
336
337 do
338 {
339 if (pTmr->flags & TMRF_WAITING)
340 {
341 pLE = pTmr->ptmrList.Flink;
342 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
343 continue;
344 }
345
346 if (pTmr->flags & TMRF_INIT)
347 pTmr->flags &= ~TMRF_INIT; // Skip this run.
348 else
349 {
350 if (pTmr->cmsCountdown < 0)
351 {
352 if (!(pTmr->flags & TMRF_READY))
353 {
354 if (pTmr->flags & TMRF_ONESHOT)
355 pTmr->flags |= TMRF_WAITING;
356
357 if (pTmr->flags & TMRF_RIT)
358 {
359 // Hard coded call here, inside raw input thread.
360 pTmr->pfn(NULL, WM_SYSTIMER, pTmr->nID, (LPARAM)pTmr);
361 }
362 else
363 {
364 pTmr->flags |= TMRF_READY; // Set timer ready to be ran.
365 // Set thread message queue for this timer.
366 if (pTmr->pti->MessageQueue)
367 { // Wakeup thread
368 pTmr->pti->MessageQueue->WakeMask |= QS_TIMER;
369 KeSetEvent(pTmr->pti->MessageQueue->NewMessages, IO_NO_INCREMENT, FALSE);
370 }
371 }
372 }
373 pTmr->cmsCountdown = pTmr->cmsRate;
374 }
375 else
376 pTmr->cmsCountdown -= Time - TimeLast;
377 }
378 pLE = pTmr->ptmrList.Flink;
379 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
380 } while (pTmr != FirstpTmr);
381
382 // Restart the timer thread!
383 KeSetTimer(MasterTimer, DueTime, NULL);
384
385 TimeLast = Time;
386
387 UserLeave();
388 }
389
390 //
391 //
392 // Old Timer Queueing
393 //
394 //
395 UINT_PTR FASTCALL
396 IntSetTimer(HWND Wnd, UINT_PTR IDEvent, UINT Elapse, TIMERPROC TimerFunc, BOOL SystemTimer)
397 {
398 PWINDOW_OBJECT Window;
399 UINT_PTR Ret = 0;
400 PTHREADINFO pti;
401 PUSER_MESSAGE_QUEUE MessageQueue;
402
403 DPRINT("IntSetTimer wnd %x id %p elapse %u timerproc %p systemtimer %s\n",
404 Wnd, IDEvent, Elapse, TimerFunc, SystemTimer ? "TRUE" : "FALSE");
405
406 if ((Wnd == NULL) && ! SystemTimer)
407 {
408 DPRINT("Window-less timer\n");
409 /* find a free, window-less timer id */
410 IntLockWindowlessTimerBitmap();
411 IDEvent = RtlFindClearBitsAndSet(&WindowLessTimersBitMap, 1, HintIndex);
412
413 if (IDEvent == (UINT_PTR) -1)
414 {
415 IntUnlockWindowlessTimerBitmap();
416 DPRINT1("Unable to find a free window-less timer id\n");
417 SetLastWin32Error(ERROR_NO_SYSTEM_RESOURCES);
418 return 0;
419 }
420
421 HintIndex = ++IDEvent;
422 IntUnlockWindowlessTimerBitmap();
423 Ret = IDEvent;
424 pti = PsGetCurrentThreadWin32Thread();
425 MessageQueue = pti->MessageQueue;
426 }
427 else
428 {
429 if (!(Window = UserGetWindowObject(Wnd)))
430 {
431 DPRINT1("Invalid window handle\n");
432 return 0;
433 }
434
435 if (Window->pti->pEThread->ThreadsProcess != PsGetCurrentProcess())
436 {
437 DPRINT1("Trying to set timer for window in another process (shatter attack?)\n");
438 SetLastWin32Error(ERROR_ACCESS_DENIED);
439 return 0;
440 }
441
442 Ret = IDEvent;
443 MessageQueue = Window->pti->MessageQueue;
444 }
445
446 #if 0
447
448 /* Windows NT/2k/XP behaviour */
449 if (Elapse > 0x7fffffff)
450 {
451 DPRINT("Adjusting uElapse\n");
452 Elapse = 1;
453 }
454
455 #else
456
457 /* Windows XP SP2 and Windows Server 2003 behaviour */
458 if (Elapse > 0x7fffffff)
459 {
460 DPRINT("Adjusting uElapse\n");
461 Elapse = 0x7fffffff;
462 }
463
464 #endif
465
466 /* Windows 2k/XP and Windows Server 2003 SP1 behaviour */
467 if (Elapse < 10)
468 {
469 DPRINT("Adjusting uElapse\n");
470 Elapse = 10;
471 }
472
473 if (! MsqSetTimer(MessageQueue, Wnd,
474 IDEvent, Elapse, TimerFunc,
475 SystemTimer ? WM_SYSTIMER : WM_TIMER))
476 {
477 DPRINT1("Failed to set timer in message queue\n");
478 SetLastWin32Error(ERROR_NO_SYSTEM_RESOURCES);
479 return 0;
480 }
481
482
483 return Ret;
484 }
485
486
487 BOOL FASTCALL
488 IntKillTimer(HWND Wnd, UINT_PTR IDEvent, BOOL SystemTimer)
489 {
490 PTHREADINFO pti;
491 PWINDOW_OBJECT Window = NULL;
492
493 DPRINT("IntKillTimer wnd %x id %p systemtimer %s\n",
494 Wnd, IDEvent, SystemTimer ? "TRUE" : "FALSE");
495
496 pti = PsGetCurrentThreadWin32Thread();
497 if (Wnd)
498 {
499 Window = UserGetWindowObject(Wnd);
500
501 if (! MsqKillTimer(pti->MessageQueue, Wnd,
502 IDEvent, SystemTimer ? WM_SYSTIMER : WM_TIMER))
503 {
504 // Give it another chance to find the timer.
505 if (Window && !( MsqKillTimer(Window->pti->MessageQueue, Wnd,
506 IDEvent, SystemTimer ? WM_SYSTIMER : WM_TIMER)))
507 {
508 DPRINT1("Unable to locate timer in message queue for Window.\n");
509 SetLastWin32Error(ERROR_INVALID_PARAMETER);
510 return FALSE;
511 }
512 }
513 }
514
515 /* window-less timer? */
516 if ((Wnd == NULL) && ! SystemTimer)
517 {
518 if (! MsqKillTimer(pti->MessageQueue, Wnd,
519 IDEvent, SystemTimer ? WM_SYSTIMER : WM_TIMER))
520 {
521 DPRINT1("Unable to locate timer in message queue for Window-less timer.\n");
522 SetLastWin32Error(ERROR_INVALID_PARAMETER);
523 return FALSE;
524 }
525
526 /* Release the id */
527 IntLockWindowlessTimerBitmap();
528
529 ASSERT(RtlAreBitsSet(&WindowLessTimersBitMap, IDEvent - 1, 1));
530 RtlClearBits(&WindowLessTimersBitMap, IDEvent - 1, 1);
531
532 HintIndex = IDEvent - 1;
533
534 IntUnlockWindowlessTimerBitmap();
535 }
536
537 return TRUE;
538 }
539
540 NTSTATUS FASTCALL
541 InitTimerImpl(VOID)
542 {
543 ULONG BitmapBytes;
544
545 ExInitializeFastMutex(&Mutex);
546
547 BitmapBytes = ROUND_UP(NUM_WINDOW_LESS_TIMERS, sizeof(ULONG) * 8) / 8;
548 WindowLessTimersBitMapBuffer = ExAllocatePoolWithTag(PagedPool, BitmapBytes, TAG_TIMERBMP);
549 if (WindowLessTimersBitMapBuffer == NULL)
550 {
551 return STATUS_UNSUCCESSFUL;
552 }
553
554 RtlInitializeBitMap(&WindowLessTimersBitMap,
555 WindowLessTimersBitMapBuffer,
556 BitmapBytes * 8);
557
558 /* yes we need this, since ExAllocatePool isn't supposed to zero out allocated memory */
559 RtlClearAllBits(&WindowLessTimersBitMap);
560
561 return STATUS_SUCCESS;
562 }
563
564 UINT_PTR
565 APIENTRY
566 NtUserSetTimer
567 (
568 HWND hWnd,
569 UINT_PTR nIDEvent,
570 UINT uElapse,
571 TIMERPROC lpTimerFunc
572 )
573 {
574 DECLARE_RETURN(UINT_PTR);
575
576 DPRINT("Enter NtUserSetTimer\n");
577 UserEnterExclusive();
578
579 RETURN(IntSetTimer(hWnd, nIDEvent, uElapse, lpTimerFunc, FALSE));
580
581 CLEANUP:
582 DPRINT("Leave NtUserSetTimer, ret=%i\n", _ret_);
583 UserLeave();
584 END_CLEANUP;
585 }
586
587
588 BOOL
589 APIENTRY
590 NtUserKillTimer
591 (
592 HWND hWnd,
593 UINT_PTR uIDEvent
594 )
595 {
596 DECLARE_RETURN(BOOL);
597
598 DPRINT("Enter NtUserKillTimer\n");
599 UserEnterExclusive();
600
601 RETURN(IntKillTimer(hWnd, uIDEvent, FALSE));
602
603 CLEANUP:
604 DPRINT("Leave NtUserKillTimer, ret=%i\n", _ret_);
605 UserLeave();
606 END_CLEANUP;
607 }
608
609
610 UINT_PTR
611 APIENTRY
612 NtUserSetSystemTimer(
613 HWND hWnd,
614 UINT_PTR nIDEvent,
615 UINT uElapse,
616 TIMERPROC lpTimerFunc
617 )
618 {
619 DECLARE_RETURN(UINT_PTR);
620
621 DPRINT("Enter NtUserSetSystemTimer\n");
622 UserEnterExclusive();
623
624 // This is wrong, lpTimerFunc is NULL!
625 RETURN(IntSetTimer(hWnd, nIDEvent, uElapse, lpTimerFunc, TRUE));
626
627 CLEANUP:
628 DPRINT("Leave NtUserSetSystemTimer, ret=%i\n", _ret_);
629 UserLeave();
630 END_CLEANUP;
631 }
632
633
634 /* EOF */