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