/*
 * $XConsortium: SSMailbox.c,v 1.35 89/10/09 16:51:44 jim Exp $
 *
 * Copyright 1988 Massachusetts Institute of Technology
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of M.I.T. not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  M.I.T. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * Author:  Jim Fulton, MIT X Consortium
 *
 * I recommend that you use the new mailfull and mailempty bitmaps instead of
 * the ugly mailboxes:
 *
 *         XBiff*fullPixmap:  mailfull
 *         XBiff*emptyPixmap:  mailempty
 */

#include <stdio.h>			/* for printing error messages */
#include <ctype.h>			/* for tolower() */
#include <pwd.h>			/* for getting username */

#ifdef sparc
# include <netdb.h>			/* for hostent struct */
#endif

#include <X11/cursorfont.h>		/* for cursor constants */
#include <X11/StringDefs.h>		/* for useful atom names */
#include <X11/IntrinsicP.h>		/* for toolkit stuff */
#include <sys/types.h>
#include <sys/stat.h>			/* for stat() ** needs types.h ***/

#include <X11/bitmaps/mailfull>		/* for flag up (mail present) bits */
#include <X11/bitmaps/mailempty>	/* for flag down (mail not here) */

#include <X11/Xaw/XawInit.h>
#include "SSMailboxP.h"			/* for implementation mailbox stuff */

#include <X11/Xmu/Converters.h>		/* for XmuCvtStringToBitmap */

#ifdef SHAPE
#include <X11/extensions/shape.h>
#endif

#include "strtok.h"

/*
 * The default user interface is to have the mailbox turn itself off whenever
 * the user presses a button in it.  Expert users might want to make this 
 * happen on EnterWindow.  It might be nice to provide support for some sort of
 * exit callback so that you can do things like press q to quit.
 */

static char defaultTranslations[] = 
  "<Btn1Down>:  unset()\n\
   <Btn3Down>:  review()";

static void Check(), Set(), Unset(), Review();

static XtActionsRec actionsList[] = { 
    { "check",	Check },
    { "unset",	Unset },
    { "set",	Set },
    { "review", Review },
};


/* Initialization of defaults */

#define offset(field) XtOffset(SSMailboxWidget,ssmailbox.field)
#define goffset(field) XtOffset(Widget,core.field)

static Dimension defDim = 48;
static Pixmap nopix = None;

static XtResource resources[] = {
    { XtNwidth, XtCWidth, XtRDimension, sizeof (Dimension), 
	goffset (width), XtRDimension, (caddr_t)&defDim },
    { XtNheight, XtCHeight, XtRDimension, sizeof (Dimension),
	goffset (height), XtRDimension, (caddr_t)&defDim },
    { XtNupdate, XtCInterval, XtRInt, sizeof (int),
	offset (update), XtRString, "30" },
    { XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
	offset (foreground_pixel), XtRString, "black" },
    { XtNbackground, XtCBackground, XtRPixel, sizeof (Pixel),
	goffset (background_pixel), XtRString, "white" },
    { XtNreverseVideo, XtCReverseVideo, XtRBoolean, sizeof (Boolean),
	offset (reverseVideo), XtRString, "FALSE" },
    { XtNfile, XtCFile, XtRString, sizeof (String),
	offset (filename), XtRString, NULL },
    { XtNcheckCommand, XtCCheckCommand, XtRString, sizeof(char*),
	offset (check_command), XtRString, NULL},
    { XtNvolume, XtCVolume, XtRInt, sizeof(int),
	offset (volume), XtRString, "50"},
    { XtNonceOnly, XtCBoolean, XtRBoolean, sizeof(Boolean),
	offset (once_only), XtRImmediate, (caddr_t)False },
    { XtNfullPixmap, XtCPixmap, XtRBitmap, sizeof(Pixmap),
	offset (full.bitmap), XtRString, "flagup" },
    { XtNfullPixmapMask, XtCPixmapMask, XtRBitmap, sizeof(Pixmap),
	offset (full.mask), XtRBitmap, (caddr_t) &nopix },
    { XtNemptyPixmap, XtCPixmap, XtRBitmap, sizeof(Pixmap),
	offset (empty.bitmap), XtRString, "flagdown" },
    { XtNemptyPixmapMask, XtCPixmapMask, XtRBitmap, sizeof(Pixmap),
	offset (empty.mask), XtRBitmap, (caddr_t) &nopix },
    { XtNflip, XtCFlip, XtRBoolean, sizeof(Boolean),
	offset (flipit), XtRString, "true" },
#ifdef SHAPE
    { XtNshapeWindow, XtCShapeWindow, XtRBoolean, sizeof(Boolean),
        offset (shapeit), XtRString, "false" },
#endif
    { XtNsound, XtCSound, XtRString, sizeof (String),
	offset (sound), XtRString, NULL },
    { XtNnoMailSound, XtCNoMailSound, XtRString, sizeof (String),
	offset (no_mail_sound), XtRString, NULL },
    { XtNsoundBindings, XtCSoundBindings, XtRString, sizeof (String),
	offset (sound_bindings), XtRString, NULL },
    { XtNfaceBindings, XtCFaceBindings, XtRString, sizeof (String),
	offset (face_bindings), XtRString, NULL },
    { XtNcaseSensitive, XtCCaseSensitive, XtRBoolean, sizeof(Boolean),
      offset (case_sensitive), XtRBoolean, "true" },
    { XtNautoResize, XtCAutoResize, XtRBoolean, sizeof(Boolean),
      offset (auto_resize), XtRBoolean, "false" },
    { XtNuseLED, XtCUseLED, XtRBoolean, sizeof(Boolean),
      offset (use_LED), XtRBoolean, "false" },
    { XtNwhichLED, XtCWhichLED, XtRInt, sizeof(int),
      offset (which_LED), XtRString, "1" }
};

