checklines.c

/* This is free software.  See LICENSE for terms.
 * Copyright 2004 - 2015, Patricia Kirk.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <sys/socket.h>
#include <getopt.h>
#include <time.h>

#include "proto.h"

extern struct global g;
extern struct pixstack *iconpix, *mainpix;
extern struct data fresh;

extern Pixmap iconmask, icondraw, mainmask, maindraw;
extern Display *maindisplay;
extern GC icongc, maingc;

extern char *filename;	/* Stops spurious log entries at startup. */

struct lineopts *linelist;

char smalbuf[SMALBUF];

static struct option options[] = {
    /* Line types. */
    {"memfree",		0,		NULL,		0},	/*  0 */
    {"memused",		0,		NULL,		0},	/*  1 */
    {"memtotal",	0,		NULL,		0},	/*  2 */
    {"swapfree",	0,		NULL,		0},	/*  3 */
    {"swapused",	0,		NULL,		0},	/*  4 */
    {"swaptotal",	0,		NULL,		0},	/*  5 */
    {"swapdevices",	0,		NULL,		0},	/*  6 */
    {"load1",		0,		NULL,		0},	/*  7 */
    {"load5",		0,		NULL,		0},	/*  8 */
    {"load15",		0,		NULL,		0},	/*  9 */
    {"processes",	0,		NULL,		0},	/* 10 */
    {"share",		0,		NULL,		0},	/* 11 */
    {"buffer",		0,		NULL,		0},	/* 12 */
    {"cache",		0,		NULL,		0},	/* 13 */
    {"uphours",		0,		NULL,		0},	/* 14 */
    {"updays",		0,		NULL,		0},	/* 15 */
    {"ostype",		0,		NULL,		0},	/* 16 */
    {"osversion",	0,		NULL,		0},	/* 17 */
    {"network",		0,		NULL,		0},	/* 18 */
    {"moonphase",	0,		NULL,		0},	/* 19 */
    {"powerstate",	0,		NULL,		0},	/* 20 */
    {"volume",		0,		NULL,		0},	/* 21 */
    {"display",		0,		NULL,		0},	/* 22 */
    {"user",		2,		NULL,		0},	/* 23 */
    {"nice",		2,		NULL,		0},	/* 24 */
    {"system",		2,		NULL,		0},	/* 25 */
    {"interrupt",	2,		NULL,		0},	/* 26 */
    {"idle",		2,		NULL,		0},	/* 27 */
    {"hostname",	2,		NULL,		0},	/* 28 */
    {"timer",		2,		NULL,		0},	/* 29 */
    {"alarm",		2,		NULL,		0},	/* 30 */
    {"reset",		2,		NULL,		0},	/* 31 */
    {"elapsed",		2,		NULL,		0},	/* 32 */
    {"calendar",	2,		NULL,		0},	/* 33 */
    {"thermal",		1,		NULL,		0},	/* 34 */
    {"format",		1,		NULL,		0},	/* 35 */
    {"device",		1,		NULL,		0},	/* 36 */
    {"devdevice",	1,		NULL,		0},	/* 37 */
    {"mount",		1,		NULL,		0},	/* 38 */
    {"interface",	1,		NULL,		0},	/* 39 */
    {"remotehost",	1,		NULL,		0},	/* 40 */
    {"devinfo",		1,		NULL,		0},	/* 41 keep as last */
    /* End of line types. */
    /* Line specific values. */
    {"warning",		1,		NULL,		0},	/* 42 */
    {"critical",	1,		NULL,		0},	/* 43 */
    {"color",		1,		NULL,		0},	/* 44 */
    {"color1",		1,		NULL,		0},	/* 45 */
    {"color2",		1,		NULL,		0},	/* 46 */
    {"color3",		1,		NULL,		0},	/* 47 */
    {"sound",		1,		NULL,		0},	/* 48 */
    {"sound1",		1,		NULL,		0},	/* 49 */
    {"panel",		1,		NULL,		0},	/* 50 */
    {"linenum",		1,		NULL,		0},	/* 51 */
    {"setting",		1,		NULL,		0},	/* 52 */
    {"command",		1,		NULL,		0},	/* 53 */
    /* End of options. */
    {NULL,		0,		NULL,		0}
};

/* Scroll through the linked list of lines and
 * call the function indicated by line->index.
 * Send the resulting info to the draw window.
 */
