/*
	Code based on wmppp/wmifs

	[Orig WMPPP comments]

	This code was mainly put together by looking at the
	following programs:

	asclock
		A neat piece of equip, used to display the date
		and time on the screen.
		Comes with every AfterStep installation.

		Source used:
			How do I create a not so solid window?
			How do I open a window?
			How do I use pixmaps?

	------------------------------------------------------------

	Authors: Martijn Pieterse (pieterse@xs4all.nl)
		 Antoine Nulle (warp@xs4all.nl)

	This program is distributed under the GPL license.
	(as were asclock and pppstats)

	----
	Changes:
	----

	17/03/2014 (Pedro Gimeno Fortea)
		* Fix jiffy counter overflowing long on 32-bit systems.
	17/06/2012 (Rodolfo García Peñas (kix), <kix@kix.es>)
		* Code style.
	13/3/2012 (Barry Kelly (wbk), <coydog@devio.us>)
		* Fixed get_statistics() I/O features to work with newer
		  /proc/diskstats instead of the old /proc/stat.
		* Fixes to graph/meter scaling for I/O. Original code let
		  the scaling grow out of control due to inappropriate static
		  data.
		* Eliminated rounding down relatively low stats in getWidth()
		  and DrawStats_io() by using double and float types instead
		  of ints. We now round up tiny values to prevent the system
		  appearing idle when it's not.
	    * Style/readbility edits.
		* TODO: Merge in Gentoo and possibly BSD local patches. This
		  should aid in fixing I/O monitoring on non-Linux systems.
		* TODO: Color swapping. User supplies color values in .rc, and
		  app modifies pixmap in memory on startup. Should be simple.
	    * TODO: address compiler warnings (GCC has gotten pickier over
		  the years).
	17/10/2009 (Romuald Delavergne, romuald.delavergne@free.fr)
		* Support SMP processors in realtime CPU stress meter
	15/05/2004 (Simon Law, sfllaw@debian.org)
		* Support disabling of mode-cycling
	23/10/2003 (Simon Law, sfllaw@debian.org)
		* Eliminated exploitable static buffers
		* Added -geometry support.
		* /proc/meminfo support for Linux 2.6
	18/05/1998 (Antoine Nulle, warp@xs4all.nl)
		* MEM/SWAP/UPTIME only updated when visible
		* Using global file descriptors to reduce file
		  system overhead, both updates are based on a diff
		  supplied by Dave Harden (dharden@wisewire.com)
	15/05/1998 (Antoine Nulle, warp@xs4all.nl)
		* Fixed memory overflow in the MEM gaugebar
		* MEM gauge displays now real used mem
		  (buffered + cached mem removed)
	14/05/1998 (Antoine Nulle, warp@xs4all.nl)
		* Added -i & -s kludge for selecting startupmode,
		  tijno, don't hate me for this :)
	12/05/1998 (Antoine Nulle, warp@xs4all.nl)
		* Finetuned master-xpm, tijno don't worry, no
		  reprogramming needed this time ;-)
	07/05/1998 (Martijn Pieterse, pieterse@xs4all.nl)
		* Added disk i/o
	03/05/1998 (Antoine Nulle, warp@xs4all.nl)
		* Added new master-xpm which contains the gfx
		  for the upcoming SysInfo part :P
	02/05/1998 (Martijn Pieterse, pieterse@xs4all.nl)
		* Removed a lot of code, that was put in wmgeneral
	23/04/1998 (Martijn Pieterse, pieterse@xs4all.nl)
		* Added zombie destroying code (aka wait :) )
	18/04/1998 (Martijn Pieterse, pieterse@xs4all.nl)
		* Added CPU-on-screen.
		* Added -display command line
	15/04/1998 (Martijn Pieterse, pieterse@xs4all.nl)
		* Fixed a bug in the stats routine
		  (Top 3 bright pixels were not shown when 100% loaded)
		* Changed xpm to a no-title one.
		  This included the reprogramming of all positions.
		  warpstah, i hate you! ;)
	05/04/1998 (Martijn Pieterse, pieterse@xs4all.nl)
		* First Working Version
*/

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

#include <sys/wait.h>

#include <X11/X.h>
#include <X11/Xlib.h>

#include <libdockapp/wmgeneral.h>
#include <libdockapp/misc.h>

