/*
*  File             : asmem.c 
*  Version          : 0.1-1
*  Written by       : alinden@netcologne.de
*  Date             : Apr 2 1998
*  Compiler         : gcc 2.7.2 on Linux 2.0.33
*  Portability      : libXpm, libX11, /PROC filesystem, should be POSIX compliant
*  Copyright:       : Gnu Public License (GPL)
*
*  DESCRIPTION: Memory/cache/swap display, can be swallowed by an icon bar.
*
*  Updates:
*  		albert@mail.dma.be 18 Oct 1998
*  		Added option -noborder and -version
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <X11/Xlib.h>
#include <X11/xpm.h>
#include "background.xpm"
#include "arrow.xpm"

/* Arrow 1,2,3 Y offsets */
#define ARROWY 10, 26, 41

/* Arrows X offset for value 0 */
#define ARROWX 3

/* Arrows number of X pixels between min and max values */ 
#define ARROWMOVE 41

/* Default background color */
#define BACKCOLOR "#000000" 

/* Default foreground colors */
#define FORECOLOR "#D3D3D3","#00FF00","#FFFF00","#FF0000"

typedef struct
{
	XpmAttributes attributes;
	Pixmap pixmap, shape;
} XPM_IMAGE;

Window Root, xwindow, xicon;
GC xgc;
Display *xdsp;
char *Bexec = NULL;  /* Exec command for button */
pid_t progpid;  /* own pid */
Pixmap pix_draw;
XPM_IMAGE xpm_bg, xpm_arrow[3];

void flush_expose(Window actwindow)   /* remove expose events */
{
	XEvent xevents;
  
	while(XCheckTypedWindowEvent(xdsp, actwindow, Expose, &xevents));
}

void prog_term(void)   /* exit handler */
{
	if(progpid == getpid())
		XCloseDisplay(xdsp);
}

void draw_window(int *apos)
{
	int i;
	static aypos[] = { ARROWY };

	XCopyArea(xdsp, xpm_bg.pixmap, pix_draw, xgc, 0, 0, xpm_bg.attributes.width, xpm_bg.attributes.height, 0, 0);  

	for(i = 0; i < 3; i++)
		XCopyArea(xdsp, xpm_arrow[i].pixmap, pix_draw, xgc, 0, 0, xpm_arrow[i].attributes.width, xpm_arrow[i].attributes.height, apos[i], aypos[i]);
		
	flush_expose(xwindow);
	XCopyArea(xdsp, pix_draw, xwindow, xgc, 0, 0, xpm_bg.attributes.width, xpm_bg.attributes.height, 0, 0);  
	flush_expose(xicon);
	XCopyArea(xdsp, pix_draw, xicon, xgc, 0, 0, xpm_bg.attributes.width, xpm_bg.attributes.height, 0, 0);
	
	XFlush(xdsp);	
}

int *read_mem(FILE *memfile)  /* read memory stat, return arrow positions */
{
	float ptot, pused, pcached, pstot, psused;
	static int apos[3];
	static int aposerr[] = { ARROWX, ARROWX, ARROWX };
	
	rewind(memfile);
	fflush(memfile);

	if(fscanf(memfile, "%*[^\n]%*s %f %f %*u %*u %*u %f\n%*s %f %f ", &ptot, &pused, &pcached, &pstot, &psused) < 5)
		return(aposerr);

	apos[0] = (int)((pused/ptot)*ARROWMOVE)+ARROWX;     /* Memory */
	apos[1] = (int)((pcached/ptot)*ARROWMOVE)+ARROWX;   /* Cache  */
	apos[2] = (int)((psused/pstot)*ARROWMOVE)+ARROWX;   /* Swap   */
	
	apos[0] -= apos[1];
	
	return(apos);
}

