6e478f61725b1104be9f66f54bf7761e8d3f0b8f
[xscreensaver] / OSX / jwxyz-timers.m
1 /* xscreensaver, Copyright (c) 2006-2012 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 /* If this is defined, we implement timers in terms of CFRunLoopTimerCreate.
19    But I couldn't get that to work right: when the left window was accepting
20    input, the right window would starve.  So I implemented them in terms of
21    select() instead.  This is more efficient anyway, since instead of the
22    event loop telling us that input is ready constantly, we only check once
23    just before the draw method is called.
24  */
25 #undef USE_COCOA_SOURCES
26
27
28 #import <stdlib.h>
29
30 #ifdef USE_COCOA_SOURCES
31 # import <Cocoa/Cocoa.h>
32 #endif
33
34 #import "jwxyz.h"
35 #import "jwxyz-timers.h"
36
37
38
39 #ifdef DEBUG_TIMERS
40 # define LOGT(str,arg1,arg2) NSLog(str,arg1,arg2)
41 #else
42 # define LOGT(str,arg1,arg2)
43 #endif
44
45 #ifdef DEBUG_SOURCES
46 # define LOGI(str,arg1,arg2,arg3) NSLog(str,arg1,arg2,arg3)
47 #else
48 # define LOGI(str,arg1,arg2,arg3)
49 #endif
50
51
52 XtAppContext
53 XtDisplayToApplicationContext (Display *dpy)
54 {
55   return (XtAppContext) dpy;
56 }
57
58 #define app_to_display(APP) ((Display *) (APP))
59
60
61 struct jwxyz_XtIntervalId {
62   CFRunLoopTimerRef cftimer;
63   int refcount;
64
65   XtTimerCallbackProc cb;
66   XtPointer closure;
67 };
68
69 struct jwxyz_XtInputId {
70 # ifdef USE_COCOA_SOURCES
71   CFRunLoopSourceRef cfsource;
72   CFSocketRef socket;
73 # else
74   XtAppContext app;
75 # endif
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   LOGT(@"timer  0x%08X: retain %d", (unsigned int) data, data->refcount);
90   return arg;
91 }
92
93 static void
94 jwxyz_timer_release (const void *arg)
95 {
96   struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) arg;
97   data->refcount--;
98   LOGT(@"timer  0x%08X: release %d", (unsigned int) data, data->refcount);
99   if (data->refcount < 0) abort();
100   if (data->refcount == 0) free (data);
101 }
102
103 static const void *
104 jwxyz_source_retain (const void *arg)
105 {
106   struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *) arg;
107   data->refcount++;
108   LOGI(@"source 0x%08X %2d: retain %d", (unsigned int) data, data->fd, 
109        data->refcount);
110   return arg;
111 }
112
113 static void
114 jwxyz_source_release (const void *arg)
115 {
116   struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *) arg;
117   data->refcount--;
118   LOGI(@"source 0x%08X %2d: release %d", (unsigned int) data, data->fd,
119        data->refcount);
120   if (data->refcount < 0) abort();
121   if (data->refcount == 0) {
122 # ifdef USE_COCOA_SOURCES
123     if (data->socket)
124       CFRelease (data->socket);
125     if (data->cfsource)
126       CFRelease (data->cfsource);
127 # endif /* USE_COCOA_SOURCES */
128     memset (data, 0xA1, sizeof(*data));
129     data->fd = -666;
130     free (data);
131   }
132 }
133
134
135 static void
136 jwxyz_timer_cb (CFRunLoopTimerRef timer, void *arg)
137 {
138   struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) arg;
139   LOGT(@"timer  0x%08X: fire", (unsigned int) data, 0);
140   data->cb (data->closure, &data);
141
142   // Our caller (__CFRunLoopDoTimer) will now call CFRunLoopTimerInvalidate,
143   // which will call jwxyz_timer_release.
144 }
145
146
147 #ifdef USE_COCOA_SOURCES
148
149 /* whether there is data available to be read on the file descriptor
150  */
151 static int
152 input_available_p (int fd)
153 {
154   struct timeval tv = { 0, };
155   fd_set fds;
156   FD_ZERO (&fds);
157   FD_SET (fd, &fds);
158   return select (fd+1, &fds, NULL, NULL, &tv);
159 }
160
161
162 static void
163 jwxyz_source_cb (CFSocketRef s, CFSocketCallBackType type,
164                  CFDataRef address, const void *call_data, void *info)
165 {
166   struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *) info;
167
168   if (type != kCFSocketReadCallBack) abort();
169   if (call_data != 0) abort();  // not used for kCFSocketRead
170
171   // We are sometimes called when there is not, in fact, data available!
172   // So don't call data->cb if we're being fed a pack of lies.
173   //
174   if (! input_available_p (data->fd)) {
175     LOGI(@"source 0x%08X %2d: false alarm!", (unsigned int) data, data->fd, 0);
176     return;
177   }
178
179   LOGI(@"source 0x%08X %2d: fire", (unsigned int) data, data->fd, 0);
180
181   data->cb (data->closure, &data->fd, &data);
182 }
183
184 #endif /* USE_COCOA_SOURCES */
185
186
187 XtIntervalId
188 XtAppAddTimeOut (XtAppContext app, unsigned long msecs,
189                  XtTimerCallbackProc cb, XtPointer closure)
190 {
191   struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *)
192     calloc (1, sizeof(*data));
193   data->cb = cb;
194   data->closure = closure;
195
196   LOGT(@"timer  0x%08X: alloc %d", (unsigned int) data, msecs);
197   
198   CFRunLoopTimerContext ctx = { 0, };
199   ctx.info    = data;
200   ctx.retain  = jwxyz_timer_retain;
201   ctx.release = jwxyz_timer_release;
202
203   CFAbsoluteTime time = CFAbsoluteTimeGetCurrent() + (msecs / 1000.0);
204
205   data->cftimer =
206     CFRunLoopTimerCreate (NULL, // allocator
207                           time, 0, 0, 0, // interval, flags, order
208                           jwxyz_timer_cb, &ctx);
209   // CFRunLoopTimerCreate called jwxyz_timer_retain.
210
211   CFRunLoopAddTimer (CFRunLoopGetCurrent(), data->cftimer,
212                      kCFRunLoopCommonModes);
213   return data;
214 }
215
216
217 void
218 XtRemoveTimeOut (XtIntervalId id)
219 {
220   LOGT(@"timer  0x%08X: remove", (unsigned int) id, 0);
221   if (id->refcount <= 0) abort();
222   if (!id->cftimer) abort();
223
224   CFRunLoopRemoveTimer (CFRunLoopGetCurrent(), id->cftimer,
225                         kCFRunLoopCommonModes);
226   CFRunLoopTimerInvalidate (id->cftimer);
227   // CFRunLoopTimerInvalidate called jwxyz_timer_release.
228 }
229
230
231 #ifndef USE_COCOA_SOURCES
232
233 struct jwxyz_sources_data {
234   int count;
235   XtInputId ids[FD_SETSIZE];
236 };
237
238 jwxyz_sources_data *
239 jwxyz_sources_init (XtAppContext app)
240 {
241   jwxyz_sources_data *td = (jwxyz_sources_data *) calloc (1, sizeof (*td));
242   return td;
243 }
244
245 void
246 jwxyz_sources_free (jwxyz_sources_data *td)
247 {
248   free (td);
249 }
250
251
252 static void
253 jwxyz_source_select (XtInputId id)
254 {
255   jwxyz_sources_data *td = display_sources_data (app_to_display (id->app));
256   if (id->fd <= 0 || id->fd >= FD_SETSIZE) abort();
257   if (td->ids[id->fd]) abort();
258   td->ids[id->fd] = id;
259   td->count++;
260 }
261
262 static void
263 jwxyz_source_deselect (XtInputId id)
264 {
265   jwxyz_sources_data *td = display_sources_data (app_to_display (id->app));
266   if (td->count <= 0) abort();
267   if (id->fd <= 0 || id->fd >= FD_SETSIZE) abort();
268   if (td->ids[id->fd] != id) abort();
269   td->ids[id->fd] = 0;
270   td->count--;
271 }
272
273 void
274 jwxyz_sources_run (jwxyz_sources_data *td)
275 {
276   if (td->count == 0) return;
277
278   struct timeval tv = { 0, };
279   fd_set fds;
280   int i;
281   int max = 0;
282
283   FD_ZERO (&fds);
284   for (i = 0; i < FD_SETSIZE; i++) {
285     if (td->ids[i]) {
286       FD_SET (i, &fds);
287       max = i;
288     }
289   }
290
291   if (!max) abort();
292
293   if (0 < select (max+1, &fds, NULL, NULL, &tv)) {
294     for (i = 0; i < FD_SETSIZE; i++) {
295       if (FD_ISSET (i, &fds)) {
296         XtInputId id = td->ids[i];
297         if (!id || !id->cb) abort();
298         if (id->fd != i) abort();
299         id->cb (id->closure, &id->fd, &id);
300       }
301     }
302   }
303 }
304
305 #endif /* !USE_COCOA_SOURCES */
306
307
308 XtInputId
309 XtAppAddInput (XtAppContext app, int fd, XtPointer flags,
310                XtInputCallbackProc cb, XtPointer closure)
311 {
312   struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *)
313     calloc (1, sizeof(*data));
314   data->cb = cb;
315   data->fd = fd;
316   data->closure = closure;
317
318   LOGI(@"source 0x%08X %2d: alloc", (unsigned int) data, data->fd, 0);
319
320 # ifdef USE_COCOA_SOURCES
321
322   CFSocketContext ctx = { 0, };
323   ctx.info    = data;
324   ctx.retain  = jwxyz_source_retain;
325   ctx.release = jwxyz_source_release;
326
327   data->socket = CFSocketCreateWithNative (NULL, fd, kCFSocketReadCallBack,
328                                            jwxyz_source_cb, &ctx);
329   // CFSocketCreateWithNative called jwxyz_source_retain.
330   
331   CFSocketSetSocketFlags (data->socket,
332                           kCFSocketAutomaticallyReenableReadCallBack
333                           );
334   // not kCFSocketCloseOnInvalidate.
335
336   data->cfsource = CFSocketCreateRunLoopSource (NULL, data->socket, 0);
337   
338   CFRunLoopAddSource (CFRunLoopGetCurrent(), data->cfsource,
339                      kCFRunLoopCommonModes);
340   
341 # else  /* !USE_COCOA_SOURCES */
342
343   data->app = app;
344   jwxyz_source_retain (data);
345   jwxyz_source_select (data);
346
347 # endif /* !USE_COCOA_SOURCES */
348
349   return data;
350 }
351
352 void
353 XtRemoveInput (XtInputId id)
354 {
355   LOGI(@"source 0x%08X %2d: remove", (unsigned int) id, id->fd, 0);
356   if (id->refcount <= 0) abort();
357 # ifdef USE_COCOA_SOURCES
358   if (! id->cfsource) abort();
359   if (! id->socket) abort();
360
361   CFRunLoopRemoveSource (CFRunLoopGetCurrent(), id->cfsource,
362                          kCFRunLoopCommonModes);
363   CFSocketInvalidate (id->socket);
364   // CFSocketInvalidate called jwxyz_source_release.
365
366 # else  /* !USE_COCOA_SOURCES */
367
368   jwxyz_source_deselect (id);
369   jwxyz_source_release (id);
370
371 # endif /* !USE_COCOA_SOURCES */
372 }
373
374 void
375 jwxyz_XtRemoveInput_all (Display *dpy)
376 {
377 # ifdef USE_COCOA_SOURCES
378   abort();
379 # else  /* !USE_COCOA_SOURCES */
380
381   jwxyz_sources_data *td = display_sources_data (dpy);
382   int i;
383   for (i = 0; i < FD_SETSIZE; i++) {
384     XtInputId id = td->ids[i];
385     if (id) XtRemoveInput (id);
386   }
387
388 # endif /* !USE_COCOA_SOURCES */
389 }
390
391
392 XtInputMask
393 XtAppPending (XtAppContext app)
394 {
395   return XtIMAlternateInput;  /* just always say yes */
396 }
397
398 void
399 XtAppProcessEvent (XtAppContext app, XtInputMask mask)
400 {
401   jwxyz_sources_data *td = display_sources_data (app_to_display (app));
402   jwxyz_sources_run (td);
403 }