static char rcsid[]= "$Id: xlbiff.c,v 1.64 1992/09/24 15:04:51 PDT esm Exp $";
/*\
|* xlbiff  --  X Literate Biff
|*
|* DESCRIPTION:
|*
|* 	xlbiff is yet another biff utility.  It lurks around, polling
|*	a mail file until its size changes.  When this happens, it pops
|*	up a window containing a `scan' of the contents of the mailbox.
|*	Xlbiff is modeled after xconsole; it remains invisible at non-
|*	useful times, eg, when no mail is present.  See README for details.
|*
|*	Author:		Eduardo Santiago Munoz,  esm@auspex.com
|* 	Created:	20 August 1991
|*	Last Updated:	24 September 1992
|*
|*	Copyright 1991, 1992 Eduardo Santiago Munoz
|*	Portions copyright 1991 Digital Equipment Corporation
|*
|*	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.  The author makes no repre-
|*	sentation about the suitability of this software for any purpose.
|*	It is provided "as is" without express or implied warranty.
|*
\*/

#include "patchlevel.h"

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xaw/Command.h>
#include <X11/Xos.h>

#include <stdio.h>
#include <sys/stat.h>
#include <pwd.h>
#include <errno.h>

/*
** This grody gunk stolen outright from mit/lib/Xaw/Mailbox.h
*/
#ifndef X_NOT_POSIX
#ifdef _POSIX_SOURCE
# include <sys/wait.h>
#else
#define _POSIX_SOURCE
# include <sys/wait.h>
#undef _POSIX_SOURCE
#endif
# define waitCode(w)    WEXITSTATUS(w)
# define waitSig(w)     WIFSIGNALED(w)
typedef int             waitType;
# define INTWAITTYPE
#else /* ! X_NOT_POSIX */
#ifdef SYSV
# define waitCode(w)    (((w) >> 8) & 0x7f)
# define waitSig(w)     ((w) & 0xff)
typedef int             waitType;
# define INTWAITTYPE
#else
# include       <sys/wait.h>
# define waitCode(w)    ((w).w_T.w_Retcode)
# define waitSig(w)     ((w).w_T.w_Termsig)
typedef union wait      waitType;
#endif /* SYSV else */
#endif /* ! X_NOT_POSIX else */


#ifdef	NEED_STRERROR
char	*strerror();
#endif

/*
** if compiled with -DDEBUG *and* run with debugging on, this does lots
** of useless/useful printfs.
*/
#ifdef	DEBUG
#define	DP(x)	if (lbiff_data.debug) printf x
#else
#define DP(x)
#endif

/*
** This defines the file we need to monitor.  If not defined explicitly
** on the command line, we pick this default.
*/
#ifndef	MAILPATH
#define	MAILPATH	"/usr/spool/mail/%s"
#endif

/*****************************************************************************\
**                                prototypes                                 **
\*****************************************************************************/
char		*doScan();
void		Popdown(),Popup();
void		Usage();
extern char	*getlogin();

#ifdef	FUNCPROTO
void	Shrink(Widget, caddr_t, XEvent*, Boolean*);
void	handler(XtPointer,XtIntervalId*);
void	initStaticData(int*,int*,int*);
void	Exit(Widget, XEvent*, String*, Cardinal*);
void	lbiffUnrealize(), lbiffRealize(char*);
void	getDimensions(char*,Dimension*,Dimension*);
void	toggle_key_led(int);
void	ErrExit(Boolean,char*);
Bool	CheckEvent(Display*,XEvent*,caddr_t);
#else
void	Shrink();
void	handler();
void	initStaticData();
void	Exit();
void	lbiffUnrealize(), lbiffRealize();
void	getDimensions();
void	toggle_key_led();
void	ErrExit();
Bool	CheckEvent();
#endif

/*****************************************************************************\
**                                 globals				     **
\*****************************************************************************/
extern int errno;

