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