/* This file is part of the Project Athena Zephyr Notification System.
 * It contains the main body of the windowgram client.
 *
 *	Created by:	Mark W Eichin
 *
 *	$Source: /mit/zephyr/src/zwgc/RCS/main.c,v $
 *	$Author: jtkohl $
 *
 *	Copyright (c) 1987,1988,1989 by the Massachusetts Institute of
 *		Technology.
 *	For copying and distribution information, see the file
 *	"mit-copyright.h". 
 */

#include <zephyr/mit-copyright.h>

#ifndef lint
static char rcsid_stubx_c[] = "$Header: main.c,v 2.19 89/07/20 10:14:48 jtkohl Exp $";
#include "zwgc.vers"
#endif lint

#include <zephyr/zephyr.h>
#define EXPOSE_DEFAULT NULL

char loopback[4];

#ifdef X11
#include <X11/Xlib.h>
#endif

/* The following is some syntactic sugar for the way I like error messages. */
int __com_stat;
#define TRAP(f,e) if(__com_stat=(f)){com_err(__FILE__,__com_stat,e);exit(0); }
#define NF_TRAP(f,e) if (__com_stat=(f)) { com_err(__FILE__,__com_stat,e);}

#ifdef X11
extern Display *dpy;
#endif
char *saved;

#include "minmax.h"
#include "file_defs.h"
#include <signal.h>
#define SELECT_TIMEOUT 0
#define SELECT_ERROR -1
#include "ropes.h"
#include "comp_types.h"
#include "support.h"

int x_recent_error=0;
int logout_flag;
int ok_int_flag=0;
int reload_flag=0;
int debug_flag=0;

char *load_desc();
extern rope *ruser, *rsys, *rpanic, *rblankmsg;

int strand_field_max = -1;
int strand_head_max = -1;
int strand_raw_max = -1;

/* the following maintain the /tmp/wg.{uid} file used to hold the port
 * number for the wgc. 
 */   
char *getenv();

void save_port_num(port)
     u_short port;
{
  FILE *fd;
  char st[1024];
  char *portfile = getenv("WGFILE");

  if(!portfile)
    {
      sprintf(st,"/tmp/wg.%d",getuid());
      portfile = st;
    }
  fd = fopen(portfile,"w");
  if(!fd) {
    perror(portfile);
    exit(1);
  }

  fprintf(fd, "%d\n", port);

  fclose(fd);
}

void unsave_port_num()
{
  char st[1024];
  char *portfile = getenv("WGFILE");

  if(!portfile)
    {
      sprintf(st,"/tmp/wg.%d",getuid());
      portfile = st;
    }

  unlink(portfile);
}

extern int ttymode;
int xdebug;

/* detach() taken bodily out of ../zhm/zhm.c [eichin:19880629.0426EST]*/
/* arguments for appropriate action added... */
#include <sys/ioctl.h>
#include <sys/file.h>
#include <ctype.h>
#define DET_FORK	001
#define DET_CLOSE	002

void detach(arg)
     int arg;
{
  /* detach from terminal and fork. */
  register int i, z = ZGetFD(), x = 0, size = getdtablesize();

  if(xdebug) return;		/* don't do *ANY* of this... */
  
#ifdef X11
  if(!ttymode) x = ConnectionNumber(dpy);
#endif /* X11 */
  

  setpgrp(0, getpgrp(getppid()));

  if(arg & DET_FORK) {       
    /* fork off and let parent exit... */
    if (i = fork()) {
      if (i < 0)
	perror("fork");
      exit(0);
    }
  }

  if(arg & DET_CLOSE) {
    /* close all other file descriptors (not ttymode...) */
    for (i = 0; i < size; i++)
      if (!((i == z) || (x && i == x) || (i==1) || (i==2)) ) /* for stdout? */
	(void) close(i);

    if ((i = open("/dev/tty", O_RDWR, 666)) < 0)
      ;				/* Can't open tty, but don't flame about it. */
    else {
      (void) ioctl(i, TIOCNOTTY, (caddr_t) 0);
      (void) close(i);
    }
  }
}	


