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