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