/*
 * xbatt -- Display battery status for FreeBSD-2.x & Linux
 *
 * Copyright (c) 1995,1997 by Toshihisa ETO	<eto@osl.fujitsu.co.jp>
 *						<eto@forus.or.jp>
 *
 * This software may be used, modified, copied, and distributed, in
 * both source and binary form provided that the above copyright and
 * these terms are retained. Under no circumstances is the author 
 * responsible for the proper functioning of this software, nor does 
 * the author assume any responsibility for damages incurred with its 
 * use.
 *
 */

/*
 * HISTORY
 *	95/03/27	0.1	XBM version
 *	95/03/28	0.2	refine bitmap management
 *	95/03/30	0.3	use XPM library
 *	95/04/01	0.3.1	by Noriyuki Takahashi (nor@aecl.ntt.jp)
 *		Display plug and charging status.
 *		Support gray-scale and mono (compile-time option).
 *	95/04/03	0.4	Support gray-scale and mono (auto detect).
 *				Update status, when catch key event.
 *	95/04/19	1.0	Official release with some documents!!!!
 *	95/04/27	1.1	Support Linux apm-bios 0.5
 *				by pcs00294@asciinet.or.jp (Koyama Tadayoshi)
 *	97/01/01	1.2b1	Modify some place
 *	97/01/17	1.2b3	Support Linux-1.3.58 or lator
 *					by hisama@bunff.trad.pfu.co.jp
 *					   mac@pssys.flab.fujitsu.co.jp
 *					   yoshi@funival.mfd.cs.fujitsu.co.jp
 *				Support for PC which does not return
 *				buttery life (e.g. ThinkPad 535)
 *					by umemoto
 *				Add check timer routine for resume problem
 *				Show battery life, when the mouse
 *				cursor located on battery icon.
 *	97/01/20	1.2b4	Changes for Xt related part (e.g. resources)
 *					by mi@sy.ssl.fujitsu.co.jp
 *				Change small bugs
 */

#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <X11/StringDefs.h>
#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <X11/extensions/shape.h>
#include <X11/Xaw/Cardinals.h>

#include <X11/xpm.h>

#ifdef XKB
# include <X11/extensions/XKBbells.h>
#endif

#ifdef __FreeBSD__
# include <machine/apm_bios.h>
# define APMDEV21	"/dev/apm0"
# define APMDEV22	"/dev/apm"
#endif

#include "pixmaps/battery.xpm"
#include "pixmaps/unknown.xpm"
#include "bitmaps/full.xbm"
#include "bitmaps/digit0.xbm"
#include "bitmaps/digit1.xbm"
#include "bitmaps/digit2.xbm"
#include "bitmaps/digit3.xbm"
#include "bitmaps/digit4.xbm"
#include "bitmaps/digit5.xbm"
#include "bitmaps/digit6.xbm"
#include "bitmaps/digit7.xbm"
#include "bitmaps/digit8.xbm"
#include "bitmaps/digit9.xbm"
#include "bitmaps/percent.xbm"

/* reflash Interval (in seconds) */
#ifndef UPDATE_INTERVAL
# ifndef NOAPM
#  define UPDATE_INTERVAL	30
# else
#  define UPDATE_INTERVAL	5
# endif
#endif

#define	APM_STAT_UNKNOWN	255
/* aip->ai_acline */
#define	APM_STAT_LINE_OFF	0
#define	APM_STAT_LINE_ON	1

/* aip->ai_batt_stat */
#define	APM_STAT_BATT_HIGH	0
#define	APM_STAT_BATT_LOW	1
#define	APM_STAT_BATT_CRITICAL	2
#define	APM_STAT_BATT_CHARGING	3

struct status {
    u_int	remain;
    u_int	acline;
    u_int	charge;
};
static struct status	lastStat = {APM_STAT_UNKNOWN, APM_STAT_UNKNOWN,
			    APM_STAT_UNKNOWN};

static	void		updateStatus(XtPointer, XtIntervalId*);
static	struct status	getBatteryStatus();
static	void		updateWindow(struct status);
static	void		initWindowResource();

#define	BatteryWidth	39
#define	BatteryHeight	39

