update for HEAD-2003091401
[reactos.git] / subsys / 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 /* $Id$
20  *
21  * COPYRIGHT:        See COPYING in the top level directory
22  * PROJECT:          ReactOS kernel
23  * PURPOSE:          Window timers messages
24  * FILE:             subsys/win32k/ntuser/timer.c
25  * PROGRAMER:        Gunnar
26  * REVISION HISTORY:
27  *
28  */
29
30 /* INCLUDES ******************************************************************/
31
32 #include <ddk/ntddk.h>
33 #include <win32k/win32k.h>
34 #include <win32k/ntuser.h>
35 #include <internal/ntoskrnl.h>
36 #include <internal/ps.h>
37 #include <include/msgqueue.h>
38 #include <messages.h>
39 #include <napi/win32.h>
40
41 #define NDEBUG
42 #include <debug.h>
43
44 /* GLOBALS *******************************************************************/
45
46 //windows 2000 has room for 32768 handle-less timers
47 #define NUM_HANDLE_LESS_TIMERS   1024
48
49 static FAST_MUTEX     Mutex;
50 static LIST_ENTRY     TimerListHead;
51 static KTIMER         Timer;
52 static RTL_BITMAP     HandleLessTimersBitMap;
53 static PVOID          HandleLessTimersBitMapBuffer;
54 static ULONG          HintIndex = 0;
55 static HANDLE        MsgTimerThreadHandle;
56 static CLIENT_ID     MsgTimerThreadId;
57
58
59 typedef struct _MSG_TIMER_ENTRY{
60    LIST_ENTRY     ListEntry;
61    LARGE_INTEGER  Timeout;
62    HANDLE          ThreadID;
63    UINT           Period;
64    MSG            Msg;
65 } MSG_TIMER_ENTRY, *PMSG_TIMER_ENTRY;
66
67
68 /* FUNCTIONS *****************************************************************/
69
70
71 //return true if the new timer became the first entry
72 //must hold mutex while calling this
73 BOOL
74 FASTCALL
75 InsertTimerAscendingOrder(PMSG_TIMER_ENTRY NewTimer)
76 {
77    PLIST_ENTRY EnumEntry, InsertAfter;
78    PMSG_TIMER_ENTRY MsgTimer;
79
80    InsertAfter = NULL;
81
82    EnumEntry = TimerListHead.Flink;
83    while (EnumEntry != &TimerListHead)
84    {
85       MsgTimer = CONTAINING_RECORD(EnumEntry, MSG_TIMER_ENTRY, ListEntry);
86       if (NewTimer->Timeout.QuadPart > MsgTimer->Timeout.QuadPart)
87       {
88          InsertAfter = EnumEntry;
89       }
90       EnumEntry = EnumEntry->Flink;
91    }
92
93    if (InsertAfter)
94    {
95       InsertTailList(InsertAfter, &NewTimer->ListEntry);
96       return FALSE;
97    }
98
99    //insert as first entry
100    InsertHeadList(&TimerListHead, &NewTimer->ListEntry);
101    return TRUE;
102
103 }
104
105 //must hold mutex while calling this
106 PMSG_TIMER_ENTRY
107 FASTCALL
108 RemoveTimer(HWND hWnd, UINT_PTR IDEvent, HANDLE ThreadID)
109 {
110    PMSG_TIMER_ENTRY MsgTimer;
111    PLIST_ENTRY EnumEntry;
112
113    //remove timer if allready in the queue
114    EnumEntry = TimerListHead.Flink;
115    while (EnumEntry != &TimerListHead)
116    {
117       MsgTimer = CONTAINING_RECORD(EnumEntry, MSG_TIMER_ENTRY, ListEntry);
118       EnumEntry = EnumEntry->Flink;
119
120       if (MsgTimer->Msg.hwnd == hWnd && 
121           MsgTimer->Msg.wParam == (WPARAM)IDEvent &&
122           MsgTimer->ThreadID == ThreadID)
123       {
124          RemoveEntryList(&MsgTimer->ListEntry);
125          return MsgTimer;
126       }
127    }
128
129    return NULL;
130 }
131
132
133 /* 
134  * NOTE: It doesn't kill timers. It just removes them from the list.
135  */
136 VOID
137 FASTCALL
138 RemoveTimersThread(HANDLE ThreadID)
139 {
140    PMSG_TIMER_ENTRY MsgTimer;
141    PLIST_ENTRY EnumEntry;
142
143    ExAcquireFastMutex(&Mutex);
144
145    EnumEntry = TimerListHead.Flink;
146    while (EnumEntry != &TimerListHead)
147    {
148       MsgTimer = CONTAINING_RECORD(EnumEntry, MSG_TIMER_ENTRY, ListEntry);
149       EnumEntry = EnumEntry->Flink;
150
151       if (MsgTimer->ThreadID == ThreadID)
152       {
153          if (MsgTimer->Msg.hwnd == NULL)
154          {
155             RtlClearBits(&HandleLessTimersBitMap, ((UINT_PTR)MsgTimer->Msg.wParam) - 1, 1);   
156          }
157
158          RemoveEntryList(&MsgTimer->ListEntry);
159          ExFreePool(MsgTimer);
160       }
161    }
162
163    ExReleaseFastMutex(&Mutex);
164
165 }
166
167
168
169 UINT_PTR
170 STDCALL
171 NtUserSetTimer
172 (
173  HWND hWnd,
174  UINT_PTR nIDEvent,
175  UINT uElapse,
176  TIMERPROC lpTimerFunc
177 )
178 {
179  ULONG Index;
180  PMSG_TIMER_ENTRY MsgTimer = NULL;
181  PMSG_TIMER_ENTRY NewTimer;
182  LARGE_INTEGER CurrentTime;
183  HANDLE ThreadID;
184
185  //FIXME: WINE: window must be owned by the calling thread
186 #if 0
187  if(hWnd && !(hWnd = WIN_IsCurrentThread(hWnd))
188  {
189   return STATUS_UNSUCCESSFUL;
190  }
191 #endif
192
193  ThreadID = PsGetCurrentThreadId();
194  KeQuerySystemTime(&CurrentTime);
195  ExAcquireFastMutex(&Mutex);
196
197  if(hWnd == NULL)
198  {
199   /* find a free, handle-less timer id */
200   Index = RtlFindClearBitsAndSet(&HandleLessTimersBitMap, 1, HintIndex);
201
202   if(Index == (ULONG) -1)
203   {
204    /* FIXME: set the last error */
205    ExReleaseFastMutex(&Mutex);
206    return 0;
207   }
208
209   ++ Index;
210   HintIndex = Index;
211   ExReleaseFastMutex(&Mutex);
212   return Index;
213  }
214  else
215  {
216   /* remove timer if already in the queue */
217   MsgTimer = RemoveTimer(hWnd, nIDEvent, ThreadID); 
218  }
219
220  if(MsgTimer)
221  {
222   /* modify existing (removed) timer */
223   NewTimer = MsgTimer;
224
225   NewTimer->Period = uElapse;
226   NewTimer->Timeout.QuadPart = CurrentTime.QuadPart + (uElapse * 10000);
227   NewTimer->Msg.lParam = (LPARAM)lpTimerFunc;
228  }
229  else
230  {
231   /* FIXME: use lookaside? */
232   NewTimer = ExAllocatePool(PagedPool, sizeof(MSG_TIMER_ENTRY));
233
234   NewTimer->Msg.hwnd = hWnd;
235   NewTimer->Msg.message = WM_TIMER;
236   NewTimer->Msg.wParam = (WPARAM)nIDEvent;
237   NewTimer->Msg.lParam = (LPARAM)lpTimerFunc;
238   NewTimer->Period = uElapse;
239   NewTimer->Timeout.QuadPart = CurrentTime.QuadPart + (uElapse * 10000);
240   NewTimer->ThreadID = ThreadID;
241  }
242
243  if(InsertTimerAscendingOrder(NewTimer))
244  {
245   /* new timer is first in queue and expires first */
246   KeSetTimer(&Timer, NewTimer->Timeout, NULL);
247  }
248
249  ExReleaseFastMutex(&Mutex);
250
251  return 1;
252 }
253
254
255 BOOL
256 STDCALL
257 NtUserKillTimer
258 (
259  HWND hWnd,
260  UINT_PTR uIDEvent
261 )
262 {
263  PMSG_TIMER_ENTRY MsgTimer;
264  
265  ExAcquireFastMutex(&Mutex);
266
267  /* handle-less timer? */
268  if(hWnd == NULL)
269  {
270   if(!RtlAreBitsSet(&HandleLessTimersBitMap, uIDEvent - 1, 1))
271   {
272    /* bit was not set */
273    /* FIXME: set the last error */
274    ExReleaseFastMutex(&Mutex); 
275    return FALSE;
276   }
277
278   RtlClearBits(&HandleLessTimersBitMap, uIDEvent - 1, 1);
279  }
280
281  MsgTimer = RemoveTimer(hWnd, uIDEvent, PsGetCurrentThreadId());
282
283  ExReleaseFastMutex(&Mutex);
284
285  if(MsgTimer == NULL)
286  {
287   /* didn't find timer */
288   /* FIXME: set the last error */
289   return FALSE;
290  }
291
292  /* FIXME: use lookaside? */
293  ExFreePool(MsgTimer);
294
295  return TRUE;
296 }
297
298 DWORD
299 STDCALL
300 NtUserSetSystemTimer(
301   DWORD Unknown0,
302   DWORD Unknown1,
303   DWORD Unknown2,
304   DWORD Unknown3)
305 {
306   UNIMPLEMENTED
307
308   return 0;
309 }
310
311
312 static VOID STDCALL_FUNC
313 TimerThreadMain(
314    PVOID StartContext
315    )
316 {
317    NTSTATUS Status;
318    LARGE_INTEGER CurrentTime;
319    PLIST_ENTRY EnumEntry;
320    PMSG_TIMER_ENTRY MsgTimer;
321    PETHREAD Thread;
322
323    for (;;)
324    {
325
326       Status = KeWaitForSingleObject(   &Timer,
327                                         Executive,
328                                         KernelMode,
329                                         FALSE,
330                                         NULL
331                                         );
332       if (!NT_SUCCESS(Status))
333       {
334          DPRINT1("Error waiting in TimerThreadMain\n");
335          KEBUGCHECK(0);
336       }
337
338       ExAcquireFastMutex(&Mutex);
339
340       KeQuerySystemTime(&CurrentTime);
341
342       EnumEntry = TimerListHead.Flink;
343       while (EnumEntry != &TimerListHead)
344       {
345          MsgTimer = CONTAINING_RECORD(EnumEntry, MSG_TIMER_ENTRY, ListEntry);
346          EnumEntry = EnumEntry->Flink;
347
348          if (CurrentTime.QuadPart >= MsgTimer->Timeout.QuadPart)
349          {
350             RemoveEntryList(&MsgTimer->ListEntry);
351
352             /* 
353              * FIXME: 1) Find a faster way of getting the thread message queue? (lookup by id is slow)
354              */
355
356             if (!NT_SUCCESS(PsLookupThreadByThreadId(MsgTimer->ThreadID, &Thread)))
357             {
358                ExFreePool(MsgTimer);
359                continue;
360             }
361             
362             MsqPostMessage(((PW32THREAD)Thread->Win32Thread)->MessageQueue, MsqCreateMessage(&MsgTimer->Msg));
363
364             ObDereferenceObject(Thread);
365
366             //set up next periodic timeout
367             MsgTimer->Timeout.QuadPart += (MsgTimer->Period * 10000); 
368             InsertTimerAscendingOrder(MsgTimer);
369
370          }                          
371          else                       
372          {
373             break;
374          }
375       }
376    
377       //set up next timeout from first entry (if any)
378       if (!IsListEmpty(&TimerListHead))
379       {
380          MsgTimer = CONTAINING_RECORD( TimerListHead.Flink, MSG_TIMER_ENTRY, ListEntry);
381          KeSetTimer(&Timer, MsgTimer->Timeout, NULL);
382       }
383       else
384       {
385          /* Reinitialize the timer, this reset the state of the timer event on which we wait */
386          KeInitializeTimer(&Timer);
387       }
388
389       ExReleaseFastMutex(&Mutex);
390
391    }
392
393 }
394
395
396
397 NTSTATUS FASTCALL
398 InitTimerImpl(VOID)
399 {
400    NTSTATUS Status;
401    ULONG BitmapBytes;
402
403    BitmapBytes = ROUND_UP(NUM_HANDLE_LESS_TIMERS, sizeof(ULONG) * 8) / 8;
404
405    InitializeListHead(&TimerListHead);
406    KeInitializeTimer(&Timer);
407    ExInitializeFastMutex(&Mutex);
408
409    HandleLessTimersBitMapBuffer = ExAllocatePool(PagedPool, BitmapBytes);
410    RtlInitializeBitMap(&HandleLessTimersBitMap,
411                        HandleLessTimersBitMapBuffer,
412                        BitmapBytes * 8);
413
414    //yes we need this, since ExAllocatePool isn't supposed to zero out allocated memory
415    RtlClearAllBits(&HandleLessTimersBitMap); 
416
417    Status = PsCreateSystemThread(&MsgTimerThreadHandle,
418                                  THREAD_ALL_ACCESS,
419                                  NULL,
420                                  NULL,
421                                  &MsgTimerThreadId,
422                                  TimerThreadMain,
423                                  NULL);
424    return Status;
425 }
426
427 /* EOF */