/*
 * Copyright (C) 1995 Free Software Foundation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you can either send email to this
 * program's author (see below) or write to:
 *
 *              The Free Software Foundation, Inc.
 *              675 Mass Ave.
 *              Cambridge, MA 02139, USA. 
 *
 * Please send bug reports, etc. to zappo@gnu.ai.mit.edu.
 *
 * xphone.c
 *
 * Purpose:
 *   To provide a small window with a phone image in it.  When a talk
 * request comes in, the phone will "ring" providing information to the
 *  user that someone is trying to call them.
 *
 * $Log: xphone.c,v $
 * Revision 1.8  1995/07/08  16:25:55  zappo
 * Removed X11R6 references, to use X11R5 calls only
 *
 * Revision 1.7  1995/05/29  12:56:33  zappo
 * Added geometry, size, and window-manager hints and appropriate
 * resources
 *
 * Revision 1.6  1995/05/27  02:17:37  zappo
 * Added geometry options to command line and resources
 *
 * Revision 1.5  1995/05/27  01:36:53  zappo
 * Add the ability to add read in X resources
 *
 * Revision 1.4  1995/05/09  23:46:21  zappo
 * Added command line argument for --copyright, and --quiet
 *
 * Revision 1.3  1995/05/03  22:03:49  zappo
 * Added parameter to run some sort of other audio program when a message
 * is recieved.
 *
 * Revision 1.2  1995/04/25  22:45:14  zappo
 * Added command line arguments for fg/gb and images files
 *
 * Revision 1.1  1995/04/18  22:17:08  zappo
 * Initial revision
 *
 * ::Header:: xphone.h
 */
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include "getopt.h"
#include "etalklib.h"
#include "otalk.h"
#include "talk.h"
#include "gtalk.h"
#include "xphone.h"
#include "phone.xbm"
#include "ring.xbm"

#if HAVE_SIGNAL_H == 1
#include <signal.h>
#endif

int verbose     = FALSE;	/* verbosity variable            */
int syslogdebug = FALSE;	/* syslogdebug variable          */

extern RETSIGTYPE xphone_sighandle(); /* signal handler function       */

