/* wmload - system load monitor designed for Window Maker
*  Copyright (C) 1996 Beat Christen <bchriste@iiic.ethz.ch>
*  Copyright (C) 1997 Ryan Land <rland@bc1.com>
*  Copyright (C) 2015 Window Maker Developers Team
*                     <wmaker-dev@lists.windowmaker.org>
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License along
*  with this program; if not, write to the Free Software Foundation, Inc.,
*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

#define _POSIX_C_SOURCE 200112L

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <sys/wait.h>

#include <X11/Xlib.h>
#include <X11/xpm.h>
#include <X11/extensions/shape.h>
#include <time.h>
#include <math.h>
#include <fcntl.h>
#include <X11/Xatom.h>

#include "back.xpm"
#include "mask2.xbm"
#include "mask.xpm"

#define major_VER 0
#define minor_VER 9
#define patch_VER 7
#define MW_EVENTS   (ExposureMask | ButtonPressMask | StructureNotifyMask)
#define FALSE 0
#define Shape(num) (ONLYSHAPE ? num-5 : num)
#define NCPUSTATES 4

/* Global Data storage/structures ********************************************/
static long cp_time[NCPUSTATES];
static long last[NCPUSTATES];
int ONLYSHAPE=0; /* default value is noshape */
int updatespeed = 4;
static char *help_message[] = {
"where options include:",
"    -u <secs>               updatespeed",
"    -exe <program>          program to start on click",
"    -led <color>            color of the led",
"    -position [+|-]x[+|-]y  position of wmload",
"    -shape                  without groundplate",
"    -iconic                 start up as icon",
"    -withdrawn              start up withdrawn",
"    -ver                    output version",
NULL
};

/* X11 Variables *************************************************************/
Display *dpy;	  /* welches DISPLAY */
Window Root;      /* Hintergrund-Drawable */
int screen;
int x_fd;
int d_depth;
XSizeHints mysizehints;
XWMHints mywmhints;
Pixel back_pix, fore_pix;
GC NormalGC;
Window iconwin, win;       /* My home is my window */
char *ProgName;
char *Geometry;
char *LedColor = "LightSeaGreen";
char Execute[] = "echo no program has been specified";
char *ERR_colorcells = "not enough free color cells\n";
char *ampers = " &";

/* XPM Structures & Variables ************************************************/
typedef struct _XpmIcon {
    Pixmap pixmap;
    Pixmap mask;
    XpmAttributes attributes;
}        XpmIcon;

XpmIcon wmload;
XpmIcon visible;
time_t actualtime;
long actualmin;

/* Function definitions ******************************************************/
void GetXPM(void);
Pixel GetColor(char *name);
void RedrawWindow( XpmIcon *v);
void InitLoad();
void InsertLoad();

/*****************************************************************************/
/* Source Code <--> Function Implementations                                 */
/*****************************************************************************/
void usage()
{
  char **cpp;

  fprintf(stderr,"\nusage:  %s [-options ...] \n", ProgName);
  for (cpp = help_message; *cpp; cpp++) {
    fprintf(stderr, "%s\n", *cpp);
  }
  fprintf(stderr,"\n");
  exit(1);
}

/*
 * Copied from ascpu - albert@tigr.net - 09 Mar 2000
 *
 * This function executes an external command while
 * checking whether we should drop the privileges.
 *
 * Since we might need privileges later we fork and
 * then drop privileges in one of the instances which
 * will then execute the command and die.
 *
 * This fixes the security hole for FreeBSD and AIX
 * where this program needs privileges to access
 * the system information.
 */