void checkLines() {
    struct lineopts *line, *l;
    struct tm *timeptr;
    time_t timeval=0, timenow=time(NULL);
    char buffer[BUFPLUS], showbuf[SHOWBUF];
    char *ptr=NULL, *cp0=NULL;
    int days=0, hours=0, minutes=0, seconds=0;
    int i, j, k=0, utc;
#if WITH_SUID
    static int powerstop=0, thermstop=0;
#endif	/* WITH_SUID */

    if (g.pixmap) {
	if (g.iconized) {
	    XSetClipMask(maindisplay, icongc, None);
	    XCopyPlane(maindisplay, iconpix->mask, iconmask, icongc, 0, 0, g.isize, g.isize, 0, 0, 1);
	} else {
	    XSetClipMask(maindisplay, maingc, None);
	    XCopyPlane(maindisplay, mainpix->mask, mainmask, maingc, 0, 0, g.msize, g.msize, 0, 0, 1);
	}
    }
    if (g.iconized) {
	XCopyArea(maindisplay, iconpix->pixmap, icondraw, icongc, 0, 0, g.isize, g.isize, 0, 0);
    } else if (g.panel && !g.pixmap) {
	XCopyArea(maindisplay, mainpix->pixmap, maindraw, maingc, 0, 0, g.psize, g.psize, 0, 0);
	XCopyArea(maindisplay, mainpix->pixmap, maindraw, maingc, 0, 0, g.psize, g.psize, g.psize, 0);
	XCopyArea(maindisplay, mainpix->pixmap, maindraw, maingc, 0, 0, g.psize, g.psize, 0, g.psize);
	XCopyArea(maindisplay, mainpix->pixmap, maindraw, maingc, 0, 0, g.psize, g.psize, g.psize, g.psize);
    } else {
	XCopyArea(maindisplay, mainpix->pixmap, maindraw, maingc, 0, 0, g.msize, g.msize, 0, 0);
    }
    for (line = linelist; line; line = line->next) {
	switch (line->index) {
	    case  0:	/* Free memory. */
		getMem();
		if (line->setting && (!strcmp(line->setting, "percent") ||
			!strcmp(line->setting, "%"))) {
		    snprintf(showbuf, sizeof(showbuf), "MemF|   .  %%");
		    formatLine(1, line, ((fresh.memfree / fresh.memtotal) * 10000), showbuf);
		} else {
		    snprintf(showbuf, sizeof(showbuf), "MemF|        ");
		    formatLine(0, line, fresh.memfree, showbuf);
		}
		break;
	    case  1:	/* Used memory. */
		getMem();
		if (line->setting && (!strcmp(line->setting, "percent") ||
			!strcmp(line->setting, "%"))) {
		    snprintf(showbuf, sizeof(showbuf), "MemU|   .  %%");
		    formatLine(1, line, ((fresh.memused / fresh.memtotal) * 10000), showbuf);
		} else {
		    snprintf(showbuf, sizeof(showbuf), "MemU|        ");
		    formatLine(0, line, fresh.memused, showbuf);
		}
		break;
	    case  2:	/* Total memory. */
		getMem();
		snprintf(showbuf, sizeof(showbuf), "MemT|        ");
		formatLine(0, line, fresh.memtotal, showbuf);
		break;
	    case  3:	/* Free swap. */
		getSwap();
		if (line->setting && fresh.swapfree > 0 && (!strcmp(line->setting, "%") ||
			!strcmp(line->setting, "percent"))) {
		    snprintf(showbuf, sizeof(showbuf), "SwpF|   .  %%");
		    formatLine(1, line, ((fresh.swapfree / fresh.swaptotal) * 10000), showbuf);
		} else {
		    snprintf(showbuf, sizeof(showbuf), "SwpF|        ");
		    formatLine(0, line, fresh.swapfree, showbuf);
		}
		break;
	    case  4:	/* Used swap. */
		getSwap();
		if (line->setting && fresh.swapfree > 0 && (!strcmp(line->setting, "%") ||
			!strcmp(line->setting, "percent"))) {
		    snprintf(showbuf, sizeof(showbuf), "SwpU|   .  %%");
		    formatLine(1, line, ((fresh.swapused / fresh.swaptotal) * 10000), showbuf);
		} else {
		    snprintf(showbuf, sizeof(showbuf), "SwpU|        ");
		    formatLine(0, line, fresh.swapused, showbuf);
		}
		break;
	    case  5:	/* Total swap. */
		getSwap();
		snprintf(showbuf, sizeof(showbuf), "SwpT|        ");
		formatLine(0, line, fresh.swaptotal, showbuf);
		break;
	    case  6:	/* Number of swap devices or files. */
		getSwap();
		if (fresh.swapdev == 0)
		    snprintf(showbuf, sizeof(showbuf), "|No|swap|devs|");
		else if (fresh.swapdev == 1)
		    snprintf(showbuf, sizeof(showbuf), "|One|swap|dev|");
		else
		    snprintf(showbuf, sizeof(showbuf), "%d|swap|devs", fresh.swapdev);
		lineToDraw(showbuf, line);
		break;
	    case  7:	/* 1min */
		getLoads();
		snprintf(showbuf, sizeof(showbuf), "Load|1|   .  ");
		formatLine(1, line, (fresh.loads[0] * 100), showbuf);
		break;
	    case  8:	/* 5min */
		getLoads();
		snprintf(showbuf, sizeof(showbuf), "Load|5|   .  ");
		formatLine(1, line, (fresh.loads[1] * 100), showbuf);
		break;
	    case  9:	/* 15min */
		getLoads();
		snprintf(showbuf, sizeof(showbuf), "Load|15|   .  ");
		formatLine(1, line, (fresh.loads[2] * 100), showbuf);
		break;
	    case 10:	/* Running and other processes. */
		getProcs();
		snprintf(showbuf, sizeof(showbuf), "Proc|  ,|    ");
		i = fresh.proc[0];
		for (j = 12; j > 8; j--) {
		    showbuf[j] = (i % 10) + AZERO;
		    i /= 10;
		    if (!i)
			break;					/* Stop at 9999 active procs */
		}
		j = fresh.proc[1];
		showbuf[6] = (j % 10) + AZERO;
		j /= 10;
		showbuf[5] = j ? (j % 10) + AZERO : SPACE;	/* Stop at 99 running procs */
		j /= 10;
		if (i || j) {
		    showbuf[13] = PIPE;
		    showbuf[14] = '+';
		    showbuf[15] = '\0';
		}
		lineToDraw(showbuf, line);
		break;
	    case 11:	/* Shared memory. */
		getABC();
		snprintf(showbuf, sizeof(showbuf), "Shar|        ");
		formatLine(0, line, fresh.share, showbuf);
		break;
	    case 12:	/* Buffer memory. */
		getABC();
		snprintf(showbuf, sizeof(showbuf), "Buff|        ");
		formatLine(0, line, fresh.buffer, showbuf);
		break;
	    case 13:	/* Cache memory. */
		getABC();
		snprintf(showbuf, sizeof(showbuf), "Cach|        ");
		formatLine(0, line, fresh.cache, showbuf);
		break;
	    case 14:	/* Uptime hhhhh:mm:ss with time over 100,000 hours indicated by >. */
		getUptime();
		snprintf(showbuf, sizeof(showbuf), "Up|     :  :  ");
		k = fresh.uptime;	/* seconds */
		j = k % 60;
		k /= 60;		/* minutes */
		showbuf[13] = (j % 10) + AZERO;
		j /= 10;
		showbuf[12] = j + AZERO;
		j = k % 60;
		k /= 60;		/* hours */
		showbuf[10] = (j % 10) + AZERO;
		j /= 10;
		showbuf[9] = j + AZERO;
		for (i = 7; i > 2; i--) {
		    showbuf[i] = (k % 10) + AZERO;
		    k /= 10;
		    if (!k)
			break;
		}
		if (k)
		    showbuf[1] = '>';
		lineToDraw(showbuf, line);
		break;
	    case 15:	/* Uptime ddd hh:mm with a roll over at 1000 days. */
		getUptime();
		snprintf(showbuf, sizeof(showbuf), "Up|   d|  :  ");
		k = fresh.uptime / 60;			/* minutes */
		j = (k / 1440) % 1000;			/* days */
		k = k % 1440;				/* minutes - days */
		for (i = 5; i > 2; i--) {
		    showbuf[i] = (j % 10) + AZERO;
		    j /= 10;
		    if (!j)
			break;
		}
		j = k / 60;				/* hours */
		k = k % 60;				/* minutes */
		showbuf[12] = (k % 10) + AZERO;		/* minutes */
		showbuf[11] = (k / 10) + AZERO;		/* tens minutes, zero if zero */
		showbuf[9] = (j % 10) + AZERO;		/* hours */
		j /= 10;
		showbuf[8] = j ? j + AZERO : SPACE;	/* tens hours, space if zero */
		lineToDraw(showbuf, line);
		break;
	    case 16:	/* Operating system name. */
		lineToDraw(g.ostype, line);
		break;
	    case 17:	/* Operating system version. */
		lineToDraw(g.osvers, line);
		break;
	    case 18:	/* Local network. */
		getHostNet();
		snprintf(showbuf, sizeof(showbuf), "|%s|", g.network);
		lineToDraw(showbuf, line);
		break;
	    case 19:	/* Moon phase in text. */
		timeval = timenow;
		if (g.phasecalc && line->status) {
		    for (l = linelist; l; l = l->next) {
			if (l->status && !strcmp(l->type, "display"))
			    break;
		    }
		    if (g.phasetime) {
			timenow = g.phasetime;
			STRDUP(l->setting, "|Quarters|");
		    } else {
			STRDUP(l->setting, "|Current|");
		    }
		}
		getPhase(timenow, showbuf);
		lineToDraw(showbuf, line);
		timenow = timeval;
		break;
	    case 20:	/* AC line or battery. */
		/* line->status == 0, AC line on
		 * line->status == 1, Battery >= line->warn
		 * line->status == 2, Battery < line->warn
		 * line->status == 3, SHUTDOWN
		 */
		getPower();
		k = parseStrtol(line->status, TRUE, "checkLines");
		if (fresh.power < 0) {
		    snprintf(showbuf, sizeof(showbuf), "No|Pwr|Status");
		    line->color = line->color3;
		} else if (fresh.power == 1) {
		    snprintf(showbuf, sizeof(showbuf), "|AC|Line|ON|");
		    snprintf(buffer, sizeof(buffer), "AC line on");
#if WITH_SUID
		    if (powerstop && !thermstop && !g.effectiveuid) {
			/* Line is back, kill shutdown unless a tzone has gone critical.
			 * execShutdown() will return without making an attempt if
			 * not SUID.  Don`t run through here unless !g.effectiveuid
			 */
			stringcat(buffer, ", cancelling shutdown", sizeof(buffer));
			powerstop = execShutdown(-1);
		    }
#endif	/* WITH_SUID */
		    if (k && g.powerlog && !filename)
			/* If filename exists, this is the first
			 * pass through checkLines().  Don`t log
			 * any changes at startup.
			 */
			logMessage(buffer);
		    line->color = line->color1;
		    STRDUP(line->status, "0");
		} else if (fresh.power < line->crit) {
		    /* Display shutdown state even if the pause
		     * to catch battery jitter clears it.
		     */
		    snprintf(showbuf, sizeof(showbuf), "|SHUTDOWN|");
		    if (k != 3) {
			/* k == 3 if we've already been through here. */
			sleep(g.refresh * 2);
			getPower();
			if (fresh.power < line->crit) {
			    if (g.powerlog) {
				snprintf(buffer, sizeof(buffer), "Power state critical");
#if WITH_SUID
				if (g.poweroff && !g.effectiveuid)
				    stringcat(buffer, ", executing shutdown", sizeof(buffer));
#endif	/* WITH_SUID */
				logMessage(buffer);
			    }
			    /* Sound a warning if there is one. */
			    makeNoise(line->sound1);
#if WITH_SUID
			    /* Attempt to shut things down. */
			    if (g.poweroff)
				powerstop = execShutdown(g.powertime);
#endif	/* WITH_SUID */
#if WITH_COMMANDS || WITH_STEPEXEC
			    /* User poweroff command. */
			    if (g.powercmd)
				execComm(g.powercmd);
#endif	/* WITH_COMMANDS || WITH_STEPEXEC */
			    line->color = line->color3;
			    STRDUP(line->status, "3");
			} else {
			    line->color = line->color2;
			    STRDUP(line->status, "2");
			}
		    }
		} else if (k < 2 && fresh.power < line->warn) {
		    if (g.powerlog)
			logMessage("Power state warning");
		    /* Sound warning at battery < warning level */
		    makeNoise(line->sound);
		    goto setstat;
		} else {
		    if (!k) {
			if (g.powerlog && !filename)
			    logMessage("AC line off");
			if (!filename)
			    /* Sound warning at AC dropout. */
			    makeNoise(line->sound);
		    }
setstat:
		    if (fresh.power >= line->warn) {
			line->color = line->color2;
			STRDUP(line->status, "1");
		    } else {
			line->color = line->color3;
			STRDUP(line->status, "2");
		    }
		    snprintf(showbuf, sizeof(showbuf), "|Battery|    |");
		    j = (fresh.power > 9999) ? 9999 : fresh.power;
		    for (i = 12; i > 8; i--) {
			showbuf[i] = (j % 10) + AZERO;
			j /= 10;
			if (!j)
			    break;
		    }
		}
		lineToDraw(showbuf, line);
		break;
	    case 21:	/* Volume */
		getVolume();
		snprintf(showbuf, sizeof(showbuf), "|||   x   |||");
		if (g.volconn)
		    showbuf[6] = '-';
		i = fresh.volume[0];
		showbuf[5] = (i % 10) + AZERO;
		i /= 10;
		if (i)
		    showbuf[4] = (i % 10) + AZERO;
		i /= 10;
		if (i)
		    showbuf[3] = i + AZERO;;
		i = fresh.volume[1];
		j = 8 + (i >= 100);
		showbuf[j--] = (i % 10) + AZERO;
		i /= 10;
		if (i)
		    showbuf[j--] = (i % 10) + AZERO;
		i /= 10;
		if (i)
		    showbuf[j] = i + AZERO;;
		lineToDraw(showbuf, line);
		line = line->next;
		drawSlider(line);
		line = line->next;
		drawSlider(line);
		break;
	    case 22:	/* Display */
		/* line->setting defaults to "Display". */
		snprintf(showbuf, sizeof(showbuf), "%s", line->setting);
		lineToDraw(showbuf, line);
		break;
	    case 23:	/* user */
		/* Linux: user, system, nice and idle percentages.
		 * Other: user, nice, system, interrupt and idle percentages.
		 */
		getSplit();
		snprintf(showbuf, sizeof(showbuf), "User|   .  %%");
		i = 0;
		goto formline;
	    case 24:	/* nice */
		getSplit();
		snprintf(showbuf, sizeof(showbuf), "Nice|   .  %%");
#if defined(__linux__)
		i = 2;
#else
		i = 1;
#endif
		goto formline;
	    case 25:	/* system */
		getSplit();
		snprintf(showbuf, sizeof(showbuf), "Sys|   .  %%");
#if defined(__linux__)
		i = 1;
#else
		i = 2;
#endif
		goto formline;
	    case 26:	/* interrupt */
#if !defined(__linux__)
		getSplit();
		snprintf(showbuf, sizeof(showbuf), "Intr|   .  %%");
		i = 3;
		goto formline;
#endif
	    case 27:	/* idle */
		getSplit();
		snprintf(showbuf, sizeof(showbuf), "Idle|   .  %%");
#if defined(__linux__)
		i = 3;
#else
		i = 4;
#endif
formline:
		if (line->argument) {
		    k = parseStrtol(line->argument, TRUE, "checkLines") - 1;
#if defined(__linux__)
		    j = *fresh.splits[i + (k * 4)] * 10000;
#else
		    j = *fresh.splits[i + (k * 5)] * 10000;
#endif
		} else {
		    j = fresh.states[i] * 10000;
		}
		formatLine(1, line, j, showbuf);
		break;
	    case 28:	/* Localhost name. */
		getHostNet();
		snprintf(showbuf, sizeof(showbuf), "|%s|", g.hostname);
		if (line->argument)
		    showbuf[1] = toupper((int)showbuf[1]);
		lineToDraw(showbuf, line);
		break;
	    case 29:	/* Timer. */
		if (!line->status) {
		    snprintf(showbuf, sizeof(showbuf), "|Timer|%s|", line->argument);
		} else if (!strcmp(line->status, "value") ||
			!strcmp(line->status, "setting")) {
		    splitSeconds(&days, &hours, &minutes, &seconds, parseStrtol(line->buffer, TRUE, "checkLines"));
		    snprintf(showbuf, sizeof(showbuf), "T||%03d||%02d:%02d:%02d",
			    days, hours, minutes, seconds);
		} else if (!strcmp(line->status, "running")) {
		    timeval = parseStrtol(line->buffer, TRUE, "checkLines") - timenow;
		    if (timeval <= 0) {
			FREE(line->status);
			FREE(line->buffer);
			snprintf(showbuf, sizeof(showbuf), "|Timer|%s|", line->argument);
			makeNoise(line->sound);
#if WITH_COMMANDS || WITH_STEPEXEC
			if (line->command) {
#if WITH_SUID
			    if (!strcmp(line->command, "shutdown"))
				/* Shutdown at timer timeout. */
				execShutdown(0);
			    else
#endif	/* WITH_SUID */
				execComm(line->command);
			}
#endif	/* WITH_COMMANDS || WITH_STEPEXEC */
		    } else {
			splitSeconds(&days, &hours, &minutes, &seconds, timeval);
			if (days > 9) {
			    snprintf(showbuf, sizeof(showbuf), "Tmr|>|%d|days", days);
			} else if (days > 1) {
			    snprintf(showbuf, sizeof(showbuf), "Tmr|%d|>|%d|days",
				    parseStrtol(line->argument, TRUE, "checkLines"), days);
			} else if (days == 1) {
			    snprintf(showbuf, sizeof(showbuf), "Tmr|%d|>|%d|day",
				    parseStrtol(line->argument, TRUE, "checkLines"), days);
			} else {
			    snprintf(showbuf, sizeof(showbuf), "Tmr|%d|%d:%02d:%02d",
				    parseStrtol(line->argument, TRUE, "checkLines"), hours, minutes, seconds);
			}
		    }
		}
		lineToDraw(showbuf, line);
		break;
	    case 30:	/* Alarm. */
	    case 31:	/* Reset alarm. */
		if (!line->status) {
		    if (!strcmp(line->type, "alarm"))
			snprintf(showbuf, sizeof(showbuf), "|Alarm|%s|", line->argument);
		    else
			snprintf(showbuf, sizeof(showbuf), "|Reset|%s|", line->argument);
		} else if (!strcmp(line->status, "setting") || !strcmp(line->status, "value")) {
		    if (line->buffer) {
			timeval = parseStrtol(line->buffer, TRUE, "checkLines");
			timeptr = localtime(&timeval);
		    } else {
			timeptr = localtime(&timenow);
		    }
		    if (!strcmp(line->type, "reset")) {
			if (!strcmp(line->status, "setting")) {
			    splitSeconds(&days, &hours, &minutes, &seconds, timeval);
			    snprintf(showbuf, sizeof(showbuf), "Interval|%02d:%02d",
				    hours + (days * 24), minutes);
			} else {
			    strftime(showbuf, sizeof(showbuf), "R||%m/%d||%H:%M", timeptr);
			}
		    } else {
			strftime(showbuf, sizeof(showbuf), "A||%m/%d||%H:%M", timeptr);
		    }
		} else if (!strcmp(line->status, "running")) {
		    timeval = parseStrtol(line->buffer, TRUE, "checkLines");
		    if (timeval <= timenow) {
			if (!strcmp(line->type, "alarm")) {
			    FREE(line->buffer);
			    FREE(line->status);
			    snprintf(showbuf, sizeof(showbuf), "|Alarm|%s|", line->argument);
			} else {
			    if (line->setting)
				k = parseStrtol(line->setting, TRUE, "checkLines");
			    else
				k = 86400;
			    while (timeval <= timenow)
				timeval += k;
			    snprintf(showbuf, sizeof(showbuf), "%d", (int)timeval);
			    STRDUP(line->buffer, showbuf);
			    timeptr = localtime(&timeval);
			    snprintf(showbuf, sizeof(showbuf), "Rs|%d/%02d|%02d:%02d",
				    timeptr->tm_mon + 1, timeptr->tm_mday,
				    timeptr->tm_hour, timeptr->tm_min);
			}
			if (!strcmp(line->type, "alarm") || timeval == timenow + k) {
			    makeNoise(line->sound);
#if WITH_COMMANDS || WITH_STEPEXEC
			    if (line->command) {
#if WITH_SUID
				if (!strcmp(line->command, "shutdown"))
				    execShutdown(0);
				else
#endif	/* WITH_SUID */
				    execComm(line->command);
			    }
#endif	/* WITH_COMMANDS || WITH_STEPEXEC */
			}
		    } else {
			timeptr = localtime(&timeval);
			snprintf(showbuf, sizeof(showbuf), "Al|%d/%02d|%02d:%02d",
				timeptr->tm_mon + 1, timeptr->tm_mday,
				timeptr->tm_hour, timeptr->tm_min);
			if (!strcmp(line->type, "reset")) {
			    showbuf[0] = 'R';
			    showbuf[1] = 's';
			}
		    }
		}
		lineToDraw(showbuf, line);
		break;
	    case 32:	/* Elapsed. */
		if (!line->status) {
		    snprintf(showbuf, sizeof(showbuf), "|Elapsed|%s|", line->argument);
		} else if (!strcmp(line->status, "running")) {
		    timeval = timenow - parseStrtol(line->value, TRUE, "checkLines");
		    goto showela;
		} else if (!strcmp(line->status, "stopped")) {
		    if (!line->buffer) {
			snprintf(showbuf, sizeof(showbuf), "%d", (int)timenow);
			STRDUP(line->buffer, showbuf);
		    }
		    timeval = parseStrtol(line->buffer, TRUE, "checkLines") - parseStrtol(line->value, TRUE, "checkLines");
showela:
		    splitSeconds(&days, &hours, &minutes, &seconds, timeval);
		    hours += (days * 24);
		    snprintf(showbuf, sizeof(showbuf), "Ela|%d|%d:%02d:%02d",
			     parseStrtol(line->argument, TRUE, "checkLines"), hours, minutes, seconds);
		} else if (!strcmp(line->status, "clear")) {
		    snprintf(showbuf, sizeof(showbuf), "Clear|Ela|%d?",
			    parseStrtol(line->argument, TRUE, "checkLines"));
		}
		lineToDraw(showbuf, line);
		break;
	    case 33:	/* Calendar */
		timeval = timenow;
		timenow += 86400 * parseStrtol(line->setting, FALSE, "checkLines");
		goto calends;
	    case 34:	/* Thermal zone. */
		/* line->argument is the device number
		 * line->setting is the optional scale
		 * line->status == 0, temp <= line->warn
		 * line->status == 1, temp <= line->crit
		 * line->status == 2, temp > line->crit
		 */
		getTherm();
		i = parseStrtol(line->argument, TRUE, "checkLines");
		if ((i + 1) > fresh.maxtherm || !*fresh.zones[i]) {
		    snprintf(showbuf, sizeof(showbuf), "tz[%d]|N/A", i);
		    line->color = line->color3;
		    lineToDraw(showbuf, line);
		    break;
		}
		j = *fresh.zones[i] - CELSIUS;
		k = parseStrtol(line->status, TRUE, "checkLines");
		if (j / 10 <= line->warn) {
#if WITH_SUID
		    if (k == 2 && thermstop && !powerstop)
			/* State changed from crit to norm. */
			thermstop = execShutdown(-1);
#endif	/* WITH_SUID */
		    if (g.thermlog && k != 0) {
			snprintf(buffer, sizeof(buffer), "Thermal zone #%s normal",
				line->argument);
			logMessage(buffer);
		    }
		    line->color = line->color1;
		    STRDUP(line->status, "0");
		} else if (j / 10 <= line->crit) {
		    if (!k && !filename)
			/* Changed state from norm to warn. */
			makeNoise(line->sound);
#if WITH_SUID
		    if (k == 2 && thermstop && !powerstop)
			/* State changed from crit to warn. */
			thermstop = execShutdown(-1);
#endif	/* WITH_SUID */
		    if (g.thermlog && k != 1) {
			snprintf(buffer, sizeof(buffer), "Thermal zone #%s warning",
				line->argument);
			logMessage(buffer);
		    }
		    line->color = line->color2;
		    STRDUP(line->status, "1");
		} else {
		    if (k < 2) {
			/* State changed from norm or warn to crit. */
			makeNoise(line->sound1);
#if WITH_SUID
			if (g.thermoff)
			    /* Attempt to shut things down. */
			    thermstop = execShutdown(g.thermtime);
#endif	/* WITH_SUID */
#if WITH_COMMANDS || WITH_STEPEXEC
			/* User thermal shutdown command. */
			if (g.thermcmd)
			    execComm(g.thermcmd);
#endif	/* WITH_COMMANDS || WITH_STEPEXEC */
			if (g.thermlog) {
			    snprintf(buffer, sizeof(buffer), "Thermal zone #%s critical",
				    line->argument);
#if WITH_SUID
			    if (thermstop)
				stringcat(buffer, ", executing shutdown", sizeof(buffer));
#endif	/* WITH_SUID */
			    logMessage(buffer);
			}
		    }
		    line->color = line->color3;
		    STRDUP(line->status, "2");
		}
		if (!line->setting || !strncasecmp("c", line->setting, 1)) {
		    snprintf(showbuf, sizeof(showbuf), "tz[%d]|%d.%d C", i, j / 10, j % 10);
		    showbuf[strlen(showbuf) - 2] = DEG;
		} else if (!strncasecmp("f", line->setting, 1)) {
		    j = ((j * 9) / 5) + 32;
		    snprintf(showbuf, sizeof(showbuf), "tz[%d]|%d.%d F", i, j / 10, j % 10);
		    showbuf[strlen(showbuf) - 2] = DEG;
		} else if (!strncasecmp("k", line->setting, 1)) {
		    j = *fresh.zones[i];
		    snprintf(showbuf, sizeof(showbuf), "tz[%d]|%d.%dk", i, j / 10, j % 10);
		}
		lineToDraw(showbuf, line);
		break;
	    case 35:	/* Current time. */
		timeval = timenow;
		if (g.phasetime && line->status)
		    timenow = g.phasetime;
calends:
		if (strstr(line->argument, "UTC") == line->argument) {
		    /* Universal time. */
		    timeptr = gmtime(&timenow);
		    utc = 3;
		} else {
		    timeptr = localtime(&timenow);
		    utc = 0;
		}
		if (!timeptr->tm_sec && line->setting && g.talker) {
		    /* Talking clock timeout. */
		    smalbuf[0] = '\0';
		    if (strchr(line->setting, ':')) {
			strftime(buffer, sizeof(buffer), "%H:%M", timeptr);
			STRDUP(ptr, line->setting);
			cp0 = ptr;
			while (ptr) {
			    if (!strcmp(buffer, strsep(&ptr, "_"))) {
				snprintf(smalbuf, sizeof(smalbuf), "%s", buffer);
				break;
			    }
			}
			FREE(cp0);
		    } else {
			k = parseStrtol(line->setting, TRUE, "checkLines");
			if (!(timeptr->tm_min % k))
			    strftime(smalbuf, sizeof(smalbuf), "%k:%M", timeptr);
		    }
		    if (strlen(smalbuf)) {
			if (!timeptr->tm_hour && !timeptr->tm_min) {
			    snprintf(smalbuf, sizeof(smalbuf), "zero hundred hours");
			} else if (!timeptr->tm_hour) {
			    strftime(smalbuf, sizeof(smalbuf), "zero hours:%M minutes", timeptr);
			    ptr = strchr(smalbuf, ':') + 1;
			    if (*ptr == '0')
				*ptr = ' ';
			} else if (!timeptr->tm_min) {
			    strftime(smalbuf, sizeof(smalbuf), "%k:hundred hours", timeptr);
			}
			snprintf(buffer, sizeof(buffer), "%s \"the time is %s\" &",
				g.talker, smalbuf);
			execComm(buffer);
		    }
		}
		snprintf(buffer, sizeof(buffer), "%s", line->argument + utc);
		if ((ptr = strstr(buffer, "--"))) {
		    /* Steering wheel. */
		    ptr -= 2;
		    if (strftime(showbuf, sizeof(showbuf), ptr, timeptr)) {
			errno = 0;
			j = (int)strtol(showbuf, NULL, 10);
			if (!errno) {
			    j = j % 100;
			    ptr += 2;
			    if ((j >= 11 && j <= 13) || !(j % 10) || (j % 10) >= 4) {
				*ptr++ = 't';
				*ptr = 'h';
			    } else {
				j = j % 10;
				if (j == 1) {
				    *ptr++ = 's';
				    *ptr = 't';
				} else if (j == 2) {
				    *ptr++ = 'n';
				    *ptr = 'd';
				} else {
				    *ptr++ = 'r';
				    *ptr = 'd';
				}
			    }
			}
		    }
		}
		if (!strftime(showbuf, sizeof(showbuf), buffer, timeptr))
		    snprintf(showbuf, sizeof(showbuf), "|Long|time|line|");
		if (utc && (ptr = strstr(showbuf, "GMT"))) {
		    /* If the local time zone is Greenwich, let it be
		     * so, but Coordinated Universal Time hasn`t been
		     * Greenwich Mean Time for decades.
		     */
		    *ptr++ = 'U';
		    *ptr++ = 'T';
		    *ptr = 'C';
		}
#if defined(__NetBSD__)
		/* Fix funny NetBSD strftime(), returns local zone instead
		 * of UTC or GMT with gmtime().  See what zone name is
		 * returned and look for it in the showbuf.
		 */
		if (utc && strstr(line->argument, "%Z") &&
			(i = strftime(buffer, sizeof(buffer), "%Z", timeptr)) &&
			(ptr = strstr(showbuf, buffer))) {
		    *ptr++ = 'U';
		    *ptr++ = 'T';
		    *ptr = 'C';
		}
#endif	/* defined(__NetBSD__) */
		lineToDraw(showbuf, line);
		timenow = timeval;
		break;
	    case 36:	/* Device. */
		/* This only determines which routine to use
		 * and changes the index and type to fit.
		 * It's only called once per line and only if
		 * the line->type is the generic type "device".
		 */
		if (!strncmp(line->argument, "/dev/", 5)) {
		    line->index = getIndex("devdevice");
		} else if (!strncmp(line->argument, "/", 1)) {
		    line->index = getIndex("mount");
		} else if (interFace(line)) {
		    line->index = getIndex("interface");
		} else if (remoteHost(line)) {
		    line->index = getIndex("remotehost");
		} else if (devInfo(line)) {
		    line->index = getIndex("devinfo");
		} else {
		    snprintf(buffer, sizeof(buffer),
			    "checkLines() device \"%s\" not recognized",
			    line->argument);
		    logMessage(buffer);
		    if (!line->setting) {
			STRDUP(line->setting, line->argument);
		    }
		    line->index = getIndex("display");
		}
		STRDUP(line->type, (char *)options[line->index].name);
		break;
	    case 37:	/* Devdevice */
		devDevice(line);
		goto showdev;
	    case 38:	/* Mount */
		mntPoint(line);
		goto showdev;
	    case 39:	/* Interface */
		interFace(line);
		goto showdev;
	    case 40:	/* Remotehost */
		remoteHost(line);
		goto showdev;
	    case 41:	/* Devinfo */
		devInfo(line);
showdev:
		if (line->buffer)
		    snprintf(showbuf, sizeof(showbuf), "|%s|", line->buffer);
		else if (line->setting)
		    snprintf(showbuf, sizeof(showbuf), "%s", line->setting);
		else
		    snprintf(showbuf, sizeof(showbuf), "|%s|", line->argument);
		lineToDraw(showbuf, line);
		break;
	    default:
		/* The rest are line specific options, not line types. */
		snprintf(buffer, sizeof(buffer), "checkLines() unrecognized type \"%s\"",
			line->type);
		gdayMate(buffer, NULL);
	}
    }
    /* This stops spurious log entries at startup. */
    FREE(filename);
}