#undef offset
#undef goffset

static void GetMailFile(), CloseDown();
static void check_ssmailbox(), redraw_ssmailbox(), reshape_ssmailbox();
static void ClassInitialize(), Initialize(), Realize(), Destroy(), Redisplay();
static void beep(), frob_leds();
static Boolean SetValues();
Boolean show_face ();
static int newmsgs();

SSMailboxClassRec ssmailboxClassRec = {
    { /* core fields */
    /* superclass		*/	&widgetClassRec,
    /* class_name		*/	"SSMailbox",
    /* widget_size		*/	sizeof(SSMailboxRec),
    /* class_initialize		*/	ClassInitialize,
    /* class_part_initialize	*/	NULL,
    /* class_inited		*/	FALSE,
    /* initialize		*/	Initialize,
    /* initialize_hook		*/	NULL,
    /* realize			*/	Realize,
    /* actions			*/	actionsList,
    /* num_actions		*/	XtNumber(actionsList),
    /* resources		*/	resources,
    /* resource_count		*/	XtNumber(resources),
    /* xrm_class		*/	NULL,
    /* compress_motion		*/	TRUE,
    /* compress_exposure	*/	TRUE,
    /* compress_enterleave	*/	TRUE,
    /* visible_interest		*/	FALSE,
    /* destroy			*/	Destroy,
    /* resize			*/	NULL,
    /* expose			*/	Redisplay,
    /* set_values		*/	SetValues,
    /* set_values_hook		*/	NULL,
    /* set_values_almost	*/	XtInheritSetValuesAlmost,
    /* get_values_hook		*/	NULL,
    /* accept_focus		*/	NULL,
    /* version			*/	XtVersion,
    /* callback_private		*/	NULL,
    /* tm_table			*/	defaultTranslations,
    /* query_geometry		*/	XtInheritQueryGeometry,
    /* display_accelerator	*/	XtInheritDisplayAccelerator,
    /* extension		*/	NULL
    }
};

struct SoundStruct 
{
    char *sound;
    char *face;
    struct SoundStruct *next;
};

struct MsgStruct 
{
    struct SoundStruct *sounds;
    struct MsgStruct *next;
};

WidgetClass ssmailboxWidgetClass = (WidgetClass) &ssmailboxClassRec;

/*
 * widget initialization
 */

static void ClassInitialize ()
{
    static XtConvertArgRec screenConvertArg[] = {
    { XtWidgetBaseOffset, (caddr_t) XtOffset(Widget, core.screen), sizeof(Screen *) }
    };

    XawInitializeWidgetSet();
    XtAddConverter (XtRString, XtRBitmap, XmuCvtStringToBitmap,
		    screenConvertArg, XtNumber(screenConvertArg));
    return;
}

static GC get_ssmailbox_gc (w)
    SSMailboxWidget w;
{
    XtGCMask valuemask;
    XGCValues xgcv;

    valuemask = GCForeground | GCBackground | GCFunction | GCGraphicsExposures;
    xgcv.foreground = w->ssmailbox.foreground_pixel;
    xgcv.background = w->core.background_pixel;
    xgcv.function = GXcopy;
    xgcv.graphics_exposures = False;	/* this is Bool, not Boolean */
    return (XtGetGC ((Widget) w, valuemask, &xgcv));
}


/* ARGSUSED */
static void Initialize (request, new)
    Widget request, new;
{
    SSMailboxWidget w = (SSMailboxWidget) new;
#ifdef SHAPE
    int shape_event_base, shape_error_base;
#endif

    if (!w->ssmailbox.filename) GetMailFile (w);

    if (w->core.width <= 0) w->core.width = 1;
    if (w->core.height <= 0) w->core.height = 1;

    if (w->ssmailbox.reverseVideo) {
	Pixel tmp;

	tmp = w->ssmailbox.foreground_pixel;
	w->ssmailbox.foreground_pixel = w->core.background_pixel;
	w->core.background_pixel = tmp;
    }

#ifdef SHAPE
    if (w->ssmailbox.shapeit && !XShapeQueryExtension (XtDisplay (w),
						     &shape_event_base,
						     &shape_error_base))
      w->ssmailbox.shapeit = False;
    w->ssmailbox.shape_cache.mask = None;
#endif

    w->ssmailbox.gc = get_ssmailbox_gc (w);
    w->ssmailbox.interval_id = (XtIntervalId) 0;
    w->ssmailbox.full.image = None;
    w->ssmailbox.empty.image = None;
    w->ssmailbox.current_face.image = None;
    w->ssmailbox.full.colors = None;
    w->ssmailbox.empty.colors = None;
    w->ssmailbox.current_face.colors = None;
    w->ssmailbox.full.ncolors = 0;
    w->ssmailbox.empty.ncolors = 0;
    w->ssmailbox.current_face.ncolors = 0;
    w->ssmailbox.last_numnew = 0;
    w->ssmailbox.bindings = 0;

    w->ssmailbox.face = 0;

    w->ssmailbox.not_on_console = not_on_console_p (XtDisplay (w));

    return;
}


int not_on_console_p (display)
     Display *display;
{
#ifdef sparc
  /* When running on a SparcStation, we cannot use digitized sounds as
     beeps unless emacs is running on the same machine that $DISPLAY
     points to, and $DISPLAY points to screen 0 of that machine.
   */
  int not_on_console;
  char *dpy = display->display_name;
  char *tail = (char *) strchr (dpy, ':');
  if (! tail ||
      strncmp (tail, ":0", 2))
    not_on_console = 1;
  else {
    char dpyname[255], localname[255];
    strncpy (dpyname, dpy, tail-dpy);
    dpyname [tail-dpy] = 0;
    if (dpyname[0] == '\0' || !strcmp(dpyname, "unix") ||
      !strcmp(dpyname, "localhost"))
      not_on_console = 0;
    else if (gethostname (localname, 255))
      not_on_console = 1;  /* can't find hostname? */
    else {
      /* gethostbyname() reuses the structure it returns,
	 so we have to copy the string out of it. */
      struct hostent *h = gethostbyname (dpyname);
      not_on_console = !!(strcmp (localname, h->h_name));
    }
  }
  return not_on_console;
#else
  return 1;
#endif
}