#include "ulllib.h"
#include "wmmon-master.xpm"
#include "wmmon-mask.xbm"

  /***********/
 /* Defines */
/***********/
#define HISTORY_ENTRIES 55
#define MAX_CPU (10) /* depends on graph height */
#define MAX_STAT_DEVICES (4)

  /********************/
 /* Global Variables */
/********************/
int stat_current = 0; /* now global */
int mode_cycling = 1; /* Allow mode-cycling */
int cpu_avg_max  = 0; /* CPU stress meter with average and max for SMP */
int show_buffers = 0; /* wbk adding per Gentoo -b enhancement. */

FILE *fp_meminfo;
FILE *fp_stat;
FILE *fp_loadavg;
FILE *fp_diskstats;    /* wbk new io stats API */

/* functions */
void usage(char*);
void printversion(void);
void DrawStats(int *, int, int, int, int);
void DrawStats_io(int *, int, int, int, int);
void wmmon_routine(int, char **);

int main(int argc, char *argv[])
{
	int i;
	char *name = argv[0];

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

		if (*arg=='-')
			switch (arg[1]) {
			case 'd' :
				if (strcmp(arg+1, "display")) {
					usage(name);
					return 1;
				}
				break;
			case 'g' :
				if (strcmp(arg+1, "geometry")) {
					usage(name);
					return 1;
				}
			case 'l' :
				mode_cycling = 0;
				break;
			case 'c' :
				cpu_avg_max = 1;
				break;
			case 'i' :
				stat_current = 1;
				break;
			case 'b' :
				show_buffers = 1;
				break;
			case 's' :
				stat_current = 2;
				break;
			case 'v' :
				printversion();
				return 0;
			default:
				usage(name);
				return 1;
			}
	}

	wmmon_routine(argc, argv);
	exit(0);
}

/*******************************************************************************\
|* wmmon_routine								*|
\*******************************************************************************/

typedef struct {
	char name[5];	/* "cpu0..cpuz", eventually.. :) */
	int his[HISTORY_ENTRIES];
	int hisaddcnt;
	long rt_stat;
	ullong statlast;
	long rt_idle;
	ullong idlelast;
	/* Processors stats */
	long *cpu_stat;
	ullong *cpu_last;
	long *idle_stat;
	ullong *idle_last;
} stat_dev;

stat_dev stat_device[MAX_STAT_DEVICES];

char *left_action, *right_action, *middle_action;
int nb_cpu, cpu_max;

int getNbCPU(void);
unsigned long getWidth(long, long);
int checksysdevs(void);
void get_statistics(char *, long *, ullong *, ullong *, ullong *, ullong *);
void DrawActive(char *);

void update_stat_cpu(stat_dev *, ullong *, ullong *);
void update_stat_io(stat_dev *);
void update_stat_mem(stat_dev *st, stat_dev *st2);
void update_stat_swp(stat_dev *);

