merge the C branch into HEAD
[mikachu/openbox.git] / openbox / timer.c
1 #include "timer.h"
2
3 #ifdef    HAVE_SYS_TIME_H
4 #  include <sys/time.h>
5 #endif
6
7 static GTimeVal now;
8 static GTimeVal ret_wait;
9 static GSList *timers; /* nearest timer is at the top */
10
11 #define NEAREST_TIMEOUT (((Timer*)timers->data)->timeout)
12
13 static void insert_timer(Timer *self)
14 {
15     GSList *it;
16     for (it = timers; it != NULL; it = it->next) {
17         Timer *t = it->data;
18         if (!timercmp(&self->timeout, &t->timeout, >)) {
19             timers = g_slist_insert_before(timers, it, self);
20             break;
21         }
22     }
23     if (it == NULL) /* didnt fit anywhere in the list */
24         timers = g_slist_append(timers, self);
25 }
26
27 void timer_startup()
28 {
29     g_get_current_time(&now);
30     timers = NULL;
31 }
32
33 void timer_shutdown()
34 {
35     GSList *it;
36     for (it = timers; it != NULL; it = it->next) {
37         g_free(it->data);
38     }
39     g_slist_free(timers);
40     timers = NULL;
41 }
42
43 Timer *timer_start(long delay, TimeoutHandler cb, void *data)
44 {
45     Timer *self = g_new(Timer, 1);
46     self->delay = delay;
47     self->action = cb;
48     self->data = data;
49     self->del_me = FALSE;
50     self->last = self->timeout = now;
51     g_time_val_add(&self->timeout, delay);
52
53     insert_timer(self);
54
55     return self;
56 }
57
58 void timer_stop(Timer *self)
59 {
60     self->del_me = TRUE;
61 }
62
63 /* find the time to wait for the nearest timeout */
64 static gboolean nearest_timeout_wait(GTimeVal *tm)
65 {
66   if (timers == NULL)
67     return FALSE;
68
69   tm->tv_sec = NEAREST_TIMEOUT.tv_sec - now.tv_sec;
70   tm->tv_usec = NEAREST_TIMEOUT.tv_usec - now.tv_usec;
71
72   while (tm->tv_usec < 0) {
73     tm->tv_usec += G_USEC_PER_SEC;
74     tm->tv_sec--;
75   }
76   tm->tv_sec += tm->tv_usec / G_USEC_PER_SEC;
77   tm->tv_usec %= G_USEC_PER_SEC;
78   if (tm->tv_sec < 0)
79     tm->tv_sec = 0;
80
81   return TRUE;
82 }
83
84
85 void timer_dispatch(GTimeVal **wait)
86 {
87     g_get_current_time(&now);
88
89     while (timers != NULL) {
90         Timer *curr = timers->data; /* get the top element */
91         /* since timer_stop doesn't actually free the timer, we have to do our
92            real freeing in here.
93         */
94         if (curr->del_me) {
95             timers = g_slist_delete_link(timers, timers); /* delete the top */
96             g_free(curr);
97             continue;
98         }
99         
100         /* the queue is sorted, so if this timer shouldn't fire, none are 
101            ready */
102         if (!timercmp(&now, &NEAREST_TIMEOUT, >))
103             break;
104
105         /* we set the last fired time to delay msec after the previous firing,
106            then re-insert.  timers maintain their order and may trigger more
107            than once if they've waited more than one delay's worth of time.
108         */
109         timers = g_slist_delete_link(timers, timers);
110         g_time_val_add(&curr->last, curr->delay);
111         curr->action(curr->data);
112         g_time_val_add(&curr->timeout, curr->delay);
113         insert_timer(curr);
114
115         /* if at least one timer fires, then don't wait on X events, as there
116            may already be some in the queue from the timer callbacks.
117         */
118         ret_wait.tv_sec = ret_wait.tv_usec = 0;
119         *wait = &ret_wait;
120         return;
121     }
122
123     if (nearest_timeout_wait(&ret_wait))
124         *wait = &ret_wait;
125     else
126         *wait = NULL;
127 }