int getIndex(const char *name) {
    int i=0;

    for (;; i++) {
	if (!options[i].name || !strcmp(options[i].name, name))
	    break;
    }
    return i;
}

/* Allocate and set initial values for a lineopts struct.
 */
struct lineopts *parseLine(char *linearg) {
    static int timers=1, alarms=1, reset=1, elapsed=1;
    struct lineopts *line, *l;
    int i, optval, stashind, count=1, maxlen=0, lasttype;
    char **args, *ptr=NULL, *cp0=NULL, *cp1=NULL, *buffer=NULL;
    char showbuf[SHOWBUF];

    CALLOC(line, 1, sizeof(struct lineopts), "parseLine");
    CALLOC(buffer, BUFSIZE, sizeof(char), "parseLine");
    if (!linelist) {
	linelist = line;
    } else {
	for (l = linelist; l->next; l = l->next);
	l->next = line;
	line->prev = l;
    }
    STRDUP(ptr, linearg);
    cp0 = ptr;
    while (ptr) {
#if defined(__NetBSD__)
	snprintf(buffer, BUFSIZE, "--%s", strsep(&ptr, "_"));
#else
	snprintf(buffer, BUFSIZE, "-%s", strsep(&ptr, "_"));
#endif
	if (strlen(buffer) >= maxlen)
	    maxlen = strlen(buffer) + 1;
	count++;
    }
    FREE(cp0);
    if (!(args = (char **)calloc(count * maxlen, sizeof(char))))
	allocFailed("parseLine");
    STRDUP(ptr, linearg);
    cp0 = ptr;
    for (i = 0; i < count; i++) {
	args[i] = (char *)calloc(maxlen, sizeof(char));
	if (!i) {
	    snprintf(args[i], maxlen, "salmon");
	} else {
	    snprintf(buffer, BUFSIZE, "%s", strsep(&ptr, "_"));
	    if ((cp1 = strchr(buffer, ' ')))
		*cp1 = '\0';
	    optval = getIndex(buffer);
	    if (cp1) {
		/* Fixes left out equals signs in config files. */
		if (options[optval].name && options[optval].has_arg)
		    *cp1 = '=';
		else
		    *cp1 = ' ';
	    }
#if defined(__NetBSD__)
	    snprintf(args[i], maxlen, "--%s", buffer);
#else
	    snprintf(args[i], maxlen, "-%s", buffer);
#endif
	}
    }
    FREE(cp0);
    lasttype = getIndex("devinfo");
    stashind = optind;
    optind = 0;
    while ((i = GETOPT(count, args, "", options, &optval)) != -1) {
	if (i == '?') {
	    optval = -1;
	} else if (optval >= 0 && optval <= lasttype) {
	    STRDUP(line->type, (char *)options[optval].name);
	}
	switch (optval) {
	    case  0: case  1: case  2: case  3: case  4: case  5: case  6:
	    case  7: case  8: case  9: case 10: case 11: case 12: case 13:
	    case 14: case 15: case 16: case 17: case 18: case 19: case 20:
	    case 22:
		/* Options that take no arguments. */
		break;
	    case 21:	/* Volume */
		if (!g.volconn) {
		    g.linecount += 2;
		    g.volconn = 1;
		}
		break;
	    case 23: case 24: case 25: case 26: case 27:
		/* user, nice, system, interrupt, idle */
		if (optarg) {
		    if (parseStrtol(optarg, FALSE, "parseLine") < 0) {	/* Trap funnies. */
			snprintf(smalbuf, SMALBUF,
				"parseLine() cpu number must be >= 0");
			gdayMate(smalbuf, strerror(EINVAL));
		    }
		    STRDUP(line->argument, optarg);
		}
		break;
	    case 28:	/* Localhost name */
		if (optarg) {
		    STRDUP(line->argument, "?");
		}
		break;
	    case 29:	/* Timer. */
		snprintf(showbuf, sizeof(showbuf), "%d", timers++);
		goto tarjump;
	    case 30:	/* Alarm */
		snprintf(showbuf, sizeof(showbuf), "%d", alarms++);
		goto tarjump;
	    case 31:	/* Resetting alarm */
		snprintf(showbuf, sizeof(showbuf), "%d", reset++);
tarjump:
		STRDUP(line->argument, showbuf);
		if (optarg) {
		    STRDUP(line->value, optarg);
		}
		break;
	    case 32:	/* Elapsed */
		snprintf(showbuf, sizeof(showbuf), "%d", elapsed++);
		STRDUP(line->argument, showbuf);
		if (optarg) {
		    snprintf(showbuf, sizeof(showbuf), "%d", (int)time(NULL));
		    STRDUP(line->value, showbuf);
		    STRDUP(line->status, "running");
		}
		break;
	    case 33:	/* Calendar */
		if (!g.calendar) {	/* Only do it once. */
		    g.calendar = 1;
		    if (optarg)
			g.calendar = 2;
		    g.linecount += 5;
		}
		break;
	    case 34:	/* Thermal zone */
		/* line->argument is the device number
		 * line->warn is the warning temp
		 * line->crit is the critical temp
		 * line->setting is the optional scale
		 * line->status is "0" temp <= warn
		 * line->status is "1" temp <= crit
		 * line->status is "2" temp > crit
		 */
		if (parseStrtol(optarg, FALSE, "parseLine") < 0)
		    gdayMate("parseLine() thermal zones must be >= 0", strerror(EDOM));
		STRDUP(line->argument, optarg);
		STRDUP(line->status, "0");
		break;
	    case 35:	/* Format */
	    case 36:	/* Device */
	    case 37:	/* Devdevice */
	    case 38:	/* Mount */
	    case 39:	/* Interface */
	    case 40:	/* Remotehost */
	    case 41:	/* Devinfo */
		STRDUP(line->argument, optarg);
		break;
	    case 42:
		line->warn = parseStrtol(optarg, TRUE, "parseLine");
		break;
	    case 43:
		line->crit = parseStrtol(optarg, TRUE, "parseLine");
		break;
	    case 44:
		line->color = pixAlloc(optarg);
		break;
	    case 45:
		line->color1 = pixAlloc(optarg);
		break;
	    case 46:
		line->color2 = pixAlloc(optarg);
		break;
	    case 47:
		line->color3 = pixAlloc(optarg);
		break;
	    case 48:
		line->sound = parseStrtol(optarg, FALSE, "parseLine");
		break;
	    case 49:
		line->sound1 = parseStrtol(optarg, TRUE, "parseLine");
		break;
	    case 50:
		line->panel = parseStrtol(optarg, TRUE, "parseLine");
		if (line->panel > 1)
		    g.panel = 1;
		break;
	    case 51:
		line->linenum = parseStrtol(optarg, TRUE, "parseLine");
		break;
	    case 52:
		STRDUP(line->setting, optarg);
		break;
	    case 53:
		STRDUP(line->command, optarg);
		break;
	    default:
		snprintf(buffer, BUFSIZE, "parseLine() option \"%s\" not recognized"
			" or missing/excess argument", args[optind - 1]);
		gdayMate(buffer, NULL);
	}
    }
    if (!line->type) {
	/* Default type, may not be set. */
	STRDUP(line->type, "display");
    }
    line->index = getIndex(line->type);
    optind = stashind;
    g.linecount++;
    FREE(buffer);
    FREE(args);
    return line;
}