static	void setColorSymbol(unsigned int, unsigned int, unsigned int);
static	void goOut();
static	void CallbackTyped(Widget, char*, XEvent*, Boolean*);
static	void CallbackEnter(Widget, char*, XEvent*, Boolean*);
static	void CallbackLeave(Widget, char*, XEvent*, Boolean*);

static	time_t		lastUpdateTime = 0;
static	int		showStatus = 0;
static	int		forceRedraw = 0;
static	int		apmfd = 0;
static	Widget		toplevel;
static	Pixmap		xpmData, xpmMask;
static	GC		gc;
static	XpmAttributes	attr;
static	XtIntervalId	timer = 0;
static	int		displayType;	/* Mono, Gray, Color */
#define	Color	0
#define	Gray	1
#define	Mono	2
#define	MAXSYM		30
static	XpmColorSymbol	sym[MAXSYM];
static	unsigned int	nsym = 0;

typedef struct _SetupRec{
    int	timerInterval;
} SetupRec, *SetupPtr;

static SetupRec appResources;

static XtResource resources[] = {
    {"timerInterval", "TimerInterval", XtRInt, sizeof(int),
     XtOffsetOf(struct _SetupRec, timerInterval), XtRImmediate,
     (XtPointer)UPDATE_INTERVAL},
};

static XrmOptionDescRec	options[] = {
    {"-timerInterval", ".timerInterval", XrmoptionSepArg,
     (XtPointer)UPDATE_INTERVAL},
};

struct	Digits {
    unsigned int	width;
    unsigned int	height;
    unsigned char*	bits;
};

struct Digits	digits[] = {
    {digit0_width, digit0_height, digit0_bits},
    {digit1_width, digit1_height, digit1_bits},
    {digit2_width, digit2_height, digit2_bits},
    {digit3_width, digit3_height, digit3_bits},
    {digit4_width, digit4_height, digit4_bits},
    {digit5_width, digit5_height, digit5_bits},
    {digit6_width, digit6_height, digit6_bits},
    {digit7_width, digit7_height, digit7_bits},
    {digit8_width, digit8_height, digit8_bits},
    {digit9_width, digit9_height, digit9_bits}
};

main(
  int	argc,
  char	*argv[]
) {
    XSizeHints		size_hints;
    Visual*	visual;
    int		depth;
    XtAppContext appContext;

#if !defined(NOAPM) && defined(__FreeBSD__)
    /* initialize APM Interface */
    if ((apmfd = open(APMDEV21, O_RDWR)) == -1) {
	if ((apmfd = open(APMDEV22, O_RDWR)) == -1) {
 	    fprintf(stderr, "xbatt: cannot open apm device (%s or %s): %s\n",
        		APMDEV21, APMDEV22, strerror(errno));
	    exit(1);
	}
    }
#endif

    /* start X-Window session */
    XtSetLanguageProc( NULL, NULL, NULL );
    toplevel = XtOpenApplication(&appContext, "XBatt",
				 options, XtNumber(options),
				 &argc, argv, NULL, 
				 sessionShellWidgetClass, NULL, ZERO);
    if (!toplevel) {
	fprintf(stderr, "xbatt: cannot start x-session\n");
	exit(1);
    }
    if (argc != 1) {
	fprintf(stderr, "usage: xbatt [X-Toolkit standard option]\n");
	exit(1);
    }

    /* get rsource information */
    XtGetApplicationResources(toplevel, &appResources,
			      resources, XtNumber(resources), NULL, 0);


    /* get display information */
    visual = XDefaultVisual(XtDisplay(toplevel),
			    DefaultScreen(XtDisplay(toplevel)));
    depth = XDefaultDepth(XtDisplay(toplevel),
			  DefaultScreen(XtDisplay(toplevel)));
    if (depth == 1) {
	displayType = Mono;
    } else {
	switch(visual->class) {
	  case PseudoColor:
	  case DirectColor:
	  case StaticColor:
	  case TrueColor:
	    displayType = Color;
	    break;
	  case GrayScale:
	  case StaticGray:
	    displayType = Gray;
	    break;
	  default:
	    displayType = Mono;
	}
    }

    /* set toplevel property */
    XtVaSetValues(toplevel,
		  XtNmappedWhenManaged, False,
		  XtNinput, True,
		  XtNwidth, BatteryWidth,
		  XtNheight,BatteryHeight,
		  XtNminWidth, BatteryWidth,
		  XtNminHeight, BatteryHeight,
		  XtNiconName, "xbatt",
		  NULL);

    /* realize toplevel widget */
    XtRealizeWidget(toplevel);

    /* set window information and callback procedure */
    initWindowResource();
    XtAddEventHandler(toplevel, KeyPressMask, False,
		      (XtEventHandler)CallbackTyped, NULL);
    XtAddEventHandler(toplevel, EnterWindowMask, False,
		      (XtEventHandler)CallbackEnter, NULL);
    XtAddEventHandler(toplevel, LeaveWindowMask, False,
		      (XtEventHandler)CallbackLeave, NULL);

    /* display current battery status, and set timer callback */
    updateStatus(NULL, NULL);

    /* start main loop */
    XtAppMainLoop(appContext);

    /* free resource, and go out*/
    goOut(0);
}