setup_subscriptions()
{
  /* fork and exec zctl for now :-) */
  int pid;

  if(!(pid = vfork()))
    {
      execl(ZCTLPROG,"zctl","load",0); /* DO THIS RIGHT! */
      perror("zwgc exec");
      fprintf(stderr,"Unable to load subscriptions.\n");
      exit(0);
    }
  if(pid == -1)
    {
      perror("zwgc exec");
      fprintf(stderr,"Unable to fork and load subscriptions.\n");
    }
}

#define ANew(type,cnt) (type *)malloc(cnt * sizeof(type))

ZSubscription_t *cat_subs;
int cat_nsubs;
  
Code_t go_catatonic()
{
  int i;
  
  NF_TRAP(ZRetrieveSubscriptions(0,&cat_nsubs),
	  "retrieving subscriptions before going catatonic");
  if(__com_stat!=ZERR_NONE) return(__com_stat);

  cat_subs = ANew(ZSubscription_t, cat_nsubs);

  NF_TRAP(ZGetSubscriptions(cat_subs,&cat_nsubs),
	  "getting subscriptions before going catatonic");
  if(__com_stat!=ZERR_NONE) return(__com_stat);
  
  /* ZFlushSubscriptions(); */ /* DON'T! the subs are not copied... */
  
  NF_TRAP(ZCancelSubscriptions(0),
	  "canceling subscriptions while going catatonic");
  if(__com_stat!=ZERR_NONE) return(__com_stat);

  return(ZERR_NONE);
}

Code_t go_noncat()
{
  int i;
  
  if(!cat_nsubs)
    {
      setup_subscriptions();
    }
  else
    {
      NF_TRAP(ZSubscribeTo(cat_subs, cat_nsubs, 0),
	      "resubscribing to subscriptions leaving catatonia");
      cat_nsubs = 0;
      free(cat_subs);
      cat_subs = NULL;
    }
  return(ZERR_NONE);
}

setup_initprog(progname)
     char *progname;
{
  int status;

  if(!progname) return;

  status = system(progname);

  if(status==127)
    {
      perror("zwgc initprog exec");
      fprintf(stderr,"zwgc initprog of <%s> failed: no shell.\n",
	      progname);
    }
  else if (status!=-1 && status>>8)
    {
      perror("zwgc initprog exec");
      fprintf(stderr,"zwgc initprog of <%s> failed with status [%d].\n",
	      progname, status>>8);
    }      
}

#include <pwd.h>
void try_alternate()
{
  int f;
  struct passwd *pwd;
  char *hmdir, *xfnam;
  char reentry[BUFSIZ];

  hmdir = getenv("HOME");

  if(!hmdir)
    {
      if (!(pwd = getpwuid(getuid())))
	{
	  printf("zwgc:main Who are you? Your userid is not in the passwd file!\n");
	  exit (1);
	}
      xfnam = malloc(strlen(pwd->pw_dir)+1+strlen(USRDESC)+1);
      (void)strcpy(xfnam,pwd->pw_dir);
    }
  else
    {
      xfnam = malloc(strlen(hmdir)+1+strlen(USRDESC)+1);
      (void)strcpy(xfnam,hmdir);
    }
  strcat(xfnam,"/");
  strcat(xfnam,USRDESC);

  f = open(xfnam, O_RDONLY, 0);
  free(xfnam);

  if(f<0)
    {
      return;
    }
  
  if(read(f, reentry, BUFSIZ)==-1)
    {
      close(f);
      return;
    }

  close(f);

  if(reentry[0] == '#' && reentry[1] == '!') 
    /* found magic number... */
    {
      char *ex, *scn=reentry;

      scn += 2;
      while(*scn && is_blank(*scn)) scn++;
      ex = scn;
      while(*scn
	    && *scn != ' ' 
	    && *scn != '\n' 
	    && *scn != '\t')
	scn++;
      scn[0] = '\0';
      execl(ex, ex, "-reenter", 0);
      perror("zwgc:main Couldn't execute substitute program\n");
    }
}
char *parse_exposure();
int catatonic;
char *exposure;