void ExecuteExternal()
{
	uid_t ruid, euid;
	int pid;
#ifdef DEBUG
	printf("asload: system(%s)\n",Execute);
#endif
	if( Execute[0] == '\0' ) {
		return;
	}
	ruid = getuid();
	euid = geteuid();
	if ( ruid == euid ) {
		if (system( Execute ) == -1)
			fprintf(stderr, "system(%s) returned an error",
				Execute);
		return;
	}
	pid = fork();
	if ( pid == -1 ) {
		printf("asload : fork() failed (%s), command not executed",
				strerror(errno));
		return;
	}
	if ( pid != 0 ) {
		/* parent process simply waits for the child and continues */
		if ( waitpid(pid, 0, 0) == -1 ) {
			printf("asload : waitpid() for child failed (%s)",
				strerror(errno));
		}
		return;
	}
	/*
	 * child process drops the privileges
	 * executes the command and dies
	 */
	if ( setuid(ruid) ) {
		printf("asload : setuid failed (%s), command not executed",
				strerror(errno));
		exit(127);
	}
	if (system( Execute ) == -1)
		fprintf(stderr, "system(%s) returned an error", Execute);
	exit(0);
}
int main(int argc,char *argv[])
{
  int i;
  unsigned int borderwidth ;
  char *display_name = NULL;
  char *wname = "wmload";
  XGCValues gcv;
  unsigned long gcm;
  XEvent Event;
  XTextProperty name;
  XClassHint classHint;
  Pixmap pixmask;
  Atom _XA_WM_DELETE_WINDOW 	= None;
  Geometry = "";
  mywmhints.initial_state = NormalState;

  /* Parse command line options */
  ProgName = argv[0];

  for(i=1;i<argc;i++) {
    char *arg= argv[i];

    if (arg[0] == '-') {
      switch(arg[1]) {
      case 'u':
	if(++i >=argc) usage();
	sscanf(argv[i], "%d", &updatespeed);
	continue;
      case 'e':
	if(++i >=argc) usage();
	strcpy(&Execute[0], argv[i]);
	strcat(&Execute[0], " &");
	continue;
      case 's':
	ONLYSHAPE=1;
	continue;
      case 'p':
	if(++i >=argc) usage();
	Geometry = argv[i];
	continue;
      case 'i':
	mywmhints.initial_state = IconicState;
	continue;
      case 'w':
	mywmhints.initial_state = WithdrawnState;
	continue;
      case 'l':
	if(++i >=argc) usage();
	LedColor = argv[i];
	continue;
      case 'v':
	fprintf(stdout, "\nwmload version: %i.%i.%i\n", major_VER, minor_VER, patch_VER);
	if(argc == 2) exit(0);
	continue;
      default:
	usage();
      }
    }
    else
      {
        fprintf(stderr, "\nInvalid argument: %s\n", arg);
        usage();
      }
  }

  /* Open the display */
  if (!(dpy = XOpenDisplay(display_name)))
    {
      fprintf(stderr,"wmload: can't open display %s\n",
	      XDisplayName(display_name));
      exit (1);
    }

  screen= DefaultScreen(dpy);
  Root = RootWindow(dpy, screen);
  d_depth = DefaultDepth(dpy, screen);
  x_fd = XConnectionNumber(dpy);
  _XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);

  /* Convert XPM Data to XImage */
  GetXPM();

  /* Create a window to hold the banner */
  mysizehints.flags= USSize|USPosition;
  mysizehints.x = 0;
  mysizehints.y = 0;

  back_pix = GetColor("white");
  fore_pix = GetColor("black");

  XWMGeometry(dpy, screen, Geometry, NULL, (borderwidth =1), &mysizehints,
	      &mysizehints.x,&mysizehints.y,&mysizehints.width,&mysizehints.height, &i);

  mysizehints.width = wmload.attributes.width;
  mysizehints.height= wmload.attributes.height;

  win = XCreateSimpleWindow(dpy,Root,mysizehints.x,mysizehints.y,
			    mysizehints.width,mysizehints.height,
			    borderwidth,fore_pix,back_pix);
  iconwin = XCreateSimpleWindow(dpy,win,mysizehints.x,mysizehints.y,
				mysizehints.width,mysizehints.height,
				borderwidth,fore_pix,back_pix);

  /* activate hints */
  XSetWMNormalHints(dpy, win, &mysizehints);
  classHint.res_name =  "wmload";
  classHint.res_class = "WMLoad";
  XSetClassHint(dpy, win, &classHint);

  XSelectInput(dpy,win,MW_EVENTS);
  XSelectInput(dpy,iconwin,MW_EVENTS);
  XSetCommand(dpy,win,argv,argc);

  if (XStringListToTextProperty(&wname, 1, &name) ==0) {
    fprintf(stderr, "wmload: can't allocate window name\n");
    exit(-1);
  }
  XSetWMName(dpy, win, &name);

  /* Create a GC for drawing */
  gcm = GCForeground|GCBackground|GCGraphicsExposures;
  gcv.foreground = fore_pix;
  gcv.background = back_pix;
  gcv.graphics_exposures = FALSE;
  NormalGC = XCreateGC(dpy, Root, gcm, &gcv);

  if (ONLYSHAPE) { /* try to make shaped window here */
    pixmask = XCreateBitmapFromData(dpy, win, (char *)mask2_bits, mask2_width,
				    mask2_height);
    XShapeCombineMask(dpy, win, ShapeBounding, 0, 0, pixmask, ShapeSet);
    XShapeCombineMask(dpy, iconwin, ShapeBounding, 0, 0, pixmask, ShapeSet);
  }

  mywmhints.icon_window = iconwin;
  mywmhints.icon_x = mysizehints.x;
  mywmhints.icon_y = mysizehints.y;
  mywmhints.window_group = win;
  mywmhints.flags = StateHint | IconWindowHint | IconPositionHint
      | WindowGroupHint;
  XSetWMHints(dpy, win, &mywmhints);
  XSetWMProtocols (dpy, win, &_XA_WM_DELETE_WINDOW, 1);

  XMapWindow(dpy,win);
  InitLoad();
  InsertLoad();
  RedrawWindow(&visible);
  while(1)
    {
      if (actualtime != time(0))
	{
	  actualtime = time(0);

	  if(actualtime % updatespeed == 0)
	    InsertLoad();

	  RedrawWindow(&visible);
	}

      /* read a packet */
      while (XPending(dpy))
	{
	  XNextEvent(dpy,&Event);
	  switch(Event.type)
	    {
	    case Expose:
	      if(Event.xexpose.count == 0 )
		RedrawWindow(&visible);
	      break;
	    case ButtonPress:
	      ExecuteExternal();
	      break;
	    case ClientMessage:
    	      if ((Event.xclient.format != 32) ||
		  ((Atom)Event.xclient.data.l[0] != _XA_WM_DELETE_WINDOW))
		break;
	    case DestroyNotify:
	      XFreeGC(dpy, NormalGC);
	      XDestroyWindow(dpy, iconwin);
              XDestroyWindow(dpy, win);
              XCloseDisplay(dpy);
	      exit(0);
	      break ;
	    default:
	      break;
	    }
	}
      XFlush(dpy);
#ifdef SYSV
      poll((struct poll *) 0, (size_t) 0, 50);
#else
      {
        struct timespec ts;

        ts.tv_sec = 0;
        ts.tv_nsec = 50000000L;        /* 5/100 sec */
        nanosleep(&ts, NULL);
      }
#endif
    }
  return 0;
}