void wmmon_routine(int argc, char **argv)
{
	rckeys wmmon_keys[] = {
		{ "left", &left_action },
		{ "right", &right_action },
		{ "middle", &middle_action },
		{ NULL, NULL }
	};

	int i, j;
	long k;
	XEvent Event;
	int but_stat = -1;

	int stat_online;

	long starttime, curtime, nexttime;
	ullong istat, idle, *istat2, *idle2;

	FILE *fp;
	char *conffile = NULL;

	int xpm_X = 0, xpm_Y = 0;

	long online_time = 0;
	long ref_time = 0;
	long cnt_time;


	fp = fopen("/proc/uptime", "r");
	fp_meminfo = fopen("/proc/meminfo", "r");
	fp_loadavg = fopen("/proc/loadavg", "r");
	fp_stat = fopen("/proc/stat", "r");
	fp_diskstats = fopen("/proc/diskstats", "r");

	if (fp) {
		if (fscanf(fp, "%ld", &online_time) == EOF)
			perror("Error! fscanf() of /proc/uptime failed!\n");
		ref_time = time(0);
		fclose(fp);
	}

	for (i = 0; i < MAX_STAT_DEVICES; i++) {
		for (j = 0; j < HISTORY_ENTRIES; j++)
			stat_device[i].his[j] = 0;

		stat_device[i].hisaddcnt = 0;
	}

	/* wbk - I don't fully understand this. Probably just a means of providing
	 * test cases. ifdef'ing to clear compiler warnings. TODO: remove.		*/
#ifdef LEFT_ACTION
	if (LEFT_ACTION)
	  left_action = strdup(LEFT_ACTION);
#endif
#ifdef RIGHT_ACTION
	if (RIGHT_ACTION)
		right_action = strdup(RIGHT_ACTION);
#endif
#ifdef MIDDLE_ACTION
	if (MIDDLE_ACTION)
		middle_action = strdup(MIDDLE_ACTION);
#endif

	/* Scan through the .rc files */
	if (asprintf(&conffile, "/etc/wmmonrc") >= 0) {
		parse_rcfile(conffile, wmmon_keys);
		free(conffile);
	}

	if (asprintf(&conffile, "%s/.wmmonrc", getenv("HOME")) >= 0) {
		parse_rcfile(conffile, wmmon_keys);
		free(conffile);
	}

	if (asprintf(&conffile, "/etc/wmmonrc.fixed") >= 0) {
		parse_rcfile(conffile, wmmon_keys);
		free(conffile);
	}

	stat_online = checksysdevs();

	nb_cpu = getNbCPU();
	stat_device[0].cpu_stat = calloc(nb_cpu, sizeof(long));
	stat_device[0].cpu_last = calloc(nb_cpu, sizeof(ullong));
	stat_device[0].idle_stat = calloc(nb_cpu, sizeof(long));
	stat_device[0].idle_last = calloc(nb_cpu, sizeof(ullong));
	if (!stat_device[0].cpu_stat ||
	    !stat_device[0].cpu_last ||
	    !stat_device[0].idle_stat ||
	    !stat_device[0].idle_last) {
		fprintf(stderr, "%s: Unable to alloc memory !\n", argv[0]);
		exit(1);
	}

	istat2 = calloc(nb_cpu, sizeof(ullong));
	idle2 = calloc(nb_cpu, sizeof(ullong));
	if (!istat2 || !idle2) {
		fprintf(stderr, "%s: Unable to alloc memory !!\n", argv[0]);
		exit(1);
	}

	openXwindow(argc, argv, wmmon_master_xpm, (char *)wmmon_mask_bits,
		    wmmon_mask_width, wmmon_mask_height);

	/* add mouse region */
	AddMouseRegion(0, 12, 13, 58, 57);
	AddMouseRegion(1, 5, 5, 24, 14);

	starttime = time(0);
	nexttime = starttime + 10;

	/* Collect information on each panel */
	for (i = 0; i < stat_online; i++) {
		get_statistics(stat_device[i].name, &k, &istat, &idle, istat2, idle2);
		stat_device[i].statlast = istat;
		stat_device[i].idlelast = idle;
		if (i == 0 && nb_cpu > 1) {
			int cpu;
			for (cpu = 0; cpu < nb_cpu; cpu++) {
				stat_device[i].cpu_last[cpu] = istat2[cpu];
				stat_device[i].idle_last[cpu] = idle2[cpu];
			}
		}
	}

	/* Set the mask for the current window */
	switch (stat_current) {
		case 0:
		case 1:
			xpm_X = 0;
			setMaskXY(0, 0);
			break;
		case 2:
			xpm_X = 64;
			setMaskXY(-64, 0);
		default:
			break;
	}

	/* Draw statistics */
	if (stat_current == 0) {
		DrawStats(stat_device[stat_current].his,
                HISTORY_ENTRIES-1, 40, 5, 58);
	} else if (stat_current == 1) {
		DrawStats_io(stat_device[stat_current].his,
                HISTORY_ENTRIES, 40, 5, 58);
	}

	DrawActive(stat_device[stat_current].name);

	while (1) {
		curtime = time(NULL);

		waitpid(0, NULL, WNOHANG);


		update_stat_cpu(&stat_device[0], istat2, idle2);
		update_stat_io(&stat_device[1]);

		if(stat_current == 2)
			update_stat_mem(&stat_device[2], &stat_device[3]);

		if (stat_current < 2) {
			i = stat_current;

			/* Load ding is 45 pixels hoog */
			copyXPMArea(0, 64, 32, 12, 28, 4);

			if (i == 0 && nb_cpu > 1) {
				if (nb_cpu > MAX_CPU || cpu_avg_max) {
					/* show average CPU */
					j = getWidth(stat_device[i].rt_stat, stat_device[i].rt_idle);
					copyXPMArea(32, 64, j, 6, 28, 4);
					/* Show max CPU */
					j = getWidth(stat_device[i].cpu_stat[cpu_max],
									stat_device[i].idle_stat[cpu_max]);
					copyXPMArea(32, 70, j, 6, 28, 10);
				} else {
					int cpu;
					for (cpu = 0; cpu < nb_cpu; cpu++) {
						j = getWidth(stat_device[i].cpu_stat[cpu],
										stat_device[i].idle_stat[cpu]);
						copyXPMArea(32, 65, j,
									  MAX_CPU / nb_cpu, 28,
									  5 + (MAX_CPU / nb_cpu) * cpu);
					}
				}
			} else {
				j = getWidth(stat_device[i].rt_stat, stat_device[i].rt_idle);
				copyXPMArea(32, 64, j, 12, 28, 4);
			}
		} else {
			/* Nu zal ie wel 3 zijn. */

			copyXPMArea(0, 64, 32, 12, 28+64, 4);
			copyXPMArea(0, 64, 32, 12, 28+64, 18);

			j = stat_device[2].rt_idle;
			if (j != 0) {
				j = (stat_device[2].rt_stat * 100) / j;
			}
			j = j * 0.32;
			if (j > 32) j = 32;
			copyXPMArea(32, 64, j, 12, 28+64, 4);

			/*--------------------- swap?     ------------------*/
			j = stat_device[3].rt_idle;
			if (j != 0)
				j = (stat_device[3].rt_stat * 100) / j;

			j = j * 0.32;
			if (j > 32) j = 32;
			copyXPMArea(32, 64, j, 12, 28+64, 18);

			/*----------- online tijd neerzetten! ----------*/
			cnt_time = time(0) - ref_time + online_time;

			/* cnt_time = uptime in seconden */
			/*
				secs = 108,47
				mins = 89,47
				uren = 70,47
				digits = 40,78, 6breed, 9hoog
			*/
			i = cnt_time % 60;
			cnt_time /= 60;
			copyXPMArea(40 + (i % 10)*7, 78, 6, 9, 115, 47);
			copyXPMArea(40 + (i / 10)*7, 78, 6, 9, 108, 47);

			i = cnt_time % 60;
			cnt_time /= 60;
			copyXPMArea(40 + (i % 10)*7, 78, 6, 9, 96, 47);
			copyXPMArea(40 + (i / 10)*7, 78, 6, 9, 89, 47);

			i = cnt_time % 24;
			cnt_time /= 24;
			copyXPMArea(40 + (i % 10)*7, 78, 6, 9, 77, 47);
			copyXPMArea(40 + (i / 10)*7, 78, 6, 9, 70, 47);

			/* De rest is dagen!  5x7*/
			i = cnt_time;
			copyXPMArea(66 + (i % 10)*6, 66, 5, 7, 88, 35);
			i /= 10;
			copyXPMArea(66 + (i % 10)*6, 66, 5, 7, 82, 35);
			i /= 10;
			copyXPMArea(66 + (i % 10)*6, 66, 5, 7, 76, 35);
			i /= 10;
			copyXPMArea(66 + (i % 10)*6, 66, 5, 7, 70, 35);
		}

		if (curtime >= nexttime) {
			nexttime+=10;

			if (curtime > nexttime) /* dont let APM suspends make this crazy */
				nexttime = curtime;

			for (i=0; i<stat_online; i++) {
			        stat_dev *sd = stat_device + i;

				if (sd->his[HISTORY_ENTRIES-1])
					sd->his[HISTORY_ENTRIES-1] /= sd->hisaddcnt;

				for (j = 1; j < HISTORY_ENTRIES; j++)
					sd->his[j-1] = sd->his[j];

				if (i == stat_current) {
					if (i == 0)
						DrawStats(sd->his, HISTORY_ENTRIES - 1, 40, 5, 58);
					else if (i == 1)
						DrawStats_io(sd->his, HISTORY_ENTRIES - 1, 40, 5, 58);
				}
				sd->his[HISTORY_ENTRIES-1] = 0;
				sd->hisaddcnt = 0;

			}
		}
		RedrawWindowXY(xpm_X, xpm_Y);

		while (XPending(display)) {
			XNextEvent(display, &Event);
			switch (Event.type) {
			case Expose:
				RedrawWindowXY(xpm_X, xpm_Y);
				break;
			case DestroyNotify:
				XCloseDisplay(display);
				exit(0);
				break;
			case ButtonPress:
				but_stat = CheckMouseRegion(Event.xbutton.x, Event.xbutton.y);
				break;
			case ButtonRelease:
				i = CheckMouseRegion(Event.xbutton.x, Event.xbutton.y);
				if (but_stat == i && but_stat >= 0) {
					switch (but_stat) {
					case 0:
						switch (Event.xbutton.button) {
						case 1:
							if (left_action)
								execCommand(left_action);
							break;
						case 2:
							if (middle_action)
								execCommand(middle_action);
							break;
						case 3:
							if (right_action)
								execCommand(right_action);
							break;
						}
						break;
					case 1:
						if (mode_cycling) {
							stat_current++;
							if (stat_current == stat_online)
								stat_current = 0;

							DrawActive(stat_device[stat_current].name);
							if (stat_current == 0)
								DrawStats(stat_device[stat_current].his,
									  HISTORY_ENTRIES-1, 40, 5, 58);

							if (stat_current == 1)
								DrawStats_io(stat_device[stat_current].his,
									     HISTORY_ENTRIES-1, 40, 5, 58);

							if (stat_current == 2) {
								xpm_X = 64;
								setMaskXY(-64, 0);
							} else {
								xpm_X = 0;
								setMaskXY(0, 0);
							}
							RedrawWindowXY(xpm_X, xpm_Y);
						}
						break;
					}
				}
				break;
			}
		}
		usleep(250000L);
	}
}


