Made the timer code thread safe.
[wine] / windows / timer.c
1 /*
2  * Timer functions
3  *
4  * Copyright 1993 Alexandre Julliard
5  */
6
7 #include "winuser.h"
8 #include "queue.h"
9 #include "task.h"
10 #include "winproc.h"
11 #include "debug.h"
12
13
14 typedef struct tagTIMER
15 {
16     HWND           hwnd;
17     HQUEUE16         hq;
18     UINT16           msg;  /* WM_TIMER or WM_SYSTIMER */
19     UINT           id;
20     UINT           timeout;
21     struct tagTIMER *next;
22     DWORD            expires;  /* Next expiration, or 0 if already expired */
23     HWINDOWPROC      proc;
24 } TIMER;
25
26 #define NB_TIMERS            34
27 #define NB_RESERVED_TIMERS    2  /* for SetSystemTimer */
28
29 static TIMER TimersArray[NB_TIMERS];
30
31 static TIMER * pNextTimer = NULL;  /* Next timer to expire */
32
33 static CRITICAL_SECTION csTimer;
34
35   /* Duration from 'time' until expiration of the timer */
36 #define EXPIRE_TIME(pTimer,time) \
37           (((pTimer)->expires <= (time)) ? 0 : (pTimer)->expires - (time))
38
39
40 /***********************************************************************
41  *           TIMER_Init
42  *
43  * Initialize critical section for the timer.
44  */
45 BOOL TIMER_Init( void )
46 {
47     InitializeCriticalSection( &csTimer );
48     MakeCriticalSectionGlobal( &csTimer );
49
50     return TRUE;
51 }
52
53
54 /***********************************************************************
55  *           TIMER_InsertTimer
56  *
57  * Insert the timer at its place in the chain.
58  */
59 static void TIMER_InsertTimer( TIMER * pTimer )
60 {
61     EnterCriticalSection( &csTimer );
62     
63     if (!pNextTimer || (pTimer->expires < pNextTimer->expires))
64     {
65         pTimer->next = pNextTimer;
66         pNextTimer = pTimer;
67     }
68     else
69     {
70         TIMER * ptr = pNextTimer;       
71         while (ptr->next && (pTimer->expires >= ptr->next->expires))
72             ptr = ptr->next;
73         pTimer->next = ptr->next;
74         ptr->next = pTimer;
75     }
76     
77     LeaveCriticalSection( &csTimer );
78 }
79
80
81 /***********************************************************************
82  *           TIMER_RemoveTimer
83  *
84  * Remove the timer from the chain.
85  */
86 static void TIMER_RemoveTimer( TIMER * pTimer )
87 {
88     TIMER **ppTimer = &pNextTimer;
89
90     EnterCriticalSection( &csTimer );
91     
92     while (*ppTimer && (*ppTimer != pTimer)) ppTimer = &(*ppTimer)->next;
93     if (*ppTimer) *ppTimer = pTimer->next;
94     pTimer->next = NULL;
95     
96     LeaveCriticalSection( &csTimer );
97     
98     if (!pTimer->expires) QUEUE_DecTimerCount( pTimer->hq );
99 }
100
101
102 /***********************************************************************
103  *           TIMER_ClearTimer
104  *
105  * Clear and remove a timer.
106  */
107 static void TIMER_ClearTimer( TIMER * pTimer )
108 {
109     TIMER_RemoveTimer( pTimer );
110     pTimer->hwnd    = 0;
111     pTimer->msg     = 0;
112     pTimer->id      = 0;
113     pTimer->timeout = 0;
114     WINPROC_FreeProc( pTimer->proc, WIN_PROC_TIMER );
115 }
116
117
118 /***********************************************************************
119  *           TIMER_SwitchQueue
120  */
121 void TIMER_SwitchQueue( HQUEUE16 old, HQUEUE16 new )
122 {
123     TIMER * pT;
124
125     EnterCriticalSection( &csTimer );
126
127     pT = pNextTimer;
128     while (pT)
129     {
130         if (pT->hq == old) pT->hq = new;
131         pT = pT->next;
132     }
133     
134     LeaveCriticalSection( &csTimer );
135 }
136
137
138 /***********************************************************************
139  *           TIMER_RemoveWindowTimers
140  *
141  * Remove all timers for a given window.
142  */
143 void TIMER_RemoveWindowTimers( HWND hwnd )
144 {
145     int i;
146     TIMER *pTimer;
147
148     EnterCriticalSection( &csTimer );
149     
150     for (i = NB_TIMERS, pTimer = TimersArray; i > 0; i--, pTimer++)
151         if ((pTimer->hwnd == hwnd) && pTimer->timeout)
152             TIMER_ClearTimer( pTimer );
153     
154     LeaveCriticalSection( &csTimer );
155 }
156
157
158 /***********************************************************************
159  *           TIMER_RemoveQueueTimers
160  *
161  * Remove all timers for a given queue.
162  */
163 void TIMER_RemoveQueueTimers( HQUEUE16 hqueue )
164 {
165     int i;
166     TIMER *pTimer;
167
168     EnterCriticalSection( &csTimer );
169     
170     for (i = NB_TIMERS, pTimer = TimersArray; i > 0; i--, pTimer++)
171         if ((pTimer->hq == hqueue) && pTimer->timeout)
172             TIMER_ClearTimer( pTimer );
173     
174     LeaveCriticalSection( &csTimer );
175 }
176
177
178 /***********************************************************************
179  *           TIMER_RestartTimers
180  *
181  * Restart an expired timer.
182  */
183 static void TIMER_RestartTimer( TIMER * pTimer, DWORD curTime )
184 {
185     TIMER_RemoveTimer( pTimer );
186     pTimer->expires = curTime + pTimer->timeout;
187     TIMER_InsertTimer( pTimer );
188 }
189
190                                
191 /***********************************************************************
192  *           TIMER_GetNextExpiration
193  *
194  * Return next timer expiration time, or -1 if none.
195  */
196 LONG TIMER_GetNextExpiration(void)
197 {
198     LONG retValue;
199     
200     EnterCriticalSection( &csTimer );
201     retValue = pNextTimer ? EXPIRE_TIME( pNextTimer, GetTickCount() ) : -1;
202     LeaveCriticalSection( &csTimer );
203
204     return retValue;
205 }
206
207
208 /***********************************************************************
209  *           TIMER_ExpireTimers
210  *
211  * Mark expired timers and wake the appropriate queues.
212  */
213 void TIMER_ExpireTimers(void)
214 {
215     TIMER *pTimer;
216     DWORD curTime = GetTickCount();
217
218     EnterCriticalSection( &csTimer );
219     
220     pTimer = pNextTimer;
221     
222     while (pTimer && !pTimer->expires)  /* Skip already expired timers */
223         pTimer = pTimer->next;
224     while (pTimer && (pTimer->expires <= curTime))
225     {
226         pTimer->expires = 0;
227         QUEUE_IncTimerCount( pTimer->hq );
228         pTimer = pTimer->next;
229     }
230     
231     LeaveCriticalSection( &csTimer );
232 }
233
234
235 /***********************************************************************
236  *           TIMER_GetTimerMsg
237  *
238  * Build a message for an expired timer.
239  */
240 BOOL TIMER_GetTimerMsg( MSG *msg, HWND hwnd,
241                           HQUEUE16 hQueue, BOOL remove )
242 {
243     TIMER *pTimer;
244     DWORD curTime = GetTickCount();
245
246     EnterCriticalSection( &csTimer );
247
248     pTimer = pNextTimer;
249     
250     if (hwnd)  /* Find first timer for this window */
251         while (pTimer && (pTimer->hwnd != hwnd)) pTimer = pTimer->next;
252     else   /* Find first timer for this queue */
253         while (pTimer && (pTimer->hq != hQueue)) pTimer = pTimer->next;
254
255     if (!pTimer || (pTimer->expires > curTime))
256     {
257         LeaveCriticalSection( &csTimer );
258         return FALSE; /* No timer */
259     }
260     
261     if (remove) TIMER_RestartTimer( pTimer, curTime );  /* Restart it */
262
263     TRACE(timer, "Timer expired: %04x, %04x, %04x, %08lx\n", 
264                    pTimer->hwnd, pTimer->msg, pTimer->id, (DWORD)pTimer->proc);
265
266       /* Build the message */
267     msg->hwnd    = pTimer->hwnd;
268     msg->message = pTimer->msg;
269     msg->wParam  = pTimer->id;
270     msg->lParam  = (LONG)pTimer->proc;
271     msg->time    = curTime;
272
273     LeaveCriticalSection( &csTimer );
274     
275     return TRUE;
276 }
277
278
279 /***********************************************************************
280  *           TIMER_SetTimer
281  */
282 static UINT TIMER_SetTimer( HWND hwnd, UINT id, UINT timeout,
283                               WNDPROC16 proc, WINDOWPROCTYPE type, BOOL sys )
284 {
285     int i;
286     TIMER * pTimer;
287
288     if (!timeout) return 0;
289
290     EnterCriticalSection( &csTimer );
291     
292       /* Check if there's already a timer with the same hwnd and id */
293
294     for (i = 0, pTimer = TimersArray; i < NB_TIMERS; i++, pTimer++)
295         if ((pTimer->hwnd == hwnd) && (pTimer->id == id) &&
296             (pTimer->timeout != 0))
297         {
298               /* Got one: set new values and return */
299             TIMER_RemoveTimer( pTimer );
300             pTimer->timeout = timeout;
301             WINPROC_FreeProc( pTimer->proc, WIN_PROC_TIMER );
302             pTimer->proc = (HWINDOWPROC)0;
303             if (proc) WINPROC_SetProc( &pTimer->proc, proc,
304                                        type, WIN_PROC_TIMER );
305             pTimer->expires = GetTickCount() + timeout;
306             TIMER_InsertTimer( pTimer );
307             LeaveCriticalSection( &csTimer );
308             return id;
309         }
310
311       /* Find a free timer */
312     
313     for (i = 0, pTimer = TimersArray; i < NB_TIMERS; i++, pTimer++)
314         if (!pTimer->timeout) break;
315
316     if ( (i >= NB_TIMERS) ||
317          (!sys && (i >= NB_TIMERS-NB_RESERVED_TIMERS)) )
318     {
319         LeaveCriticalSection( &csTimer );
320         return 0;
321     }
322     
323     if (!hwnd) id = i + 1;
324     
325       /* Add the timer */
326
327     pTimer->hwnd    = hwnd;
328     pTimer->hq      = (hwnd) ? GetThreadQueue16( GetWindowThreadProcessId( hwnd, NULL ) )
329                              : GetFastQueue16( );
330     pTimer->msg     = sys ? WM_SYSTIMER : WM_TIMER;
331     pTimer->id      = id;
332     pTimer->timeout = timeout;
333     pTimer->expires = GetTickCount() + timeout;
334     pTimer->proc    = (HWINDOWPROC)0;
335     if (proc) WINPROC_SetProc( &pTimer->proc, proc, type, WIN_PROC_TIMER );
336     TRACE(timer, "Timer added: %p, %04x, %04x, %04x, %08lx\n", 
337                    pTimer, pTimer->hwnd, pTimer->msg, pTimer->id,
338                    (DWORD)pTimer->proc );
339     TIMER_InsertTimer( pTimer );
340     
341     LeaveCriticalSection( &csTimer );
342     
343     if (!id) return TRUE;
344     else return id;
345 }
346
347
348 /***********************************************************************
349  *           TIMER_KillTimer
350  */
351 static BOOL TIMER_KillTimer( HWND hwnd, UINT id, BOOL sys )
352 {
353     int i;
354     TIMER * pTimer;
355     
356     EnterCriticalSection( &csTimer );
357     
358     /* Find the timer */
359     
360     for (i = 0, pTimer = TimersArray; i < NB_TIMERS; i++, pTimer++)
361         if ((pTimer->hwnd == hwnd) && (pTimer->id == id) &&
362             (pTimer->timeout != 0)) break;
363
364     if ( (i >= NB_TIMERS) ||
365          (!sys && (i >= NB_TIMERS-NB_RESERVED_TIMERS)) ||
366          (!sys && (pTimer->msg != WM_TIMER)) ||
367          (sys && (pTimer->msg != WM_SYSTIMER)) )
368     {
369         LeaveCriticalSection( &csTimer );
370         return FALSE;
371     }
372
373     /* Delete the timer */
374
375     TIMER_ClearTimer( pTimer );
376     
377     LeaveCriticalSection( &csTimer );
378     
379     return TRUE;
380 }
381
382
383 /***********************************************************************
384  *           SetTimer16   (USER.10)
385  */
386 UINT16 WINAPI SetTimer16( HWND16 hwnd, UINT16 id, UINT16 timeout,
387                           TIMERPROC16 proc )
388 {
389     TRACE(timer, "%04x %d %d %08lx\n",
390                    hwnd, id, timeout, (LONG)proc );
391     return TIMER_SetTimer( hwnd, id, timeout, (WNDPROC16)proc,
392                            WIN_PROC_16, FALSE );
393 }
394
395
396 /***********************************************************************
397  *           SetTimer32   (USER32.511)
398  */
399 UINT WINAPI SetTimer( HWND hwnd, UINT id, UINT timeout,
400                           TIMERPROC proc )
401 {
402     TRACE(timer, "%04x %d %d %08lx\n",
403                    hwnd, id, timeout, (LONG)proc );
404     return TIMER_SetTimer( hwnd, id, timeout, (WNDPROC16)proc,
405                            WIN_PROC_32A, FALSE );
406 }
407
408
409 /***********************************************************************
410  *           SetSystemTimer16   (USER.11)
411  */
412 UINT16 WINAPI SetSystemTimer16( HWND16 hwnd, UINT16 id, UINT16 timeout,
413                                 TIMERPROC16 proc )
414 {
415     TRACE(timer, "%04x %d %d %08lx\n", 
416                    hwnd, id, timeout, (LONG)proc );
417     return TIMER_SetTimer( hwnd, id, timeout, (WNDPROC16)proc,
418                            WIN_PROC_16, TRUE );
419 }
420
421
422 /***********************************************************************
423  *           SetSystemTimer32   (USER32.509)
424  */
425 UINT WINAPI SetSystemTimer( HWND hwnd, UINT id, UINT timeout,
426                                 TIMERPROC proc )
427 {
428     TRACE(timer, "%04x %d %d %08lx\n", 
429                    hwnd, id, timeout, (LONG)proc );
430     return TIMER_SetTimer( hwnd, id, timeout, (WNDPROC16)proc,
431                            WIN_PROC_32A, TRUE );
432 }
433
434
435 /***********************************************************************
436  *           KillTimer16   (USER.12)
437  */
438 BOOL16 WINAPI KillTimer16( HWND16 hwnd, UINT16 id )
439 {
440     TRACE(timer, "%04x %d\n", hwnd, id );
441     return TIMER_KillTimer( hwnd, id, FALSE );
442 }
443
444
445 /***********************************************************************
446  *           KillTimer32   (USER32.354)
447  */
448 BOOL WINAPI KillTimer( HWND hwnd, UINT id )
449 {
450     TRACE(timer, "%04x %d\n", hwnd, id );
451     return TIMER_KillTimer( hwnd, id, FALSE );
452 }
453
454
455 /***********************************************************************
456  *           KillSystemTimer16   (USER.182)
457  */
458 BOOL16 WINAPI KillSystemTimer16( HWND16 hwnd, UINT16 id )
459 {
460     TRACE(timer, "%04x %d\n", hwnd, id );
461     return TIMER_KillTimer( hwnd, id, TRUE );
462 }
463
464
465 /***********************************************************************
466  *           KillSystemTimer32   (USER32.353)
467  */
468 BOOL WINAPI KillSystemTimer( HWND hwnd, UINT id )
469 {
470     TRACE(timer, "%04x %d\n", hwnd, id );
471     return TIMER_KillTimer( hwnd, id, TRUE );
472 }