Widget	topLevel,textBox;		/* my widgets			*/
XtAppContext app_context;		/* application context		*/
Boolean	visible;			/* is window visible?		*/
Boolean hasdata;			/* Something is to be displayed */
char	*default_file;			/* default filename		*/
char	*progname;			/* my program name		*/
long	acknowledge_time = 0;		/* time window was acknowledged	*/
long	popup_time = 0;			/* time window was popped up	*/

static Atom wm_delete_window;		/* for handling WM_DELETE	*/

typedef struct {
    Boolean	debug;			/* print out useful stuff 	*/
    char	*file;			/* file to monitor size of 	*/
    char	*checkCmd;		/* command to run for check     */
    char	*cmd;			/* command to run for output	*/
    int		update;			/* update interval, in seconds	*/
    int		fade;			/* popdown interval, in seconds */
    int		columns;		/* number of columns across	*/
    int		rows;			/* max# of lines in display	*/
    int		volume;			/* bell volume, 0-100 percent	*/
    Boolean	bottom;			/* Put window at window bottom  */
    Boolean	resetSaver;		/* reset screensaver on popup   */
    long	refresh;		/* seconds before reposting msg	*/
    int		led;			/* led number to light up	*/
    Boolean	ledPopdown;		/* turn off LED on popdown?	*/
    char	*sound;			/* Sound file to use		*/
} AppData, *AppDataPtr;
AppData		lbiff_data;

#define offset(field)	XtOffset(AppDataPtr,field)

static XtResource	xlbiff_resources[] = {
    { "debug", "Debug", XtRBoolean, sizeof(Boolean),
	offset(debug), XtRImmediate, False},
    { "file", "File", XtRString, sizeof(String),
	offset(file), XtRString, NULL},
    { "checkCommand", "CheckCommand", XtRString, sizeof(String),
	offset(checkCmd), XtRString, NULL},
    { "scanCommand", "ScanCommand", XtRString, sizeof(String),
	offset(cmd), XtRString, "scan -file %s -width %d" },
    { "update", "Interval", XtRInt, sizeof(int),
	offset(update), XtRImmediate, (XtPointer)15},
    { "fade", "Fade", XtRInt, sizeof(int),
	offset(fade), XtRImmediate, (XtPointer)0},
    { "columns", "Columns", XtRInt, sizeof(int),
	offset(columns), XtRImmediate, (XtPointer)80},
    { "rows", "Rows", XtRInt, sizeof(int),
	offset(rows), XtRImmediate, (XtPointer)20},
    { "sound", "Sound", XtRString, sizeof(String),
	offset(sound), XtRString, "" },
    { "volume", "Volume", XtRInt, sizeof(int),
	offset(volume), XtRImmediate, (XtPointer)100},
    { "bottom", "Bottom", XtRBoolean, sizeof(Boolean),
	offset(bottom), XtRImmediate, False},
    { "resetSaver", "ResetSaver", XtRBoolean, sizeof(Boolean),
	offset(resetSaver), XtRImmediate, False},
    { "refresh", "Refresh", XtRInt, sizeof(int),
	offset(refresh), XtRImmediate, (XtPointer)1800},
    { "led", "Led", XtRInt, sizeof(int),
	offset(led), XtRImmediate, (XtPointer)0},
    { "ledPopdown", "LedPopdown", XtRBoolean, sizeof(Boolean),
	offset(ledPopdown), XtRImmediate, False}
};

