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