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