char **saved_argv;		/* for setting WM_COMMAND property */
int saved_argc;			/* for setting WM_COMMAND property */

int do_x_configwindow = 0;	/* set if we need to react to a
				   ConfigureWindow error (it's set at
				   "interrupt" level) */
extern void handle_x_configwindow();

main(argc,argv)
     int argc;
     char **argv;
{
  int Zfd,Xfd;
  u_short port=0;
  int nfound, nfds;
  fd_set readfds, savefds;
  int reenter=0;
  int nzqueued;

  saved_argv = argv;
  saved_argc = argc;
  catatonic = 0;
#ifdef X11
  ttymode = 0;
#else /* !X11 */
  ttymode = 1;
#endif /* X11 */
  xdebug = 0;			/* ifdef this? */

  if(argc>1)
    {
      int ac=1;
      while(ac<argc)
	{
	  if(!strcmp(argv[ac],"-reenter"))
	    {
	      reenter = 1;
	    }
	  else if (!strcmp(argv[ac],"-ttymode"))
	    {
	      ttymode = 1;
	    }
	  else if (!strcmp(argv[ac],"-nofork"))
	    {
	      xdebug = 1;
	    }
	  ac++;
	}
    }

  if(!reenter) try_alternate();
  
  error_handler_initialize();

  Xfd = xtx_draw_init(FALSE);

  TRAP(ZInitialize(), "Initializing Zephyr");

  TRAP(ZOpenPort(&port), "Opening Port");

  save_loopback_address();
  save_port_num(port);
  exposure = parse_exposure(ZGetVariable("exposure"));
  if (exposure)
    {
      NF_TRAP(ZSetLocation(exposure), "Setting Location"); 
    }
  else
    {
      catatonic = 1;
    }

  /* nothing else can lose, here. */
  detach(ttymode?(DET_FORK):(DET_FORK|DET_CLOSE));
  if(!catatonic) setup_subscriptions();	/* what if tickets fail? hmm... */
  setup_initprog(ZGetVariable("initprogs"));

  Zfd=ZGetFD();

  /* initalize fieldrp: statics can't be init'ed from procedures. */
  fieldrp = ropinit(ROPE_SIZE);
  token_init();
  saved = load_desc(USRDESC, DEFDESC);
  free(saved);			/* anachronism, with try_alternate now */

  FD_ZERO(&savefds);
  FD_SET(Zfd, &savefds);
#ifdef X11
  if (!ttymode)
    {
      FD_SET(Xfd, &savefds);
      nfds = max(Zfd,Xfd)+1;
    }
  else
    {
      nfds = Zfd + 1;
    }
#else /* !X11 */
  nfds = Zfd + 1;
#endif /* !X11 */

  for (;;) {
    readfds=savefds;

    /* We must handle all events we can until the queue is of zero */
    /* length. Once it is, THEN we select since further events will */
    /* have to arrive via the file descriptor. */

#ifdef X11
    while (do_x_configwindow || (!ttymode && XPending(dpy)) || ZPending()>0)
      {
	if (do_x_configwindow) handle_x_configwindow();
	if (!ttymode && XPending(dpy)) handle_x_event();
	if (ZPending()>0) handle_z_event();
      }
#else /* !X11 */
    while (ZPending()>0)
      {
	handle_z_event();
      }
#endif /* X11 */

    nfound = select(nfds, &readfds, NULL, NULL, NULL);
    
    if(nfound==SELECT_TIMEOUT)
      {
	handle_select_timeout();
      }
    if(nfound==SELECT_ERROR)
      {
	handle_select_error();
      }
    else
      {
#ifdef X11
	if(!ttymode && FD_ISSET(Xfd, &readfds))
	  {
	    if ( XPending(dpy) == 0 )
	      {
		if(x_recent_error--)
		  {
		    /* ignoring packet slurped by XError. */
		  }
		else
		  {
		    printf("zwgc:main Select error: X Socket seemed to close; the server probably died.\n");
		    exit_cleanly();
		  }
	      }
	    else
	      {
		handle_x_event();
	      }
	  }
#endif /* X11 */
	if(FD_ISSET(Zfd, &readfds))
	  {
	    if ( (nzqueued = ZPending()) == -1 )
	      {
		if(errno == ZERR_EOF)
		  {
		    com_err("zwgc",errno," on select");
		    exit(-1);	/* if eof, we still can't exit_cleanly */
		  }
		else
		  {
		    com_err("zwgc",errno," on select");
		    exit(-1);	/* who knows WHAT happened... */
		  }
	      }
	    else if (nzqueued > 0)
	      {
		handle_z_event();
	      }
	  }
      }
  }
}

