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