/*	Cardmon:
 *		PCMCIA card monitor utility for X11
 *		Written by KIMURA Takamiti, <takamiti@tsden.org>
 *		cardmon.c 0.1 1998/02/15
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <regex.h>

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

#include <Xm/Xm.h>
#include <Xm/Form.h>
#include <Xm/Label.h>
#include <Xm/PushB.h>
#include <Xm/Separator.h>

#define MY_FRAME_WIDTH		250

#define EMPTY_COLOR		"gray40"
#define ACTIVE_COLOR		"antiquewhite2"
#define HIBERNATE_COLOR		"gold4"
#define POWER_COLOR		"magenta2"
#define SAFE_COLOR		"cyan4"

#define CONFIG_FILE		"/usr/local/lib/xpccard/config"
#define SERVER_NAME		"/var/tmp/.pccardd"
#define CLIENT_NAME		"/tmp/.xpccard"

#define CMD_NR_SLOTS		"S\n"
#define CMD_GET_INFO		"N%d\n"
#define CMD_PWR_ON		"P%d\n"
#define CMD_PWR_OFF		"Q%d\n"

#define MAX_PC_SLOT		4
#define MAX_INFO_LEN		64
#define MAX_CARD_NAME		MAX_INFO_LEN * 2
#define MAX_NAME_FEILD		5
#define NA			"N/A"

struct SlotFrame {
	Widget pwrbutton;
	Widget drvbutton;
	Widget label;
	Widget sep;
};
static struct SlotFrame slotFrame[MAX_PC_SLOT];
static XtAppContext appContext;
static Widget top, frame;
static Pixel statColor[4], PowerColor, SafeColor;

static int SOCKET;
static int nr_slot = 0;
static int slen, clen;
static char *myname;
static struct sockaddr_un saddr, caddr;

struct CardInfo {
	int  stat;
	char manuf[MAX_INFO_LEN+4];
	char  vers[MAX_INFO_LEN+4];
	char   drv[MAX_INFO_LEN+4];
	char  name[MAX_CARD_NAME+4];
};
static struct CardInfo cardInfo[MAX_PC_SLOT];

struct CardAlias {
	char *alias;
	char *manuf;
	char *vers;
	char *buff;
	struct CardAlias *next;
};
static struct CardAlias *aliasTop = NULL;
static struct CardAlias *aliasPrev;

static int  openSocket(void);
static void closeSocket(void);
static int  communicate(char *, char *, int);
static int  ismatch(char *, char*);
static char *nameAlias(struct CardAlias *, struct CardInfo *);
static int  parseCardName(char *, struct CardInfo *);
static int  shellWord(char **, char **);
static int  readConfig(char *);
static void freeAliasArray(struct CardAlias **);
static void sigHandl(int);
/* Xm, Xt */
static void getMyColor(void);
static void powerButton(Widget, XtPointer, XtPointer);
static void drvButton(Widget, XtPointer, XtPointer);
static void statChanged(Widget, int *, XtInputId *);
static int  widgetYsize(Widget *);
static int  widgetXsize(Widget *);
static int  makeSlotFrame(int);