void update_stat_cpu(stat_dev *st, ullong *istat2, ullong *idle2)
{
	long k;
	ullong istat, idle;

	get_statistics(st->name, &k, &istat, &idle, istat2, idle2);

	st->rt_idle = ullsub(&idle, &st->idlelast);
	st->idlelast = idle;

	st->rt_stat = ullsub(&istat, &st->statlast);
	st->statlast = istat;

	if (nb_cpu > 1) {
		int cpu;
		unsigned long  max, j;
		cpu_max = 0; max = 0;
		for (cpu = 0; cpu < nb_cpu; cpu++) {
			st->idle_stat[cpu] = ullsub(&idle2[cpu], &st->idle_last[cpu]);
			st->idle_last[cpu] = idle2[cpu];

			st->cpu_stat[cpu] = ullsub(&istat2[cpu], &st->cpu_last[cpu]);
			st->cpu_last[cpu] = istat2[cpu];

			j = st->cpu_stat[cpu] + st->idle_stat[cpu];

			if (j != 0)
				j = (st->cpu_stat[cpu] << 7) / j;

			if (j > max) {
				max = j;
				cpu_max = cpu;
			}
		}
	}

	st->his[HISTORY_ENTRIES-1] += k;
	st->hisaddcnt += 1;
}


void update_stat_io(stat_dev *st)
{
	long j, k;
	ullong istat, idle;

	/* Periodically re-sample. Sometimes we get anomalously high readings;
	 * this discards them. */
	static int stalemax = 300;
	static long maxdiskio = 0;
	if (--stalemax <= 0) {
		maxdiskio = 0;
		stalemax = 300;
	}

	get_statistics(st->name, &k, &istat, &idle, NULL, NULL);

	st->rt_idle = ullsub(&idle, &st->idlelast);
	st->idlelast = idle;

	st->rt_stat = ullsub(&istat, &st->statlast);
	st->statlast = istat;

	/* remember peak for scaling of upper-right meter. */
	j = st->rt_stat;
	if (maxdiskio < j)
		maxdiskio = j;

	/* Calculate scaling factor for upper-right meter. "/ 5" will clip
	* the highest peaks, but makes moderate values more visible. We are
	* compensating for wild fluctuations which are probably caused by
	* kernel I/O buffering.
	*/
	st->rt_idle = (maxdiskio - j) / 5;
	if (j > 0 && st->rt_idle < 1)
		st->rt_idle = 1;      /* scale up tiny values so they are visible */

	st->his[HISTORY_ENTRIES-1] += st->rt_stat;
	st->hisaddcnt += 1;
}