void updateStatus(
  XtPointer	client_data,
  XtIntervalId	*t
) {
    struct status s;

    /* if this function called by another function, */
    /* then remove current timer */
    if ((t == NULL) && (timer != 0)) {
	XtRemoveTimeOut(timer);
	timer = 0;
    }

    /* get and update current status */
    s = getBatteryStatus();

    /* if status has changed, update window */
    if (((s.remain != lastStat.remain)
	 && (((s.remain % 5) == 0) || showStatus != 0))
	|| (s.acline != lastStat.acline)
	|| (s.charge != lastStat.charge)
	|| (forceRedraw != 0)
	) {
	/* update window */
	updateWindow(s);

	/* remember current status */
	lastStat = s;

	forceRedraw = 0;
    }

    /* start timer for next interval */
    timer = XtAppAddTimeOut(XtWidgetToApplicationContext(toplevel),
			    appResources.timerInterval * 1000,
			    updateStatus, toplevel);

    /* save time stamp */
    lastUpdateTime = time(NULL);
}

struct status getBatteryStatus()
{
    struct status	ret;
#ifdef	__FreeBSD__
    struct apm_info	info;

#ifndef NOAPM
    /* get APM information */
    if (ioctl(apmfd, APMIO_GETINFO, &info) == -1) {
	fprintf(stderr, "xbatt: ioctl APMIO_GETINFO failed\n");
	exit(1);
    }
#else	/* NOAPM */
    /* set dummy battery life */
    if ((lastStat.remain < 1) || (lastStat.remain > 10)) {
	info.ai_batt_life = 100;
    } else {
	info.ai_batt_life = (lastStat.remain - 1) * 10;
    }
#endif	/* NOAPM */

    /* get current status */
    if (info.ai_batt_life == APM_STAT_UNKNOWN) {
	switch (info.ai_batt_stat) {
	  case APM_STAT_BATT_HIGH:
	    ret.remain = 100;
	    break;
	  case APM_STAT_BATT_LOW:
	    ret.remain = 40;
	    break;
	  case APM_STAT_BATT_CRITICAL:
	    ret.remain = 10;
	    break;
	  default:        /* expected to be APM_STAT_UNKNOWN */
	    ret.remain = APM_STAT_UNKNOWN;
	}
    } else if (info.ai_batt_life > 100) {
	/* some APM BIOSes return values slightly > 100 */
	ret.remain = 100;
    } else {
	ret.remain = info.ai_batt_life;
    }

    /* get AC-line status */
    if (info.ai_acline == APM_STAT_LINE_ON) {
	ret.acline = APM_STAT_LINE_ON;
    } else {
	ret.acline = APM_STAT_LINE_OFF;
    }

    /* get charging status */
    if (info.ai_batt_stat == APM_STAT_BATT_CHARGING) {
	ret.charge = APM_STAT_BATT_CHARGING;
    } else {
	ret.charge = APM_STAT_BATT_HIGH;	/* I only want to know,	*/
						/* chrging or not.	*/
    }
#endif	/* FreeBSD */

#ifdef	__linux__
    char	buffer[64];
    FILE*	fp;
    int		battLife;
     char	driver_version[64];
     int		apm_bios_info_major;
     int		apm_bios_info_minor;
     int		apm_bios_info_flags;
     int		ac_line_status;
     int		battery_status;
     int		battery_flag;
     int		time_units;
     char	units[64];