/*
 * handle_select_timeout
 *
 * Detects a select timeout from inner loop. May need to use it
 * someday...
 *
 */

handle_select_timeout()
{
  printf("zwgc:main Select timed out\n");
}

/*
 * handle_select_error
 *
 * Checks if select was interrupted, check for interrupt flags, and
 * deal with them. Gracefully shutdown on a logout signal.
 *
 */

handle_select_error()
{
  if(errno==EINTR)
    {
      if(logout_flag)
	{
	  exit_cleanly();
	}
      else if (ok_int_flag)
	{
	  ok_int_flag = 0;	/* we'll get you next time... he he he */
	}
      else if (reload_flag)
	{
	  reload_flag = 0;

	  saved = load_desc(USRDESC,DEFDESC);

	  if (saved)
	    {
	      free(saved);
	    }
	  else
	    {
	      printf("zwgc Unsuccessful reload of description file.\n");
	    }
	}
      else if (debug_flag)
	{
	  printf("zwgc:main global debug call:A signal hits... --more--\n");
	  global_debug();
	  debug_flag = 0;
	}
      else
	{
	  printf("zwgc:main Surprising signal, ignoring\n");
	}
    }
  else
    {
      TRAP(errno,"On select");
    }
}

#ifdef X11
/*
 * handle_x_event
 *
 * Should call XNextEvent and then whatever handler exists for generic
 * events. Will go through the toolkit. For now, just blat out that we
 * got one.
 *
 */
Window curwin = 0;
int curflg = False;

handle_x_event()
{
  XEvent ev;

#ifdef FLUSH_X_FIRST
  while (QLength(dpy)!=0) {
#endif FLUSH_X_FIRST

    XNextEvent(dpy, &ev);
    /* [icky comment from dix/events.c:]
     * 
     * Remember to strip off the leading bit of type in case
     * this event was sent with "SendEvent."
     * (*EventSwapVector[eventFrom->u.u.type & 0177])
     */

    switch(ev.type & 0177)
      {
      case ButtonRelease:
	if(ev.xbutton.window == curwin && curflg) {
	  XDestroyWindow(dpy, ev.xbutton.window);
	  xtx_unregister_expose(ev.xbutton.window);
	} else {
	  /* should we flame the user for punting? */
	  /*
	   * we are already taking advantage of the implicit grab, so
	   * we don't have to undo anything here.
	   */
	}
	break;
      case Expose:
	xtx_handle_expose(&ev);
	break;
      case ButtonPress:
	curwin = ev.xbutton.window;
	curflg = True;
	break;
      case LeaveNotify:
	if(ev.xcrossing.window == curwin) {
	  curflg = False;
	} else {
	  curwin = 0;		/* deal with unexpected departure... */
	  curflg = False;
	}
	break;
      case MappingNotify:
	/* But we don't *use* the keyboard, officer! */
	break;
      default:
	printf("zwgc:main Unexpected event: %d on resource %x\n",
	       ev.type, ev.xbutton.window); /* well, ev.frep.window... */
	break;
      }

#ifdef BLAT_XEVENTS
    printf("zwgc:main Got an x_event, 0x%xd.\n",ev.type);
#endif BLAT_XEVENTS

/*    (void)XtDispatchEvent(&ev); */

#ifdef FLUSH_X_FIRST
  }
#endif FLUSH_X_FIRST

}
#endif /* X11 */