int main(argc, argv)
     int   argc;
     char *argv[];
{
  struct PhoneContext  Ctxt;
  Colormap             cmap;
  char                *window_name = "xphone", *icon_name = "xphone";
  char                *idleimage = NULL, *ringimage = NULL;
  char                *fgc1=NULL, *bgc1=NULL, *fgc2=NULL, *bgc2=NULL;
  char                *geometry;
  int                  winwidth, winheight, ringwidth, ringheight;
  XColor               tmpcolor, tmpcolor2;
  unsigned int         xpos = 0, ypos = 0;
  XSetWindowAttributes attr;
  XTextProperty        windowName, iconName;
  XClassHint           class_hints;
  XSizeHints           sizehints;
  XWMHints             wmhints;
  XGCValues            newvalues;

  /* First things first, install signal handlers.  Need this to zap */
  /* rogue invites/etc, and to zap .ringer files.                   */
  signal(SIGHUP, xphone_sighandle);
  signal(SIGINT, xphone_sighandle);
  signal(SIGKILL, xphone_sighandle);
  signal(SIGTERM, xphone_sighandle);

  /* Initialize some flags. */
  Ctxt.ringcommand = NULL;
  Ctxt.silent = FALSE;

  /* Here is a chunk from the GNU getopt manual page with the correct
   * extensions for etalk.
   */
  while(1)
    {
      int option_index = 0;	/* options returned index */
      int c;			/* return value           */
      static char *short_options = "?Chsv";
      static struct option long_options[] =
	{
	  { "bellcommand", TRUE, NULL, 'b' },
	  { "copyright", FALSE, NULL, 'C' },
	  { "help", FALSE, NULL, 'h' },
	  { "ibg", TRUE, NULL, 'I' },
	  { "ibackground", TRUE, NULL, 'I' },
	  { "ifg", TRUE, NULL, 'i' },
	  { "iforeground", TRUE, NULL, 'i' },
	  { "idleimage", TRUE, NULL, '(' },
	  { "geometry", TRUE, NULL, 'g' },
	  { "rbg", TRUE, NULL, 'R' },
	  { "rbackground", TRUE, NULL, 'R' },
	  { "rfg", TRUE, NULL, 'r' },
	  { "rforeground", TRUE, NULL, 'r' },
	  { "ringimage", TRUE, NULL, ')' },
	  { "silent", FALSE, NULL, 's' },
	  { "quiet", FALSE, NULL, 's' },
	  { "verbose", FALSE, NULL, 'v' },
	  { "version", FALSE, NULL, 0 },
	  { NULL, FALSE, NULL, 0 }
	};

      c = getopt_long(argc, argv, short_options,
		      long_options, &option_index);

      if(c == -1) break;
            
      switch(c) {
      case 0:
	/* version is the only weird one, and it has already been
	 * printed, so just exit
	 */
	exit(0);
      case 'b':
	Ctxt.ringcommand = strdup(optarg);
	break;
      case 'C':
	CW_display_copywrite();
	exit(0);
      case 'g':
	geometry = strdup(optarg);
	break;
      case 'h':
      case '?':
	printf("Xphone command line options:\n");
	printf(" --copyright\t-C\tDisplay copyright information.\n");
	printf(" --bellcommand\t\tAlternate command used to make a ringing noise.\n");
	printf(" --help,\t-h,-?\tPrint this help.\n");
	printf(" --geometry\t\tThe initial display geometry.\n");
	printf(" --silent,\t-s\tStart silently, and print no announcements.\n");
	printf(" --quiet,\t-s\tStart silently, and print no announcements.\n");
	printf(" --ibg, --ibackground\tSet background color of idle phone.\n");
	printf(" --ifg, --iforeground\tSet foreground color of idle phone.\n");
	printf(" --idleimage\t\tSet idle image bitmap file data.\n");
	printf(" --rbg, --rbackground\tSet background color of ringing phone.\n");
	printf(" --rfg, --rforeground\tSet foreground color of ringing phone.\n");
	printf(" --ringimage\t\tSet ringing image bitmap file data.\n");
	printf(" --verbose\t-v\tPrint debugging information as we go.\n");
	printf(" --version\t\tPrint version and exit.\n\n");
	exit(0);
      case 'i':
	fgc1 = optarg;
	break;
      case 'I':
	bgc1 = optarg;
	break;
      case 'r':
	fgc2 = optarg;
	break;
      case 'R':
	bgc2 = optarg;
	break;
      case 'v':
	printf("Verbosity Set.\n");
	verbose = TRUE;
	break;
      case 's':
	Ctxt.silent = TRUE;
	break;
      case '(':
	idleimage = optarg;
	break;
      case ')':
	ringimage = optarg;
	break;
      default:
	printf("Illegal option %c\n", c);
	exit(0);
      }
    }
  
  if(!Ctxt.silent)
    {
      printf("%s\n", XPHONE_);
      fflush(stdout);
    }

  if(DMN_check_compile() == Fail)
    /* Return error status on this check. */
    return 1;

  /* Set up ringer port.  Reset UDP creators in between. */
  Ctxt.me                 = HOST_gen_local_host();
  UDP_setup_localport();
  Ctxt.udp_ring           = UDP_host(Ctxt.me->name);
  Ctxt.udp_ring->name     = "ring_port";

  Ctxt.pid            = getpid();

  Ctxt.ringing        = FALSE;

  /* Now turn on the ringer! */
  RING_activate(&Ctxt.ringerflag, Ctxt.udp_ring, XPH_read);

  /* Now lets do some of those primitive X things! */
  Ctxt.display = XOpenDisplay(NULL);
  if(!Ctxt.display)
    {
      fprintf(stderr, "Cannot open display!\n");
      exit(0);
    }

  Ctxt.x_dev = ETL_x(Ctxt.display);
  Ctxt.x_dev->readme  = XPH_xread;
  Ctxt.x_dev->timeout = 0;
  Ctxt.x_dev->timefn  = NULL;
  Ctxt.x_dev->timemsg = NULL;

  /* Lets load some resources! */
  if(!Ctxt.ringcommand)
    {
      XrmValue value;
      if(ETL_get_resource(Ctxt.display, 
			  "xphone.ringcommand", "Xphone.ringcommand",
			  &value))
	{
	  Ctxt.ringcommand = malloc(value.size+1);
	  strncpy(Ctxt.ringcommand, value.addr, value.size);
	}
    }
  if(!fgc1)
    {
      XrmValue value;
      if(ETL_get_resource(Ctxt.display, 
			  "xphone.idle.foreground", "Xphone.Idle.Foreground",
			  &value))
	{
	  fgc1 = malloc(value.size+1);
	  strncpy(fgc1, value.addr, value.size);
	}
    }
  if(!bgc1)
    {
      XrmValue value;
      if(ETL_get_resource(Ctxt.display, 
			  "xphone.idle.background", "Xphone.Idle.Background",
			  &value))
	{
	  bgc1 = malloc(value.size+1);
	  strncpy(bgc1, value.addr, value.size);
	}
    }
  if(!fgc2)
    {
      XrmValue value;
      if(ETL_get_resource(Ctxt.display, 
			  "xphone.ring.foreground", "Xphone.Ring.Foreground",
			  &value))
	{
	  fgc2 = malloc(value.size+1);
	  strncpy(fgc2, value.addr, value.size);
	}
    }
  if(!bgc2)
    {
      XrmValue value;
      if(ETL_get_resource(Ctxt.display, 
			  "xphone.ring.background", "Xphone.ring.Background",
			  &value))
	{
	  bgc2 = malloc(value.size+1);
	  strncpy(bgc2, value.addr, value.size);
	}
    }
  if(!idleimage)
    {
      XrmValue value;
      if(ETL_get_resource(Ctxt.display, 
			  "xphone.idle.bitmap", "Xphone.Idle.Bitmap",
			  &value))
	{
	  idleimage = malloc(value.size+1);
	  strncpy(idleimage, value.addr, value.size);
	}
    }
  if(!ringimage)
    {
      XrmValue value;
      if(ETL_get_resource(Ctxt.display, 
			  "xphone.ring.bitmap", "Xphone.Ring.Bitmap",
			  &value))
	{
	  ringimage = malloc(value.size+1);
	  strncpy(ringimage, value.addr, value.size);
	}
    }
  /* Get resources for window title, etc */
  {
    XrmValue value;

    if(ETL_get_resource(Ctxt.display, 
			"xphone.title", "Xphone.Title",
			&value))
      {
	window_name = malloc(value.size+1);
	strncpy(window_name, value.addr, value.size);
      }
    if(ETL_get_resource(Ctxt.display, 
			"xphone.iconname", "Xphone.IconName",
			&value))
      {
	icon_name = malloc(value.size+1);
	strncpy(icon_name, value.addr, value.size);
      }
  }
  if(!geometry)
    {
      XrmValue value;

      if(ETL_get_resource(Ctxt.display, 
			  "xphone.geometry", "Xphone.Geometry",
			  &value))
	{
	  geometry = malloc(value.size+1);
	  strncpy(geometry, value.addr, value.size);
	}
    }

  /* Find the images to display */
  if(idleimage)
    {
      int hxr, hyr;

      if(XReadBitmapFile(Ctxt.display, 
			 XDefaultRootWindow(Ctxt.display),
			 idleimage,
			 &winwidth, &winheight,
			 &Ctxt.idle,
			 &hxr, &hyr))
	{
	  fprintf(stderr, "Error reading bitmap file %s.\n", idleimage);
	  return 1;
	}
    }
  else
    {
      if((Ctxt.idle = XCreateBitmapFromData(Ctxt.display, 
					    XDefaultRootWindow(Ctxt.display),
					    phone_bits,
					    phone_width, phone_height))
	 == 0)
	{
	  fprintf(stderr, "Error creating Bitmap from data for IDLE image.\n");
	  return 1;
	}
      winwidth = phone_width;
      winheight = phone_height;
    }
  if(ringimage)
    {
      int hxr, hyr;

      if(XReadBitmapFile(Ctxt.display, 
			 XDefaultRootWindow(Ctxt.display),
			 ringimage,
			 &ringwidth, &ringheight,
			 &Ctxt.active,
			 &hxr, &hyr))
	{
	  fprintf(stderr, "Error reading bitmap file %s.\n", idleimage);
	  return 1;
	}
    }
  else
    {
      if((Ctxt.active = XCreateBitmapFromData(Ctxt.display, 
					      XDefaultRootWindow(Ctxt.display),
					      ring_bits,
					      ring_width, ring_height))
	 == 0)
	{
	  fprintf(stderr, "Error creating Bitmap from data for RINGER image.\n");
	  return 1;
	}
      ringwidth = ring_width;
      ringheight = ring_height;
    }

  if(geometry)
    {
      long flags;
      int x, y, width, height;

      flags = XParseGeometry(geometry, &x, &y, &width, &height);
      if(flags & XValue)
	{
	  if(XNegative & flags)
	    xpos = DisplayHeight(Ctxt.display, DefaultScreen(Ctxt.display))
	      + x - winwidth;
	  xpos = x;
	}
      if(flags & YValue)
	{
	  if(YNegative & flags)
	    ypos = DisplayHeight(Ctxt.display, DefaultScreen(Ctxt.display))
	      + y - winheight;
	  ypos = y;
	}
    }

  /* Make a simple window... */
  Ctxt.window = 
    XCreateSimpleWindow(Ctxt.display,
			XDefaultRootWindow(Ctxt.display),
			xpos, ypos,		/* position */
			winwidth, winheight,
			2,
			WhitePixel(Ctxt.display, DefaultScreen(Ctxt.display)),
			BlackPixel(Ctxt.display, DefaultScreen(Ctxt.display)));
  
  /* Only grab a new color map if we are going to change it. */
  if(fgc1 || bgc1 || fgc2 || bgc2)
    cmap = DefaultColormap(Ctxt.display, DefaultScreen(Ctxt.display));

  if(fgc1) 
    {
      if(XAllocNamedColor(Ctxt.display, cmap, fgc1, &tmpcolor, &tmpcolor2))
	newvalues.foreground = tmpcolor.pixel;
      else
	{
	  fprintf(stderr, "Error allocating color %s.\n", fgc1);
	  return 1;
	}
    }
  else newvalues.foreground = BlackPixel(Ctxt.display, DefaultScreen(Ctxt.display));

  if(bgc1) 
    {
      if(XAllocNamedColor(Ctxt.display, cmap, bgc1, &tmpcolor, &tmpcolor2))
	newvalues.background = tmpcolor.pixel;
      else
	{
	  fprintf(stderr, "Error allocating color %s.\n", bgc1);
	  return 1;
	}
    }
  else newvalues.background = WhitePixel(Ctxt.display, DefaultScreen(Ctxt.display));

  Ctxt.idle_gc = XCreateGC(Ctxt.display,
			   Ctxt.window,
			   GCForeground | GCBackground,
			   &newvalues);
  
  if(fgc2)
    {
      if(XAllocNamedColor(Ctxt.display, cmap, fgc2, &tmpcolor, &tmpcolor2))
	newvalues.foreground = tmpcolor.pixel;
      else
	{
	  fprintf(stderr, "Error allocating color %s.\n", fgc2);
	  return 1;
	}
    }
  else newvalues.foreground = WhitePixel(Ctxt.display, DefaultScreen(Ctxt.display));

  if(bgc2) 
    {
      if(XAllocNamedColor(Ctxt.display, cmap, bgc2, &tmpcolor, &tmpcolor2))
	newvalues.background = tmpcolor.pixel;
      else
	{
	  fprintf(stderr, "Error allocating color %s.\n", bgc2);
	  return 1;
	}
    }
  else newvalues.background = BlackPixel(Ctxt.display, DefaultScreen(Ctxt.display));
  
  Ctxt.active_gc = XCreateGC(Ctxt.display,
			     Ctxt.window,
			     GCForeground | GCBackground,
			     &newvalues);
  
  attr.event_mask = ExposureMask;
  XChangeWindowAttributes(Ctxt.display, Ctxt.window, 
			  CWEventMask, 
			  &attr);

  /* Set some sizing constraints */
  sizehints.flags = PMinSize | PMaxSize | PSize;
  sizehints.width = sizehints.min_width = sizehints.max_width = winwidth;
  sizehints.height = sizehints.min_height = sizehints.max_height = winheight;

  /* Set some icon stuff */
  wmhints.flags = IconPixmapHint;
  wmhints.icon_pixmap = Ctxt.idle;

  /* Lets name our windows, and give then some class names, etc */
  if(XStringListToTextProperty(&window_name, 1, &windowName) == 0)
    {
      fprintf(stderr, "Error converting window name to text property\n");
      return -1;
    }
  if(XStringListToTextProperty(&icon_name, 1, &iconName) == 0)
    {
      fprintf(stderr, "Error converting icon name to text property\n");
      return -1;
    }

  class_hints.res_name = argv[0];
  class_hints.res_class = "xphone";

  /* Make it so! */
  XSetWMProperties(Ctxt.display, Ctxt.window, &windowName, &iconName,
		   argv, argc, &sizehints, &wmhints, &class_hints);

  XMapWindow(Ctxt.display, Ctxt.window);
  
  /* Due to the nature of our connection, we must ALWAYS flush our
   * X queue because we don't call XNextEvent directly.
   */
  XFlush(Ctxt.display);

  /* Loop forever on all available input devices. */
  ET_select_all(&Ctxt, NULL);

  /* If we get here, then there is some sort of error.
   */
  return 1;
}


