summaryrefslogtreecommitdiffstats
path: root/hacks/webcollage-cocoa.m
diff options
context:
space:
mode:
Diffstat (limited to 'hacks/webcollage-cocoa.m')
-rw-r--r--hacks/webcollage-cocoa.m428
1 files changed, 428 insertions, 0 deletions
diff --git a/hacks/webcollage-cocoa.m b/hacks/webcollage-cocoa.m
new file mode 100644
index 0000000..e8bc2b2
--- /dev/null
+++ b/hacks/webcollage-cocoa.m
@@ -0,0 +1,428 @@
+/* xscreensaver, Copyright (c) 2006-2015 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+ /* This is the Cocoa shim for webcollage.
+
+ It runs the webcollage perl script
+ (in "WebCollage.saver/Contents/Resources/webcollage")
+ at the end of a pipe; each time that script updates the image file on
+ disk it prints the file name, and this program loads and displays that
+ image.
+
+ The script uses "WebCollage.saver/Contents/Resources/webcollage-helper"
+ to paste the images together in the usual way.
+ */
+
+#include <math.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#import <Cocoa/Cocoa.h>
+
+#include "screenhack.h"
+
+
+typedef struct {
+ Display *dpy;
+ Window window;
+ XWindowAttributes xgwa;
+ int delay;
+ pid_t pid;
+ FILE *pipe_fd;
+ XtInputId pipe_id;
+ Bool verbose_p;
+} state;
+
+
+/* Violating the cardinal rule of "don't use global variables",
+ but we need to get at these from the atexit() handler, and
+ the callback doesn't take a closure arg. Because apparently
+ those hadn't been invented yet in the seventies.
+ */
+static state *all_states[50] = { 0, };
+
+static void webcollage_atexit (void);
+static void signal_handler (int sig);
+
+
+static void
+subproc_cb (XtPointer closure, int *source, XtInputId *id)
+{
+ /* state *st = (state *) closure; */
+ /* st->input_available_p = True; */
+}
+
+
+/* whether there is data available to be read on the file descriptor
+ */
+static int
+input_available_p (int fd)
+{
+ struct timeval tv = { 0, };
+ fd_set fds;
+# if 0
+ /* This breaks on BSD, which uses bzero() in the definition of FD_ZERO */
+ FD_ZERO (&fds);
+# else
+ memset (&fds, 0, sizeof(fds));
+# endif
+ FD_SET (fd, &fds);
+ return select (fd+1, &fds, NULL, NULL, &tv);
+}
+
+
+static void
+display_image (Display *dpy, Window window, state *st, const char *file)
+{
+ NSImage *image = [[NSImage alloc]
+ initWithContentsOfFile:
+ [NSString stringWithCString: file
+ encoding: NSUTF8StringEncoding]];
+
+ if (! image) {
+ fprintf (stderr, "webcollage: failed to load \"%s\"\n", file);
+ return;
+ }
+
+ CGFloat w = [image size].width;
+ CGFloat h = [image size].height;
+ if (w <= 1 || h <= 1) {
+ fprintf (stderr, "webcollage: unparsable image \"%s\"\n", file);
+ [image release];
+ return;
+ }
+
+ jwxyz_draw_NSImage_or_CGImage (dpy, window, True, image, 0, 0);
+ [image release];
+}
+
+
+static void
+open_pipe (state *st)
+{
+ /* This mess is because popen() doesn't give us the pid.
+ */
+
+ pid_t forked;
+ int fds [2];
+ int in, out;
+ char buf[1024];
+
+ char *av[20];
+ int ac = 0;
+
+ int timeout = get_integer_resource (st->dpy, "timeout", "Timeout");
+ int delay = get_integer_resource (st->dpy, "delay", "Delay");
+ float opacity = get_float_resource (st->dpy, "opacity", "Opacity");
+ char *filter = get_string_resource (st->dpy, "filter", "Filter");
+ char *filter2 = get_string_resource (st->dpy, "filter2", "Filter2");
+
+ av[ac++] = strdup ("webcollage");
+ av[ac++] = strdup ("-cocoa");
+
+ av[ac++] = strdup ("-size");
+ sprintf (buf, "%dx%d", st->xgwa.width, st->xgwa.height);
+ av[ac++] = strdup (buf);
+
+ av[ac++] = strdup ("-timeout"); sprintf (buf, "%d", timeout);
+ av[ac++] = strdup (buf);
+ av[ac++] = strdup ("-delay"); sprintf (buf, "%d", delay);
+ av[ac++] = strdup (buf);
+ av[ac++] = strdup ("-opacity"); sprintf (buf, "%.2f", opacity);
+ av[ac++] = strdup (buf);
+
+ if (filter && *filter) {
+ av[ac++] = strdup ("-filter");
+ av[ac++] = filter;
+ }
+ if (filter2 && *filter2) {
+ av[ac++] = strdup ("-filter2");
+ av[ac++] = filter2;
+ }
+
+ av[ac] = 0;
+
+
+ if (st->verbose_p) {
+ fprintf (stderr, "webcollage: launching:");
+ int i;
+ for (i = 0; i < ac; i++)
+ fprintf (stderr, " %s", av[i]);
+ fprintf (stderr, "\n");
+ }
+
+
+ if (pipe (fds))
+ {
+ perror ("webcollage: error creating pipe");
+ exit (1);
+ }
+
+ in = fds [0];
+ out = fds [1];
+
+ switch ((int) (forked = fork ()))
+ {
+ case -1:
+ {
+ perror ("webcollage: couldn't fork");
+ exit (1);
+ }
+ case 0:
+ {
+ int stdout_fd = 1;
+
+ close (in); /* don't need this one */
+
+ if (dup2 (out, stdout_fd) < 0) /* pipe stdout */
+ {
+ perror ("could not dup() a new stdout:");
+ exit (1);
+ }
+
+ execvp (av[0], av); /* shouldn't return. */
+
+ if (errno != ENOENT)
+ {
+ /* Ignore "no such file or directory" errors, unless verbose.
+ Issue all other exec errors, though. */
+ sprintf (buf, "webcollage: %s", av[0]);
+ perror (buf);
+ }
+
+ exit (1); /* exits fork */
+ break;
+ }
+ default:
+ {
+ st->pipe_fd = fdopen (in, "r");
+ close (out); /* don't need this one */
+ }
+ }
+
+ while (ac > 0)
+ free (av[--ac]);
+
+ if (! st->pipe_fd) abort();
+
+ st->pid = forked;
+ st->pipe_id =
+ XtAppAddInput (XtDisplayToApplicationContext (st->dpy),
+ fileno (st->pipe_fd),
+ (XtPointer) (XtInputReadMask | XtInputExceptMask),
+ subproc_cb, (XtPointer) st);
+
+ if (st->verbose_p)
+ fprintf (stderr, "webcollage: subprocess pid: %d\n", st->pid);
+}
+
+
+static void *
+webcollage_init (Display *dpy, Window window)
+{
+ state *st = (state *) calloc (1, sizeof(*st));
+ int i;
+ st->dpy = dpy;
+ st->window = window;
+ XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
+
+ st->delay = 1000000; /* check once a second */
+
+ // Log to syslog when FPS is turned on.
+ st->verbose_p = get_boolean_resource (dpy, "doFPS", "DoFPS");
+
+
+ static int done_once = 0;
+ if (! done_once) {
+ done_once = 1;
+
+ if (atexit (webcollage_atexit)) { // catch calls to exit()
+ perror ("webcollage: atexit");
+ exit (-1);
+ }
+
+ int sigs[] = { SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT,
+ SIGFPE, SIGBUS, SIGSEGV, SIGSYS, /*SIGPIPE,*/
+ SIGALRM, SIGTERM };
+ for (i = 0; i < sizeof(sigs)/sizeof(*sigs); i++) {
+ if (signal (sigs[i], signal_handler)) {
+ perror ("webcollage: signal");
+ //exit (1);
+ }
+ }
+ }
+
+
+ open_pipe (st);
+
+ i = 0;
+ while (all_states[i]) i++;
+ all_states[i] = st;
+
+ return st;
+}
+
+
+static unsigned long
+webcollage_draw (Display *dpy, Window window, void *closure)
+{
+ state *st = (state *) closure;
+
+ if (! st->pipe_fd)
+ exit (1);
+
+ if (! input_available_p (fileno (st->pipe_fd)))
+ return st->delay;
+
+ char buf[10240];
+ int n = read (fileno (st->pipe_fd),
+ (void *) buf,
+ sizeof(buf) - 1);
+ if (n <= 0)
+ {
+ XtRemoveInput (st->pipe_id);
+ st->pipe_id = 0;
+ // #### sometimes hangs -- pclose (st->pipe_fd);
+ st->pipe_fd = 0;
+
+ if (st->verbose_p)
+ fprintf (stderr, "webcollage: subprocess has exited: bailing.\n");
+
+ return st->delay * 10;
+ }
+
+ buf[n] = 0;
+ char *s = strchr (buf, '\n');
+ if (s) *s = 0;
+
+ const char *target = "COCOA LOAD ";
+ if (!strncmp (target, buf, strlen(target))) {
+ const char *file = buf + strlen(target);
+ display_image (dpy, window, st, file);
+ }
+
+ return st->delay;
+}
+
+
+static void
+webcollage_reshape (Display *dpy, Window window, void *closure,
+ unsigned int w, unsigned int h)
+{
+}
+
+
+static Bool
+webcollage_event (Display *dpy, Window window, void *closure, XEvent *event)
+{
+ return False;
+}
+
+
+static void
+webcollage_atexit (void)
+{
+ int i = 0;
+
+ if (all_states[0] && all_states[0]->verbose_p)
+ fprintf (stderr, "webcollage: atexit handler\n");
+
+ while (all_states[i]) {
+ state *st = all_states[i];
+ if (st->pid) {
+ if (st->verbose_p)
+ fprintf (stderr, "webcollage: kill %d\n", st->pid);
+ if (kill (st->pid, SIGTERM) < 0) {
+ fprintf (stderr, "webcollage: kill (%d, TERM): ", st->pid);
+ perror ("webcollage: kill");
+ }
+ st->pid = 0;
+ }
+ all_states[i] = 0;
+ i++;
+ }
+}
+
+
+static void
+signal_handler (int sig)
+{
+ if (all_states[0] && all_states[0]->verbose_p)
+ fprintf (stderr, "webcollage: signal %d\n", sig);
+ webcollage_atexit ();
+ exit (sig);
+}
+
+
+/* This is important because OSX doesn't actually kill the screen savers!
+ It just sends them a [ScreenSaverView stopAnimation] method. Were
+ they to actually exit, the resultant SIGPIPE should reap the children,
+ but instead, we need to do it here.
+
+ On top of that, there's an atexit() handler because otherwise the
+ inferior perl script process was failing to die when SaverTester or
+ System Preferences exited. I don't pretend to understand.
+
+ It still fails to clean up when I hit the stop button in Xcode.
+ WTF.
+ */
+static void
+webcollage_free (Display *dpy, Window window, void *closure)
+{
+ state *st = (state *) closure;
+
+ if (st->verbose_p)
+ fprintf (stderr, "webcollage: free cb\n");
+
+ // Dammit dammit dammit! Why won't this shit die when we pclose it!
+// killpg (0, SIGTERM);
+
+ webcollage_atexit();
+
+ if (st->pipe_id)
+ XtRemoveInput (st->pipe_id);
+
+ if (st->pipe_fd)
+ fclose (st->pipe_fd);
+
+ // Reap zombies.
+# undef sleep
+ sleep (1);
+ int wait_status = 0;
+ waitpid (-1, &wait_status, 0);
+
+ free (st);
+}
+
+
+static const char *webcollage_defaults [] = {
+ ".background: black",
+ ".foreground: white",
+
+ "*timeout: 30",
+ "*delay: 2",
+ "*opacity: 0.85",
+ "*filter: ",
+ "*filter2: ",
+ 0
+};
+
+static XrmOptionDescRec webcollage_options [] = {
+ { "-timeout", ".timeout", XrmoptionSepArg, 0 },
+ { "-delay", ".delay", XrmoptionSepArg, 0 },
+ { "-opacity", ".opacity", XrmoptionSepArg, 0 },
+ { "-filter", ".filter", XrmoptionSepArg, 0 },
+ { "-filter2", ".filter2", XrmoptionSepArg, 0 },
+ { 0, 0, 0, 0 }
+};
+
+
+XSCREENSAVER_MODULE ("WebCollage", webcollage)