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