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