void update_stat_mem(stat_dev *st, stat_dev *st2)
{
	static char *line = NULL;
	static size_t line_size = 0;

	unsigned long swapfree;
	unsigned long free, shared, buffers, cached;

	if (freopen("/proc/meminfo", "r", fp_meminfo) == NULL)
		perror("freopen() of /proc/meminfo failed!)\n");

	while ((getline(&line, &line_size, fp_meminfo)) > 0) {
		/* The original format for the first two lines of /proc/meminfo was
		 * Mem: total used free shared buffers cached
		 * Swap: total used free
		 *
		 * As of at least 2.5.47 these two lines were removed, so that the
		 * required information has to come from the rest of the lines.
		 * On top of that, used is no longer recorded - you have to work
		 * this out yourself, from total - free.
		 *
		 * So, these changes below should work. They should also work with
		 * older kernels, too, since the new format has been available for
		 * ages.
		 */
		if (strstr(line, "MemTotal:"))
			sscanf(line, "MemTotal: %ld", &st->rt_idle);
		else if (strstr(line, "MemFree:"))
			sscanf(line, "MemFree: %lu", &free);
		else if (strstr(line, "MemShared:"))
			sscanf(line, "MemShared: %lu", &shared);
		else if (strstr(line, "Buffers:"))
			sscanf(line, "Buffers: %lu", &buffers);
		else if (strstr(line, "Cached:"))
			sscanf(line, "Cached: %lu", &cached);
		else if (strstr(line, "SwapTotal:"))
			sscanf(line, "SwapTotal: %ld", &st2->rt_idle);
		else if (strstr(line, "SwapFree:"))
			sscanf(line, "SwapFree: %lu", &swapfree);
	}

	/* memory use - rt_stat is the amount used, it seems, and this isn't
	 * recorded in current version of /proc/meminfo (as of 2.5.47), so we
	 * calculate it from MemTotal - MemFree
	 */
	st->rt_stat = st->rt_idle - free;

	/* wbk -b flag (from Gentoo patchkit) */
	if (!show_buffers)
		st->rt_stat -= buffers+cached;
	/* As with the amount of memory used, it's not recorded any more, so
	 * we have to calculate it ourselves.
	 */
	st2->rt_stat = st2->rt_idle - swapfree;
}