void event_loop(void)   /* main loop */
{
	FILE *memfile;
	XEvent xevents;
	int *apos, apos1[3];

	if((memfile = fopen("/proc/meminfo", "r")) == NULL)
	{
		perror("/proc/meminfo");
		exit(EXIT_FAILURE);
	}	

	draw_window((apos = read_mem(memfile)));

	while(1)
	{
		apos1[0] = apos[0];
		apos1[1] = apos[1];
		apos1[2] = apos[2];

		sleep(1);

		while(XPending(xdsp))
		{
			XNextEvent(xdsp, &xevents);
			switch(xevents.type)
			{
				case Expose:
					if(xevents.xexpose.count == 0)
						draw_window(apos);
					break;
				case ButtonPress:
					if(Bexec != NULL && fork() == 0)
					{
						system(Bexec);
						exit(0);
					}
					break;	
				case DestroyNotify:
					fclose(memfile);
					return;
			}
		}

		waitpid(-1, NULL, WNOHANG);

		apos = read_mem(memfile);
		if(apos[0] != apos1[0] || apos[1] != apos1[1] || apos[2] != apos1[2])   /* values changed */
			draw_window(apos);
	}		
}

int main (int argc, char *argv[])
{
        int xscreen;
        unsigned long xforeground, xbackground;
        XEvent xevent;
        XSizeHints actsizehints;
        int i,grav;
        XGCValues gcv;
        char *geometry = NULL;
        unsigned int borderwith = 1;
        XClassHint classHint;
        XWMHints actwmhints;
        char *wname = "Mem";
        XTextProperty name;
        XpmColorSymbol xpm_colors[2];
        char *bgcolor = BACKCOLOR; 
        char *coltbl[] = { FORECOLOR }; 
        char *msghelp = "Usage: asmem [-option ...]\n\
Options:\n\
    -h                            Display this help page\n\
    -v                            Show the version number\n\
    -e         <command>          Command to execute on click\n\
    -i                            Start in icon mode\n\
    -w                            Withdrawn mode\n\
    -noborder                     No border around\n\
    -p         [+|-]<x>[+|-]<y>   Window position\n\
    -bg        <color>            Background color\n\
    -fg        <color>            Foreground color\n\
    -a{1|2|3}  <color>            Arrow colors\n";
        char msgcolor[] = "Cannot allocate colors\n";
        char *msgversion = "asmem: AfterStep memory monitor version 0.1-1\n";

	actwmhints.initial_state = NormalState;
	
        /* process command line */

        for(i = 1; i < argc; i++)
        {
        	if(*argv[i] != '-')
        		continue;

        	switch(*(argv[i]+1))
        	{
        		case 'e':
        			if(++i < argc)
        				Bexec = argv[i];
        			break;
        		case 'i':
        			actwmhints.initial_state = IconicState;
        			break;
        		case 'w':
        			actwmhints.initial_state = WithdrawnState;
        			break;
        		case 'n':
        			borderwith = 0;
        			break;
        		case 'p':
        			geometry = argv[++i];
        			break;	
        		case 'b':
        			bgcolor = argv[++i];
        			break;
        		case 'f':
        			coltbl[0] = argv[++i];
        			break;
        		case 'a':
        			switch(*(argv[i++]+2))
        			{
        				case '1':
        					coltbl[1] = argv[i];
        					break;
        				case '2':
        					coltbl[2] = argv[i];
        					break;
        				case '3':
        					coltbl[3] = argv[i];
        			}
        			break;
			case 'v':
				fputs(msgversion, stdout);
				exit(0);
        		default:
        			fputs(msghelp, stderr);
        			exit(0);
        	}			
        						
        }
        
        /* connect to the X server */
        
        if((xdsp = XOpenDisplay(NULL)) == NULL)
        {
                fprintf (stderr, "Cannot open display %s\n", XDisplayName(NULL));
                exit (EXIT_FAILURE);
        }

	progpid = getpid();
        atexit((void *)prog_term);  /* exit handler */

        xscreen = DefaultScreen(xdsp);   /* get default screen */
        Root = RootWindow(xdsp, xscreen);

        xbackground = BlackPixel(xdsp, xscreen);
        xforeground = WhitePixel(xdsp, xscreen);

	/* Create Images */

	xpm_bg.attributes.valuemask = XpmReturnPixels | XpmColorSymbols;
	xpm_bg.attributes.colorsymbols = xpm_colors;
	xpm_bg.attributes.numsymbols = 2;

	xpm_colors[0].name = "bg";
	xpm_colors[1].name = "fg";
	xpm_colors[0].value = bgcolor;
	xpm_colors[1].value = coltbl[0];

	if(XpmCreatePixmapFromData(xdsp, Root, background_xpm, &xpm_bg.pixmap, &xpm_bg.shape, &xpm_bg.attributes) != XpmSuccess)
	{
		fprintf(stderr, msgcolor);
		exit(EXIT_FAILURE);
	} 
	
	pix_draw = XCreatePixmap(xdsp, Root, xpm_bg.attributes.width, xpm_bg.attributes.height, DefaultDepth(xdsp, xscreen));

	for(i = 0; i < 3; i++)
	{
		xpm_arrow[i].attributes.valuemask = XpmReturnPixels | XpmColorSymbols;
		xpm_arrow[i].attributes.colorsymbols = xpm_colors;
		xpm_arrow[i].attributes.numsymbols = 2;
		
		xpm_colors[1].value = coltbl[i+1];  

	   	if(XpmCreatePixmapFromData(xdsp, Root, arrow_xpm, &xpm_arrow[i].pixmap, &xpm_arrow[i].shape, &xpm_arrow[i].attributes) != XpmSuccess)
	   	{
			fprintf(stderr, msgcolor);
			exit(EXIT_FAILURE);
		}
	}

	/* Window settings */

        ( geometry == NULL ) ? 
        	( actsizehints.flags = USSize | PPosition ) :
        	( actsizehints.flags = USSize | USPosition );
	actsizehints.x = actsizehints.y = 0;
	
        XWMGeometry(xdsp, xscreen, geometry, NULL, borderwith, &actsizehints, &actsizehints.x, 
        	    &actsizehints.y, &actsizehints.width, &actsizehints.height, &grav);

	actsizehints.width = xpm_bg.attributes.width;
	actsizehints.height = xpm_bg.attributes.height;

        if((xwindow = XCreateSimpleWindow(xdsp, Root, actsizehints.x, actsizehints.y, actsizehints.width, actsizehints.height, borderwith, xforeground, xbackground)) == 0 ||
           (xicon = XCreateSimpleWindow(xdsp, xwindow, actsizehints.x, actsizehints.y, actsizehints.width, actsizehints.height, borderwith, xforeground, xbackground)) == 0)
	{
        	fprintf(stderr, "Cannot open window\n");
        	exit (EXIT_FAILURE);
        }

	XSetWMNormalHints(xdsp, xwindow, &actsizehints);
	classHint.res_name  = classHint.res_class = "asmem";
	XSetClassHint(xdsp, xwindow, &classHint);

	if(XStringListToTextProperty(&wname, 1, &name))
		XSetWMName(xdsp, xwindow, &name);

	/* Create GC */

	gcv.graphics_exposures = 0;
	gcv.foreground = xforeground;
	gcv.background = xbackground;
	xgc= XCreateGC(xdsp, Root, GCForeground | GCBackground | GCGraphicsExposures ,&gcv);

	actwmhints.icon_window = xicon;
	actwmhints.icon_x = actsizehints.x;
	actwmhints.icon_y = actsizehints.y;
	actwmhints.window_group = xwindow;
	actwmhints.flags = StateHint | IconWindowHint | WindowGroupHint;

	XSetWMHints(xdsp, xwindow, &actwmhints);
	
	XMapWindow(xdsp, xwindow); /* pop up window */
        
        XSelectInput(xdsp, xwindow, ExposureMask | ButtonPressMask | StructureNotifyMask);
        XSelectInput(xdsp, xicon, ExposureMask | ButtonPressMask | StructureNotifyMask);
        
        XSetCommand(xdsp, xwindow, argv, argc);

        XMaskEvent(xdsp, ExposureMask, &xevent); /* wait for window */ 
	
	event_loop();
	
	exit(0);
}