const char *linestrings[] = {
    "free memory",
    "used memory",
    "total memory",
    "free swap",
    "used swap",
    "total swap",
    "number of swap devices",
    "one minute load average",
    "five minute load average",
    "fifteen minute load average",
    "running and total process counts",
    "share memory",
    "buffer memory",
    "cache memory",
    "uptime as hhhhh:mm:ss",
    "uptime as ddd hh:mm",
    "operating system type",
    "operating system version",
    "name of the local network",
    "phase of the moon in text",
    "power system status",
    "volume control function, three lines",
    "\"Display\" or setting=\"|One|Other|\"",
    NULL,
    "user cpu percent",
    "nice cpu percent",
    "system cpu percent",
    "interrupt cpu percent",
    "idle cpu percent",
    "name of the local host",
    "egg timer",
    "one time alarm",
    "resetting alarm",
    "time elapsed since started",
    "one or four panel calendar",
    "thermal zone status",
    "current time, user format",
    "generic device line, reset on first pass",
    "device is a file in the /dev/ directory",
    "device is a mountable file system",
    "device is a network interface",
    "device is a remote host",
    "device is a FreeBSD devinfo device",
    NULL,
    "warning level for power or temperature",
    "critical level for power or temperature",
    "color of this line, may be over ridden",
    "color for normal operation",
    "color for warning status",
    "color for critical status",
    "sound at expiration or when going into warning state",
    "sound when shifting into critical state",
    "panel number when operating in four panel mode",
    "location of line in display",
    "information specific to line type",
    "command to execute at expiration or when clicked",
    "",
    "The line syntax is: line= followed by an underscore separated list",
    "of options in any order.  See the salmon documentation for details.",
    "",
    "* A numeric argument to usage splits specifies cpu number (1 - n),",
    "\tno argument or argument equals zero for totals.",
    "* An argument to hostname[=?] capitalizes the first letter.",
    "* A timer argument of [[days ]hours:minutes:seconds] begins counting",
    "\tdown at salmon startup.",
    "* An alarm argument as [HH:MM [yy/mm/dd]] will expire when due or at",
    "\tstartup if past due.",
    "* A reset alarm argument as [HH:MM [yy/mm/dd]] will expire when due",
    "\tand reset to twenty-four hours or the specified time ahead.",
    "* An argument to elapsed[=?] begins counting up at startup.",
    "* An argument to calendar[=?] replaces local time with UTC.",
    "\n\t\t\t\t* * o * *\n",
    NULL                
};