    /* open power status */
    if ((fp = fopen("/proc/apm", "r")) == NULL) {
	fprintf(stderr, "xbatt: cannot optn /proc/apm\n");
	exit(1);
    }

    ret.charge = APM_STAT_BATT_HIGH;
    ret.acline = APM_STAT_LINE_OFF;
    while (fgets(buffer, 64, fp) != NULL) {
	/*
	 * for linux-1.3.58 or later
	 */
	if (sscanf(buffer, "%s %d.%d 0x%x 0x%x 0x%x 0x%x %d%% %d %s\n",
		driver_version,
		&apm_bios_info_major,
		&apm_bios_info_minor,
		&apm_bios_info_flags,
		&ac_line_status,
		&battery_status,
		&battery_flag,
		&battLife,
		&time_units,
		units) == 10) {
	    if (battLife < 0) {
		ret.remain = APM_STAT_UNKNOWN;
	    } else {
		ret.remain = battLife;
	    }
	    if (ac_line_status == 0x01) {
		ret.acline = APM_STAT_LINE_ON; 
	    }
	    if (battery_status == 0x03) {
		ret.charge = APM_STAT_BATT_CHARGING;
	    }
	}
	/*
	 * for apm_bios-0.5
	 */
	else if (sscanf(buffer, "Battery life: %d%%\n", &battLife) == 1) {
	    ret.remain = battLife;
	} else if (strcmp("AC: on line\n", buffer)==0) {
	    ret.acline = APM_STAT_LINE_ON; 
	} else if (strcmp("Battery status: charging\n", buffer) == 0) {
	    ret.charge = APM_STAT_BATT_CHARGING;
	}
    }
    fclose(fp);
#endif	/* Linux */

    return ret;
}

void initWindowResource()
{
    gc = XCreateGC(XtDisplay(toplevel), XtWindow(toplevel), 0, 0);
    XSetState(XtDisplay(toplevel), gc, 1, 0, GXcopy, AllPlanes);
    XSetFillStyle(XtDisplay(toplevel), gc, FillSolid);
    XFlush(XtDisplay(toplevel));
}