/* ======================================= */
main(argc, argv)
int argc;
char **argv;
{
    int r, i, n;
    int minY, maxY, minX;
    char cmd[64], ansbuf[BUFSIZ];
    Arg av[10];

    if ((myname = strrchr(*argv, '/')) == NULL) {
	myname = *argv;
    } else {
	myname++;
    }

    if (openSocket() < 0) {
	perror("openSocket");
	exit(1);
    }

    if ((r = communicate(CMD_NR_SLOTS, ansbuf, BUFSIZ)) > 0) {
	nr_slot = atoi(ansbuf);
    }
    if (nr_slot == 0) {
	fprintf(stderr, "No card slots found\n");
	closeSocket();
	exit(0);
    }

    readConfig(CONFIG_FILE);
    /* dumpalias() */

    for(i = 0; i < nr_slot; i++) {
	sprintf(cmd, "N%d\n", i);
	if ((r = communicate(cmd, ansbuf, BUFSIZ)) > 0) {
	    parseCardName(ansbuf, &cardInfo[i]);
	    /* dumpname(i); */
	}
    }

    signal(SIGHUP,  sigHandl);
    signal(SIGINT,  sigHandl);
    signal(SIGTERM, sigHandl);
    signal(SIGQUIT, sigHandl);

    XtSetLanguageProc(NULL, NULL, NULL);
    top = XtOpenApplication(&appContext, "XPccard", NULL, 0, &argc, argv, NULL,
					sessionShellWidgetClass, NULL, 0);
    if (!top) {
	fprintf(stderr, "%s: cannot start X session\n", myname);
	closeSocket();
	exit(1);
    }
    if (argc > 1) {
	fprintf(stderr, "Usage: %s [X-Toolkit options]\n", myname);
	closeSocket();
	exit(1);
    }

    getMyColor();
    XtSetArg(av[0], XmNwidth, MY_FRAME_WIDTH);
    frame = XmCreateForm(top, "frame", av, 1);
    XtManageChild(frame);
    for(i = maxY = 0; i < nr_slot; i++) {
	maxY += makeSlotFrame(i);
    }

    if (nr_slot > 1) {
	minY = maxY / nr_slot + 2;
	maxY += 2;
    } else
	minY = maxY;
    minX = 6 + widgetXsize(&slotFrame[0].pwrbutton)
	     + widgetXsize(&slotFrame[0].drvbutton);
    n = 0;
    XtSetArg(av[n], XmNminHeight, minY); n++;
    XtSetArg(av[n], XmNmaxHeight, maxY); n++;
    XtSetArg(av[n], XmNminWidth,  minX); n++;
    XtSetArg(av[n], XmNmaxWidth,  MY_FRAME_WIDTH * 2); n++;
    XtSetValues(top, av, n);

    XtAppAddInput(appContext, SOCKET, (caddr_t)XtInputReadMask,
					(XtInputCallbackProc)statChanged, NULL);
    XtRealizeWidget(top);
    XtAppMainLoop(appContext);
}

