/* xscreensaver, Copyright (c) 1997, 2005 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 a kludge that lets xscreensaver work with SGI demos that expect
   to be run from `haven'.  It runs the program given on the command line,
   then waits for an X window to be created whose name is that of the 
   program.  Then, it resizes that window to fill the screen.  Run it
   like this:

       xscreensaver-sgigl /usr/demos/bin/ep -S
       xscreensaver-sgigl -n ant /usr/demos/General_Demos/ant/RUN
       xscreensaver-sgigl -n atlantis /usr/demos/General_Demos/atlantis/RUN
       xscreensaver-sgigl -n /usr/demos/General_Demos/powerflip/powerflip \
          /usr/demos/General_Demos/powerflip/RUN

   Except that doesn't really work.  You have to do this instead:

       xscreensaver-sgigl -n ant ant.sh

   where ant.sh contains

       #!/bin/sh
       cd /usr/demos/General_Demos/ant
       exec ./ant -S

   There's no way to make this work with powerflip at all, since it doesn't
   take a -S option to run in the foreground.
 */

/* #### Another way to do this would be:
   instead of exec'ing the hack, fork it; then wait for that fork to die.
   If it dies, but the window ID is still valid, then that means the 
   sub-process has forked itself (as those fuckwits at SGI are wont to do.)
   In that case, this process should go to sleep, and set up a signal handler
   that will destroy the X window when it is killed.  That way, the caller
   is given a foreground pid which, when killed, will cause the hack to die
   (by a roundabout mechanism.)

   This would all be so much simpler if those assholes would just:

   1: get out of the habit of writing programs that magically background
      themselves, and

   2: give the fucking programs arguments which control the window size
      instead of always making 100x100 windows!

   I won't even dream of having a "-root" option that understood virtual-roots;
   that would just be too outlandish to even dream about.
 */

static char *progname;

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xmu/Error.h>

#include "vroot.h"
#undef RootWindowOfScreen
#undef RootWindow
#undef DefaultRootWindow


static int
BadWindow_ehandler (Display *dpy, XErrorEvent *error)
{
  if (error->error_code == BadWindow ||
      error->error_code == BadMatch ||
      error->error_code == BadDrawable)
    return 0;
  else
    {
      fprintf (stderr, "\nX error in %s:\n", progname);
      if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
	exit(1);
      else
	fprintf (stderr, " (nonfatal.)\n");
    }
  return 0;
}


void
main(int ac, char **av)
{
  char buf [512];
  pid_t parent, forked;
  Display *dpy;
  Screen *screen;
  char *s;
  char *n1 = 0;
  char *n2 = 0;
  Bool verbose = False;
  Window root, vroot;
  XSizeHints h;
  long ls;

  progname = av[0];

  s = strrchr(progname, '/');
  if (s) progname = s+1;

  if (ac < 1)
    {
      fprintf(stderr,
	      "usage: %s [ -v ] [ -n window-name ] program arguments...\n",
	      progname);
      exit(1);
    }

  if (ac > 2 && !strcmp(av[1], "-v"))
    {
      verbose = True;
      av++;
      ac--;
    }

  if (ac > 2 && !strcmp(av[1], "-n"))
    {
      n2 = av[2];
      av += 2;
      ac -= 2;
    }

  n1 = strrchr(av[1], '/');
  if (n1) n1++;
  else n1 = av[1];


  dpy = XOpenDisplay(0);
  if (!dpy)
    {
      fprintf(stderr, "%s: couldn't open display\n", progname);
      exit(1);
    }

  screen = DefaultScreenOfDisplay(dpy);
  root   = XRootWindowOfScreen (screen);
  vroot  = VirtualRootWindowOfScreen (screen);

  XSelectInput (dpy, root, SubstructureNotifyMask);
  if (root != vroot)
    XSelectInput (dpy, vroot, SubstructureNotifyMask);

  XSetErrorHandler (BadWindow_ehandler);

  if (verbose)
    fprintf(stderr, "%s: selected SubstructureNotifyMask on 0x%x / 0x%x\n",
	    progname, root, vroot);

  parent = getpid();

  if (verbose)
    fprintf(stderr, "%s: pid is %d\n", progname, parent);

  switch ((int) (forked = fork ()))
    {
    case -1:
      {
	sprintf (buf, "%s: couldn't fork", progname);
	perror (buf);
	exit (1);
	break;
      }
    case 0:	/* forked */
      {
	time_t start = time((time_t) 0);
	XEvent event;

	if (verbose)
	  fprintf(stderr, "%s: forked pid is %d\n", progname, getpid());

	while (1)
	  {
	    XNextEvent(dpy, &event);

	    if (event.xany.type == CreateNotify)
	      {
		char *name = 0;
		Window w = event.xcreatewindow.window;
		XSync(dpy, False);

		XFetchName(dpy, w, &name);
		if (!name)
		  {
		    /* Try again to see if the name has been set later... */
		    XSync(dpy, False);
		    sleep(1);
		    XFetchName(dpy, w, &name);
		  }

		if (name &&
		    ((n1 && !strcmp(name, n1)) ||
		     (n2 && !strcmp(name, n2))))
		  {
		    if (verbose)
		      fprintf(stderr, "%s: resizing 0x%x\n", progname, w);

                    /* Make sure the window allows itself to be resized. */
		    XGetWMNormalHints (dpy, w, &h, &ls);
		    h.flags |= PMaxSize;
		    h.max_width = WidthOfScreen(screen)+128;
		    h.max_height = HeightOfScreen(screen)+128;
		    XSetWMNormalHints (dpy, w, &h);

		    XMoveResizeWindow(dpy, w, 0, 0,
				      WidthOfScreen(screen),
				      HeightOfScreen(screen));

#if 0
		    if (vroot && vroot != root &&
			event.xcreatewindow.parent == root)
		      {
			if (verbose)
			  fprintf(stderr,
				  "%s: reparenting 0x%x from 0x%x to 0x%x\n",
				  progname, w, root, vroot);
			XReparentWindow(dpy, w, vroot, 0, 0);
		      }
#endif

		    XSync(dpy, False);
		    fflush(stdout);
		    fflush(stderr);
		    exit(0);	/* Note that this only exits a child fork.  */
		  }
	      }

	    if (start + 5 < time((time_t) 0))
	      {
		fprintf(stderr,
		    "%s: timed out: no window named \"%s\" has been created\n",
			progname, (n2 ? n2 : n1));
		fflush(stdout);
		fflush(stderr);
		kill(parent, SIGTERM);
		exit(1);
	      }
	  }
	break;
      }
    default:	/* foreground */
      {
	close (ConnectionNumber (dpy));		/* close display fd */
	execvp (av[1], av+1);			/* shouldn't return. */
	sprintf (buf, "%s: execvp(\"%s\") failed", progname, av[1]);
	perror (buf);
	fflush(stderr);
	fflush(stdout);
	exit (1);
	break;
      }
    }
}