/*****************************************************************************/
void nocolor(char *a, char *b)
{
 fprintf(stderr,"wmload: can't %s %s\n", a,b);
}

/*****************************************************************************/
/* convert the XPMIcons to XImage */
void GetXPM(void)
{
  static char **alt_xpm;
  XColor col;
  XWindowAttributes attributes;
  int ret;
  char tempc1[12],tempc2[12],tempc3[12];
  float colr,colg,colb;

  alt_xpm =ONLYSHAPE ? mask_xpm : back_xpm;

  /* for the colormap */
  XGetWindowAttributes(dpy,Root,&attributes);

  /* get user-defined color or validate the default */
  if (!XParseColor (dpy, attributes.colormap, LedColor, &col))
    {
      nocolor("parse",LedColor);
    }
  else
    {
      /* scale down the Xcolor values */
      colr = col.red   / 257;
      colg = col.green / 257;
      colb = col.blue  / 257;
      /* the brightest color */
      sprintf(tempc1, "S c #%.2x%.2x%.2x", (int)colr, (int)colg, (int)colb);
      back_xpm[47] = tempc1;

      /* make medium color */
      colr = (colr /100) *89;
      colg = (colg /100) *89;
      colb = (colb /100) *89;
      sprintf(tempc2, "R c #%.2x%.2x%.2x", (int)colr, (int)colg, (int)colb);
      back_xpm[46] = tempc2;

      /* make darkest color */
      colr = (colr /100) *89;
      colg = (colg /100) *89;
      colb = (colb /100) *89;
      sprintf(tempc3, "Q c #%.2x%.2x%.2x", (int)colr, (int)colg, (int)colb);
      back_xpm[45] = tempc3;
    }

  wmload.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions);
  ret = XpmCreatePixmapFromData(dpy, Root, alt_xpm, &wmload.pixmap,
				&wmload.mask, &wmload.attributes);
  if(ret != XpmSuccess)
    {fprintf(stderr, "%s\n", ERR_colorcells);exit(1);}

  visible.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions);
  ret = XpmCreatePixmapFromData(dpy, Root, back_xpm, &visible.pixmap,
				&visible.mask, &visible.attributes);
  if(ret != XpmSuccess)
    {fprintf(stderr, "%s\n", ERR_colorcells);exit(1);}

}

/*****************************************************************************/
/* Removes expose events for a specific window from the queue */
int flush_expose (Window w)
{
  XEvent dummy;
  int i=0;

  while (XCheckTypedWindowEvent (dpy, w, Expose, &dummy))i++;
  return i;
}

/*****************************************************************************/
/* Draws the icon window */
void RedrawWindow( XpmIcon *v)
{
  flush_expose (iconwin);
  XCopyArea(dpy,v->pixmap,iconwin,NormalGC,
	    0,0,v->attributes.width, v->attributes.height,0,0);
  flush_expose (win);
  XCopyArea(dpy,v->pixmap,win,NormalGC,
	    0,0,v->attributes.width, v->attributes.height,0,0);

}

