From http://www.jwz.org/xscreensaver/xscreensaver-5.39.tar.gz
[xscreensaver] / jwxyz / jwxyz-timers.c
1 /* xscreensaver, Copyright (c) 2006-2017 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 /* This is the portable implementation of Xt timers and inputs, for libjwxyz.
13  */
14
15 #ifdef HAVE_CONFIG_H
16 # include "config.h"
17 #endif
18
19 #ifdef HAVE_JWXYZ /* whole file */
20
21
22 #undef DEBUG_TIMERS
23 #undef DEBUG_SOURCES
24
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/time.h>
29 #include <sys/select.h>
30 #include "jwxyz.h"
31 #include "jwxyz-timers.h"
32
33 #ifdef HAVE_ANDROID
34 extern void Log(const char *format, ...);
35 #endif
36
37 #ifdef HAVE_COCOA
38 # define Log(S, ...) fprintf(stderr, "xscreensaver: " S "\n", __VA_ARGS__)
39 #endif
40
41 #ifdef DEBUG_TIMERS
42 # define LOGT(...) Log(__VA_ARGS__)
43 #else
44 # define LOGT(...)
45 #endif
46
47 #ifdef DEBUG_SOURCES
48 # define LOGI(...) Log(__VA_ARGS__)
49 #else
50 # define LOGI(...)
51 #endif
52
53 #define ASSERT_RET(C,S) do {                    \
54     if (!(C)) {                                 \
55       jwxyz_abort ("jwxyz-timers: %s",(S));     \
56       return;                                   \
57  }} while(0)
58
59 #define ASSERT_RET0(C,S) do {                   \
60     if (!(C)) {                                 \
61       jwxyz_abort ("jwxyz-timers: %s",(S));     \
62       return 0;                                 \
63  }} while(0)
64
65
66 XtAppContext
67 XtDisplayToApplicationContext (Display *dpy)
68 {
69   return (XtAppContext) dpy;
70 }
71
72 #define app_to_display(APP) ((Display *) (APP))
73
74 #define DISPLAY_SOURCES_DATA(APP) \
75   JWXYZ_VTBL(app_to_display (APP))->display_sources_data (app_to_display (APP))
76
77
78 struct jwxyz_sources_data {
79   int fd_count;
80   XtInputId ids[FD_SETSIZE];
81   XtIntervalId all_timers;
82 };
83
84 struct jwxyz_XtIntervalId {
85   XtAppContext app;
86   int refcount;
87
88   double run_at;
89   XtTimerCallbackProc cb;
90   XtPointer closure;
91
92   XtIntervalId next;
93 };
94
95 struct jwxyz_XtInputId {
96   XtAppContext app;
97   int refcount;
98
99   XtInputCallbackProc cb;
100   XtPointer closure;
101   int fd;
102 };
103
104
105 static double
106 double_time (void)
107 {
108   struct timeval now;
109 # ifdef GETTIMEOFDAY_TWO_ARGS
110   struct timezone tzp;
111   gettimeofday(&now, &tzp);
112 # else
113   gettimeofday(&now);
114 # endif
115
116   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
117 }
118
119
120 jwxyz_sources_data *
121 jwxyz_sources_init (XtAppContext app)
122 {
123   jwxyz_sources_data *td = (jwxyz_sources_data *) calloc (1, sizeof (*td));
124   return td;
125 }
126
127 XtIntervalId
128 XtAppAddTimeOut (XtAppContext app, unsigned long msecs,
129                  XtTimerCallbackProc cb, XtPointer closure)
130 {
131   jwxyz_sources_data *td = DISPLAY_SOURCES_DATA (app);
132   XtIntervalId data = (XtIntervalId) calloc (1, sizeof(*data));
133   double now = double_time();
134   data->app = app;
135   data->cb = cb;
136   data->closure = closure;
137   data->refcount++;
138   data->run_at = now + (msecs / 1000.0);
139
140   data->next = td->all_timers;
141   td->all_timers = data;
142
143   LOGT("timer  0x%08lX: alloc %lu %.2f", (unsigned long) data, msecs,
144        data->run_at - now);
145
146   return data;
147 }
148
149
150 /* This is called both by the user to manually kill a timer,
151    and by the run loop after a timer has fired.
152  */
153 void
154 XtRemoveTimeOut (XtIntervalId data)
155 {
156   jwxyz_sources_data *td = DISPLAY_SOURCES_DATA (data->app);
157
158   LOGT("timer  0x%08lX: remove", (unsigned long) data);
159   ASSERT_RET (data->refcount > 0, "already freed");
160
161   data->refcount--;
162   LOGT("timer  0x%08lX: release %d", (unsigned long) data, data->refcount);
163   ASSERT_RET (data->refcount >= 0, "double free");
164
165   if (data->refcount == 0) {
166
167     /* Remove it from the list of live timers. */
168     XtIntervalId prev, timer;
169     int hit = 0;
170     for (timer = td->all_timers, prev = 0;
171          timer;
172          prev = timer, timer = timer->next) {
173       if (timer == data) {
174         ASSERT_RET (!hit, "circular timer list");
175         if (prev)
176           prev->next = timer->next;
177         else
178           td->all_timers = timer->next;
179         timer->next = 0;
180         hit = 1;
181       } else {
182         ASSERT_RET (timer->refcount > 0, "timer list corrupted");
183       }
184     }
185
186     free (data);
187   }
188 }
189
190
191 XtInputId
192 XtAppAddInput (XtAppContext app, int fd, XtPointer flags,
193                XtInputCallbackProc cb, XtPointer closure)
194 {
195   jwxyz_sources_data *td = DISPLAY_SOURCES_DATA (app);
196   XtInputId data = (XtInputId) calloc (1, sizeof(*data));
197   data->cb = cb;
198   data->fd = fd;
199   data->closure = closure;
200   data->app = app;
201   data->refcount++;
202
203   LOGI("source 0x%08lX %2d: alloc", (unsigned long) data, data->fd);
204
205   ASSERT_RET0 (fd > 0 && fd < FD_SETSIZE, "fd out of range");
206   ASSERT_RET0 (td->ids[fd] == 0, "sources corrupted");
207   td->ids[fd] = data;
208   td->fd_count++;
209
210   return data;
211 }
212
213
214 void
215 XtRemoveInput (XtInputId id)
216 {
217   jwxyz_sources_data *td = DISPLAY_SOURCES_DATA (id->app);
218
219   LOGI("source 0x%08lX %2d: remove", (unsigned long) id, id->fd);
220   ASSERT_RET (id->refcount > 0, "sources corrupted");
221   ASSERT_RET (td->fd_count > 0, "sources corrupted");
222   ASSERT_RET (id->fd > 0 && id->fd < FD_SETSIZE, "fd out of range");
223   ASSERT_RET (td->ids[id->fd] == id, "sources corrupted");
224
225   td->ids[id->fd] = 0;
226   td->fd_count--;
227   id->refcount--;
228
229   LOGI("source 0x%08lX %2d: release %d", (unsigned long) id, id->fd,
230        id->refcount);
231   ASSERT_RET (id->refcount >= 0, "double free");
232   if (id->refcount == 0) {
233     memset (id, 0xA1, sizeof(*id));
234     id->fd = -666;
235     free (id);
236   }
237 }
238
239
240 static void
241 jwxyz_timers_run (jwxyz_sources_data *td)
242 {
243   /* Iterate the timer list, being careful because XtRemoveTimeOut removes
244      the current item from that list. */
245   if (td->all_timers) {
246     XtIntervalId timer, next;
247     double now = double_time();
248     int count = 0;
249
250     for (timer = td->all_timers, next = timer->next;
251          timer;
252          timer = next, next = (timer ? timer->next : 0)) {
253       if (timer->run_at <= now) {
254         LOGT("timer  0x%08lX: fire %.02f", (unsigned long) timer,
255              now - timer->run_at);
256         timer->cb (timer->closure, &timer);
257         XtRemoveTimeOut (timer);
258         count++;
259         ASSERT_RET (count < 10000, "way too many timers to run");
260       }
261     }
262   }
263 }
264
265
266 static void
267 jwxyz_sources_run (jwxyz_sources_data *td)
268 {
269   if (td->fd_count == 0) return;
270
271   struct timeval tv = { 0, };
272   fd_set fds;
273   int i;
274   int max = 0;
275
276   FD_ZERO (&fds);
277   for (i = 0; i < FD_SETSIZE; i++) {
278     if (td->ids[i]) {
279       FD_SET (i, &fds);
280       max = i;
281     }
282   }
283
284   ASSERT_RET (max > 0, "no fds");
285
286   if (0 < select (max+1, &fds, NULL, NULL, &tv)) {
287     for (i = 0; i < FD_SETSIZE; i++) {
288       if (FD_ISSET (i, &fds)) {
289         XtInputId id = td->ids[i];
290         ASSERT_RET (id && id->cb, "sources corrupted");
291         ASSERT_RET (id->fd == i, "sources corrupted");
292         id->cb (id->closure, &id->fd, &id);
293       }
294     }
295   }
296 }
297
298
299 static void
300 jwxyz_XtRemoveInput_all (jwxyz_sources_data *td)
301 {
302   int i;
303   for (i = 0; i < FD_SETSIZE; i++) {
304     XtInputId id = td->ids[i];
305     if (id) XtRemoveInput (id);
306   }
307 }
308
309
310 static void
311 jwxyz_XtRemoveTimeOut_all (jwxyz_sources_data *td)
312 {
313   XtIntervalId timer, next;
314   int count = 0;
315
316   /* Iterate the timer list, being careful because XtRemoveTimeOut removes
317      the current item from that list. */
318   if (td->all_timers) {
319     for (timer = td->all_timers, next = timer->next;
320          timer;
321          timer = next, next = (timer ? timer->next : 0)) {
322       XtRemoveTimeOut (timer);
323       count++;
324       ASSERT_RET (count < 10000, "way too many timers to free");
325     }
326     ASSERT_RET (!td->all_timers, "timer list didn't empty");
327   }
328 }
329
330
331 void
332 jwxyz_sources_free (jwxyz_sources_data *td)
333 {
334   jwxyz_XtRemoveInput_all (td);
335   jwxyz_XtRemoveTimeOut_all (td);
336   memset (td, 0xA1, sizeof(*td));
337   free (td);
338 }
339
340
341 XtInputMask
342 XtAppPending (XtAppContext app)
343 {
344   return XtIMAlternateInput;  /* just always say yes */
345 }
346
347 void
348 XtAppProcessEvent (XtAppContext app, XtInputMask mask)
349 {
350   jwxyz_sources_data *td = DISPLAY_SOURCES_DATA (app);
351   if (mask & XtIMAlternateInput)
352     jwxyz_sources_run (td);
353   if (mask & XtIMTimer)
354     jwxyz_timers_run (td);
355 }
356
357 #endif /* HAVE_JWXYZ */