1 /* xscreensaver, Copyright (c) 2006-2014 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
21 #import "jwxyz-timers.h"
26 # define LOGT( str,arg1) NSLog(str,arg1)
27 # define LOGT2(str,arg1,arg2) NSLog(str,arg1,arg2)
29 # define LOGT( str,arg1)
30 # define LOGT2(str,arg1,arg2)
34 # define LOGI( str,arg1,arg2) NSLog(str,arg1,arg2)
35 # define LOGI2(str,arg1,arg2,arg3) NSLog(str,arg1,arg2,arg3)
37 # define LOGI( str,arg1,arg2)
38 # define LOGI2(str,arg1,arg2,arg3)
41 #define ASSERT_RET(C,S) do { \
43 jwxyz_abort ("jwxyz-timers: %s",(S)); \
49 XtDisplayToApplicationContext (Display *dpy)
51 return (XtAppContext) dpy;
54 #define app_to_display(APP) ((Display *) (APP))
57 struct jwxyz_sources_data {
59 XtInputId ids[FD_SETSIZE];
60 struct jwxyz_XtIntervalId *all_timers;
63 struct jwxyz_XtIntervalId {
65 CFRunLoopTimerRef cftimer;
68 XtTimerCallbackProc cb;
71 struct jwxyz_XtIntervalId *next;
74 struct jwxyz_XtInputId {
78 XtInputCallbackProc cb;
85 jwxyz_timer_retain (const void *arg)
87 struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) arg;
89 LOGT2(@"timer 0x%08X: retain %d", (unsigned int) data, data->refcount);
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).
97 jwxyz_timer_release (const void *arg)
99 struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) arg;
100 jwxyz_sources_data *td = display_sources_data (app_to_display (data->app));
103 LOGT2(@"timer 0x%08X: release %d", (unsigned int) data, data->refcount);
104 ASSERT_RET (data->refcount >= 0, "double free");
106 if (data->refcount == 0) {
108 // Remove it from the list of live timers.
109 XtIntervalId prev, timer;
111 for (timer = td->all_timers, prev = 0;
113 prev = timer, timer = timer->next) {
115 ASSERT_RET (!hit, "circular timer list");
117 prev->next = timer->next;
119 td->all_timers = timer->next;
123 ASSERT_RET (timer->refcount > 0, "timer list corrupted");
132 jwxyz_source_retain (const void *arg)
134 struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *) arg;
136 LOGI2(@"source 0x%08X %2d: retain %d", (unsigned int) data, data->fd,
142 jwxyz_source_release (const void *arg)
144 struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *) arg;
146 LOGI2(@"source 0x%08X %2d: release %d", (unsigned int) data, data->fd,
148 ASSERT_RET (data->refcount >= 0, "double free");
149 if (data->refcount == 0) {
150 memset (data, 0xA1, sizeof(*data));
158 jwxyz_timer_cb (CFRunLoopTimerRef timer, void *arg)
160 struct jwxyz_XtIntervalId *data = (struct jwxyz_XtIntervalId *) arg;
161 LOGT(@"timer 0x%08X: fire", (unsigned int) data);
162 data->cb (data->closure, &data);
164 // Our caller (__CFRunLoopDoTimer) will now call CFRunLoopTimerInvalidate,
165 // which will call jwxyz_timer_release.
170 XtAppAddTimeOut (XtAppContext app, unsigned long msecs,
171 XtTimerCallbackProc cb, XtPointer closure)
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));
178 data->closure = closure;
180 LOGT2(@"timer 0x%08X: alloc %lu", (unsigned int) data, msecs);
182 CFRunLoopTimerContext ctx = { 0, };
184 ctx.retain = jwxyz_timer_retain;
185 ctx.release = jwxyz_timer_release;
187 CFAbsoluteTime time = CFAbsoluteTimeGetCurrent() + (msecs / 1000.0);
190 CFRunLoopTimerCreate (NULL, // allocator
191 time, 0, 0, 0, // interval, flags, order
192 jwxyz_timer_cb, &ctx);
193 // CFRunLoopTimerCreate called jwxyz_timer_retain.
195 data->next = td->all_timers;
196 td->all_timers = data;
198 CFRunLoopAddTimer (CFRunLoopGetCurrent(), data->cftimer,
199 kCFRunLoopCommonModes);
205 XtRemoveTimeOut (XtIntervalId id)
207 LOGT(@"timer 0x%08X: remove", (unsigned int) id);
208 ASSERT_RET (id->refcount > 0, "already freed");
209 ASSERT_RET (id->cftimer, "timers corrupted");
211 CFRunLoopRemoveTimer (CFRunLoopGetCurrent(), id->cftimer,
212 kCFRunLoopCommonModes);
213 CFRunLoopTimerInvalidate (id->cftimer);
214 // CFRunLoopTimerInvalidate called jwxyz_timer_release.
219 jwxyz_sources_init (XtAppContext app)
221 jwxyz_sources_data *td = (jwxyz_sources_data *) calloc (1, sizeof (*td));
226 jwxyz_source_select (XtInputId id)
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;
236 jwxyz_source_deselect (XtInputId id)
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");
247 jwxyz_sources_run (jwxyz_sources_data *td)
249 if (td->fd_count == 0) return;
251 struct timeval tv = { 0, };
257 for (i = 0; i < FD_SETSIZE; i++) {
264 ASSERT_RET (max > 0, "no fds");
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);
280 XtAppAddInput (XtAppContext app, int fd, XtPointer flags,
281 XtInputCallbackProc cb, XtPointer closure)
283 struct jwxyz_XtInputId *data = (struct jwxyz_XtInputId *)
284 calloc (1, sizeof(*data));
287 data->closure = closure;
289 LOGI(@"source 0x%08X %2d: alloc", (unsigned int) data, data->fd);
292 jwxyz_source_retain (data);
293 jwxyz_source_select (data);
299 XtRemoveInput (XtInputId id)
301 LOGI(@"source 0x%08X %2d: remove", (unsigned int) id, id->fd);
302 ASSERT_RET (id->refcount > 0, "sources corrupted");
304 jwxyz_source_deselect (id);
305 jwxyz_source_release (id);
309 jwxyz_XtRemoveInput_all (jwxyz_sources_data *td)
312 for (i = 0; i < FD_SETSIZE; i++) {
313 XtInputId id = td->ids[i];
314 if (id) XtRemoveInput (id);
320 jwxyz_XtRemoveTimeOut_all (jwxyz_sources_data *td)
322 struct jwxyz_XtIntervalId *timer, *next;
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;
330 timer = next, next = (timer ? timer->next : 0)) {
331 XtRemoveTimeOut (timer);
333 ASSERT_RET (count < 10000, "way too many timers to free");
335 ASSERT_RET (!td->all_timers, "timer list didn't empty");
341 jwxyz_sources_free (jwxyz_sources_data *td)
343 jwxyz_XtRemoveInput_all (td);
344 jwxyz_XtRemoveTimeOut_all (td);
345 memset (td, 0xA1, sizeof(*td));
351 XtAppPending (XtAppContext app)
353 return XtIMAlternateInput; /* just always say yes */
357 XtAppProcessEvent (XtAppContext app, XtInputMask mask)
359 jwxyz_sources_data *td = display_sources_data (app_to_display (app));
360 jwxyz_sources_run (td);