/*
 * action procedures
 */

/*
 * pretend there is new mail; put widget in flagup state
 */

/* ARGSUSED */
static void Set (gw, event, params, nparams)
    Widget gw;
    XEvent *event;
    String *params;
    Cardinal *nparams;
{
    SSMailboxWidget w = (SSMailboxWidget) gw;

    w->ssmailbox.last_numnew = -1;

    check_ssmailbox (w, TRUE, FALSE, FALSE);	/* redraw, no reset, no review*/

    return;
}


/*
 * ack the existing mail; put widget in flagdown state
 */

/* ARGSUSED */
static void Unset (gw, event, params, nparams)
    Widget gw;
    XEvent *event;
    String *params;
    Cardinal *nparams;
{
    SSMailboxWidget w = (SSMailboxWidget) gw;

    check_ssmailbox (w, TRUE, TRUE, FALSE);	/* redraw, reset, no review */

    return;
}

/*
 * pretend all mail is new; review sounds
 */

/* ARGSUSED */
static void Review (gw, event, params, nparams)
    Widget gw;
    XEvent *event;
    String *params;
    Cardinal *nparams;
{
    SSMailboxWidget w = (SSMailboxWidget) gw;

    w->ssmailbox.last_numnew = 0;
    check_ssmailbox (w, TRUE, FALSE, TRUE);	/* redraw, no reset, review */

    return;
}


/*
 * look to see if there is new mail; if so, Set, else Unset
 */

/* ARGSUSED */
static void Check (gw, event, params, nparams)
    Widget gw;
    XEvent *event;
    String *params;
    Cardinal *nparams;
{
    SSMailboxWidget w = (SSMailboxWidget) gw;

    check_ssmailbox (w, TRUE, FALSE, TRUE);	/* redraw, no reset, review */

    return;
}


/* ARGSUSED */
static void clock_tic (client_data, id)
    caddr_t client_data;
    XtIntervalId *id;
{
    SSMailboxWidget w = (SSMailboxWidget) client_data;

    check_ssmailbox (w, FALSE, FALSE, FALSE);	/* no redraw, no reset, no review */

    /*
     * and reset the timer
     */

    w->ssmailbox.interval_id = XtAddTimeOut (w->ssmailbox.update * 1000,
					   clock_tic, (caddr_t) w);

    return;
}

static Pixmap make_pixmap (dpy, w, bitmap, depth, flip, widthp, heightp)
    Display *dpy;
    SSMailboxWidget w;
    Pixmap bitmap;
    Boolean flip;
    int depth;
    int *widthp, *heightp;
{
    Window root;
    int x, y;
    unsigned int width, height, bw, dep;
    unsigned long fore, back;

    if (!XGetGeometry (dpy, bitmap, &root, &x, &y, &width, &height, &bw, &dep))
      return None;

    *widthp = (int) width;
    *heightp = (int) height;
    if (flip) {
	fore = w->core.background_pixel;
	back = w->ssmailbox.foreground_pixel;
    } else {
	fore = w->ssmailbox.foreground_pixel;
	back = w->core.background_pixel;
    }
    return XmuCreatePixmapFromBitmap (dpy, w->core.window, bitmap, 
				      width, height, depth, fore, back);
}