/*
 * handle_z_event
 *
 * Recieve and process notice. Should call the toolkit code and pop up
 * a window or flash an icon or something. For now, blat the notice to
 * stdout.
 *
 */

handle_z_event()
{
  ZNotice_t notice;
  int auth;
  struct sockaddr_in from_field;

#ifdef BLAT_ZEVENTS
  printf("zwgc:main z_event Here...\n");
#endif BLAT_ZEVENTS

  TRAP(ZReceiveNotice(&notice, &from_field),
       "Receiving notice");
  auth = ZCheckAuthentication(&notice,&from_field);

  /* IGNORE the auth field, just use it to carry the retval. */
  notice.z_auth = auth;

#ifdef BLAT_ZEVENTS
  /* Note that the following code could be used to provide some sort
   * of tty/ascii interface, albeit a poor one.
   */
  printf("notice:\n");
  printf("\tz_kind: %d\n", notice.z_kind);
  printf("\tz_port: %u\n", htons(notice.z_port)); /* or is this not htons? */
  printf("\tz_class: %s\n", notice.z_class);
  printf("\tz_class_inst: %s\n", notice.z_class_inst);
  printf("\tz_opcode: %s\n", notice.z_opcode);
  printf("\tz_sender: %s\n", notice.z_sender);
  printf("\tz_recip: %s\n", notice.z_recipient);
  printf("\tMessage[%d]:\n",notice.z_message_len);
  pbody = notice.z_message;
  for (bodylen = strlen(pbody)+1; bodylen <= notice.z_message_len; bodylen++) {
    printf("\t%s\n", pbody);
    pbody += strlen(pbody)+1;
    bodylen += strlen(pbody);
  }
#endif BLAT_ZEVENTS
  /* we should have some abstraction here, this is kind of gross... */
  if ((bcmp(loopback, &from_field.sin_addr, 4) == 0) &&
      (!strcmp(notice.z_class, WG_CTL_CLASS)) &&
      (!strcmp(notice.z_class_inst, WG_CTL_USER)))
    {
      handle_ctl_notice(&notice);
      ZFreeNotice(&notice);
      return;
    }

  if(catatonic)
    {
      /* Don't even bother parsing the rest, just throw it away... */
      ZFreeNotice(&notice);
      return;
    }

  init_fields(notice.z_message, notice.z_message_len); 
  /* We do the fields first, so default has numbers to use... */
  init_headers(notice);
#include "eval_stat.h"
  {
    int stat = NE_OK;

    stat = new_eval(ruser, stat);
    if(!NE_IS_DONE(stat))
      {
	stat = new_eval(rsys, stat);
	if(!NE_IS_DONE(stat))
	  {
	    stat = new_eval(rpanic, stat);
	  }
      }
  }
      
      
  if(ttymode)
    {
      fflush(stdout);
    }
  /* 
   * Once we parse it, we have a disp object, and the rest is window
   * magic. We don't need the Zephyr structures or the interface, and
   * it isn't tacked to the display anyway. If it were, we (1) could
   * skip some copying (2) could destroy it when the disp got destroyed.
   * ---MWE 14 Aug 1987
   */
  ZFreeNotice(&notice); /* ignore the error return, it is fake anyway */
  /* varropes, except for the fields, point into description ropes!
   * Don't try to use clear_strand on varropes.
   */
  ropfree(fieldrp);
  
  clear_strand(strand_field,strand_field_max);
  clear_strand(strand_raw,strand_raw_max);
  clear_strand(strand_head,strand_head_max);
  /* reset the pointers... */
  fieldrp = ropinit(ROPE_SIZE);
  strand_field_max = -1;
  strand_head_max = -1;
  strand_raw_max = -1;
}

int dbgfrep=0;

dbg_printf(a,b,c,d,e,f,g,h)			/* None, for now... */
{
  if(dbgfrep)
    printf(a,b,c,d,e,f,g,h);
  return;
}

