From http://www.jwz.org/xscreensaver/xscreensaver-5.31.tar.gz
[xscreensaver] / hacks / webcollage-cocoa.m
1 /* xscreensaver, Copyright (c) 2006-2014 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 (Display *dpy, Window window, 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   CGFloat w = [image size].width;
96   CGFloat h = [image size].height;
97   if (w <= 1 || h <= 1) {
98     fprintf (stderr, "webcollage: unparsable image \"%s\"\n", file);
99     return;
100   }
101
102   jwxyz_draw_NSImage_or_CGImage (dpy, window, True, image, 0, 0);
103   [image release];
104 }
105
106
107 static void
108 open_pipe (state *st)
109 {
110   /* This mess is because popen() doesn't give us the pid.
111    */
112
113   pid_t forked;
114   int fds [2];
115   int in, out;
116   char buf[1024];
117
118   char *av[20];
119   int ac = 0;
120
121   int timeout   = get_integer_resource (st->dpy, "timeout", "Timeout");
122   int delay     = get_integer_resource (st->dpy, "delay",   "Delay");
123   float opacity = get_float_resource   (st->dpy, "opacity", "Opacity");
124   char *filter  = get_string_resource  (st->dpy, "filter",  "Filter");
125   char *filter2 = get_string_resource  (st->dpy, "filter2", "Filter2");
126
127   av[ac++] = "webcollage";
128   av[ac++] = "-cocoa";
129
130   av[ac++] = "-size";
131   sprintf (buf, "%dx%d", st->xgwa.width, st->xgwa.height);
132   av[ac++] = strdup (buf);
133
134   av[ac++] = "-timeout"; sprintf (buf, "%d", timeout);
135   av[ac++] = strdup (buf);
136   av[ac++] = "-delay";   sprintf (buf, "%d", delay);
137   av[ac++] = strdup (buf);
138   av[ac++] = "-opacity"; sprintf (buf, "%.2f", opacity);
139   av[ac++] = strdup (buf);
140
141   if (filter && *filter) {
142     av[ac++] = "-filter";
143     av[ac++] = filter;
144   }
145   if (filter2 && *filter2) {
146     av[ac++] = "-filter2";
147     av[ac++] = filter2;
148   }
149
150   av[ac] = 0;
151
152
153   if (st->verbose_p) {
154     fprintf (stderr, "webcollage: launching:");
155     int i;
156     for (i = 0; i < ac; i++)
157       fprintf (stderr, " %s", av[i]);
158     fprintf (stderr, "\n");
159   }
160
161
162   if (pipe (fds))
163     {
164       perror ("webcollage: error creating pipe");
165       exit (1);
166     }
167
168   in = fds [0];
169   out = fds [1];
170
171   switch ((int) (forked = fork ()))
172     {
173     case -1:
174       {
175         perror ("webcollage: couldn't fork");
176         exit (1);
177       }
178     case 0:
179       {
180         int stdout_fd = 1;
181
182         close (in);  /* don't need this one */
183
184         if (dup2 (out, stdout_fd) < 0)          /* pipe stdout */
185           {
186             perror ("could not dup() a new stdout:");
187             exit (1);
188           }
189
190         execvp (av[0], av);                     /* shouldn't return. */
191
192         if (errno != ENOENT)
193           {
194             /* Ignore "no such file or directory" errors, unless verbose.
195                Issue all other exec errors, though. */
196             sprintf (buf, "webcollage: %s", av[0]);
197             perror (buf);
198           }
199
200         exit (1);                              /* exits fork */
201         break;
202       }
203     default:
204       {
205         st->pipe_fd = fdopen (in, "r");
206         close (out);  /* don't need this one */
207       }
208     }
209
210   if (! st->pipe_fd) abort();
211
212   st->pid = forked;
213   st->pipe_id =
214     XtAppAddInput (XtDisplayToApplicationContext (st->dpy), 
215                    fileno (st->pipe_fd),
216                    (XtPointer) (XtInputReadMask | XtInputExceptMask),
217                    subproc_cb, (XtPointer) st);
218
219   if (st->verbose_p)
220     fprintf (stderr, "webcollage: subprocess pid: %d\n", st->pid);
221 }
222
223
224 static void *
225 webcollage_init (Display *dpy, Window window)
226 {
227   state *st = (state *) calloc (1, sizeof(*st));
228   int i;
229   st->dpy = dpy;
230   st->window = window;
231   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
232
233   st->delay = 1000000;  /* check once a second */
234
235   // Log to syslog when FPS is turned on.
236   st->verbose_p = get_boolean_resource (dpy, "doFPS", "DoFPS");
237
238
239   static int done_once = 0;
240   if (! done_once) {
241     done_once = 1;
242
243     if (atexit (webcollage_atexit)) {   // catch calls to exit()
244       perror ("webcollage: atexit");
245       exit (-1);
246     }
247
248     int sigs[] = { SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT,
249                    SIGFPE, SIGBUS, SIGSEGV, SIGSYS, /*SIGPIPE,*/
250                    SIGALRM, SIGTERM };
251     for (i = 0; i < sizeof(sigs)/sizeof(*sigs); i++) {
252       if (signal (sigs[i], signal_handler)) {
253         perror ("webcollage: signal");
254         //exit (1);
255       }
256     }
257   }
258
259
260   open_pipe (st);
261
262   i = 0;
263   while (all_states[i]) i++;
264   all_states[i] = st;
265
266   return st;
267 }
268
269
270 static unsigned long
271 webcollage_draw (Display *dpy, Window window, void *closure)
272 {
273   state *st = (state *) closure;
274
275   if (! st->pipe_fd) 
276     exit (1);
277
278   if (! input_available_p (fileno (st->pipe_fd)))
279     return st->delay;
280
281   char buf[10240];
282   int n = read (fileno (st->pipe_fd),
283                 (void *) buf,
284                 sizeof(buf) - 1);
285   if (n <= 0)
286     {
287       XtRemoveInput (st->pipe_id);
288       st->pipe_id = 0;
289       // #### sometimes hangs -- pclose (st->pipe_fd);
290       st->pipe_fd = 0;
291
292       if (st->verbose_p)
293         fprintf (stderr, "webcollage: subprocess has exited: bailing.\n");
294
295       return st->delay * 10;
296     }
297
298   buf[n] = 0;
299   char *s = strchr (buf, '\n');
300   if (s) *s = 0;
301
302   const char *target = "COCOA LOAD ";
303   if (!strncmp (target, buf, strlen(target))) {
304     const char *file = buf + strlen(target);
305     display_image (dpy, window, st, file);
306   }
307
308   return st->delay;
309 }
310
311
312 static void
313 webcollage_reshape (Display *dpy, Window window, void *closure, 
314                     unsigned int w, unsigned int h)
315 {
316 }
317
318
319 static Bool
320 webcollage_event (Display *dpy, Window window, void *closure, XEvent *event)
321 {
322   return False;
323 }
324
325
326 static void
327 webcollage_atexit (void)
328 {
329   int i = 0;
330
331   if (all_states[0] && all_states[0]->verbose_p)
332     fprintf (stderr, "webcollage: atexit handler\n");
333
334   while (all_states[i]) {
335     state *st = all_states[i];
336     if (st->pid) {
337       if (st->verbose_p)
338         fprintf (stderr, "webcollage: kill %d\n", st->pid);
339       if (kill (st->pid, SIGTERM) < 0) {
340         fprintf (stderr, "webcollage: kill (%d, TERM): ", st->pid);
341         perror ("webcollage: kill");
342       }
343       st->pid = 0;
344     }
345     all_states[i] = 0;
346     i++;
347   }
348 }
349
350
351 static void
352 signal_handler (int sig)
353 {
354   if (all_states[0] && all_states[0]->verbose_p)
355     fprintf (stderr, "webcollage: signal %d\n", sig);
356   webcollage_atexit ();
357   exit (sig);
358 }
359
360
361 /* This is important because OSX doesn't actually kill the screen savers!
362    It just sends them a [ScreenSaverView stopAnimation] method.  Were
363    they to actually exit, the resultant SIGPIPE should reap the children,
364    but instead, we need to do it here.
365
366    On top of that, there's an atexit() handler because otherwise the
367    inferior perl script process was failing to die when SaverTester or
368    System Preferences exited.  I don't pretend to understand.
369
370    It still fails to clean up when I hit the stop button in Xcode.
371    WTF.
372  */
373 static void
374 webcollage_free (Display *dpy, Window window, void *closure)
375 {
376   state *st = (state *) closure;
377
378   if (st->verbose_p)
379     fprintf (stderr, "webcollage: free cb\n");
380
381   // Dammit dammit dammit!  Why won't this shit die when we pclose it!
382 //  killpg (0, SIGTERM);
383
384   webcollage_atexit();
385
386   if (st->pipe_id)
387     XtRemoveInput (st->pipe_id);
388
389   if (st->pipe_fd)
390     fclose (st->pipe_fd);
391
392   // Reap zombies.
393 # undef sleep
394   sleep (1);
395   int wait_status = 0;
396   waitpid (-1, &wait_status, 0);
397
398   free (st);
399 }
400
401
402 static const char *webcollage_defaults [] = {
403   ".background:         black",
404   ".foreground:         white",
405
406   "*timeout:            30",
407   "*delay:              2",
408   "*opacity:            0.85",
409   "*filter:             ",
410   "*filter2:            ",
411   0
412 };
413
414 static XrmOptionDescRec webcollage_options [] = {
415   { "-timeout",         ".timeout",     XrmoptionSepArg, 0 },
416   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
417   { "-opacity",         ".opacity",     XrmoptionSepArg, 0 },
418   { "-filter",          ".filter",      XrmoptionSepArg, 0 },
419   { "-filter2",         ".filter2",     XrmoptionSepArg, 0 },
420   { 0, 0, 0, 0 }
421 };
422
423
424 XSCREENSAVER_MODULE ("WebCollage", webcollage)