static void Realize (gw, valuemaskp, attr)
    Widget gw;
    XtValueMask *valuemaskp;
    XSetWindowAttributes *attr;
{
    SSMailboxWidget w = (SSMailboxWidget) gw;
    register Display *dpy = XtDisplay (w);
    int depth = w->core.depth;

    *valuemaskp |= (CWBitGravity | CWCursor);
    attr->bit_gravity = ForgetGravity;
    attr->cursor = XCreateFontCursor (dpy, XC_top_left_arrow);

    XtCreateWindow (gw, InputOutput, (Visual *) CopyFromParent,
		    *valuemaskp, attr);

    /*
     * build up the pixmaps that we'll put into the image
     */
    if (w->ssmailbox.full.bitmap == None) {
	w->ssmailbox.full.bitmap = 
	  XCreateBitmapFromData (dpy, w->core.window, mailfull_bits,
				 mailfull_width, mailfull_height);
    }
    if (w->ssmailbox.empty.bitmap == None) {
	w->ssmailbox.empty.bitmap =
	  XCreateBitmapFromData (dpy, w->core.window, mailempty_bits,
				 mailempty_width, mailempty_height);
    }

    {
	Window root;
	int x, y;
	unsigned int bw, dep;
	unsigned long fore, back;

	XGetGeometry (dpy, w->ssmailbox.full.bitmap, &root, &x, &y,
		      &w->ssmailbox.full.width, &w->ssmailbox.full.height,
		      &bw, &dep);
#if 0
	printf ("GG:  %d,%d  %d x %d x %d  /%d\n",
		x, y, w->ssmailbox.full.width,
		w->ssmailbox.full.height, dep, bw);
	fflush(stdout);
#endif
	XGetGeometry (dpy, w->ssmailbox.empty.bitmap, &root, &x, &y,
		      &w->ssmailbox.empty.width, &w->ssmailbox.empty.height,
		      &bw, &dep);
    }

    w->ssmailbox.full.image =
      XGetImage (dpy, w->ssmailbox.full.bitmap, 0, 0,
		 w->ssmailbox.full.width,
		 w->ssmailbox.full.height,
		 AllPlanes, ZPixmap);
    w->ssmailbox.empty.image =
      XGetImage (dpy, w->ssmailbox.empty.bitmap, 0, 0,
		 w->ssmailbox.empty.width,
		 w->ssmailbox.empty.height,
		 AllPlanes, ZPixmap);
    {
      unsigned int fg = w->ssmailbox.foreground_pixel;
      unsigned int bg = w->core.background_pixel;
      Boolean flipit  = w->ssmailbox.flipit;
      FrobnicateImage (dpy, DefaultScreen(dpy), &w->ssmailbox.full.image, 0,
		       flipit ? bg : fg, flipit ? fg : bg);
      FrobnicateImage (dpy, DefaultScreen(dpy), &w->ssmailbox.empty.image, 0,
		       fg, bg);
    }

/*
    w->ssmailbox.empty.pixmap = make_pixmap (dpy, w, w->ssmailbox.empty.bitmap,
					   depth, False,
					   &w->ssmailbox.empty.width,
					   &w->ssmailbox.empty.height);
    w->ssmailbox.full.pixmap = make_pixmap (dpy, w, w->ssmailbox.full.bitmap,
					  depth, w->ssmailbox.flipit,
					  &w->ssmailbox.full.width,
					  &w->ssmailbox.full.height);
 */
/*
    w->ssmailbox.empty.image =
      XCreateImage(dpy, DefaultVisual(dpy, DefaultScreen(dpy)),
		   1, XYBitmap, 0,
		   w->ssmailbox.empty.bitmap,
		   w->ssmailbox.empty.width,
		   w->ssmailbox.empty.height,
		   8, (w->ssmailbox.empty.width + 7) / 8);
    w->ssmailbox.full.image =
      XCreateImage(dpy, DefaultVisual(dpy, DefaultScreen(dpy)),
		   1, XYBitmap, 0,
		   w->ssmailbox.full.bitmap,
		   w->ssmailbox.full.width,
		   w->ssmailbox.full.height,
		   8, (w->ssmailbox.full.width + 7) / 8);
 */
#ifdef SHAPE
    if (w->ssmailbox.empty.mask == None && w->ssmailbox.full.mask == None)
      w->ssmailbox.shapeit = False;
#endif

    w->ssmailbox.interval_id = XtAddTimeOut (w->ssmailbox.update * 1000,
					   clock_tic, (caddr_t) w);

#ifdef SHAPE
    w->ssmailbox.shape_cache.mask = None;
#endif

    if (w->ssmailbox.sound_bindings) 
    {
	Bindings *binding;
	char *pair, *name, *sound;
	
	while (pair=strtoken(&w->ssmailbox.sound_bindings,",",1)) 
	{
            name=strtoken(&pair, "|", 1);
	    if (name) sound=strtoken(&pair, "|", 1);
	    if (!name || !sound)
	    {
		fprintf(stderr, "malformed sound bindings.\n");
		continue;
	    }
	    if ( w->ssmailbox.case_sensitive == FALSE ) {
	       char *c = name;
	       for (; *c != '\0'; c++) if (isupper(*c)) *c = tolower(*c);
	    }
	    binding = (Bindings *)malloc(sizeof(Bindings));
	    binding->sound = sound;
	    binding->face = 0;
	    if (binding->pattern = regcomp(name)) 
	    {
		binding->next = w->ssmailbox.bindings;
		w->ssmailbox.bindings = binding;
	    }
	}
    }

    if (w->ssmailbox.face_bindings) 
    {
	Bindings *binding;
	char *pair, *name, *face;
	
	while (pair=strtoken(&w->ssmailbox.face_bindings,",",1)) 
	{
            name=strtoken(&pair, "|", 1);
	    if (name) face=strtoken(&pair, "|", 1);
	    if (!name || !face)
	    {
		fprintf(stderr, "malformed face bindings.\n");
		continue;
	    }
	    if ( w->ssmailbox.case_sensitive == FALSE ) {
	       char *c = name;
	       for (; *c != '\0'; c++) if (isupper(*c)) *c = tolower(*c);
	    }
	    binding = (Bindings *)malloc(sizeof(Bindings));
	    binding->sound = 0;
	    binding->face = face;
	    if (binding->pattern = regcomp(name)) 
	    {
		binding->next = w->ssmailbox.bindings;
		w->ssmailbox.bindings = binding;
	    }
	}
    }

    /* check it the first time (instead of waiting for the first timeout) */
    check_ssmailbox (w, TRUE, FALSE, FALSE);

    return;
}


static void Destroy (gw)
    Widget gw;
{
    SSMailboxWidget w = (SSMailboxWidget) gw;
    Display *dpy = XtDisplay (gw);

    /* Turn off the LED when we exit.
       This should happen on a signal-function so that it happens when
       the processes is killed as well.
     */
    w->ssmailbox.flag_up = False;
    frob_leds (w);

    XtFree (w->ssmailbox.filename);
    if (w->ssmailbox.interval_id) XtRemoveTimeOut (w->ssmailbox.interval_id);
    XtDestroyGC (w->ssmailbox.gc);
#define freepix(p) if (p) XFreePixmap (dpy, p)
#define freeimg(p) if (p) XDestroyImage (p)
    freepix (w->ssmailbox.full.bitmap);		/* until cvter does ref cnt */
    freepix (w->ssmailbox.full.mask);		/* until cvter does ref cnt */
    freeimg (w->ssmailbox.full.image);
    freepix (w->ssmailbox.empty.bitmap);	/* until cvter does ref cnt */
    freepix (w->ssmailbox.empty.mask);		/* until cvter does ref cnt */
    freeimg (w->ssmailbox.empty.image);
    freepix (w->ssmailbox.current_face.bitmap);	/* until cvter does ref cnt */
    freepix (w->ssmailbox.current_face.mask);	/* until cvter does ref cnt */
    freeimg (w->ssmailbox.current_face.image);
#ifdef SHAPE
    freepix (w->ssmailbox.shape_cache.mask);
#endif
#undef freepix
#undef freeimg
    return;
}


