http://www.jwz.org/xscreensaver/xscreensaver-5.12.tar.gz
[xscreensaver] / hacks / webcollage-cocoa.m
1 /* xscreensaver, Copyright (c) 2006-2008 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 Cocoa shim for webcollage.
13
14     It runs the webcollage perl script
15     (in "WebCollage.saver/Contents/Resources/webcollage")
16     at the end of a pipe; each time that script updates the image file on
17     disk it prints the file name, and this program loads and displays that
18    image.
19
20     The script uses "WebCollage.saver/Contents/Resources/webcollage-helper"
21     to paste the images together in the usual way.
22   */
23
24 #include <math.h>
25 #include <unistd.h>
26 #include <stdlib.h>
27 #include <signal.h>
28 #import <Cocoa/Cocoa.h>
29
30 #include "screenhack.h"
31
32
33 typedef struct {
34   Display *dpy;
35   Window window;
36   XWindowAttributes xgwa;
37   int delay;
38   pid_t pid;
39   FILE *pipe_fd;
40   XtInputId pipe_id;
41   Bool verbose_p;
42 } state;
43
44
45 /* Violating the cardinal rule of "don't use global variables",
46    but we need to get at these from the atexit() handler, and
47    the callback doesn't take a closure arg.  Because apparently
48    those hadn't been invented yet in the seventies.
49  */
50 static state *all_states[50] = { 0, };
51
52 static void webcollage_atexit (void);
53 static void signal_handler (int sig);
54
55
56 static void
57 subproc_cb (XtPointer closure, int *source, XtInputId *id)
58 {
59   /* state *st = (state *) closure; */
60   /* st->input_available_p = True; */
61 }
62
63
64 /* whether there is data available to be read on the file descriptor
65  */
66 static int
67 input_available_p (int fd)
68 {
69   struct timeval tv = { 0, };
70   fd_set fds;
71 # if 0
72   /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
73   FD_ZERO (&fds);
74 # else
75   memset (&fds, 0, sizeof(fds));
76 # endif
77   FD_SET (fd, &fds);
78   return select (fd+1, &fds, NULL, NULL, &tv);
79 }
80
81
82 static void
83 display_image (state *st, const char *file)
84 {
85   NSImage *image = [[NSImage alloc] 
86                      initWithContentsOfFile:
87                        [NSString stringWithCString: file
88                                           encoding: NSUTF8StringEncoding]];
89
90   if (! image) {
91     fprintf (stderr, "webcollage: failed to load \"%s\"\n", file);
92     return;
93   }
94
95   [image drawAtPoint: NSMakePoint (0, 0)
96             fromRect: NSMakeRect (0, 0, [image size].width, [image size].height)
97            operation: NSCompositeCopy
98             fraction: 1.0];
99   [image release];
100 }
101
102
103 static void
104 open_pipe (state *st)
105 {
106   /* This mess is because popen() doesn't give us the pid.
107    */
108
109   pid_t forked;
110   int fds [2];
111   int in, out;
112   char buf[1024];
113
114   char *av[20];
115   int ac = 0;
116
117   int timeout   = get_integer_resource (st->dpy, "timeout", "Timeout");
118   int delay     = get_integer_resource (st->dpy, "delay",   "Delay");
119   float opacity = get_float_resource   (st->dpy, "opacity", "Opacity");
120   char *filter  = get_string_resource  (st->dpy, "filter",  "Filter");
121   char *filter2 = get_string_resource  (st->dpy, "filter2", "Filter2");
122
123   av[ac++] = "webcollage";
124   av[ac++] = "-cocoa";
125
126   av[ac++] = "-size";
127   sprintf (buf, "%dx%d", st->xgwa.width, st->xgwa.height);
128   av[ac++] = strdup (buf);
129
130   av[ac++] = "-timeout"; sprintf (buf, "%d", timeout);
131   av[ac++] = strdup (buf);
132   av[ac++] = "-delay";   sprintf (buf, "%d", delay);
133   av[ac++] = strdup (buf);
134   av[ac++] = "-opacity"; sprintf (buf, "%.2f", opacity);
135   av[ac++] = strdup (buf);
136
137   if (filter && *filter) {
138     av[ac++] = "-filter";
139     av[ac++] = filter;
140   }
141   if (filter2 && *filter2) {
142     av[ac++] = "-filter2";
143     av[ac++] = filter2;
144   }
145
146   av[ac] = 0;
147
148
149   if (st->verbose_p) {
150     fprintf (stderr, "webcollage: launching:");
151     int i;
152     for (i = 0; i < ac; i++)
153       fprintf (stderr, " %s", av[i]);
154     fprintf (stderr, "\n");
155   }
156
157
158   if (pipe (fds))
159     {
160       perror ("webcollage: error creating pipe");
161       exit (1);
162     }
163
164   in = fds [0];
165   out = fds [1];
166
167   switch ((int) (forked = fork ()))
168     {
169     case -1:
170       {
171         perror ("webcollage: couldn't fork");
172         exit (1);
173       }
174     case 0:
175       {
176         int stdout_fd = 1;
177
178         close (in);  /* don't need this one */
179
180         if (dup2 (out, stdout_fd) < 0)          /* pipe stdout */
181           {
182             perror ("could not dup() a new stdout:");
183             exit (1);
184           }
185
186         execvp (av[0], av);                     /* shouldn't return. */
187
188         if (errno != ENOENT)
189           {
190             /* Ignore "no such file or directory" errors, unless verbose.
191                Issue all other exec errors, though. */
192             sprintf (buf, "webcollage: %s", av[0]);
193             perror (buf);
194           }
195
196         exit (1);                              /* exits fork */
197         break;
198       }
199     default:
200       {
201         st->pipe_fd = fdopen (in, "r");
202         close (out);  /* don't need this one */
203       }
204     }
205
206   if (! st->pipe_fd) abort();
207
208   st->pid = forked;
209   st->pipe_id =
210     XtAppAddInput (XtDisplayToApplicationContext (st->dpy), 
211                    fileno (st->pipe_fd),
212                    (XtPointer) (XtInputReadMask | XtInputExceptMask),
213                    subproc_cb, (XtPointer) st);
214
215   if (st->verbose_p)
216     fprintf (stderr, "webcollage: subprocess pid: %d\n", st->pid);
217 }
218
219
220 static void *
221 webcollage_init (Display *dpy, Window window)
222 {
223   state *st = (state *) calloc (1, sizeof(*st));
224   int i;
225   st->dpy = dpy;
226   st->window = window;
227   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
228
229   st->delay = 1000000;  /* check once a second */
230
231   // Log to syslog when FPS is turned on.
232   st->verbose_p = get_boolean_resource (dpy, "doFPS", "DoFPS");
233
234
235   static int done_once = 0;
236   if (! done_once) {
237     done_once = 1;
238
239     if (atexit (webcollage_atexit)) {   // catch calls to exit()
240       perror ("webcollage: atexit");
241       exit (-1);
242     }
243
244     int sigs[] = { SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT,
245                    SIGFPE, SIGBUS, SIGSEGV, SIGSYS, /*SIGPIPE,*/
246                    SIGALRM, SIGTERM };
247     for (i = 0; i < sizeof(sigs)/sizeof(*sigs); i++) {
248       if (signal (sigs[i], signal_handler)) {
249         perror ("webcollage: signal");
250         exit (1);
251       }
252     }
253   }
254
255
256   open_pipe (st);
257
258   i = 0;
259   while (all_states[i]) i++;
260   all_states[i] = st;
261
262   return st;
263 }
264
265
266 static unsigned long
267 webcollage_draw (Display *dpy, Window window, void *closure)
268 {
269   state *st = (state *) closure;
270
271   if (! st->pipe_fd) 
272     exit (1);
273
274   if (! input_available_p (fileno (st->pipe_fd)))
275     return st->delay;
276
277   char buf[10240];
278   int n = read (fileno (st->pipe_fd),
279                 (void *) buf,
280                 sizeof(buf) - 1);
281   if (n <= 0)
282     {
283       XtRemoveInput (st->pipe_id);
284       st->pipe_id = 0;
285       // #### sometimes hangs -- pclose (st->pipe_fd);
286       st->pipe_fd = 0;
287
288       if (st->verbose_p)
289         fprintf (stderr, "webcollage: subprocess has exited: bailing.\n");
290
291       return st->delay * 10;
292     }
293
294   buf[n] = 0;
295   char *s = strchr (buf, '\n');
296   if (s) *s = 0;
297
298   const char *target = "COCOA LOAD ";
299   if (!strncmp (target, buf, strlen(target))) {
300     const char *file = buf + strlen(target);
301     display_image (st, file);
302   }
303
304   return st->delay;
305 }
306
307
308 static void
309 webcollage_reshape (Display *dpy, Window window, void *closure, 
310                     unsigned int w, unsigned int h)
311 {
312 }
313
314
315 static Bool
316 webcollage_event (Display *dpy, Window window, void *closure, XEvent *event)
317 {
318   return False;
319 }
320
321
322 static void
323 webcollage_atexit (void)
324 {
325   int i = 0;
326
327   if (all_states[0] && all_states[0]->verbose_p)
328     fprintf (stderr, "webcollage: atexit handler\n");
329
330   while (all_states[i]) {
331     state *st = all_states[i];
332     if (st->pid) {
333       if (st->verbose_p)
334         fprintf (stderr, "webcollage: kill %d\n", st->pid);
335       if (kill (st->pid, SIGTERM) < 0) {
336         fprintf (stderr, "webcollage: kill (%d, TERM): ", st->pid);
337         perror ("webcollage: kill");
338       }
339       st->pid = 0;
340     }
341     all_states[i] = 0;
342     i++;
343   }
344 }
345
346
347 static void
348 signal_handler (int sig)
349 {
350   if (all_states[0] && all_states[0]->verbose_p)
351     fprintf (stderr, "webcollage: signal %d\n", sig);
352   webcollage_atexit ();
353   exit (sig);
354 }
355
356
357 /* This is important because OSX doesn't actually kill the screen savers!
358    It just sends them a [ScreenSaverView stopAnimation] method.  Were
359    they to actually exit, the resultant SIGPIPE should reap the children,
360    but instead, we need to do it here.
361
362    On top of that, there's an atexit() handler because otherwise the
363    inferior perl script process was failing to die when SaverTester or
364    System Preferences exited.  I don't pretend to understand.
365
366    It still fails to clean up when I hit the stop button in Xcode.
367    WTF.
368  */
369 static void
370 webcollage_free (Display *dpy, Window window, void *closure)
371 {
372   state *st = (state *) closure;
373
374   if (st->verbose_p)
375     fprintf (stderr, "webcollage: free cb\n");
376
377   // Dammit dammit dammit!  Why won't this shit die when we pclose it!
378 //  killpg (0, SIGTERM);
379
380   webcollage_atexit();
381
382   if (st->pipe_id)
383     XtRemoveInput (st->pipe_id);
384
385   if (st->pipe_fd)
386     fclose (st->pipe_fd);
387
388   // Reap zombies.
389 # undef sleep
390   sleep (1);
391   int wait_status = 0;
392   waitpid (-1, &wait_status, 0);
393
394   free (st);
395 }
396
397
398 static const char *webcollage_defaults [] = {
399   ".background:         black",
400   ".foreground:         white",
401
402   "*timeout:            30",
403   "*delay:              2",
404   "*opacity:            0.85",
405   "*filter:             ",
406   "*filter2:            ",
407   0
408 };
409
410 static XrmOptionDescRec webcollage_options [] = {
411   { "-timeout",         ".timeout",     XrmoptionSepArg, 0 },
412   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
413   { "-opacity",         ".opacity",     XrmoptionSepArg, 0 },
414   { "-filter",          ".filter",      XrmoptionSepArg, 0 },
415   { "-filter2",         ".filter2",     XrmoptionSepArg, 0 },
416   { 0, 0, 0, 0 }
417 };
418
419
420 XSCREENSAVER_MODULE ("WebCollage", webcollage)