void update_stat_swp(stat_dev *st)
{
	static char *line = NULL;
	static size_t line_size = 0;
	unsigned long swapfree;

	fseek(fp_meminfo, 0, SEEK_SET);
	while ((getline(&line, &line_size, fp_meminfo)) > 0) {
		/* As with update_stat_mem(), the format change to /proc/meminfo has
		 * forced some changes here. */
		if (strstr(line, "SwapTotal:"))
			sscanf(line, "SwapTotal: %ld", &st->rt_idle);
		else if (strstr(line, "SwapFree:"))
			sscanf(line, "SwapFree: %lu", &swapfree);
	}
	st->rt_stat = st->rt_idle - swapfree;
}

/*******************************************************************************\
|* get_statistics								*|
\*******************************************************************************/
void get_statistics(char *devname, long *is, ullong *ds, ullong *idle, ullong *ds2, ullong *idle2)
{
	int i;
	static char *line = NULL;
	static size_t line_size = 0;
	char *p;
	char *tokens = " \t\n";
	float f;
	ullong ulltmp;

	*is = 0;
	ullreset(ds);
	ullreset(idle);

	if (!strncmp(devname, "cpu", 3)) {
		fseek(fp_stat, 0, SEEK_SET);
		while ((getline(&line, &line_size, fp_stat)) > 0) {
			if (strstr(line, "cpu")) {
				int cpu = -1;	/* by default, cumul stats => average */
				if (!strstr(line, "cpu ")) {
					sscanf(line, "cpu%d", &cpu);
					ullreset(&ds2[cpu]);
					ullreset(&idle2[cpu]);
				}
				p = strtok(line, tokens);
				/* 1..3, 4 == idle, we don't want idle! */
				for (i=0; i<3; i++) {
					p = strtok(NULL, tokens);
					ullparse(&ulltmp, p);
					if (cpu == -1)
						ulladd(ds, &ulltmp);
					else
						ulladd(&ds2[cpu], &ulltmp);
				}
				p = strtok(NULL, tokens);
				if (cpu == -1)
					ullparse(idle, p);
				else
					ullparse(&idle2[cpu], p);
			}
		}
		if ((fp_loadavg = freopen("/proc/loadavg", "r", fp_loadavg)) == NULL)
			perror("ger_statistics(): freopen(proc/loadavg) failed!\n");

		if (fscanf(fp_loadavg, "%f", &f) == EOF)
			perror("fscanf() failed to read f\n");
		*is = (long) (100 * f);
	}

	if (!strncmp(devname, "i/o", 3)) {
		if (fseek(fp_diskstats, 0, SEEK_SET) == -1)
			perror("get_statistics() seek failed\n");

		/* wbk 20120308 These are no longer in /proc/stat. /proc/diskstats
		 * seems to be the closest replacement. Under modern BSD's, /proc is
		 * now deprecated, so iostat() might be the answer.
		 *	      http://www.gossamer-threads.com/lists/linux/kernel/314618
		 * has good info on this being removed from kernel. Also see
		 * kernel sources Documentation/iostats.txt
		 *
		 * TODO: We will end up with doubled values. We are adding the
		 * aggregate to the individual partition, due to device selection
		 * logic. Either grab devices' stats with numbers, or without (sda
		 * OR sda[1..10]. Could use strstr() return plus offset, but would
		 * have to be careful with bounds checking since we're in a
		 *  limited buffer. Or just divide by 2 (inefficient). Shouldn't
		 * matter for graphing (we care about proportions, not numbers).  */
		while ((getline(&line, &line_size, fp_diskstats)) > 0) {
			if (strstr(line, "sd") || strstr(line, "sr")) {
				p = strtok(line, tokens);
				/* skip 3 tokens, then use fields from
				`* linux/Documentation/iostats.txt	     */
				for (i = 1; i <= 6; i++)
					p = strtok(NULL, tokens);

				ullparse(&ulltmp, p);
				ulladd(ds, &ulltmp);
				for (i = 7; i <= 10; i++)
					p = strtok(NULL, tokens);

				ullparse(&ulltmp, p);
				ulladd(ds, &ulltmp);
			}
		}
	}
}