static void Redisplay (gw)
    Widget gw;
{
    SSMailboxWidget w = (SSMailboxWidget) gw;

    /* Yeah right, like we need to do all those regexp matches at every
       redisplay event. */
    /* check_ssmailbox (w, TRUE, FALSE, FALSE); */
    redraw_ssmailbox (w);
}


static void frob_leds (w)
     SSMailboxWidget w;
{
  if ( w->ssmailbox.use_LED ) {
    XKeyboardControl control;
    control.led = w->ssmailbox.which_LED;
    control.led_mode = (w->ssmailbox.flag_up ? LedModeOn : LedModeOff);
    XChangeKeyboardControl(XtDisplay(w), KBLed | KBLedMode, &control);
  }
}


static void check_ssmailbox (w, force_redraw, reset, review)
    SSMailboxWidget w;
    Boolean force_redraw, reset, review;
{
    long numnew = 0;
    int beepit = 0;
    struct MsgStruct *sounds = 0;  /* and faces */

    if (w->ssmailbox.check_command != NULL) {
	numnew = system(w->ssmailbox.check_command);
    }
    else {
	numnew = newmsgs(w, &sounds);
    }

    /*
     * Now check for changes.  If reset is set then we want to pretent that
     * there is no mail.  If the mailbox is empty then we want to turn off
     * the flag.  Otherwise if the mailbox has changed size then we want to
     * put the flag up.
     *
     * The cases are:
     *    o  forced reset by user                        DOWN
     *    o  no mailbox or empty (zero-sized) mailbox    DOWN
     *    o  same size as last time                      no change
     *    o  bigger than last time                       UP
     *    o  smaller than last time but non-zero         UP
     *
     * The last two cases can be expressed as different from last
     * time and non-zero.
     */

    if (reset) {			/* forced reset */
	w->ssmailbox.flag_up = FALSE;
	force_redraw = TRUE;
    } else if (numnew == 0) {
	w->ssmailbox.flag_up = FALSE;
	if (w->ssmailbox.last_numnew > 0) force_redraw = TRUE;  /* if change */
    } else if (numnew <= w->ssmailbox.last_numnew) {	/* less mail than
							   before */
	/* do nothing; don't play sounds and don't change icon since
	   user may have lowered the flag manually. */
    } else if (numnew > w->ssmailbox.last_numnew) {  /* different size */
	if (!w->ssmailbox.once_only || !w->ssmailbox.flag_up)
	    beepit = 1; 
	w->ssmailbox.flag_up = TRUE;
	force_redraw = TRUE;
    } 

    frob_leds (w);

    if (beepit || review) 
	beep(w, sounds, numnew - w->ssmailbox.last_numnew, review);
    
    if (force_redraw) {
      reshape_ssmailbox (w);
      redraw_ssmailbox (w);
    }

    w->ssmailbox.last_numnew = numnew;
    while (sounds) 
    {
	struct MsgStruct *x;
	x = sounds->next;
	while (sounds->sounds) 
	{
	    struct SoundStruct *s;
	    s = sounds->sounds->next;
	    free(sounds->sounds);
	    sounds->sounds = s;
	}
	free(sounds);
	sounds = x;
    }
    
    return;
}

/*
 * get user name for building mailbox
 */

static void GetMailFile (w)
    SSMailboxWidget w;
{
    char *getlogin();
    char *username;

    username = getlogin ();
    if (!username) {
	struct passwd *pw = getpwuid (getuid ());

	if (!pw) {
	    fprintf (stderr, "%s:  unable to find a username for you.\n",
		     "Mailbox widget");
	    CloseDown (w, 1);
	}
	username = pw->pw_name;
    }
    w->ssmailbox.filename = (String) XtMalloc (strlen (MAILBOX_DIRECTORY) + 1 +
				   	     strlen (username) + 1);
    strcpy (w->ssmailbox.filename, MAILBOX_DIRECTORY);
    strcat (w->ssmailbox.filename, "/");
    strcat (w->ssmailbox.filename, username);
    return;
}

static void CloseDown (w, status)
    SSMailboxWidget w;
    int status;
{
    Display *dpy = XtDisplay (w);

    XtDestroyWidget (w);
    XCloseDisplay (dpy);
    exit (status);
}


/* ARGSUSED */
static Boolean SetValues (gcurrent, grequest, gnew)
    Widget gcurrent, grequest, gnew;
{
    SSMailboxWidget current = (SSMailboxWidget) gcurrent;
    SSMailboxWidget new = (SSMailboxWidget) gnew;
    Boolean redisplay = FALSE;

    if (current->ssmailbox.update != new->ssmailbox.update) {
	if (current->ssmailbox.interval_id) 
	  XtRemoveTimeOut (current->ssmailbox.interval_id);
	new->ssmailbox.interval_id = XtAddTimeOut (new->ssmailbox.update * 1000,
						 clock_tic,
						 (caddr_t) gnew);
    }

    if (current->ssmailbox.foreground_pixel != new->ssmailbox.foreground_pixel
	|| current->core.background_pixel != new->core.background_pixel) {
	XtDestroyGC (current->ssmailbox.gc);
	new->ssmailbox.gc = get_ssmailbox_gc (new);
	redisplay = TRUE;
    }

    return (redisplay);
}


/*
 * drawing code
 */

