[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 if (Window && (int)Window != 1)
283 {
284 if (!Window->Wnd) return FALSE;
285 }
286
287 pti = PsGetCurrentThreadWin32Thread();
288 ThreadQueue = pti->MessageQueue;
289
290 KeEnterCriticalRegion();
291 do
292 {
293 if ( (pTmr->flags & TMRF_READY) &&
294 (pTmr->pti == pti) &&
295 (pTmr->pWnd == Window))
296 {
297 Msg.hwnd = Window->hSelf;
298 Msg.message = (pTmr->flags & TMRF_SYSTEM) ? WM_SYSTIMER : WM_TIMER;
299 Msg.wParam = (WPARAM) pTmr->nID;
300 Msg.lParam = (LPARAM) pTmr->pfn;
301 MsqPostMessage(ThreadQueue, &Msg, FALSE, QS_POSTMESSAGE);
302
303 pTmr->flags &= ~TMRF_READY;
304 ThreadQueue->WakeMask = ~QS_TIMER;
305 Hit = TRUE;
306 }
307
308 pLE = pTmr->ptmrList.Flink;
309 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
310 } while (pTmr != FirstpTmr);
311 KeLeaveCriticalRegion();
312
313 return Hit;
314 }
315
316 VOID
317 FASTCALL
318 ProcessTimers(VOID)
319 {
320 LARGE_INTEGER TickCount, DueTime;
321 LONG Time;
322 PLIST_ENTRY pLE;
323 PTIMER pTmr = FirstpTmr;
324
325 if (!pTmr) return;
326
327 UserEnterExclusive();
328
329 KeQueryTickCount(&TickCount);
330 Time = MsqCalculateMessageTime(&TickCount);
331
332 DueTime.QuadPart = (LONGLONG)(-10000000);
333
334 do
335 {
336 if (pTmr->flags & TMRF_WAITING)
337 {
338 pLE = pTmr->ptmrList.Flink;
339 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
340 continue;
341 }
342
343 if (pTmr->flags & TMRF_INIT)
344 pTmr->flags &= ~TMRF_INIT; // Skip this run.
345 else
346 {
347 if (pTmr->cmsCountdown < 0)
348 {
349 if (!(pTmr->flags & TMRF_READY))
350 {
351 if (pTmr->flags & TMRF_ONESHOT)
352 pTmr->flags |= TMRF_WAITING;
353
354 if (pTmr->flags & TMRF_RIT)
355 {
356 // Hard coded call here, inside raw input thread.
357 pTmr->pfn(NULL, WM_SYSTIMER, pTmr->nID, (LPARAM)pTmr);
358 }
359 else
360 {
361 pTmr->flags |= TMRF_READY; // Set timer ready to be ran.
362 // Set thread message queue for this timer.
363 if (pTmr->pti->MessageQueue)
364 { // Wakeup thread
365 pTmr->pti->MessageQueue->WakeMask |= QS_TIMER;
366 KeSetEvent(pTmr->pti->MessageQueue->NewMessages, IO_NO_INCREMENT, FALSE);
367 }
368 }
369 }
370 pTmr->cmsCountdown = pTmr->cmsRate;
371 }
372 else
373 pTmr->cmsCountdown -= Time - TimeLast;
374 }
375 pLE = pTmr->ptmrList.Flink;
376 pTmr = CONTAINING_RECORD(pLE, TIMER, ptmrList);
377 } while (pTmr != FirstpTmr);
378
379 // Restart the timer thread!
380 KeSetTimer(MasterTimer, DueTime, NULL);
381
382 TimeLast = Time;
383
384 UserLeave();
385 }
386
387 //
388 //
389 // Old Timer Queueing
390 //
391 //
392 UINT_PTR FASTCALL
393 IntSetTimer(HWND Wnd, UINT_PTR IDEvent, UINT Elapse, TIMERPROC TimerFunc, BOOL SystemTimer)
394 {
395 PWINDOW_OBJECT Window;
396 UINT_PTR Ret = 0;
397 PTHREADINFO pti;
398 PUSER_MESSAGE_QUEUE MessageQueue;
399
400 DPRINT("IntSetTimer wnd %x id %p elapse %u timerproc %p systemtimer %s\n",
401 Wnd, IDEvent, Elapse, TimerFunc, SystemTimer ? "TRUE" : "FALSE");
402
403 if ((Wnd == NULL) && ! SystemTimer)
404 {
405 DPRINT("Window-less timer\n");
406 /* find a free, window-less timer id */
407 IntLockWindowlessTimerBitmap();
408 IDEvent = RtlFindClearBitsAndSet(&WindowLessTimersBitMap, 1, HintIndex);
409
410 if (IDEvent == (UINT_PTR) -1)
411 {
412 IntUnlockWindowlessTimerBitmap();
413 DPRINT1("Unable to find a free window-less timer id\n");
414 SetLastWin32Error(ERROR_NO_SYSTEM_RESOURCES);
415 return 0;
416 }
417
418 HintIndex = ++IDEvent;
419 IntUnlockWindowlessTimerBitmap();
420 Ret = IDEvent;
421 pti = PsGetCurrentThreadWin32Thread();
422 MessageQueue = pti->MessageQueue;
423 }
424 else
425 {
426 if (!(Window = UserGetWindowObject(Wnd)))
427 {
428 DPRINT1("Invalid window handle\n");
429 return 0;
430 }
431
432 if (Window->pti->pEThread->ThreadsProcess != PsGetCurrentProcess())
433 {
434 DPRINT1("Trying to set timer for window in another process (shatter attack?)\n");
435 SetLastWin32Error(ERROR_ACCESS_DENIED);
436 return 0;
437 }
438
439 Ret = IDEvent;
440 MessageQueue = Window->pti->MessageQueue;
441 }
442
443 #if 0
444
445 /* Windows NT/2k/XP behaviour */
446 if (Elapse > 0x7fffffff)
447 {
448 DPRINT("Adjusting uElapse\n");
449 Elapse = 1;
450 }
451
452 #else
453
454 /* Windows XP SP2 and Windows Server 2003 behaviour */
455 if (Elapse > 0x7fffffff)
456 {
457 DPRINT("Adjusting uElapse\n");
458 Elapse = 0x7fffffff;
459 }
460
461 #endif
462
463 /* Windows 2k/XP and Windows Server 2003 SP1 behaviour */
464 if (Elapse < 10)
465 {
466 DPRINT("Adjusting uElapse\n");
467 Elapse = 10;
468 }
469
470 if (! MsqSetTimer(MessageQueue, Wnd,
471 IDEvent, Elapse, TimerFunc,
472 SystemTimer ? WM_SYSTIMER : WM_TIMER))
473 {
474 DPRINT1("Failed to set timer in message queue\n");
475 SetLastWin32Error(ERROR_NO_SYSTEM_RESOURCES);
476 return 0;
477 }
478
479
480 return Ret;
481 }
482
483
484 BOOL FASTCALL
485 IntKillTimer(HWND Wnd, UINT_PTR IDEvent, BOOL SystemTimer)
486 {
487 PTHREADINFO pti;
488 PWINDOW_OBJECT Window = NULL;
489
490 DPRINT("IntKillTimer wnd %x id %p systemtimer %s\n",
491 Wnd, IDEvent, SystemTimer ? "TRUE" : "FALSE");
492
493 pti = PsGetCurrentThreadWin32Thread();
494 if (Wnd)
495 {
496 Window = UserGetWindowObject(Wnd);
497
498 if (! MsqKillTimer(pti->MessageQueue, Wnd,
499 IDEvent, SystemTimer ? WM_SYSTIMER : WM_TIMER))
500 {
501 // Give it another chance to find the timer.
502 if (Window && !( MsqKillTimer(Window->pti->MessageQueue, Wnd,
503 IDEvent, SystemTimer ? WM_SYSTIMER : WM_TIMER)))
504 {
505 DPRINT1("Unable to locate timer in message queue for Window.\n");
506 SetLastWin32Error(ERROR_INVALID_PARAMETER);
507 return FALSE;
508 }
509 }
510 }
511
512 /* window-less timer? */
513 if ((Wnd == NULL) && ! SystemTimer)
514 {
515 if (! MsqKillTimer(pti->MessageQueue, Wnd,
516 IDEvent, SystemTimer ? WM_SYSTIMER : WM_TIMER))
517 {
518 DPRINT1("Unable to locate timer in message queue for Window-less timer.\n");
519 SetLastWin32Error(ERROR_INVALID_PARAMETER);
520 return FALSE;
521 }
522
523 /* Release the id */
524 IntLockWindowlessTimerBitmap();
525
526 ASSERT(RtlAreBitsSet(&WindowLessTimersBitMap, IDEvent - 1, 1));
527 RtlClearBits(&WindowLessTimersBitMap, IDEvent - 1, 1);
528
529 HintIndex = IDEvent - 1;
530
531 IntUnlockWindowlessTimerBitmap();
532 }
533
534 return TRUE;
535 }
536
537 NTSTATUS FASTCALL
538 InitTimerImpl(VOID)
539 {
540 ULONG BitmapBytes;
541
542 ExInitializeFastMutex(&Mutex);
543
544 BitmapBytes = ROUND_UP(NUM_WINDOW_LESS_TIMERS, sizeof(ULONG) * 8) / 8;
545 WindowLessTimersBitMapBuffer = ExAllocatePoolWithTag(PagedPool, BitmapBytes, TAG_TIMERBMP);
546 if (WindowLessTimersBitMapBuffer == NULL)
547 {
548 return STATUS_UNSUCCESSFUL;
549 }
550
551 RtlInitializeBitMap(&WindowLessTimersBitMap,
552 WindowLessTimersBitMapBuffer,
553 BitmapBytes * 8);
554
555 /* yes we need this, since ExAllocatePool isn't supposed to zero out allocated memory */
556 RtlClearAllBits(&WindowLessTimersBitMap);
557
558 return STATUS_SUCCESS;
559 }
560
561 UINT_PTR
562 APIENTRY
563 NtUserSetTimer
564 (
565 HWND hWnd,
566 UINT_PTR nIDEvent,
567 UINT uElapse,
568 TIMERPROC lpTimerFunc
569 )
570 {
571 DECLARE_RETURN(UINT_PTR);
572
573 DPRINT("Enter NtUserSetTimer\n");
574 UserEnterExclusive();
575
576 RETURN(IntSetTimer(hWnd, nIDEvent, uElapse, lpTimerFunc, FALSE));
577
578 CLEANUP:
579 DPRINT("Leave NtUserSetTimer, ret=%i\n", _ret_);
580 UserLeave();
581 END_CLEANUP;
582 }
583
584
585 BOOL
586 APIENTRY
587 NtUserKillTimer
588 (
589 HWND hWnd,
590 UINT_PTR uIDEvent
591 )
592 {
593 DECLARE_RETURN(BOOL);
594
595 DPRINT("Enter NtUserKillTimer\n");
596 UserEnterExclusive();
597
598 RETURN(IntKillTimer(hWnd, uIDEvent, FALSE));
599
600 CLEANUP:
601 DPRINT("Leave NtUserKillTimer, ret=%i\n", _ret_);
602 UserLeave();
603 END_CLEANUP;
604 }
605
606
607 UINT_PTR
608 APIENTRY
609 NtUserSetSystemTimer(
610 HWND hWnd,
611 UINT_PTR nIDEvent,
612 UINT uElapse,
613 TIMERPROC lpTimerFunc
614 )
615 {
616 DECLARE_RETURN(UINT_PTR);
617
618 DPRINT("Enter NtUserSetSystemTimer\n");
619 UserEnterExclusive();
620
621 // This is wrong, lpTimerFunc is NULL!
622 RETURN(IntSetTimer(hWnd, nIDEvent, uElapse, lpTimerFunc, TRUE));
623
624 CLEANUP:
625 DPRINT("Leave NtUserSetSystemTimer, ret=%i\n", _ret_);
626 UserLeave();
627 END_CLEANUP;
628 }
629
630
631 /* EOF */