/*
 * Function: XPH_draw_picture
 *
 *   Draw the current phone image dependant on the type if image it is.
 *
 * Returns:     Nothing
 * Parameters:  Ctxt - Context
 *              dev  - Pointer to device
 * History:
 * zappo   4/17/95    Created
 */
void XPH_draw_picture(Ctxt, dev)
     struct PhoneContext *Ctxt;
     struct InputDevice  *dev;
{
  if(!Ctxt->ringing)
    XCopyPlane(Ctxt->display,
	       Ctxt->idle,
	       Ctxt->window,
	       Ctxt->idle_gc,
	       0, 0, phone_width, phone_height, 0, 0,
	       1);
  else
    XCopyPlane(Ctxt->display,
	       Ctxt->active,
	       Ctxt->window,
	       Ctxt->active_gc,
	       0, 0, ring_width, ring_height, 0, 0,
	       1);	
  XFlush(Ctxt->display);
}


/*
 * Function: XPH_xread
 *
 *   Reads from the X file descriptor to handle any wierdness going on
 * in there.
 *
 * Returns:     Nothing
 * Parameters:  Ctxt - Context
 *              dev  - Pointer to X device
 * History:
 * zappo   4/17/95    Created
 */
void XPH_xread(Ctxt, dev)
     struct PhoneContext *Ctxt;
     struct InputDevice  *dev;
{
  XEvent event;