extern rope *cmdbuff;
exit_cleanly()
{
  int i;
  if (!catatonic)		/* if NULL, don't tell the server ANYTHING */
    {
      if(dbgfrep)
	{
	  NF_TRAP(ZCancelSubscriptions(0), "Cancelling subs");
	  NF_TRAP(ZUnsetLocation(), "unsetting location");
	}
      else
	{			/* No error messages, we are just *trying* */
	  ZCancelSubscriptions(0);
	  ZUnsetLocation();
	}	  
    }
  NF_TRAP(ZClosePort(), "closing port");
  unsave_port_num();
  /* free up some things we *know* we needed until now */

#ifdef DEBUG_MEMORY_LEAKS
  if(ruser) ropfree(ruser);
  if(rsys) ropfree(rsys);
  if(rpanic) ropfree(rpanic);
  if(rblankmsg) ropfree(rblankmsg);
  if(cmdbuff) ropfree(cmdbuff);
  if(fieldrp) ropfree(fieldrp);
  
  endhostent();			/* we use gethostbyname in init_headers */
  XFreeQ();			/* hack in XTextExt.c ... */

  for(i=0; i<max_vars; i++)
    if(variables[i]) free(variables[i]);
  free(variables);
  
  for(i=0; i<max_raws; i++)
    if(raw_table[i]) free(raw_table[i]);
  free(raw_table);
  
  xtx_shutdown();
#endif /* DEBUG_MEMORY_LEAKS */
  fflush(stdout);
  exit(0);
}

handle_ctl_notice(notice)
     ZNotice_t *notice;
{
  char *exp_op;

  exp_op = notice->z_opcode;

  if(!strcasecmp(exp_op, USER_REREAD))
    {
      xtx_draw_init(TRUE);	/* reread Xdefaults too! */
      
      saved = load_desc(USRDESC,DEFDESC);
      if (saved)
	{
	  free(saved);		/* reload does NOT do exec */
	}
      else
	{
	  fprintf(stderr, "zwgc Unsuccessful reload of WindowGram description file\n");
	}
    }
  else if(!strcasecmp(exp_op, USER_SHUTDOWN))
    {
      if(!catatonic)
	{
	  catatonic = 1;
	  go_catatonic();
	}
    }
  else if(!strcasecmp(exp_op, USER_STARTUP))
    {
      if(catatonic)
	{
	  catatonic = 0;
	  go_noncat();
	}
    }
  else
    {
      fprintf("zwgc:main invalid CTL notice, opcode = %s\n", notice->z_opcode);
    }
}


/*
 * parse_exposure
 * takes the user token for exposure level (from ZGetVariable)
 * returns the EXPOSE_ value from zephyr.h for the correct level
 * of exposure. NULL means no exposure whatsoever, do not call
 * the server at all, do not pass go, do not collect $200.00
 */
char *parse_exposure(exptype)
     char *exptype;
{
  if(!exptype)
    {
      return ( EXPOSE_DEFAULT );
    }

  if(!strcasecmp(exptype, EXPOSE_NONE))
    {
      return ( NULL );
    }
  else if(!strcasecmp(exptype, EXPOSE_OPSTAFF))
    {
      return ( EXPOSE_OPSTAFF );
    }
  else if(!strcasecmp(exptype, EXPOSE_REALMVIS))
    {
      return ( EXPOSE_REALMVIS );
    }
  else if(!strcasecmp(exptype, EXPOSE_REALMANN))
    {
      return ( EXPOSE_REALMANN );
    }
  else if(!strcasecmp(exptype, EXPOSE_NETVIS))
    {
      return ( EXPOSE_NETVIS );
    }
  else if(!strcasecmp(exptype, EXPOSE_NETANN))
    {
      return ( EXPOSE_NETANN );
    }
  else
    {
      fprintf(stderr, "zwgc: invalid exposure level <%s>\n", exptype);
      return ( EXPOSE_DEFAULT );
    }
}

/*
 * Not knowing a better way to do this, I have isolated it down here
 * so we can at least find it later.
 */

save_loopback_address()
{
  loopback[0] = 127;
  loopback[1] = 0;
  loopback[2] = 0;
  loopback[3] = 1;
}