void updateWindow(struct status s)
{
    Pixmap	bmp;
    int		ret;

    /* free old data */
    if (xpmData) {
	XFreePixmap(XtDisplay(toplevel), xpmData);
    }
    if (xpmMask) {
	XFreePixmap(XtDisplay(toplevel), xpmMask);
    }
    XpmFreeAttributes(&attr);

    /* set attribute value mask */
    attr.valuemask = 0;
    attr.valuemask |= XpmReturnPixels;
    attr.valuemask |= XpmReturnExtensions;
    attr.valuemask |= XpmColorSymbols;

    /* set color symbol for current status */
    setColorSymbol(s.remain, s.acline, s.charge);

    /* set color_class flag */
    if (displayType == Mono) {
	attr.valuemask |= XpmColorKey;
	attr.color_key = XPM_MONO;
    } else if (displayType == Gray) {
	attr.valuemask |= XpmColorKey;
	attr.color_key = XPM_GRAY;
    }

    /* create pixmap data */
    if (s.remain != APM_STAT_UNKNOWN) {
	ret = XpmCreatePixmapFromData(XtDisplay(toplevel),
				      XtWindow(toplevel),
				      batt_xpm, &xpmData,
				      &xpmMask, &attr);
    } else {
	ret = XpmCreatePixmapFromData(XtDisplay(toplevel),
				      XtWindow(toplevel),
				      ubatt_xpm, &xpmData,
				      &xpmMask, &attr);
    }
    switch (ret) {
      case XpmSuccess:
	break;
      case XpmColorError:
	fprintf(stderr,
		"xbatt: Could not parse or alloc requested color\n");
	break;
      case XpmNoMemory:
	fprintf(stderr, "xbatt: Not enough memory\n");
	goOut(1);
	break;
      case XpmColorFailed:
	fprintf(stderr, "xbatt: Failed to parse or alloc some color\n");
	goOut(1);
	break;
    }

    /* resize window size to pixmap size */
    XtResizeWidget(toplevel, attr.width, attr.height, 1);

    /* if show status is active, then draw status info */
    if ((showStatus != 0) && (s.remain != APM_STAT_UNKNOWN)) {
	Pixmap	bm;

	XSetForeground(XtDisplay(toplevel), gc,
		       BlackPixel(XtDisplay(toplevel),
                                  DefaultScreen(XtDisplay(toplevel))));
	XDrawRectangle(XtDisplay(toplevel), xpmData, gc, 17, 17, 14, 8);
	XSetForeground(XtDisplay(toplevel), gc,
		       WhitePixel(XtDisplay(toplevel),
                                  DefaultScreen(XtDisplay(toplevel))));
	XFillRectangle(XtDisplay(toplevel), xpmData, gc, 18, 18, 13, 7);

	if (s.remain == 100) {
	    bm = XCreatePixmapFromBitmapData(XtDisplay(toplevel),
					     XtWindow(toplevel),
					     full_bits, full_width,
					     full_height,
					     BlackPixel(XtDisplay(toplevel),
						 DefaultScreen(
						     XtDisplay(toplevel))),
					     WhitePixel(XtDisplay(toplevel),
						 DefaultScreen(
						     XtDisplay(toplevel))),
					     1);	/* Depth */
	    XCopyPlane(XtDisplay(toplevel), bm, xpmData, gc, 0, 0,
		       full_width, full_height,
		       19, 19, 1);
	    XFreePixmap(XtDisplay(toplevel), bm);
	} else {
	    int	d = s.remain / 10;
	    bm = XCreatePixmapFromBitmapData(XtDisplay(toplevel),
					     XtWindow(toplevel),
					     digits[d].bits,
					     digits[d].width,
					     digits[d].height,
					     BlackPixel(XtDisplay(toplevel),
						 DefaultScreen(
						     XtDisplay(toplevel))),
					     WhitePixel(XtDisplay(toplevel),
						 DefaultScreen(
						     XtDisplay(toplevel))),
					     1);
	    XCopyPlane(XtDisplay(toplevel), bm, xpmData, gc, 0, 0, 3, 5,
		       19, 19, 1);
	    XFreePixmap(XtDisplay(toplevel), bm);

	    d = s.remain % 10;
	    bm = XCreatePixmapFromBitmapData(XtDisplay(toplevel),
					     XtWindow(toplevel),
					     digits[d].bits,
					     digits[d].width,
					     digits[d].height,
					     BlackPixel(XtDisplay(toplevel),
						 DefaultScreen(
						     XtDisplay(toplevel))),
					     WhitePixel(XtDisplay(toplevel),
						 DefaultScreen(
						     XtDisplay(toplevel))),
					     1);
	    XCopyPlane(XtDisplay(toplevel), bm, xpmData, gc, 0, 0, 3, 5,
		       23, 19, 1);
	    XFreePixmap(XtDisplay(toplevel), bm);

	    bm = XCreatePixmapFromBitmapData(XtDisplay(toplevel),
					     XtWindow(toplevel),
					     percent_bits,
					     percent_width,
					     percent_height,
					     BlackPixel(XtDisplay(toplevel),
						 DefaultScreen(
						     XtDisplay(toplevel))),
					     WhitePixel(XtDisplay(toplevel),
						 DefaultScreen(
						     XtDisplay(toplevel))),
					     1);
	    XCopyPlane(XtDisplay(toplevel), bm, xpmData, gc, 0, 0, 3, 5,
		       27, 19, 1);
	    XFreePixmap(XtDisplay(toplevel), bm);
	}
    }

    /* set pixmap data */
    XSetWindowBackgroundPixmap(XtDisplay(toplevel),
			       XtWindow(toplevel),
			       xpmData);

    /* if pixmap has transparent pixel, set mask pixmap */
    if (xpmMask) {
	XShapeCombineMask(XtDisplay(toplevel), XtWindow(toplevel),
			  ShapeBounding, 0, 0, xpmMask, ShapeSet);
    }

    /* redraw window */
    XClearWindow(XtDisplay(toplevel), XtWindow(toplevel));

    /* map window */
    XtMapWidget(toplevel);
}