static XrmOptionDescRec	optionDescList[] = {
    { "-bottom",      ".bottom",      XrmoptionNoArg,	(caddr_t) "true"},
    { "+bottom",      ".bottom",      XrmoptionNoArg,	(caddr_t) "false"},
    { "-debug",       ".debug",	      XrmoptionNoArg,	(caddr_t) "true"},
    { "-file",	      ".file",        XrmoptionSepArg,	(caddr_t) NULL},
    { "-rows",        ".rows",	      XrmoptionSepArg,	(caddr_t) NULL},
    { "-columns",     ".columns",     XrmoptionSepArg,	(caddr_t) NULL},
    { "-update",      ".update",      XrmoptionSepArg,	(caddr_t) NULL},
    { "-fade",	      ".fade",        XrmoptionSepArg,	(caddr_t) NULL},
    { "-volume",      ".volume",      XrmoptionSepArg,	(caddr_t) NULL},
    { "-resetSaver",  ".resetSaver",  XrmoptionNoArg,	(caddr_t) "true"},
    { "+resetSaver",  ".resetSaver",  XrmoptionNoArg,	(caddr_t) "false"},
    { "-refresh",     ".refresh",     XrmoptionSepArg,	(caddr_t) NULL},
    { "-led",         ".led",         XrmoptionSepArg,	(caddr_t) NULL},
    { "-ledPopdown",  ".ledPopdown",  XrmoptionNoArg,	(caddr_t) "true"},
    { "+ledPopdown",  ".ledPopdown",  XrmoptionNoArg,	(caddr_t) "false"},
    { "-scanCommand", ".scanCommand", XrmoptionSepArg,	(caddr_t) NULL},
    { "-checkCommand",".checkCommand",XrmoptionSepArg,  (caddr_t) NULL}
};

static char *fallback_resources[] = {
    "*Font:		-*-clean-bold-r-normal--13-130-75-75-c-80-iso8859-1",
    "*Geometry:		+0-0",
    NULL
};

static XtActionsRec lbiff_actions[] = {
    {"exit",Exit},
    {"popdown",Popdown}
};


/*****************************************************************************\
**                                  code                                     **
\*****************************************************************************/

/**********\
|*  main  *|
\**********/
#ifdef	FUNCPROTO
main( int argc, char *argv[] )
#else
main(argc, argv)
    int argc;
    char *argv[];
#endif
{
    progname = argv[0];

    topLevel = XtVaAppInitialize(&app_context,
				 "XLbiff",
				 optionDescList, XtNumber(optionDescList),
				 &argc, argv,
				 fallback_resources,
				 XtNallowShellResize, True,
				 NULL);

    XtGetApplicationResources(topLevel, &lbiff_data,
			      xlbiff_resources, XtNumber(xlbiff_resources),
			      (ArgList)NULL,0);

#ifndef	DEBUG
    if (lbiff_data.debug)
      fprintf(stderr,"%s: DEBUG support not compiled in, sorry\n",progname);
#endif

    /*
    ** Check command line arguments
    */
    if (argc > 1) {
	if (!strncmp(argv[1],"-v",2)) {
	    fprintf(stderr,
#if	TESTLEVEL != 0
		    "%s version %d.%d.%d\n",
#else
		    "%s version %d.%d\n",
#endif
		    progname,  VERSION,PATCHLEVEL,TESTLEVEL);
	    exit(0);
	} else if (!strncmp(argv[1],"-help",strlen(argv[1]))) {
	    Usage();
	} else if (argv[1][0] != '-') {
	    lbiff_data.file = argv[1];
	} else {
	    fprintf(stderr,
		    "%s: no such option \"%s\", type '%s -help' for help\n",
		    progname,argv[1],progname);
	    exit(1);
	}
    }

    /*
    ** If no data file was explicitly given, make our best guess
    */
    if (lbiff_data.file == NULL) {
	char *username = getlogin();

	if (username == NULL || username[0] == '\0') {
	    struct passwd  *pwd = getpwuid(getuid());
	    
	    if (pwd == NULL) {
		fprintf(stderr, "%s: cannot get username\n", progname);
		exit(1);
	    }
	    username = pwd->pw_name;
	}

	default_file = (char*)malloc(strlen(MAILPATH) + strlen(username));
	if (default_file == NULL)
	  ErrExit(True,"default_file malloc()");

	sprintf(default_file,MAILPATH,username);
	lbiff_data.file = default_file;
    }
    DP(("file= %s\n",lbiff_data.file));

    textBox = XtVaCreateManagedWidget("text",
				      commandWidgetClass,
				      topLevel,
				      NULL);

    XtAddCallback(textBox,XtNcallback, Popdown, textBox);
    XtAppAddActions(app_context,lbiff_actions,XtNumber(lbiff_actions));
    XtAddEventHandler(topLevel,StructureNotifyMask,False,
		      (XtEventHandler)Shrink,(caddr_t)NULL);

    XtOverrideTranslations(topLevel,
	XtParseTranslationTable ("<Message>WM_PROTOCOLS: exit()"));

    wm_delete_window = XInternAtom (XtDisplay(topLevel), "WM_DELETE_WINDOW",
	    			    False);

    toggle_key_led(False);

    /*
    ** check to see if there's something to do, pop up window if necessary,
    ** and set up alarm to wake us up again every so often.
    */
    handler(NULL,NULL);

    /*
    ** main program loop  --  mostly just loops forever waiting for events
    **
    ** note that we will continually be interrupted by the timeout code
    */
    XtAppMainLoop(app_context);
}


