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