static int openSocket()
{
    umask(077);
    if ((SOCKET = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
	return(-1);
    }
    bzero((char *)&caddr, sizeof(caddr));
    caddr.sun_family = AF_UNIX;
    sprintf(caddr.sun_path, "%s%06d", CLIENT_NAME, getpid());
    clen = SUN_LEN(&caddr);
    if (bind(SOCKET, (struct sockaddr *)&caddr, clen) < 0) {
	return(-1);
     }
    bzero((char *)&saddr, sizeof(saddr));
    saddr.sun_family = AF_UNIX;
    strcpy(saddr.sun_path, SERVER_NAME);
    slen = SUN_LEN(&saddr);
    return(1);
}

static void closeSocket()
{
    close(SOCKET);
    unlink(caddr.sun_path);
}

static int communicate(cmd, ansbuf, buflen)
char *cmd, *ansbuf;
int buflen;
{
    struct timeval timeout;
    fd_set fds;
    int n, k;

    n = strlen(cmd);
    if (n) {
	if (sendto(SOCKET, cmd, n, 0, (struct sockaddr *)&saddr, slen) != n) {
	    perror("send");
	    exit(1);
	}
    }

    if (buflen == 0) return(0);

    FD_ZERO(&fds);
    FD_SET(SOCKET, &fds);
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;
    n = 0;
    if (select(FD_SETSIZE, &fds, NULL, NULL, &timeout) > 0) {
	if (FD_ISSET(SOCKET, &fds)) {
	    k = 0;
	    if ((n = recvfrom(SOCKET, ansbuf, buflen, 0, NULL, &k)) < 0) {
		perror("recvfrom");
		exit(1);
	    }
	}
    }
    *(ansbuf + n) = 0;
    return(n);
}

static int ismatch(key, str)
char *key, *str;
{
    int r;
    regex_t exp;

    if ((r = regcomp(&exp, key, REG_EXTENDED | REG_NOSUB)) == 0) {
	r = regexec(&exp, str, (size_t)0, NULL, 0);
	regfree(&exp);
    }
    return(r == 0);
}

static char *nameAlias(array, card)
struct CardAlias *array;
struct CardInfo *card;
{
    while(array != NULL) {
	if (ismatch(array->manuf, card->manuf) && ismatch(array->vers, card->vers)) {
	    return(array->alias);
	}
	array = array->next;
    }
    return(NULL);
}

static int parseCardName(buf, card)
char *buf;
struct CardInfo *card;
{
    char *a[MAX_NAME_FEILD];
    char *p, *r, *s;
    int i, n, slot;

    if ((p = strdup(buf)) == NULL) {
	return(-1);
    }
    s = p + strlen(buf) - 1;
    while(s > p && *s == ' ') *s-- = 0;
    for(s = p, i = 0; i < MAX_NAME_FEILD; i++) {
	while(*s && *s == ' ') s++;
	a[i] = s;
	if (*s == 0) continue;
	while(*s && *s != '~') s++;
	if (i < MAX_NAME_FEILD - 1 && *s == '~') {
	    r = s - 1;
	    *s++ = 0;
	    while(r > a[i] && *r == ' ') *r-- = 0;
	}
    }
    slot = atoi(a[0]);
    card->stat = atoi(a[4]);
    strncpy(card->manuf, *a[1] ? a[1] : NA, MAX_INFO_LEN);
    strncpy(card->vers, *a[2] ? a[2] : NA, MAX_INFO_LEN);
    strncpy(card->drv, *a[3] ? a[3] : (card->stat == 2 ? "???" : NA), MAX_INFO_LEN);

    switch (card->stat) {
    case 0:
	strcpy(card->name, "Empty");
	break;
    case 2:
	strcpy(card->name, "Hibernating Card");
	break;
    default:
	if ((s = nameAlias(aliasTop, card)) == NULL) {
	    sprintf(card->name, "%s (%s)", card->manuf, card->vers);
	} else {
	    strncpy(card->name, s, MAX_CARD_NAME);
	}
	break;
    }
    free(p);
    return(slot);
}

static int shellWord(dst, src)
char **dst, **src;
{
    int quote, n;
    char *d, *s;

    d = *dst; s = *src;
    while (*s && (*s == ' ' || *s == '\t')) s++;
    quote = n = 0;
    while (*s) {
	if (quote == 0 && (*s == ' ' || *s == '\t')) break;
	switch(*s) {
	case '"':
	    if (quote)  quote = 0;
	    else	quote = 2;
	    s++;
	    break;
	case '\\':
	    s++;
	    if (*s == 0) break;
	default:
	    *d++ = *s++;  n++;
	    break;
	}
    }
    *d = 0;   *dst = ++d; *src = s;
    return(n);
}

static int readConfig(file)
char *file;
{
    FILE *fp;
    int n, i, cnt, line, len;
    char buff[1024];
    char *s, *p, *a[10], *dst;
    struct CardAlias *alp;

    if ((fp = fopen(file, "r")) == NULL) {
	return(-1);
    }

    line = cnt = 0;
    while(fgets(buff, sizeof(buff), fp) != NULL) {
	line++;
	*(buff + strlen(buff) - 1) = 0;
	s = buff;
	while(*s && (*s == ' ' || *s == '\t')) s++;
	if (*s == 0 || *s == '#') continue;

	if ((dst = malloc(strlen(s))) == NULL) {
	    return(-1);
	}
	n = 0;
	p = dst;
	len = 0;
	while (*s) {
	    a[n++] = p;
	    len += shellWord(&p, &s);
	}

	if (n == 4 && strcmp(a[0], "alias") == 0) {
	    alp = (struct CardAlias *)malloc(sizeof(struct CardAlias));
	    alp->buff  = dst;
	    alp->alias = a[1];
	    alp->manuf = a[2];
	    alp->vers  = a[3];
	    if (aliasTop == NULL)
		aliasTop = alp;
	    else {
		aliasPrev->next = alp;
		alp->next = NULL;
	    }
	    aliasPrev = alp;
	    cnt++;
	} else {
	    fprintf(stderr, "readConfig() #%d: illegal format.\n", line);
	    free(dst);
	    continue;
	}
    }
    fclose(fp);
    return(cnt);
}

static void freeAliasArray(array)
struct CardAlias **array;
{
    struct CardAlias *p, *chan;

    p = *array;
    while(p != NULL) {	
	chan = p->next;
	free(p->buff);
	free(p);
	p = chan;
    }
    *array = NULL;
}

static void sigHandl(sig)
int sig;
{
    switch(sig) {
    case SIGINT :
    case SIGTERM :
    case SIGQUIT :
	closeSocket();
	freeAliasArray(&aliasTop);
	XCloseDisplay(XtDisplay(top));
	exit(0);
    case SIGHUP :
	freeAliasArray(&aliasTop);
	readConfig(CONFIG_FILE);
	break;
    }
}

/*  --- for debug ---
dumpname(n)
int n;
{
    printf("\nslot  = %d\n", n);
    printf("stat  = %d\n", cardInfo[n].stat);
    printf("manuf = %s\n", cardInfo[n].manuf);
    printf("vers  = %s\n", cardInfo[n].vers);
    printf("drv   = %s\n", cardInfo[n].drv);
    printf("name  = %s\n", cardInfo[n].name);
}

dumpalias(head)
{
    while(head != NULL) {
	printf("\nalias = %s\n", head->alias);
	printf("manuf = %s\n", head->manuf);
	printf("vers  = %s\n", head->vers);
	head = head->next;
    }
}
*/

static void getMyColor()
{
    int n = 0;
    Colormap cmap;
    XColor closest, exact;

    cmap = DefaultColormap(XtDisplay(top), DefaultScreen(XtDisplay(top)));

	/* empty color */
    XAllocNamedColor(XtDisplay(top), cmap, EMPTY_COLOR, &closest, &exact);
    statColor[n++] = closest.pixel;

	/* active color */
    XAllocNamedColor(XtDisplay(top), cmap, ACTIVE_COLOR, &closest, &exact);
    statColor[n++] = closest.pixel;

	/* hibernate color */
    XAllocNamedColor(XtDisplay(top), cmap, HIBERNATE_COLOR, &closest, &exact);
    statColor[n++] = closest.pixel;

	/* power color */
    XAllocNamedColor(XtDisplay(top), cmap, POWER_COLOR, &closest, &exact);
    PowerColor = closest.pixel;

	/* safe color */
    XAllocNamedColor(XtDisplay(top), cmap, SAFE_COLOR, &closest, &exact);
    SafeColor = closest.pixel;
}

static int widgetYsize(w)
Widget *w;
{
    Arg av[1];
    int n = 0;

    XtSetArg(av[0], XmNheight, &n);
    XtGetValues(*w, av, 1);
    return(n);
}

static int widgetXsize(w)
Widget *w;
{
    Arg av[1];
    int n = 0;

    XtSetArg(av[0], XmNwidth, &n);
    XtGetValues(*w, av, 1);
    return(n);
}

static void powerButton(w, client, call)
Widget w;
XtPointer client, call;
{
    int slot = (int)client;
    char cmd[8];
    int n = 0;
    Arg av[10];

    switch (cardInfo[slot].stat) {
    case 0:	/* empty */
	return;
    case 1:	/* active */
	sprintf(cmd, CMD_PWR_OFF, slot);
	break;
    case 2:	/* hibernate */
	sprintf(cmd, CMD_PWR_ON, slot);
	XtSetArg(av[n], XmNtopShadowColor, PowerColor); n++;
	XtSetArg(av[n], XmNbottomShadowColor, PowerColor); n++;
	XtSetArg(av[n], XmNbackground, PowerColor); n++;
	XtSetValues(slotFrame[slot].drvbutton, av, n);
	break;
    }
    communicate(cmd, NULL, 0);
}

static void drvButton(w, client, call)
Widget w;
XtPointer client, call;
{
    /* not implemented. */
}

static void statChanged(client, socket, xid)
Widget client;
int *socket;
XtInputId *xid;
{
    int n, k, slot;
    char ansbuf[BUFSIZ];
    struct CardInfo card;
    XmString cstr;
    Arg av[10];
    Pixel c;

    k = 0;
    n = recvfrom(*socket, ansbuf, BUFSIZ, 0, NULL, &k);
    if (n == 0) return;

    *(ansbuf + n) = 0;
    slot = parseCardName(ansbuf, &card);
    if (card.stat == 2)
	cardInfo[slot].stat = card.stat;
    else
	cardInfo[slot] = card;

    n = 0;
    cstr = XmStringCreateLocalized(cardInfo[slot].name);
    XtSetArg(av[n], XmNlabelString, cstr); n++;
    XtSetArg(av[n], XmNbackground, statColor[cardInfo[slot].stat]); n++;
    XtSetValues(slotFrame[slot].label, av, n);
    XmStringFree(cstr);
    n = 0;
    cstr = XmStringCreateLocalized(cardInfo[slot].drv);
    XtSetArg(av[n], XmNlabelString, cstr); n++;
    c = cardInfo[slot].stat == 1 ? PowerColor : SafeColor;
    XtSetArg(av[n], XmNbackground, c); n++;
    XtSetArg(av[n], XmNtopShadowColor, c); n++;
    XtSetArg(av[n], XmNbottomShadowColor, c); n++;
    XtSetValues(slotFrame[slot].drvbutton, av, n);
    XmStringFree(cstr);
    switch(cardInfo[slot].stat) {
    case 1:
	XtSetSensitive(slotFrame[slot].pwrbutton, True);
	XtSetSensitive(slotFrame[slot].drvbutton, True);
	break;
    case 0:
	XtSetSensitive(slotFrame[slot].pwrbutton, False);
    case 2:
	XtSetSensitive(slotFrame[slot].drvbutton, False);
	break;
    }
}

static int makeSlotFrame(num)
int num;
{
    int n, y;
    Arg av[40];
    XmString cstr;
    Pixel c;
    char name[4], msg[256];

    y = 1;
    /* make power button */
    n = 0;
    sprintf(msg, " %d ", num);
    cstr = XmStringCreateLocalized(msg);
    XtSetArg(av[n], XmNlabelString, cstr); n++;
    XtSetArg(av[n], XmNborderWidth, 1); n++;
    XtSetArg(av[n], XmNsensitive, cardInfo[num].stat); n++;
    XtSetArg(av[n], XmNhighlightThickness, 0); n++;
    XtSetArg(av[n], XmNleftAttachment, XmATTACH_FORM); n++;
    XtSetArg(av[n], XmNleftOffset, 2); n++;
    XtSetArg(av[n], XmNtopOffset, y); n++;
    if (num == 0) {
	XtSetArg(av[n], XmNtopAttachment, XmATTACH_FORM); n++;
    } else {
	XtSetArg(av[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
	XtSetArg(av[n], XmNtopWidget, slotFrame[num-1].sep); n++;
	XtSetArg(av[n], XmNtopOffset, 0); n++;
    }
    sprintf(name, "P%d", num);
    slotFrame[num].pwrbutton = XmCreatePushButton(frame, name, av, n);
    XtManageChild(slotFrame[num].pwrbutton);
    y += widgetYsize(&slotFrame[num].pwrbutton);
    XmStringFree(cstr);

    /* make label */
    n = 0;
    cstr = XmStringCreateLocalized(cardInfo[num].name);
    XtSetArg(av[n], XmNlabelString, cstr); n++;
    XtSetArg(av[n], XmNbackground, statColor[cardInfo[num].stat]); n++;
/*  XtSetArg(av[n], XmNsensitive, cardInfo[num].stat); n++; */
    XtSetArg(av[n], XmNborderWidth, 2); n++;
    XtSetArg(av[n], XmNshadowThickness, 1); n++;
    XtSetArg(av[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
    XtSetArg(av[n], XmNrightAttachment, XmATTACH_FORM); n++;
    XtSetArg(av[n], XmNrightOffset, 2); n++;
    XtSetArg(av[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(av[n], XmNleftWidget, slotFrame[num].pwrbutton); n++;
    XtSetArg(av[n], XmNleftOffset, 40); n++;
    if (num == 0) {
	XtSetArg(av[n], XmNtopAttachment, XmATTACH_FORM); n++;
	XtSetArg(av[n], XmNtopOffset, 1); n++;
    } else {
	XtSetArg(av[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
	XtSetArg(av[n], XmNtopWidget, slotFrame[num-1].sep); n++;
	XtSetArg(av[n], XmNtopOffset, 0); n++;
    }
    *name = 'L';
    slotFrame[num].label = XmCreateLabel(frame, name, av, n);
    XtManageChild(slotFrame[num].label);
    XmStringFree(cstr);

    /* make drv button */
    n = 0;
    sprintf(msg, " %s ", cardInfo[num].drv);
    cstr = XmStringCreateLocalized(msg);
    XtSetArg(av[n], XmNlabelString, cstr); n++;
    XtSetArg(av[n], XmNsensitive, cardInfo[num].stat); n++;
    XtSetArg(av[n], XmNborderWidth, 1); n++;
    c = cardInfo[num].stat == 1 ? PowerColor : SafeColor;
    XtSetArg(av[n], XmNbackground, c); n++;
    XtSetArg(av[n], XmNtopShadowColor, c); n++;
    XtSetArg(av[n], XmNbottomShadowColor, c); n++;
    XtSetArg(av[n], XmNhighlightThickness, 0); n++;
    XtSetArg(av[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(av[n], XmNleftWidget, slotFrame[num].pwrbutton); n++;
    XtSetArg(av[n], XmNleftOffset, 0); n++;
    XtSetArg(av[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(av[n], XmNrightWidget, slotFrame[num].label); n++;
    XtSetArg(av[n], XmNrightOffset, 0); n++;
    if (num == 0) {
	XtSetArg(av[n], XmNtopAttachment, XmATTACH_FORM); n++;
	XtSetArg(av[n], XmNtopOffset, 1); n++;
    } else {
	XtSetArg(av[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
	XtSetArg(av[n], XmNtopWidget, slotFrame[num-1].sep); n++;
	XtSetArg(av[n], XmNtopOffset, 0); n++;
    }
    *name = 'D';
    slotFrame[num].drvbutton = XmCreatePushButton(frame, name, av, n);
    XtManageChild(slotFrame[num].drvbutton);
    XmStringFree(cstr);

    XtAddCallback(slotFrame[num].pwrbutton, XmNactivateCallback,
		powerButton, (XtPointer)num);
    XtAddCallback(slotFrame[num].drvbutton, XmNactivateCallback,
		drvButton, (XtPointer)num);

    /* make separator */
    n = 0;
    XtSetArg(av[n], XmNorientation, XmHORIZONTAL); n++;
    XtSetArg(av[n], XmNseparatorType, XmNO_LINE); n++;
    XtSetArg(av[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(av[n], XmNtopWidget, slotFrame[num].pwrbutton); n++;
    XtSetArg(av[n], XmNtopOffset, 0); n++;
    *name = 'S';
    slotFrame[num].sep = XmCreateSeparator(frame, name, av, n);
    XtManageChild(slotFrame[num].sep);
    y += widgetYsize(&slotFrame[num].sep);
    return(y);
}