static void redraw_ssmailbox (w)
    SSMailboxWidget w;
{
    register Display *dpy = XtDisplay (w);
    register Window win = XtWindow (w);
    register int x, y;
    GC gc = w->ssmailbox.gc;
    Pixel back = w->core.background_pixel;
    struct _mbimage *im;

    if (! XtIsRealized(w)) return;

    if (w->ssmailbox.flag_up) {		/* paint the "up" position */
       if (w->ssmailbox.current_face.image != None)
	  im = &w->ssmailbox.current_face;
       else {
          im = &w->ssmailbox.full;
	  if (w->ssmailbox.flipit) back = w->ssmailbox.foreground_pixel;
       }
    } else {				/* paint the "down" position */
	im = &w->ssmailbox.empty;
    }

    XSetWindowBackground (dpy, win, back);
#if 0
    XClearWindow (dpy, win);  /* ## is this necessary? -jwz */
#endif

    /* center the picture in the window */
#if 0
#ifdef SHAPE
    if (im->mask)
      x = y = 0;
    else
#endif
    {
      x = (((int)w->core.width) - im->width) / 2;
      y = (((int)w->core.height) - im->height) / 2;
    }
#else
    x = y = 0; /* Oh, screw this! */
#endif

    XPutImage (dpy, win, gc, im->image, 0, 0, x, y, im->width, im->height);
    return;
}

static void reshape_ssmailbox (w)
     SSMailboxWidget w;
{
  register Display *dpy = XtDisplay (w);
  register Window win = XtWindow (w);
  register int x, y;
  struct _mbimage *im;
  
  if (! XtIsRealized(w)) return;

  if (w->ssmailbox.flag_up) {
    if (w->ssmailbox.current_face.image != None)
      im = &w->ssmailbox.current_face;
    else
      im = &w->ssmailbox.full;
  } else {
    im = &w->ssmailbox.empty;
  }
  if ( w->ssmailbox.auto_resize != False ) {
    Widget parent;
    x=0; y=0;

/*  AAAAAAAAARRHRRHRRRRRRRRRRRRRGGGGGGGGGHGGGHGHHHHHHHGGGHHHHHHHH!!!!!!!!!!
    Fuck me gently with a chainsaw!  Will someone please tell me how the fuck
    you resize a fucking widget??  Why, when I call whatever resize method
    I try with 64x64 does the fucking window end up 64x82?  WHY????????
    WHY????????  WHY????????  WHY????????  WHY????????  WHY????????  (why..?)
 */

    for (parent = (Widget) w; parent != None; parent = XtParent(parent)) {
#ifdef Xt_doesnt_suck_wet_farts_from_dead_pigeons
      XtResizeWidget(parent, im->width, im->height, parent->core.border_width);
#else
/*
      parent->core.width = im->width;
      parent->core.height = im->height;
      XtResizeWindow (w);
 */
      XResizeWindow (XtDisplay (parent), XtWindow (parent),
		     im->width, im->height);

# ifdef eat_my_shorts
      {
	int width, height, res;
	res = XtMakeResizeRequest(w, im->width, im->height, &width, &height);
	if (width != im->width || height != im->height)
	  printf("Fuck!!  %d %d; %d y:%d n:%d d:%d a:%d\n", width, height,
		 res, XtGeometryYes, XtGeometryNo, XtGeometryDone,
		 XtGeometryAlmost
		 );
      }
# endif
#endif
    }
  } else {
    x = (((int)w->core.width) - im->width) / 2;
    y = (((int)w->core.height) - im->height) / 2;
  }

#ifdef SHAPE
    /*
     * XXX - temporary hack; walk up widget tree to find top most parent (which
     * will be a shell) and mash it to have our shape.  This will be replaced
     * by a special shell widget.
     *
     *  Note: this "temporary hack" has been in here for years.
     *
     */
    if (w->ssmailbox.shapeit) {
       Widget parent;

	for (parent = (Widget) w; XtParent(parent);
	     parent = XtParent(parent)) {
	    x += parent->core.x + parent->core.border_width;
	    y += parent->core.y + parent->core.border_width;
	}

	if (/*(im->mask || w->ssmailbox.shape_cache.mask) && */
	    (im->mask != w->ssmailbox.shape_cache.mask ||
	     x != w->ssmailbox.shape_cache.x ||
	     y != w->ssmailbox.shape_cache.y)) {
#if 0
	   printf ("shaping %d\n", im->mask);
#endif
	    XShapeCombineMask (XtDisplay(parent), XtWindow(parent),
			       ShapeBounding, x, y, im->mask, ShapeSet);
	    w->ssmailbox.shape_cache.mask = im->mask;
	    w->ssmailbox.shape_cache.x = x;
	    w->ssmailbox.shape_cache.y = y;
	}
    }
#endif
}


void play_sound (w, sound, volume)
     SSMailboxWidget w;
     char *sound;
     int volume;
{
#ifdef sparc
  if (! w->ssmailbox.not_on_console)
    play_sound_file (sound, volume);
  else
#endif
    XBell (XtDisplay (w), w->ssmailbox.volume);
}

XImage *LocateBitmapFile();

