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