  XNextEvent(Ctxt->display, &event);
  
  switch(event.type)
    {
    case Expose:
      XPH_draw_picture(Ctxt, dev);
      break;
    case NoExpose:
      break;
    default:
      printf("Got a weird event (%d).\n", event.type);
      break;
    }
  XFlush(Ctxt->display);
}

/*
 * Function: XPH_read
 *
 *   Reads from the ring_socket and provides information to the user
 * saying that someone is trying to contact them.
 *
 * Returns:     Nothing
 * Parameters:  Ctxt - Context
 *              dev  - Pointer to device
 * History:
 * zappo   4/16/95    Created
 */
void XPH_read(Ctxt, dev)
     struct PhoneContext *Ctxt;
     struct InputDevice  *dev;
{
  GNU_RING_CONTROL rc;
  GNU_RING_RESPONSE rr;
  struct InputDevice *newudp;

  ET_recv(dev, &rc, sizeof(rc));

  /* Fix byte swapping in the family so we can use this address. */
  ((struct sockaddr_in *)&rc.addr)->sin_family = 
    ntohs(((struct sockaddr_in *)&rc.addr)->sin_family);

  /* We could also use dev->lraddr, but this is more stable. In     */
  /* addition, this particular address will always be recycled too. */
  newudp = UDP_byaddr(dev, (struct sockaddr_in *)&rc.addr);
  newudp->name = "daemon_ringport";
 
