1 /* xscreensaver, Copyright (c) 2006-2012 Jamie Zawinski <jwz@jwz.org>
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
12 /* This is the OSX implementation of Xt timers, for libjwxyz.
15 //#define DEBUG_TIMERS
16 //#define DEBUG_SOURCES
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.
25 #undef USE_COCOA_SOURCES
30 #ifdef USE_COCOA_SOURCES
31 # import <Cocoa/Cocoa.h>
35 #import "jwxyz-timers.h"
40 # define LOGT(str,arg1,arg2) NSLog(str,arg1,arg2)
42 # define LOGT(str,arg1,arg2)
46 # define LOGI(str,arg1,arg2,arg3) NSLog(str,arg1,arg2,arg3)
48 # define LOGI(str,arg1,arg2,arg3)
51 #define ASSERT_RET(C,S) do { \
53 jwxyz_abort ("jwxyz-timers: %s",(S)); \
59 XtDisplayToApplicationContext (Display *dpy)
61 return (XtAppContext) dpy;
64 #define app_to_display(APP) ((Display *) (APP))
67 struct jwxyz_XtIntervalId {
68 CFRunLoopTimerRef cftimer;
71 XtTimerCallbackProc cb;
75 struct jwxyz_XtInputId {
76 # ifdef USE_COCOA_SOURCES
77 CFRunLoopSourceRef cfsource;
84 XtInputCallbackProc cb;
91 jwxyz_timer_retain (const void *arg)
93 struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) arg;
95 LOGT(@"timer 0x%08X: retain %d", (unsigned int) data, data->refcount);
100 jwxyz_timer_release (const void *arg)
102 struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) arg;
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);
110 jwxyz_source_retain (const void *arg)
112 struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *) arg;
114 LOGI(@"source 0x%08X %2d: retain %d", (unsigned int) data, data->fd,
120 jwxyz_source_release (const void *arg)
122 struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *) arg;
124 LOGI(@"source 0x%08X %2d: release %d", (unsigned int) data, data->fd,
126 ASSERT_RET (data->refcount >= 0, "double free");
127 if (data->refcount == 0) {
128 # ifdef USE_COCOA_SOURCES
130 CFRelease (data->socket);
132 CFRelease (data->cfsource);
133 # endif /* USE_COCOA_SOURCES */
134 memset (data, 0xA1, sizeof(*data));
142 jwxyz_timer_cb (CFRunLoopTimerRef timer, void *arg)
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);
148 // Our caller (__CFRunLoopDoTimer) will now call CFRunLoopTimerInvalidate,
149 // which will call jwxyz_timer_release.
153 #ifdef USE_COCOA_SOURCES
155 /* whether there is data available to be read on the file descriptor
158 input_available_p (int fd)
160 struct timeval tv = { 0, };
164 return select (fd+1, &fds, NULL, NULL, &tv);
169 jwxyz_source_cb (CFSocketRef s, CFSocketCallBackType type,
170 CFDataRef address, const void *call_data, void *info)
172 struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *) info;
174 ASSERT_RET (type == kCFSocketReadCallBack, "unnknown type");
175 ASSERT_RET (!call_data, "no call data"); // not used for kCFSocketRead
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.
180 if (! input_available_p (data->fd)) {
181 LOGI(@"source 0x%08X %2d: false alarm!", (unsigned int) data, data->fd, 0);
185 LOGI(@"source 0x%08X %2d: fire", (unsigned int) data, data->fd, 0);
187 data->cb (data->closure, &data->fd, &data);
190 #endif /* USE_COCOA_SOURCES */
194 XtAppAddTimeOut (XtAppContext app, unsigned long msecs,
195 XtTimerCallbackProc cb, XtPointer closure)
197 struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *)
198 calloc (1, sizeof(*data));
200 data->closure = closure;
202 LOGT(@"timer 0x%08X: alloc %d", (unsigned int) data, msecs);
204 CFRunLoopTimerContext ctx = { 0, };
206 ctx.retain = jwxyz_timer_retain;
207 ctx.release = jwxyz_timer_release;
209 CFAbsoluteTime time = CFAbsoluteTimeGetCurrent() + (msecs / 1000.0);
212 CFRunLoopTimerCreate (NULL, // allocator
213 time, 0, 0, 0, // interval, flags, order
214 jwxyz_timer_cb, &ctx);
215 // CFRunLoopTimerCreate called jwxyz_timer_retain.
217 CFRunLoopAddTimer (CFRunLoopGetCurrent(), data->cftimer,
218 kCFRunLoopCommonModes);
224 XtRemoveTimeOut (XtIntervalId id)
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.
236 #ifndef USE_COCOA_SOURCES
238 struct jwxyz_sources_data {
240 XtInputId ids[FD_SETSIZE];
244 jwxyz_sources_init (XtAppContext app)
246 jwxyz_sources_data *td = (jwxyz_sources_data *) calloc (1, sizeof (*td));
251 jwxyz_sources_free (jwxyz_sources_data *td)
258 jwxyz_source_select (XtInputId id)
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;
268 jwxyz_source_deselect (XtInputId id)
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");
279 jwxyz_sources_run (jwxyz_sources_data *td)
281 if (td->count == 0) return;
283 struct timeval tv = { 0, };
289 for (i = 0; i < FD_SETSIZE; i++) {
296 ASSERT_RET (max > 0, "no fds");
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);
310 #endif /* !USE_COCOA_SOURCES */
314 XtAppAddInput (XtAppContext app, int fd, XtPointer flags,
315 XtInputCallbackProc cb, XtPointer closure)
317 struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *)
318 calloc (1, sizeof(*data));
321 data->closure = closure;
323 LOGI(@"source 0x%08X %2d: alloc", (unsigned int) data, data->fd, 0);
325 # ifdef USE_COCOA_SOURCES
327 CFSocketContext ctx = { 0, };
329 ctx.retain = jwxyz_source_retain;
330 ctx.release = jwxyz_source_release;
332 data->socket = CFSocketCreateWithNative (NULL, fd, kCFSocketReadCallBack,
333 jwxyz_source_cb, &ctx);
334 // CFSocketCreateWithNative called jwxyz_source_retain.
336 CFSocketSetSocketFlags (data->socket,
337 kCFSocketAutomaticallyReenableReadCallBack
339 // not kCFSocketCloseOnInvalidate.
341 data->cfsource = CFSocketCreateRunLoopSource (NULL, data->socket, 0);
343 CFRunLoopAddSource (CFRunLoopGetCurrent(), data->cfsource,
344 kCFRunLoopCommonModes);
346 # else /* !USE_COCOA_SOURCES */
349 jwxyz_source_retain (data);
350 jwxyz_source_select (data);
352 # endif /* !USE_COCOA_SOURCES */
358 XtRemoveInput (XtInputId id)
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");
366 CFRunLoopRemoveSource (CFRunLoopGetCurrent(), id->cfsource,
367 kCFRunLoopCommonModes);
368 CFSocketInvalidate (id->socket);
369 // CFSocketInvalidate called jwxyz_source_release.
371 # else /* !USE_COCOA_SOURCES */
373 jwxyz_source_deselect (id);
374 jwxyz_source_release (id);
376 # endif /* !USE_COCOA_SOURCES */
380 jwxyz_XtRemoveInput_all (Display *dpy)
382 # ifdef USE_COCOA_SOURCES
383 ASSERT_RET (0, "unimplemented");
384 # else /* !USE_COCOA_SOURCES */
386 jwxyz_sources_data *td = display_sources_data (dpy);
388 for (i = 0; i < FD_SETSIZE; i++) {
389 XtInputId id = td->ids[i];
390 if (id) XtRemoveInput (id);
393 # endif /* !USE_COCOA_SOURCES */
398 XtAppPending (XtAppContext app)
400 return XtIMAlternateInput; /* just always say yes */
404 XtAppProcessEvent (XtAppContext app, XtInputMask mask)
406 jwxyz_sources_data *td = display_sources_data (app_to_display (app));
407 jwxyz_sources_run (td);