/*******************************************************************************\
|* getWidth									*|
\*******************************************************************************/
unsigned long getWidth(long actif, long idle)
{
	/* wbk - work with a decimal value so we don't round < 1 down to zero.  */
	double j = 0;
	unsigned long r = 0;

	j = (actif + idle);
	if (j != 0)
		j = (actif * 100) / j;

	j = j * 0.32;

	/* round up very low positive values so they are visible. */
	if (actif > 0 && j < 2)
		j = 2;

	if (j > 32)
		j = 32;

	r = (unsigned long) j;
	return r;
}


/*******************************************************************************\
|* getNbCPU									*|
\*******************************************************************************/
int getNbCPU(void)
{
	static char *line = NULL;
	static size_t line_size = 0;
	int cpu = 0;

	fseek(fp_stat, 0, SEEK_SET);
	while ((getline(&line, &line_size, fp_stat)) > 0) {
		if (strstr(line, "cpu") && !strstr(line, "cpu "))
			sscanf(line, "cpu%d", &cpu);
	}

	return cpu+1;
}


/*******************************************************************************\
|* checksysdevs									*|
\*******************************************************************************/
int checksysdevs(void) {
	strcpy(stat_device[0].name, "cpu0");
	strcpy(stat_device[1].name, "i/o");
	strcpy(stat_device[2].name, "sys");

	return 3;
}


/*******************************************************************************\
|* void DrawActive(char *)							*|
\*******************************************************************************/
void DrawActive(char *name)
{

	/* Alles op X,77
	   CPU: 0
	   I/O: 21

	   20 Breed, 10 hoog
	   Destinatie: 5,5
	*/

	if (name[0] == 'c')
		copyXPMArea(0, 77, 19, 10, 5, 5);
	else if (name[0] == 'i')
		copyXPMArea(19, 77, 19, 10, 5, 5);
}