  /* Always print this: This will let us see the full message in the log. */
  if(!Ctxt->silent)
    RING_print_control(&rc);

  /* Now formulate a response ... */
  rr.vers = TALK_VERSION_GNU;
  rr.type = rc.type;
  rr.answer = SUCCESS;
  
  /* Create a new device which happens to be the talk daemon sending */
  /* this message to me.  Hrumph!                                    */
  ET_send(newudp, &rr, sizeof(rr));

  if(verbose)
    RING_print_response(&rr);

  /* Make a noise.  Call ringcommand if a command was given to make a */
  /* special noise.  If it fails, use XBell                           */
  if(Ctxt->ringcommand)
    {
      if(system(Ctxt->ringcommand))
	XBell(Ctxt->display, 100);
    }
  else
    XBell(Ctxt->display, 100);
  
  dev->timeout = 31;
  dev->timefn  = XPH_noring;

  Ctxt->ringing = TRUE;

  XPH_draw_picture(Ctxt, dev);

  return;
}


/*
 * Function: XPH_noring
 *
 *   When a ring times out, then we must set the phone pixmap back to
 * its original image.
 *
 * Returns:     Nothing
 * Parameters:  Ctxt - Context
 *              dev  - Pointer to device
 * History:
 * zappo   4/17/95    Created
 */
void XPH_noring(Ctxt, dev)
     struct PhoneContext *Ctxt;
     struct InputDevice  *dev;
{
  Ctxt->ringing = FALSE;

  XPH_draw_picture(Ctxt, dev);
}


/*
 * Function: xphone_sighandle
 *
 *   Xphone shutdown handler.  If any unpleasant signals are trapped,
 * make sure things are cleaned up whenever possible.
 *
 * Parameters:  val - Unused signal status information
 *
 * History:
 * zappo   2/11/95    Created
 */
RETSIGTYPE xphone_sighandle(val)
     int val;
{
  xphone_shutdown();
}


/*
 * Function: xphone_shutdown
 *
 *   Run all necessary shutdown procedures, then exit the program.
 *
 * Returns:     Nothing
 * Parameters:  None
 *
 * History:
 * zappo   2/11/95    Created
 */
void xphone_shutdown()
{
  RING_deactivate();
  exit(0);
}
