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