void setColorSymbol(
  unsigned int	life,
  unsigned int	acline,
  unsigned int	charge
) {
    unsigned int	i;
    char*		gage_color;

    life = (life + 9) / 10;

    /* If battery life is shore, ring a bell */
    if ((life != APM_STAT_UNKNOWN) && (life < 5) && ((life % 5) == 0)) {
#ifdef XKB
	XkbStdBell(XtDisplay(toplevel), XtWindow(toplevel), 0,
		   XkbBI_MinorError);
#else
	XBell(XtDisplay(toplevel), 100);
#endif
    }

    /* clear old data */
    for(i = 0; i < nsym; i++) {
	if (sym[i].name != NULL) {
	    free(sym[i].name);
	    sym[i].name = NULL;
	}
    }

    /* if the battery life is not UNKNOWN status */
    if (life != APM_STAT_UNKNOWN) {
	/* set default(High life) gage color */
	switch(displayType) {
	  case Mono:
	    gage_color = "black";
	    break;
	  case Gray:
	    gage_color = "grey80";
	    break;
	  case Color:
	    gage_color = "green";
	}

	/* set gage color */
	if (displayType == Color) {
	    if (life < 3) {
		gage_color = "red";
	    } else if (life < 5) {
		gage_color = "yellow";
	    }
	}

	/* set symbol for gage */
	nsym = 0;
	for(i = 1; i <= life; i++) {
	    sym[nsym].name	= (char*)malloc(7);
	    sprintf(sym[nsym].name, "gage%d", i);
	    sym[nsym].value = gage_color;
	    nsym++;
	}
    }

    /* set symbol for AC-plug */
    if(acline != APM_STAT_LINE_ON) {
	sym[nsym].name = (char*)malloc(5);
	strcpy(sym[nsym].name, "plug");
	sym[nsym].value = "None";
	nsym++;
    }

    /* set symbol for AC-plug */
    if(charge != APM_STAT_BATT_CHARGING) {
	sym[nsym].name = (char*)malloc(6);
	strcpy(sym[nsym].name, "spark");
	sym[nsym].value = "None";
	nsym++;
    }

    /* set array to pixmap attribute */
    attr.colorsymbols 	= sym;
    attr.numsymbols 	= nsym;
}

void goOut(
  int i
) {
    Arg arg;

    /* if already running XtTimer, then remove it */
    if (timer != 0) {
	XtRemoveTimeOut(timer);
    }

    /* resign from the session */
    XtSetArg(arg, XtNjoinSession, False);
    XtSetValues(toplevel, &arg, ONE);

    /* free pixmap resources */
    if (xpmData) {
	XFreePixmap(XtDisplay(toplevel), xpmData);
    }
    if (xpmMask) {
	XFreePixmap(XtDisplay(toplevel), xpmMask);
    }
    XpmFreeAttributes(&attr);

    /* close apm fd */
    close(apmfd);
    XCloseDisplay(XtDisplay(toplevel));
    exit(i);
}

void CallbackTyped(
  Widget	widget,
  char*		tag,
  XEvent*	xe,
  Boolean*	b
) {
    char c;

    /* get key character */
    c = '\0';
    XLookupString(&(xe->xkey), &c, 1, NULL, NULL);

    /* if place `q' key, then just quit */
    if (c == 'q' || c == 'Q') {
	goOut(0);
    }

    /* display current battery status, and set timer callback */
    updateStatus(NULL, NULL);
}

void CallbackEnter(
  Widget	widget,
  char*		tag,
  XEvent*	xe,
  Boolean*	b
) {
    /* set status flag */
    showStatus = 1;

    /* display current battery status, and set timer callback */
    forceRedraw = 1;
    updateStatus(NULL, NULL);

/*    fprintf(stderr, ">>> Enter [%d]\n", s.remain);*/
}

void CallbackLeave(
  Widget	widget,
  char*		tag,
  XEvent*	xe,
  Boolean*	b
) {
    /* reset status flag */
    showStatus = 0;

    /* display current battery status, and set timer callback */
    forceRedraw = 1;
    updateStatus(NULL, NULL);

/*    fprintf(stderr, "<<< Leave\n");*/
}


syntax highlighted by Code2HTML, v. 0.9.1