int ReadBitmapFile (dpy, filename, current_face, depth, drawable, gc, fg, bg)
     Display *dpy;
     char *filename;
     struct _mbimage *current_face;
     Drawable drawable;
     GC gc;
     unsigned int fg, bg;
{
    int x_hot, y_hot; /* ignored */
    unsigned char *data;
    Window root = RootWindow(dpy, DefaultScreen(dpy));
    
#if 0
    printf("ReadBitmapFile: reading \"%s\"", filename);
    if (current_face->image != None)
       printf(", clearing old face\n");
    else printf(".\n");
#endif
    
    if (current_face->image != None) {
       XDestroyImage(current_face->image);
       current_face->image = None;
    }
    current_face->bitmap = None;
    current_face->mask = None;
    current_face->image = LocateBitmapFile (dpy, DefaultScreen(dpy),
					     filename, NULL, 0,
					     &current_face->width,
					     &current_face->height,
					     &x_hot, &y_hot,
					     &current_face->colors,
					     &current_face->ncolors);
    if (current_face->image == None) return BitmapOpenFailed;

    /* LocateBitmapFile() always returns an XImage of the same depth as
       the screen we're running on.
     */
    if (current_face->ncolors != 0)  /* yar har, here be colours... */
      {
	  int i, c=0;
	  unsigned int *mappings;
	  mappings = (unsigned int *)
	             malloc(sizeof(unsigned int) * current_face->ncolors+1);
	  for (i = 0; i < current_face->ncolors; i++) {
	    int p = current_face->colors[i].pixel;
	    unsigned short r = current_face->colors[i].red;
	    unsigned short g = current_face->colors[i].green;
	    unsigned short b = current_face->colors[i].blue;

/*	    current_face->colors[i].red = 65535;
	    current_face->colors[i].green = 0;
	    current_face->colors[i].blue = 65535;
	    current_face->colors[i].flags = (DoRed|DoGreen|DoBlue);
 */
	    if (XAllocColor(dpy, DefaultColormap(dpy, DefaultScreen(dpy)),
			    &current_face->colors[i])) {
	      mappings[p] = current_face->colors[i].pixel;
	      c++;
	    } else {
	      mappings[p] = BlackPixel(dpy, DefaultScreen(dpy));
/*	      printf("color %d->%d: got [%d %d %d] instead of [%d %d %d]\n",
		     (int) p, (int) current_face->colors[i].pixel,
		     (int) current_face->colors[i].red,
		     (int) current_face->colors[i].green,
		     (int) current_face->colors[i].blue,
		     (int) r, (int) g, (int) b);
 */
	    }
	  }
	  FrobnicateImage(dpy, DefaultScreen(dpy), &current_face->image,
			  mappings, fg, bg);
	  free(mappings);
#if 0
	  printf ("  ..%d of %d colors\n", c, current_face->ncolors);
#endif
      }
    else
      FrobnicateImage(dpy, DefaultScreen(dpy), &current_face->image, 0,
		      fg, bg);

    return BitmapSuccess;
}


/* Returns TRUE if it installed an image, FALSE otherwise.
   If the given face is NULL, uninstalls everything.
 */
Boolean show_face (w, face)
     SSMailboxWidget w;
     char *face;
{
    Display *dpy = XtDisplay(w);
    Colormap colormap = DefaultColormap(dpy, DefaultScreen(dpy));
    int status;
    if (w->ssmailbox.current_face.image != None) {
#if 0
       printf("show_face: clearing current_face\n");
#endif
       XDestroyImage (w->ssmailbox.current_face.image);
       w->ssmailbox.current_face.image = (Pixmap)None;
       }
    if (w->ssmailbox.current_face.ncolors != 0) {
       int i;
#if 0
       printf("Freeing %d colors\n", w->ssmailbox.current_face.ncolors);
#endif
       for (i=0; i++; i == w->ssmailbox.current_face.ncolors) {
	  XFreeColors(dpy, colormap, 
		      &(w->ssmailbox.current_face.colors[i].pixel),
		      1, 0);
       }
       w->ssmailbox.current_face.ncolors = 0;
    }
    if (! face) return FALSE;
    status = ReadBitmapFile (dpy, face,
			     &w->ssmailbox.current_face,
			     w->core.depth,
			     w->core.window,
			     w->ssmailbox.gc,
			     w->ssmailbox.foreground_pixel,
			     w->core.background_pixel);
    /* ### is this necessary?  I doubt it */
    XSetWindowColormap(dpy, XtWindow(w),
		       DefaultColormap(dpy, DefaultScreen(dpy)));
    if (status == BitmapSuccess) return TRUE;
    /* don't exit on error, just warn. */
    if (status == BitmapFileInvalid)
       fprintf(stderr,
	       "xbiff++: input file \"%s\" is not in bitmap format\n", face);
    else if (status == BitmapOpenFailed)
       fprintf(stderr, "xbiff++: cannot read bitmap file \"%s\"\n", face);
    else
       fprintf(stderr,
	       "xbiff++: error code %d reading bitmap file \"%s\"\n",
	       status, face);
    return FALSE;
}


static void beep (w, sounds, num, review)
    SSMailboxWidget w;
    struct MsgStruct *sounds;
    int num;
    Boolean review;
{
    struct SoundStruct *ss;
    Boolean any_sounds_matched = FALSE, any_faces_matched = FALSE;

/*    show_face(w, NULL); /* ditch any old faces */
    if (num == 0) {
	if (w->ssmailbox.no_mail_sound && review) {
	    play_sound (w, w->ssmailbox.no_mail_sound, w->ssmailbox.volume);
	}
	return;
    }
    else if (sounds)
	while (sounds && num) 
	{
	    ss = sounds->sounds;
	    while(ss) 
	    {
		if (ss->sound) {
		  if (! any_sounds_matched)
		    play_sound (w, ss->sound, w->ssmailbox.volume);
		  any_sounds_matched = TRUE;
	        }
		/* don't put up a new face if one is already there.  This
		   is so that a message that is matched by several regexps
		   gets the face of the first one matched, not the last.
		   As in, someone you have a face for sends mail to a mailing
		   list you are on.  If you want this case to match the mlist
		   face instead of your friend's, put the mlist first in your
		   bindings.

		   Order of fields in the message doesn't matter; we collect
		   the TO, FROM, etc fields first, then iterate through the
		   user-defined regexps comparing them.
		   
		   it is annoying that if several messages show up at once,
		   the face corresponding to the *first* one is shown instead
		   of the *last* one.  This happens when there is mail in the
		   mailbox before XBiff is run, and when the user manually
		   rescans by clicking mouse-right.   - jwz, 10-dec-90.
		 */
		if (any_faces_matched == FALSE)
		   any_faces_matched = show_face(w, ss->face);
		ss = ss->next;
	    }
	    sounds = sounds->next;
	    --num;
	}
    if (any_sounds_matched == FALSE) {
       if (w->ssmailbox.sound)
          play_sound (w, w->ssmailbox.sound, w->ssmailbox.volume);
       else
          XBell (XtDisplay (w), w->ssmailbox.volume);
    }
    return;
}
#define LSIZE 256