/*****************************************************************************/
Pixel GetColor(char *name)
{
  XColor color;
  XWindowAttributes attributes;

  XGetWindowAttributes(dpy,Root,&attributes);
  color.pixel = 0;
   if (!XParseColor (dpy, attributes.colormap, name, &color))
     {
       nocolor("parse",name);
     }
   else if(!XAllocColor (dpy, attributes.colormap, &color))
     {
       nocolor("alloc",name);
     }
  return color.pixel;
}

/*****************************************************************************/
void InitLoad()
{
  /* Save the 4 base colors in wmload */
  XCopyArea(dpy, visible.pixmap, wmload.pixmap, NormalGC,
            6,6,3,52, Shape(6), Shape(6));

  /* Copy the base panel to visible */
  XCopyArea(dpy, wmload.pixmap, visible.pixmap, NormalGC,
	    0,0,mysizehints.width, mysizehints.height, 0 ,0);

  /* Remove the 4 base colors from visible */
  XCopyArea(dpy, visible.pixmap, visible.pixmap, NormalGC,
	    Shape(9),Shape(6),3,52, Shape(6), Shape(6));
}

static char *
skip_token(const char *p)
{
    while (isspace(*p)) p++;
    while (*p && !isspace(*p)) p++;
    return (char *)p;
}

void GetLoad(int Maximum, int *usr, int *nice, int *sys, int *free)
{
  char buffer[100];/*[4096+1];*/
  int fd, len;
  int total;
  char *p;

  fd = open("/proc/stat", O_RDONLY);
  len = read(fd, buffer, sizeof(buffer)-1);
  close(fd);
  buffer[len] = '\0';

  p = skip_token(buffer);		/* "cpu" */

  cp_time[0] = strtoul(p, &p, 0);	/* user   */
  cp_time[1] = strtoul(p, &p, 0);	/* nice   */
  cp_time[2] = strtoul(p, &p, 0);	/* system */
  cp_time[3] = strtoul(p, &p, 0);	/* idle   */

  if( (*usr  = cp_time[0] - last[0]) < 0 ) *usr = 0 ;
  if( (*nice = cp_time[1] - last[1]) < 0 ) *nice = 0 ;
  if( (*sys  = cp_time[2] - last[2]) < 0 ) *sys = 0 ;
  if( (*free = cp_time[3] - last[3]) < 0 ) *free = 0 ;

  total = *usr + *nice + *sys + *free;

  last[0] = cp_time[0];
  last[1] = cp_time[1];
  last[2] = cp_time[2];
  last[3] = cp_time[3];

  *usr = rint(Maximum * (float)(*usr)   /total);
  *nice =rint(Maximum * (float)(*nice)  /total);
  *sys = rint(Maximum * (float)(*sys)   /total);
  *free = rint(Maximum * (float)(*free) /total);
}

void InsertLoad()
{
  int UserTime, NiceTime, SystemTime, FreeTime, act, constrain;
  GetLoad( 52, &UserTime, &NiceTime, &SystemTime, &FreeTime);

  constrain = (UserTime + NiceTime + SystemTime + FreeTime);
  if(constrain == 53)
    {
      if(FreeTime > 0) FreeTime--;
      else if(SystemTime > 0) SystemTime--;
      else if(NiceTime > 0) NiceTime--;
      else if(UserTime > 0) UserTime--;
    }
  else if(constrain == 51) FreeTime++;

  /* Move the area */
    XCopyArea(dpy, visible.pixmap, visible.pixmap, NormalGC,
        	Shape(7), Shape(6), 51, 52, Shape(6), Shape(6));


    /* User Time */
    act = 58 - UserTime;
    if(UserTime > 0)
      XCopyArea(dpy, wmload.pixmap, visible.pixmap, NormalGC,
		Shape(6), Shape(6), 1, UserTime, Shape(57), Shape(act));

    /* Nice Time */
    act = act - NiceTime;
    if(NiceTime > 0)
      XCopyArea(dpy, wmload.pixmap, visible.pixmap, NormalGC,
		Shape(7), Shape(6), 1, NiceTime, Shape(57), Shape(act));

    /* System Time */
    act = act - SystemTime;
    if(SystemTime > 0)
      XCopyArea(dpy, wmload.pixmap, visible.pixmap, NormalGC,
		Shape(8), Shape(6), 1, SystemTime, Shape(57), Shape(act));

    /* Free Time */
    if(FreeTime > 0)
      XCopyArea(dpy, wmload.pixmap, visible.pixmap, NormalGC,
		Shape(9), Shape(6), 1, FreeTime, Shape(57), Shape(6));
}