void printTop() {

    fprintf(stdout, "\n\tSalmon version %s\n", PACKAGE_VERSION);
    printLines();
}

void printLines() {
    int i=0, j=0;

    fprintf(stdout, "\n\tDisplay line definitions: underscore separated list\n");
    fprintf(stdout, "\n\tsalmon ");
#if defined(__NetBSD__)
    fprintf(stdout, "-");
#endif
    fprintf(stdout, "-line=type[=argument]_option_option=argument...\n");
    fprintf(stdout, "\n\tTypes of display lines, no argument:\n");
    for (;; i++, j++) {
	if (!linestrings[j]) {
	    fprintf(stdout, "\n\tTypes that allow or require an argument:\n");
	    j++;
	    break;
	}
	fprintf(stdout, "\t%13s\t%s\n", options[i].name, linestrings[j]);
    }
    for (;; i++, j++) {
	if (!linestrings[j]) {
	    fprintf(stdout, "\n\tLine specific options:\n");
	    j++;
	    break;
	}
	if (options[i].has_arg)
	    fprintf(stdout, "\t%12s=\t%-32s", options[i].name, linestrings[j]);
	else
	    fprintf(stdout, "\t%13s\t%s", options[i].name, linestrings[j]);
	if (options[i].has_arg == 2)
	    fprintf(stdout, "%s", "* optional argument");
	fprintf(stdout, "\n");
    }
    for (;; i++, j++) {
	if (!options[i].name)
	    break;
	if (options[i].has_arg)
	    fprintf(stdout, "\t%12s=\t%-32s", options[i].name, linestrings[j]);
	else
	    fprintf(stdout, "\t%13s\t%s", options[i].name, linestrings[j]);
	if (options[i].has_arg == 2)
	    fprintf(stdout, "%s", "* optional argument");
	fprintf(stdout, "\n");
	if (!linestrings[j]) {
	    j++;
	    break;
	}
    }
    for (;; j++) {
	if (!linestrings[j])
	    break;
	fprintf(stdout, "\t%s\n", linestrings[j]);
    }
    exit(0);
}
* * o * *