/* use these to hold the appropriate fields; doesn't cope with multiple
   to, from, etc fields, and probably dies if a line is longer than 256
   chars, but isn't that what C is all about?
 */
char from_header [LSIZE];
char to_header [LSIZE];
char cc_header [LSIZE];
char subject_header [LSIZE];

void add_sound();

static int newmsgs(w, sounds)
    SSMailboxWidget w;
    struct MsgStruct **sounds;
{
    char *mbox;
    FILE *fp;
    int mcnt, status, numnew, in_header;
    static char line[LSIZE];
    Bindings *binding;
    struct MsgStruct *ms = 0;

    mbox = w->ssmailbox.filename;
    
    /* if we can't open the mailbox return 0.
     */
    if (!(fp= fopen(mbox, "r")))
	return 0;
    
    /* Most of this rewritten by jwz.
       read in the sender and subject of each letter first, then compare.
     */
    mcnt = -1; status = numnew = 0; in_header=1;
    
    while ( fgets(line, LSIZE, fp) ) {
	if (!strncmp(line, "From ", 5)) {
	   if (!status && mcnt != -1) {
	      /* this is a new (subsequent) message - add a struct
		 representing it */
	      numnew++;
	      add_sound(w, sounds, ms, status);
	   }
	   ms = 0;
	   mcnt++;
	   status = 0;
	   in_header = 1;
	   from_header[0] = '\0';	/* clear the cache */
	   to_header[0] = '\0';
	   cc_header[0] = '\0';
	   subject_header[0] = '\0';
        }
	else if (status)
	   /* message already read - ignore it. */ ;
	else if (in_header && *line != '\n') {
	   /* this is a genuine header field.  Cache it if it's from, cc, etc.
	    */
	   if ( w->ssmailbox.case_sensitive == FALSE ) {
	      char *c = line;
	      for (; *c != '\0'; c++) if (isupper(*c)) *c = tolower(*c);
	   }
	   line[strlen(line)-1]='\0'; /* replace newline w/ null for regexp */
	   
	   if (!strncmp(line, "status: ", 7) || !strncmp(line, "Status: ", 7))
	       status = 1;
	   else if ((!strncmp(line, "from: ", 5) ||
		     !strncmp(line, "From: ", 5)))
	       strcpy(from_header, line);
	   else if (!strncmp(line, "cc: ", 3) || !strncmp(line, "Cc: ", 3) ||
		    !strncmp(line, "CC: ", 3))
	       strcpy(cc_header, line);
	   else if (!strncmp(line, "subject: ", 8) ||
		    !strncmp(line, "Subject: ", 8))
	       strcpy(subject_header, line);
	   else if (!strncmp(line, "to: ", 3) || !strncmp(line, "To: ", 3) ||
		    !strncmp(line, "TO: ", 3))
	       strcpy (to_header, line);
	} else if (in_header && *line == '\n') {
	   /* at this point, we've just reached the end of the message header,
	      and have already passed over the whole header and collected
	      the four relevant fields; now match them against the db.
	    */
	   in_header = 0;
	   for (binding=w->ssmailbox.bindings; binding;
		binding = binding->next) {
	      /* the order of the comparison here shouldn't matter, since we're
		 iterating by the user's regexps, not by the fields in the
		 incoming message. */
	      if (regexec(binding->pattern, from_header) ||
		  regexec(binding->pattern, to_header) ||
		  regexec(binding->pattern, cc_header) ||
		  regexec(binding->pattern, subject_header))
		{
		    struct SoundStruct *s;
		    if (!ms) {
		       ms = (struct MsgStruct *)
		            malloc(sizeof(struct MsgStruct));
		       ms->sounds = 0;
		       ms->next = 0;
		    }
		    s = (struct SoundStruct *)
		        malloc(sizeof(struct SoundStruct));
		    s->sound = binding->sound;
		    s->face = binding->face;
		    s->next = ms->sounds;
		    ms->sounds = s;
		}
	      /* should there be an else?  Blaaah. */
	   }
        }
    }
    if (!status && mcnt != -1) {
       numnew++;
       add_sound(w, sounds, ms, status);
    }
    fclose(fp);
    return numnew;
}

static void add_sound(w, sounds, ms, status)
    SSMailboxWidget w;
    struct MsgStruct **sounds, *ms;
    int status;
{
    if (!status) 
    {
	if (!ms)
	{
	    ms = (struct MsgStruct *)malloc(sizeof(struct
						   MsgStruct));
	    ms->sounds = (struct SoundStruct *)malloc(sizeof(struct
							     SoundStruct));
	    ms->sounds->sound = w->ssmailbox.sound;
	    ms->sounds->face = w->ssmailbox.face;
	    ms->sounds->next = 0;
	}
	ms->next = *sounds;
	*sounds = ms;
    }
    else if (ms)
    {
	while (ms->sounds) 
	{
	    struct SoundStruct *x;
	    x = ms->sounds->next;
	    free((char *)ms->sounds);
	    ms->sounds = x;
	}
	free((char *)ms);
    }
}

void regerror(s)
    char *s;
{
    fprintf(stderr, "regexp(3): %s\n", s);
}
