4df2c05b12f4086112c311f5fdd7c8ffd30d50ab
[xscreensaver] / hacks / webcollage-cocoa.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 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 <signal.h>
27 #import <Cocoa/Cocoa.h>
28
29 #include "screenhack.h"
30
31
32 typedef struct {
33   Display *dpy;
34   Window window;
35   XWindowAttributes xgwa;
36   int delay;
37   pid_t pid;
38   FILE *pipe_fd;
39   XtInputId pipe_id;
40   Bool verbose_p;
41 } state;
42
43
44 static void
45 subproc_cb (XtPointer closure, int *source, XtInputId *id)
46 {
47   /* state *st = (state *) closure; */
48   /* st->input_available_p = True; */
49 }
50
51
52 /* whether there is data available to be read on the file descriptor
53  */
54 static int
55 input_available_p (int fd)
56 {
57   struct timeval tv = { 0, };
58   fd_set fds;
59 # if 0
60   /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
61   FD_ZERO (&fds);
62 # else
63   memset (&fds, 0, sizeof(fds));
64 # endif
65   FD_SET (fd, &fds);
66   return select (fd+1, &fds, NULL, NULL, &tv);
67 }
68
69
70 static void
71 display_image (state *st, const char *file)
72 {
73   NSImage *image = [[NSImage alloc] 
74                      initWithContentsOfFile:
75                        [NSString stringWithCString: file
76                                           encoding: kCFStringEncodingUTF8]];
77
78   if (! image) {
79     fprintf (stderr, "%s: failed to load \"%s\"\n", progname, file);
80     return;
81   }
82
83   [image drawAtPoint: NSMakePoint (0, 0)
84             fromRect: NSMakeRect (0, 0, [image size].width, [image size].height)
85            operation: NSCompositeCopy
86             fraction: 1.0];
87   [image release];
88 }
89
90
91 static void
92 open_pipe (state *st)
93 {
94   /* This mess is because popen() doesn't give us the pid.
95    */
96
97   pid_t forked;
98   int fds [2];
99   int in, out;
100   char buf[1024];
101
102   char *av[20];
103   int ac = 0;
104
105   int timeout   = get_integer_resource (st->dpy, "timeout", "Timeout");
106   int delay     = get_integer_resource (st->dpy, "delay",   "Delay");
107   float opacity = get_float_resource   (st->dpy, "opacity", "Opacity");
108   char *filter  = get_string_resource  (st->dpy, "filter",  "Filter");
109   char *filter2 = get_string_resource  (st->dpy, "filter2", "Filter2");
110
111   av[ac++] = "webcollage";
112   av[ac++] = "-cocoa";
113
114   av[ac++] = "-size";
115   sprintf (buf, "%dx%d", st->xgwa.width, st->xgwa.height);
116   av[ac++] = strdup (buf);
117
118   av[ac++] = "-timeout"; sprintf (buf, "%d", timeout);
119   av[ac++] = strdup (buf);
120   av[ac++] = "-delay";   sprintf (buf, "%d", delay);
121   av[ac++] = strdup (buf);
122   av[ac++] = "-opacity"; sprintf (buf, "%.2f", opacity);
123   av[ac++] = strdup (buf);
124
125   if (filter && *filter) {
126     av[ac++] = "-filter";
127     av[ac++] = filter;
128   }
129   if (filter2 && *filter2) {
130     av[ac++] = "-filter2";
131     av[ac++] = filter2;
132   }
133
134   av[ac] = 0;
135
136
137   if (st->verbose_p) {
138     fprintf (stderr, "%s: launching:", progname);
139     int i;
140     for (i = 0; i < ac; i++)
141       fprintf (stderr, " %s", av[i]);
142     fprintf (stderr, "\n");
143   }
144
145
146   if (pipe (fds))
147     {
148       perror ("error creating pipe:");
149       exit (1);
150     }
151
152   in = fds [0];
153   out = fds [1];
154
155   switch ((int) (forked = fork ()))
156     {
157     case -1:
158       {
159         sprintf (buf, "%s: couldn't fork", progname);
160         perror (buf);
161         exit (1);
162       }
163     case 0:
164       {
165         int stdout_fd = 1;
166
167         close (in);  /* don't need this one */
168
169         if (dup2 (out, stdout_fd) < 0)          /* pipe stdout */
170           {
171             perror ("could not dup() a new stdout:");
172             exit (1);
173           }
174
175         execvp (av[0], av);                     /* shouldn't return. */
176
177         if (errno != ENOENT)
178           {
179             /* Ignore "no such file or directory" errors, unless verbose.
180                Issue all other exec errors, though. */
181             sprintf (buf, "%s: %s", progname, av[0]);
182             perror (buf);
183           }
184
185         exit (1);                              /* exits fork */
186         break;
187       }
188     default:
189       {
190         st->pipe_fd = fdopen (in, "r");
191         close (out);  /* don't need this one */
192       }
193     }
194
195   if (! st->pipe_fd) abort();
196
197   st->pid = forked;
198   st->pipe_id =
199     XtAppAddInput (XtDisplayToApplicationContext (st->dpy), 
200                    fileno (st->pipe_fd),
201                    (XtPointer) (XtInputReadMask | XtInputExceptMask),
202                    subproc_cb, (XtPointer) st);
203
204   if (st->verbose_p)
205     fprintf (stderr, "%s: subprocess pid: %d\n", progname, st->pid);
206 }
207
208
209 static void *
210 webcollage_init (Display *dpy, Window window)
211 {
212   state *st = (state *) calloc (1, sizeof(*st));
213   st->dpy = dpy;
214   st->window = window;
215   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
216
217   st->delay = 1000000;  /* check once a second */
218
219   open_pipe (st);
220
221   return st;
222 }
223
224
225 static unsigned long
226 webcollage_draw (Display *dpy, Window window, void *closure)
227 {
228   state *st = (state *) closure;
229
230   if (! st->pipe_fd) 
231     exit (1);
232
233   if (! input_available_p (fileno (st->pipe_fd)))
234     return st->delay;
235
236   char buf[10240];
237   int n = read (fileno (st->pipe_fd),
238                 (void *) buf,
239                 sizeof(buf) - 1);
240   if (n <= 0)
241     {
242       XtRemoveInput (st->pipe_id);
243       st->pipe_id = 0;
244       // #### sometimes hangs -- pclose (st->pipe_fd);
245       st->pipe_fd = 0;
246
247       if (st->verbose_p)
248         fprintf (stderr, "%s: subprocess has exited: bailing.\n", progname);
249
250       return st->delay * 10;
251     }
252
253   buf[n] = 0;
254   char *s = strchr (buf, '\n');
255   if (s) *s = 0;
256
257   const char *target = "COCOA LOAD ";
258   if (!strncmp (target, buf, strlen(target))) {
259     const char *file = buf + strlen(target);
260     display_image (st, file);
261   }
262
263   return st->delay;
264 }
265
266
267 static void
268 webcollage_reshape (Display *dpy, Window window, void *closure, 
269                     unsigned int w, unsigned int h)
270 {
271 }
272
273
274 static Bool
275 webcollage_event (Display *dpy, Window window, void *closure, XEvent *event)
276 {
277   return False;
278 }
279
280
281 static void
282 webcollage_free (Display *dpy, Window window, void *closure)
283 {
284   state *st = (state *) closure;
285
286   // Dammit dammit dammit!  Why won't this shit die when we pclose it!
287 //  killpg (0, SIGTERM);
288
289   if (st->pid) {
290     if (st->verbose_p)
291       fprintf (stderr, "%s: kill %d\n", progname, st->pid);
292     if (kill (st->pid, SIGTERM) < 0) {
293       fprintf (stderr, "%s: kill (%d, TERM): ", progname, st->pid);
294       perror ("kill");
295     }
296     st->pid = 0;
297   }
298
299   if (st->pipe_id)
300     XtRemoveInput (st->pipe_id);
301
302   if (st->pipe_fd)
303     fclose (st->pipe_fd);
304
305   // Reap zombies.
306 # undef sleep
307   sleep (1);
308   int wait_status = 0;
309   waitpid (-1, &wait_status, 0);
310
311   free (st);
312 }
313
314
315 static const char *webcollage_defaults [] = {
316   ".background:         black",
317   ".foreground:         white",
318
319   "*timeout:            30",
320   "*delay:              2",
321   "*opacity:            0.85",
322   "*filter:             ",
323   "*filter2:            ",
324   0
325 };
326
327 static XrmOptionDescRec webcollage_options [] = {
328   { "-timeout",         ".timeout",     XrmoptionSepArg, 0 },
329   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
330   { "-opacity",         ".opacity",     XrmoptionSepArg, 0 },
331   { "-filter",          ".filter",      XrmoptionSepArg, 0 },
332   { "-filter2",         ".filter2",     XrmoptionSepArg, 0 },
333   { 0, 0, 0, 0 }
334 };
335
336
337 XSCREENSAVER_MODULE ("WebCollage", webcollage)