/*******************************************************************************\
|* DrawStats                                                                   *|
\*******************************************************************************/
void DrawStats(int *his, int num, int size, int x_left, int y_bottom)
{
	int pixels_per_byte, j, k, *p, d, hint_height, hint_color;

	pixels_per_byte = 100;
	p = his;

	for (j=0; j<num; j++) {
		while (p[0] > pixels_per_byte)
			pixels_per_byte += 100;
		p += 1;
	}

	p = his;

	for (k=0; k<num; k++) {
		d = (1.0 * p[0] / pixels_per_byte) * size;

		for (j=0; j<size; j++) {
			if (j < d - 3)
				copyXPMArea(2, 88, 1, 1, k+x_left, y_bottom-j);
			else if (j < d)
				copyXPMArea(2, 89, 1, 1, k+x_left, y_bottom-j);
			else
				copyXPMArea(2, 90, 1, 1, k+x_left, y_bottom-j);
		}
		p += 1;
	}

	/* Nu horizontaal op 100/200/300 etc lijntje trekken! */
	if (pixels_per_byte > 10000) {
		hint_height = 10000;
		hint_color = 93; /* red */
	} else if (pixels_per_byte > 1000) {
		hint_height = 1000;
		hint_color = 92; /* yellow */
	} else {
		hint_height = 100;
		hint_color = 91; /* green */
	}

	for (j = hint_height;  j < pixels_per_byte; j += hint_height) {
		d = (40.0 / pixels_per_byte) * j - 1;
		for (k=0; k<num; k++) {
			copyXPMArea(2, hint_color, 1, 1, k+x_left, y_bottom-d);
		}
	}
}


/*******************************************************************************\
|* DrawStats_io                                                                *|
\*******************************************************************************/
void DrawStats_io(int *his, int num, int size, int x_left, int y_bottom)
{
	float	pixels_per_byte;
	int     j, k, *p;
	/* wbk - Use a double to avoid rounding values of d < 1 to zero. */
	double d = 0;
	int border = 3;

	/* wbk - this should not be static. No need to track the scale, since
	 * we always calculate it on the fly anyway. This static variable did
	 * not get re-initialized when we entered this function, so the scale
	 * would always grow and never shrink.
	 */
	/*static int	global_io_scale = 1;*/
	int	io_scale = 1;

	p = his;
	for (j=0; j<num; j++)
		if (p[j] > io_scale) io_scale = p[j];

	pixels_per_byte = 1.0 * io_scale / size;
	if (pixels_per_byte == 0)
		pixels_per_byte = 1;

	for (k=0; k<num; k++) {
		d = (1.0 * p[0] / pixels_per_byte);

		/* graph values too low for graph resolution */
		if (d > 0 && d < 1) {
			d = 3;
			border = 2;
		} else {
			border = 3;
		}

		for (j=0; j<size; j++) {
			if (j < d - border)
				copyXPMArea(2, 88, 1, 1, k+x_left, y_bottom-j);
			else if (j < d )
				copyXPMArea(2, 89, 1, 1, k+x_left, y_bottom-j);
			else
				copyXPMArea(2, 90, 1, 1, k+x_left, y_bottom-j);
		}
		p += 1;   /* beware... */
	}
}


/*******************************************************************************\
|* usage									*|
\*******************************************************************************/
void usage(char *name)
{
	printf("Usage: %s [OPTION]...\n", name);
	printf("WindowMaker dockapp that displays system information.\n");
	printf("\n");
	printf("  -display DISPLAY     contact the DISPLAY X server\n");
	printf("  -geometry GEOMETRY   position the clock at GEOMETRY\n");
	printf("  -l                   locked view - cannot cycle modes\n");
	printf("  -c                   show average and max CPU for SMP machine.\n");
	printf("                       default if there is more than %d processors\n", MAX_CPU);
	printf("  -i                   start in Disk I/O mode\n");
	printf("  -s                   start in System Info mode\n");
	printf("  -b                   include buffers and cache in memory usage\n");
	printf("  -h                   display this help and exit\n");
	printf("  -v                   output version information and exit\n");
}


/*******************************************************************************\
|* printversion									*|
\*******************************************************************************/
void printversion(void)
{
	printf("WMMon version %s\n", PACKAGE_VERSION);
}
/* vim: sw=4 ts=4 columns=82
 */