/***********\
|*  Usage  *|  displays usage message
\***********/
void
Usage()
{
    static char *help_message[] = {
"where options include:",
"    -display host:dpy                  X server to contact",
"    -geometry +x+y                     x,y coords of window",
"    -rows height                       height of window, in lines",
"    -columns width                     width of window, in characters",
"    -file file                         file to watch",
"    -update seconds                    how often to check for mail",
"    -fade seconds			lifetime of unmodified window",
"    -volume percentage                 how loud to ring the bell",
"    -bg color                          background color",
"    -fg color                          foreground color",
"    -refresh seconds                   seconds before re-posting window",
"    -led ledNum                        keyboard LED to light up",
"    -ledPopdown                        turn off LED when popped down",
"    -scanCommand command               command to interpret and display",
"    -checkCommand command              command used to check for change",
NULL};
    char **s;

    printf("usage:\t%s  [-options ...]  [file to watch]\n", progname);
    for (s= help_message; *s; s++)
      printf("%s\n", *s);
    printf("\n");
    exit(1);
}


/**********\
|*  Exit  *|  called via callback, exits the program
\**********/
void
#ifdef	FUNCPROTO
Exit(Widget w, XEvent *event, String *params, Cardinal *num_params)
#else
Exit(w, event, params, num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
#endif
{
    DP(("++exit()\n"));

    if (event->type == ClientMessage) {
	if (event->xclient.data.l[0] != wm_delete_window) {
	    DP(("received client message that was not delete_window\n"));
	    XBell (XtDisplay(w), 0);
	    return;
	} else
	    DP(("exiting after receiving a wm_delete_window message\n"));
    }

    toggle_key_led(False);

    XCloseDisplay(XtDisplay(w));

    exit(0);
}


/***************\
|*  checksize  *|  checks mail file to see if new mail is present
|***************
|*	This routine stat's the mail spool file and compares its size
|*	with the previously obtained result.  If the size has become 
|*	zero, it pops down the window.  If nonzero, it calls a routine
|*	to execute the scanCommand.  If the result of this is non-null,
|*	it pops up a window showing it (note that users of Berkeley
|*	mail may have non-empty mail files with all old mail).
\*/
checksize()
{
    static int mailsize = 0;
    struct stat mailstat;
    int pop_window = False;
    struct timeval tp;
    struct timezone tzp;

    DP(("++checksize()..."));

    /*
    ** Do the stat to get the mail file size.  If it fails for any reason,
    ** ignore the failure and assume the file is size 0.  Failures I can
    ** think of are that should be ignored are:
    **
    **		+ nonexistent file.  Some Berkeley-style mailers delete
    **		  the spool file when they're done with it.
    **		+ NFS stale filehandle.  Yuk.  This one happens if
    ** 		  your mail spool file is on an NFS-mounted directory
    **		  _and_ your update interval is too low _and_ you use
    **		  a Berkeleyish mailer.  Yuk.
    **
    ** Doubtless there are errors we should complain about, but this 
    ** would get too ugly.
    */
    if (lbiff_data.checkCmd != NULL) {
	waitType status;

#ifdef	INTWAITTYPE
	status = 	  system(lbiff_data.checkCmd);
#else
	status.w_status = system(lbiff_data.checkCmd);
#endif
	switch (waitCode(status)) {
	case 0:					/* 0: new data */
	    mailstat.st_size = mailsize + 1;
	    break;
        case 2:					/* 2: no data (clear) */
	    mailstat.st_size = 0;
	    break;
	default:				/* 1: same as before */
	    mailstat.st_size = mailsize;
	}
    } else {	/* no checkCmd, just stat the mailfile and check size */
	if (stat(lbiff_data.file,&mailstat) != 0) {
	    DP(("stat() failed, errno=%d.  Assuming filesize=0!\n",errno));
	    mailstat.st_size = 0;
	}
    }

    /*
    ** If it's changed size, take appropriate action.
    */
    if (mailstat.st_size != mailsize) {
	DP(("changed size: %d -> %d\n",mailsize,mailstat.st_size));
	mailsize = mailstat.st_size;
	pop_window = True;
    } else if (!visible && lbiff_data.refresh && mailsize != 0) {
	/*
	** If window has been popped down, check if it's time to refresh
	*/
	if (gettimeofday(&tp,&tzp) != 0) {
	    ErrExit(True,"gettimeofday() in checksize()");
	} else {
	    if ((tp.tv_sec - acknowledge_time) > lbiff_data.refresh) {
		DP(("reposting window, repost time reached\n"));
		pop_window = True;
	    }
	}
    } else if (visible && (mailstat.st_size = mailsize)) {
	/*
	** window is visible--see if fade time has been reached, and
	** if so, popdown window
	** if fade is zero, do not pop down
	*/
	if (gettimeofday(&tp,&tzp) != 0) {
	    ErrExit(True,"gettimeofday() in checksize()");
	} else if (lbiff_data.fade > 0) {
	    if ((tp.tv_sec - popup_time) > lbiff_data.fade) lbiffUnrealize();
	}
    }


    if (pop_window) {
	if (mailsize == 0) {
	    hasdata = False;
	    toggle_key_led(False);
	    lbiffUnrealize();
	} else {				/* something was added? */
	    char *s = doScan();

	    if (strlen(s) != 0)	{		/* is there anything new? */
		if (hasdata) /* ESM && isvisible? ESM */
		  lbiffUnrealize();		/* pop down if it's up    */
		hasdata = True;
		toggle_key_led(True);
		lbiffRealize(s);		/* pop back up */
	    }
	}
    } else {
	DP(("no change\n"));
    }
}


/*************\
|*  handler  *|  Checks mail file and reschedules itself to do so again
\*************/
void
#ifdef	FUNCPROTO
handler( XtPointer closure, XtIntervalId *id )
#else
/* ARGSUSED */
handler( closure, id )
     XtPointer 	   closure;
     XtIntervalId  *id;
#endif
{
    checksize();
    XtAppAddTimeOut(app_context,lbiff_data.update * 1000, handler, NULL);
}


/************\
|*  doScan  *|  invoke MH ``scan'' command to examine mail messages
|************
|*	This routine looks at the mail file and parses the contents. It
|*	does this by invoking scan(1) or some other user-defined function.
\*/
char *
doScan()
{
    static char	*cmd_buf;
    static char *buf = NULL;
    static int	bufsize;
    static char scan_fail_msg[] = "\n---->>>>scanCommand failed<<<<<----\n";
    FILE 	*p;
    size_t	size;
    waitType	status;

    DP(("++doScan()\n"));

    /*
    ** Initialise display buffer to #rows * #cols
    ** Initialise command string
    */
    if (buf == NULL) {
	/* +1 for the newline */
	bufsize = (lbiff_data.columns + 1) * lbiff_data.rows;

	buf = (char*)malloc(bufsize + sizeof(scan_fail_msg) + 1);
	if (buf == NULL)
	  ErrExit(True,"text buffer malloc()");

	DP(("---size= %dx%d\n", lbiff_data.rows, lbiff_data.columns));

	cmd_buf = (char*)malloc(strlen(lbiff_data.cmd) +
				strlen(lbiff_data.file) + 10);
	if (cmd_buf == NULL)
	  ErrExit(True,"command buffer malloc()");

	sprintf(cmd_buf,lbiff_data.cmd, lbiff_data.file, lbiff_data.columns);
	DP(("---cmd= %s\n",cmd_buf));
    }

    /*
    ** Execute the command, read the results, then set the contents of window.
    ** If there is data remaining in the pipe, read it in (and throw it away)
    ** so our exit status is correct (eg, not "Broken pipe").
    */
    if ((p= popen(cmd_buf,"r")) == NULL)
      ErrExit(True,"popen");
    if ((size= fread(buf,1,bufsize,p)) < 0)
      ErrExit(True,"fread");
    if (size == bufsize) {
	char junkbuf[100];
	while (fread(junkbuf, 1, 100, p) > 0)
	  ;	/* Keep reading until no more left */
    }

#ifdef	INTWAITTYPE
    status = 		pclose(p);
#else
    status.w_status =	pclose(p);
#endif
    if (waitCode(status) != 0) {
	strcpy(buf+size, scan_fail_msg);
	size += strlen(scan_fail_msg);
    }

    buf[size] = '\0';				/* null-terminate it! */

    DP(("scanned:\n%s\n",buf));
    return buf;
}


/****************\
|*  CheckEvent  *|  
\****************/
Bool
#ifdef	FUNCPROTO
CheckEvent( Display *d, XEvent *e, caddr_t arg )
#else	/* ~FUNCPROTO */
CheckEvent( d, e, arg )
     Display *d;
     XEvent  *e;
     caddr_t arg;
#endif	/* FUNCPROTO */
{
    if (e->type == MapNotify || e->type == UnmapNotify)
      if (e->xmap.window == (Window)arg)
	return True;

    return False;
}

static XEvent lastEvent;

/* ARGSUSED */
/*
** Handler for map/unmap events.  Copied from xconsole.
** When unmap and map events occur consecutively, eg when resetting a
** window manager, this makes sure that only the last such event takes place.
*/
/************\
|*  Shrink  *|  get StructureNotify events, popdown if iconified
\************/
void
#ifdef	FUNCPROTO
Shrink( Widget w, caddr_t data, XEvent *e, Boolean *b )
#else
Shrink(w, data, e, b)
    Widget w;
    caddr_t data;
    XEvent *e;
    Boolean *b;
#endif
{
    DP(("++Shrink()\n"));
    if (e->type == MapNotify || e->type == UnmapNotify) {
	int    event_seen = 0;
	Window win = e->xmap.window;

#ifdef	USE_BCOPY
	bcopy((char*)e,(char*)&lastEvent,sizeof(XEvent));
#else
	memcpy((char*)&lastEvent,(char*)e,sizeof(XEvent));
#endif

	XSync(XtDisplay(w),False);

	while(XCheckIfEvent(XtDisplay(w),&lastEvent,CheckEvent,(caddr_t)win))
	  event_seen = 1;

	if (!event_seen)
	  return;

	if (lastEvent.type == UnmapNotify && visible)
	  Popdown();
	else if (lastEvent.type == MapNotify && hasdata)
	  Popup();
    }
}


/*
 ** These here routines (Popdown/Popup) bring the main window up or down.
 ** They are pretty simple except for the issue with *bottom...
 ** If running with *bottom, things are more complicated.  You can't
 ** just map/unmap(), because since the window has already been placed
 ** at the bottom (when realized) any lines that get added to it when
 ** more mail comes in will just drop off the edge of the screen.
 ** Thus when *bottom is true we need to realize() the window anew
 ** each time something changes in it.
 */
/*************\
|*  Popdown  *|  kill window
\*************/
void
Popdown()
{
    struct timeval tp;
    struct timezone tzp;

    DP(("++Popdown()\n"));
    if (visible)
      XtPopdown(topLevel);
    visible = False;

    /*
    ** Remember when we were popped down so we can refresh later
    */
    if (gettimeofday(&tp,&tzp) != 0)
      ErrExit(True,"gettimeofday() in lbiffUnrealize()");

    acknowledge_time = tp.tv_sec;

    if (lbiff_data.ledPopdown)		/* Turn off LED if so requested */
      toggle_key_led(False);
}

void
Popup()
{
    struct timeval tp;
    struct timezone tzp;

    DP(("++Popup()\n"));

    /*
    ** Remember when we were popped up so we can fade later
    */
    if (gettimeofday(&tp,&tzp) != 0)
      ErrExit(True,"gettimeofday() in Popup()");
    popup_time = tp.tv_sec;

    if (hasdata && !visible)
      XtPopup(topLevel, XtGrabNone);
    visible = True;
}


/********************\
|*  lbiffUnrealize  *|  kill window
\********************/
void
lbiffUnrealize()
{
    DP(("++lbiffUnrealize()\n"));
    if (lbiff_data.bottom)
      XtUnrealizeWidget(topLevel);
    else
      Popdown();

    visible = False;
}


/******************\
|*  lbiffRealize  *|  reformat window, set the text and bring window up
\******************/
void
#ifdef	FUNCPROTO
lbiffRealize( char *s )
#else
lbiffRealize( s )
     char *s;
#endif
{
    Arg		args[4];
    int		n;
    static int	first_time = 1;

    DP(("++lbiffRealize()\n"));

    /*
    ** Set the contents of the window
    */
    n = 0;
    XtSetArg(args[n],XtNlabel,s); n++;
    XtSetValues(textBox, args, n);

    /*
    ** If running with *bottom, we need to tell the widget what size it
    ** is before realize()ing it.  This is so the WM can position it
    ** properly at the bottom of the screen.
    */
    if (lbiff_data.bottom) {
	Dimension width,height;

	getDimensions(s,&width,&height);

	n = 0;
 	XtSetArg(args[n], XtNwidth, width); n++;
	XtSetArg(args[n], XtNheight, height); n++;
	XtSetArg(args[n], XtNy, -1); n++;

	XtSetValues(topLevel, args, n);
	XtRealizeWidget(topLevel);
    }
    Popup();

    if (first_time) {
	/* first time through this code */
	(void) XSetWMProtocols (XtDisplay(topLevel), XtWindow(topLevel),
				&wm_delete_window, 1);
	first_time = 0;
    }


    if (lbiff_data.sound[0] == '\0') {
	XBell(XtDisplay(topLevel),lbiff_data.volume - 100);
	DP(("---sound= %s\n","XBell default"));
    }
    else {
	static char	*sound_buf;

	/*
	** Initialise sound string
	*/
	if (sound_buf == NULL) {
	    sound_buf = (char*)malloc(strlen(lbiff_data.sound) + 10);
	    if (sound_buf == NULL)
	      ErrExit(True,"sound_buf malloc()");

	    sprintf(sound_buf,lbiff_data.sound, lbiff_data.volume);
	    DP(("---sound= %s\n",sound_buf));
	}
	system(sound_buf);
    }

    if (lbiff_data.resetSaver)
      XResetScreenSaver(XtDisplay(topLevel));

    visible = True;
}


/*******************\
|*  getDimensions  *|  get width x height of text string
\*******************/
void
#ifdef	FUNCPROTO
getDimensions( char *s, Dimension *width, Dimension *height )
#else
getDimensions(s,width,height)
     char *s;
     Dimension *width, *height;
#endif
{
    Dimension	tmp_width;
    int		i,
                len = strlen(s);
    static int	fontWidth, fontHeight;
    static int	borderWidth = -1;

    tmp_width = *width = *height = 1;

    if (borderWidth == -1)
      initStaticData(&borderWidth,&fontHeight,&fontWidth);

    /*
    ** count rows and columns
    */
    for (i=0; i < len-1; i++) {
	if (s[i] == '\n') {			/* new line: clear width */
	    ++*height;
	    tmp_width = 0;
	} else {
	    ++tmp_width;
	    if (tmp_width > *width)		/* monitor highest width */
	      *width = tmp_width;
	}
    }

    if (*height > lbiff_data.rows)		/* cut to fit max wid/hgt */
      *height = lbiff_data.rows;
    if (*width > lbiff_data.columns)
      *width = lbiff_data.columns;

    DP(("geom= %dx%d chars (%dx%d pixels)\n",*width,*height,
	                                     *width*fontWidth,
	                                     *height*fontHeight));

    *width  *= fontWidth;  *width  += 6;	/* convert to pixels 	  */
    *height *= fontHeight; *height += 4;	/* and add a little fudge */
}


/********************\
|*  initStaticData  *|  initializes font size & borderWidth
\********************/
void
#ifdef	FUNCPROTO
initStaticData( int *bw, int *fontH, int *fontW )
#else
initStaticData(bw, fontH, fontW)
    int *bw, *fontH, *fontW;
#endif
{
    Arg		args[2];
    XFontStruct *fs;
    int tmp;

    DP(("++initStaticData..."));
    XtSetArg(args[0],XtNfont,&fs);
    XtSetArg(args[1],XtNborderWidth,&tmp);
    XtGetValues(textBox, args, 2);
    if (fs == NULL)
      ErrExit(False,"unknown font");

    *bw	   = tmp;
    *fontW = fs->max_bounds.width;
    *fontH = fs->max_bounds.ascent + fs->max_bounds.descent;

    DP(("font= %dx%d,  borderWidth= %d\n",*fontH,*fontW,*bw));
}


/********************\
|*  toggle_key_led  *|  toggle a keyboard LED on and off
\********************/
void
#ifdef	FUNCPROTO
toggle_key_led(int flag)
#else
toggle_key_led(flag)
int	flag;
#endif
{
    XKeyboardControl	keyboard;

    if (lbiff_data.led == 0)		/* return if no led action desired */
      return;

    DP(("++toggle_key_led(%d,%s)\n", lbiff_data.led, flag ? "True" : "False"));

    if (flag)
	keyboard.led_mode = LedModeOn;
    else
	keyboard.led_mode = LedModeOff;

    keyboard.led = lbiff_data.led;

    XChangeKeyboardControl(XtDisplay(topLevel), KBLed | KBLedMode, &keyboard);
}


/*************\
|*  ErrExit  *|  print out error message, clean up and exit
|*************
|* ErrExit prints out a given error message to stderr.  If <errno_valid>
|* is True, it calls strerror(errno) to get the descriptive text for the
|* indicated error.  It then clears the LEDs and exits.
|*
|* It is the intention that someday this will bring up a popup window.
\*/
void
#ifdef	FUNCPROTO
ErrExit(Boolean errno_valid, char *s)
#else
ErrExit(errno_valid, s)
     Boolean errno_valid;
     char    *s;
#endif
{
    if (errno_valid)
      fprintf(stderr,"%s: %s: %s\n", progname, s, strerror(errno));
    else
      fprintf(stderr,"%s: %s\n", progname, s);

    toggle_key_led(False);

    XCloseDisplay(XtDisplay(topLevel));

    exit(1);
}


#ifdef	NEED_STRERROR
/**************\
|*  strerror  *|  return descriptive message text for given errno
\**************/
char *
#ifdef	FUNCPROTO
strerror(int err)
#else	/* ~FUNCPROTO */
strerror(err)
     int err;
#endif	/* FUNCPROTO */
{
    static char unknown[30];
    extern int	sys_nerr;
    extern char *sys_errlist[];

    if (err >= 0 && err < sys_nerr)
      return sys_errlist[err];

    sprintf(unknown,"Unknown error %d", err);
    return unknown;
}
#endif	/* NEED_STRERROR */


syntax highlighted by Code2HTML, v. 0.9.1