/*
 * keybind -- console/virtual tty keyboard remapper for 6386WGS systems
 *
 * Copyright 1989 by Eric S. Raymond <esr@snark.uu.net>, all rights reserved
 * You may use, modify or redistribute this code freely, but do not delete
 * this notice. Check with me before you make money with it.
 */
#ifndef lint
static char *sccsid = "@(#)keybind.c	1.3";
#endif /* lint */

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/at_ansi.h>
#include <sys/kd.h>
#include <ctype.h>
#include "ascii.h"
#include "keybind.h"

#ifndef MAXSTRINGS
#define MAXSTRINGS	STRTABLN
#endif /* MAXSTRINGS */

extern FILE *yyin;
#ifdef YYDEBUG
extern int yydebug;
#endif /* YYDEBUG */

extern void exit();
extern void perror();

#define M(c)		(0x80 | (c))
#define F(n)		((n) + K_FUNF - 1)
#define VTK(n)		((n) + K_VTF)
#define IS_VTKEY(c)	(((c) >= K_VTF) && ((c) <= K_VTL))
#define MGR(n)		((n) + K_MGRF)
#define IS_MGRKEY(c)	(((c) >= K_MGRF) && ((c) <= K_MGRL))
#define PFX(n)		((n) + K_PFXF)
#define IS_PFXKEY(c)	(((c) >= K_PFXF) && ((c) <= K_PFXL))

#define META		0x80
#define PSPECIAL(p,i)	(p->spcl & (0x80>>(i)))

typedef struct
{
    int val;
    char *name;
}
symbol;

typedef struct
{
    int val;
    char *aname;
    char *mname;
}
triple;

kbd_t kbd = {KBDHDMAGIC};			/* our workspace */

static char	funkeys[NSTRKEYS][MAXFK+1];	/* function key strings */
static int	mapmods = 0;			/* count map table changes */
static int	stringmods = 0;			/* count string key changes */
static int	ttyfd;				/* fd of console */

/*
 * The name table for printout of keycaps is generated from the master
 * keycaps table.
 */
static char *keycaps[] =
{
#include "keycaps.h"
};

/*
 * Defaults tables -- regen defaults.h with keybind -c output if the system
 * defaults ever change. Be sure to do this *before* you let vtlmgr run!
 */
#include "kdefaults.h"

static triple specvals[] =
/* note: the second-column token have to match up with the lexical analyzer */
{
    K_NOP,    "nop",	"NOP",	/* Keys with no function */
    K_LSH,    "lshft",	"LSH",	/* Left shift */
    K_RSH,    "rshft",	"RSH",	/* Right shift */
    K_CLK,    "clock",	"CLK",	/* Caps lock */
    K_NLK,    "nlock",	"NLK",	/* Num lock */
    K_SLK,    "slock",	"SLK",	/* Scroll lock */
    K_ALT,    "alt",	"ALT",	/* Alt */
    K_BTAB,   "btab",	"BTAB",	/* Back tab */
    K_CTL,    "ctl",	"CTL",	/* Control */
    K_LAL,    "lalt",	"LAL",	/* Left alt */
    K_RAL,    "ralt",	"RAL",	/* Right alt */
    K_LCT,    "lctrl",	"LCT",	/* Left control */
    K_RCT,    "rctrl",	"RCT",	/* Right control */
    K_SRQ,    "sysreq",	"SRQ",	/* System request */
    K_BRK,    "break",	"BRK",	/* Break */
    K_ESN,    "escn",	"ESN",	/* <ESC> N <unalt'd value> sequence */
    K_ESO,    "esco",	"ESO",	/* <ESC> O <unalt'd value> sequence */
    K_ESL,    "escl",	"ESL",	/* <ESC> L <unalt'd value> sequence */
    K_RBT,    "reboot",	"RBT",	/* Reboot system */
    K_DBG,    "debug",	"DBG",	/* Invoke debugger */
    K_NEXT,    "next",	"NEXT",	/* Next virtual terminal */
    K_PREV,    "prev",	"PREV",	/* Previous virtual terminal */
    K_FRCNEXT,    "frcnext",    "FRCNEXT",
    K_FRCPREV,    "frcprev",    "FRCPREV",
    -1
};

#ifdef REGEN
/*
 * This enables us to regenerate the defaults
 */
static void cdump_keymap()
{
    int	j;

    static char *mascii[] =		/* use Microsoft's IBM-style NL/HT */
    {
	"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
	"BS",  "HT",  "NL",  "VT",  "FF",  "CR",  "SO",  "SI", 
	"DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
	"CAN", "EM",  "SUB", "ESC", "FS",  "GS",  "RS",  "US",
    };

    struct key_t *kp;

    (void) printf("static keymap_t keydefaults[] = \n{\n    %d,\n    {\n",
		  kbd.keymap.n_keys);

    for (kp = &(kbd.keymap.key[0]); kp < &(kbd.keymap.key[kbd.keymap.n_keys]); kp++)
    {
	char	repbuf[10];

	(void) printf("/*%03d*/{{", kp - &(kbd.keymap.key[0]));
	for (j = 0; j < NUM_STATES; j++)
	{
	    int	c = kp->map[j];

	    if (PSPECIAL(kp, j))
	    {
		triple *sp;
	
		for (sp = specvals; sp->val != -1; sp++)
		    if (sp->val == c)
		    {
			(void) strcpy(repbuf, "K_");
			(void) strcat(repbuf, sp->mname);
			break;
		    }
		if (sp->val == -1)
		    if (IS_FUNKEY(c))
			(void) sprintf(repbuf, "F(%d)", c - K_FUNF + 1);
		    else if (IS_VTKEY(c))
			(void) sprintf(repbuf, "VTK(%d)", c - K_VTF + 1);
		    else if (IS_MGRKEY(c))
			(void) sprintf(repbuf, "MGR(%d)", c - K_MGRF + 1);
		    else if (IS_PFXKEY(c))
			(void) sprintf(repbuf, "PFX(%d)", c - K_PFXF + 1);
		    else
			(void) sprintf(repbuf, "%d", c);
	    }
	    else
	    {
		int alt = (c & 0x80);

		if (alt)
		{
		    (void) strcpy(repbuf, "M(");
		    c &= 0x7F;
		}
		else
		    (void) strcpy(repbuf, "");
		if (isprint(c))
		{
		    if (c == '\'')
			(void) strcat(repbuf, "'\\''");
		    else if (c == '\\')
			(void) strcat(repbuf, "'\\\\'");
		    else
			(void) sprintf(repbuf + strlen(repbuf), "'%c'", c);
		}
		else if (c == A_DEL)
		    (void) strcat(repbuf, "A_DEL");
		else if (iscntrl(c))
		{
		    (void) strcat(repbuf, "A_");
		    (void) strcat(repbuf, mascii[c]);
		}
		if (alt)
		    (void) strcat(repbuf + strlen(repbuf), ")");
	    }

	    if (j < NUM_STATES - 1)
		(void) strcat(repbuf + strlen(repbuf), ",");
	    (void) printf("%-7s", repbuf);
	}
	(void) printf("}, 0x%02x,0x%02x},\n", kp->spcl, kp->flgs);
    }
    (void) printf("    }\n};\n\n");

    (void) printf("static char *stringdefaults[] =\n{\n");
    for (j = 0; j < NSTRKEYS; j++)
    {
	register char *cp;

	(void) printf("    \"");
	for (cp = funkeys[j]; *cp; cp++)
	    if (isprint(*cp))
		(void) putchar(*cp);
	    else
		(void) printf("\\%03o", *cp);
	(void) printf("\",\n");
    }
    (void) printf("};\n");
}
#endif /* REGEN */

/*
 * Here are the dump functions for human use. Each of these may be
 * called by YACC productions, all are called by dump_all() below.
 */

void list_shift(flag, leader)
int flag;
char *leader;
{
    struct key_t *kp;

    for (kp = kbd.keymap.key; kp < &(kbd.keymap.key[kbd.keymap.n_keys]); kp++)
	if (kp[0].flgs & flag)
	    break;

    (void) printf("%s enabled\t", leader);
    if (kp >= kbd.keymap.key + kbd.keymap.n_keys)
    {
	(void) printf("# nowhere\n");
	return;
    }
    else
	(void) printf("%s", keycaps[kp - kbd.keymap.key]);

    kbd.keymap.key[NUM_KEYS].flgs = ~kbd.keymap.key[NUM_KEYS - 1].flgs;
    for (++kp ; kp <= kbd.keymap.key + kbd.keymap.n_keys; kp++)
	if ((kp[0].flgs & flag) != (kp[-1].flgs & flag))
	    if (kp[0].flgs & flag)
	    {
		if (kp < kbd.keymap.key + NUM_KEYS)
		    (void) printf(" %s", keycaps[kp - kbd.keymap.key]); 
	    }
    	    else
		(void) printf(" - %s", keycaps[kp - kbd.keymap.key - 1]); 
    (void) putchar('\n');
}

void dump_keymap(lower, upper)
int lower, upper;
{
    int i;

    static char *ascii[] =		/* use common names */
    {
	"nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel",
	"bs",  "tab",  "lf",  "vt",  "ff",  "cr",  "so",  "si", 
	"dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb",
	"can", "em",  "sub", "esc", "fs",  "gs",  "rs",  "us",
    };
    struct key_t *kp;

    for (kp = &(kbd.keymap.key[lower]); kp <= kbd.keymap.key + upper; kp++)
    {
	char	repbuf[10];
	int	j;

	(void) printf("%-12s  ", keycaps[kp - &(kbd.keymap.key[0])]);

	for (j = 0; j < NUM_STATES; j++)
	{
	    int	c = kp->map[j];

	    if (PSPECIAL(kp, j))
	    {
		triple *sp;
	
		for (sp = specvals; sp->val != -1; sp++)
		    if (sp->val == c)
		    {
			(void) strcpy(repbuf, sp->aname);
			break;
		    }
		if (sp->val == -1)
		    if (IS_FUNKEY(c))
			(void) sprintf(repbuf, "fkey%d", c - K_FUNF + 1);
		    else if (IS_VTKEY(c))
			(void) sprintf(repbuf, "vt%d", c - K_VTF + 1);
		    else if (IS_MGRKEY(c))
			(void) sprintf(repbuf, "mgr%d", c - K_MGRF + 1);
		    else if (IS_PFXKEY(c))
			(void) sprintf(repbuf, "pfx%d", c - K_PFXF + 1);
		    else
			(void) sprintf(repbuf, "%d", c);
	    }
	    else
	    {
		int alt = (c & META);

		if (alt)
		    c = (c & ~META);
		if (isprint(c))
		{
		    if (alt)
			(void) strcpy(repbuf, "'!");
		    else
			(void) strcpy(repbuf, "'");
		    if (c == '\'')
			(void) strcat(repbuf, "\\''");
		    else if (c == '\\')
			(void) strcat(repbuf, "\\\\'");
		    else
			(void) sprintf(repbuf+strlen(repbuf), "%c'", c);
		}
		else if (iscntrl(c) || c == A_DEL)
		{
		    if (alt)
			(void) strcpy(repbuf, "!");
		    else
			(void) strcpy(repbuf, "");
		    if (c == A_DEL)
			(void) strcat(repbuf, "del");
		    else
			(void) strcat(repbuf, ascii[c]);
		}
	    }

	    (void) printf("%-8s", repbuf);
	}
	(void) putchar('\n');
    }
}

void dump_funkeys(lower, upper)
int lower, upper;  
{
    int i;

    for (i = lower; i <= upper; i++)
	if (strlen(funkeys[i - 1]))
	{
	    (void) printf("fkey%d = \"", i);
	    dump_string(funkeys[i-1]);
	    (void) putchar('"');
	    (void) putchar('\n');
	}
}

static void dump_all()
{
    time_t	now = time((time_t *)0);
    int i;

    (void) printf("#\n# Key bindings:\n#\n");
    (void) printf(
"#                                     Ctrl             Alt    Alt    Alt-Ctrl\n");
    (void) printf(
"#Code         Normal  Shift   Ctrl    Shift   Alt     Shift   Ctrl    Shift\n");
    (void) printf(
"#--------------------------------------------------------------------------\n");

    dump_keymap(0, kbd.keymap.n_keys - 1);

    (void) printf("#\n# Shift keys:\n#\n");
    list_shift(KMF_CLOCK, "capslock");
    list_shift(KMF_NLOCK, "numlock ");

    (void) printf("#\n# Function keys:\n#\n");
    dump_funkeys(1, NSTRKEYS);
}

/*
 * Functions for meta-key enabling
 */

int weak_binding(p, state)
/* test if key state has a `weak binding', not char or important special */
struct key_t *p;
int state;
{
    if (!PSPECIAL(p, state))
	return(0);
    else
    {
	register unchar val = p->map[state];

	return(val == K_NOP || val == K_ESN || val == K_ESO || val == K_ESL);
    }
}

void enable_alt()
{
    struct key_t *kp;

    /* for each non-special key that is ASCII printable in base state */
    for (kp = &(kbd.keymap.key[0]); kp < &(kbd.keymap.key[kbd.keymap.n_keys]); kp++)
	if (!PSPECIAL(kp, NORMAL) && isprint(kp->map[NORMAL]))
	{
	    if (weak_binding(kp, ALTED))
	    {
		kp->map[ALTED] 	= (kp->map[NORMAL] | META);
		kp->spcl &=~ 0x08;
	    }
	    if (weak_binding(kp, ALTSHF))
	    {
		kp->map[ALTSHF]	= (kp->map[SHIFT] | META);
		kp->spcl &=~ 0x04;
	    }
	    if (weak_binding(kp, ALTCTL))
	    {
		kp->map[ALTCTL]	= (kp->map[CTRL] | META);
		kp->spcl &=~ 0x02;
	    }
	    if (weak_binding(kp, ALTSHFCTL))
	    {
		kp->map[ALTSHFCTL] = (kp->map[SHFCTL] | META);
		kp->spcl &=~ 0x01;
	    }
	}
    mapmods++;
}

/*
 * Other major functions. If it isn't static, it's because YACC may call it.
 */

void restore_defaults()
{
    int	i;

    (void) memcpy(&kbd.keymap, &keydefaults, sizeof(keymap_t));

    for (i = 0; i < NSTRKEYS; i++)
	(void) strcpy(funkeys[i], stringdefaults[i]);
    mapmods++;
    
}

int setkey(scancode, indx, kfunc)
int scancode;	/* scancode */
int indx;	/* map index */
int kfunc;	/* key function */
{
    struct key_t	*kp = kbd.keymap.key + scancode;
#ifdef DEBUG
    (void) printf("keybind: code %d/state %d becomes %s%d\n",
		  scancode, indx,
		  (kfunc >= NUM_KEYS ? "special ": "character "),
		  kfunc - (kfunc >= NUM_KEYS) * NUM_KEYS);
#endif /* DEBUG */

    if (kfunc < NUM_KEYS)
	kp->spcl &=~ (0x80>>indx);
    else
    {
	kp->spcl |= (0x80>>indx);
	kfunc -= NUM_KEYS;
    }
    kp->map[indx] = kfunc;
    mapmods++;
}

int setstring(fnkey, str)
int fnkey;
char *str;
{
    extern char *malloc();
    char *cp, *tp;

#ifdef DEBUG
    (void) printf("keybind: binding function key %d to \"", fnkey);
    dump_string(str);
    (void) putchar('"');
    (void) putchar('\n');
#endif /* DEBUG */

    (void) strncpy(funkeys[fnkey], str, MAXFK);
    stringmods++;
}

void clear_strings()
{
    (void) memset(funkeys, '\0', (MAXFK + 1) * NSTRKEYS);
}

void do_changes()
/* execute all pending changes to key and string maps */
{
    if (mapmods)
	if (ioctl(ttyfd, PIO_KEYMAP, &kbd.keymap) == -1)
	    perror("keybind: PIO_KEYMAP ioctl failed");

    if (stringmods)
    {
	int		i;
	unchar		*sp = kbd.strmap;
	
	for (i = 0; i < NSTRKEYS; i++)
	{
	    if (sp + strlen(sp) + 1 - kbd.strmap > MAXSTRINGS)
	    {
		(void) fprintf(stderr,
			       "keybind: out of string space at fkey %d\n", i);
		return;
	    }
	    (void) strcpy(sp, funkeys[i]);
	    sp += strlen(sp) + 1;
	}
	    
	if (ioctl(ttyfd, PIO_STRMAP, kbd.strmap) == -1)
	    perror("keybind: PIO_STRMAP ioctl failed");
    }
}

/*
 * The main sequence. All this does is the keymap read, a dispatch on options,
 * and the keymap write.
 */

main(argc, argv)
int argc;
char **argv;
{
    int i, c, inputcount = 0;
    unchar *cp;
    char ofname[BUFSIZ];

    if ((ttyfd = open("/dev/tty", O_RDONLY)) < 0
    		&& (ttyfd = open("/dev/console", O_RDONLY)) < 0)
    {
	perror("keybind: tty open failed!");
	exit(1);
    }

    if (ioctl(ttyfd, GIO_KEYMAP, &kbd.keymap) == -1)
    {
	perror("keybind: GIO_KEYMAP ioctl failed");
	exit(1);
    }

    if (ioctl(ttyfd, GIO_STRMAP, kbd.strmap) == -1)
    {
	perror("keybind: GIO_STRMAP ioctl failed");
	exit(1);
    }
    for (i = 0, cp = kbd.strmap; *cp; cp += strlen(cp) + 1)
	(void) strcpy(funkeys[i++], cp);

    for (++argv, --argc; argc; ++argv, --argc)
    {
	if (**argv == '-' && argv[0][1])
	{
	    while (*++*argv != '\0')
		switch(**argv)
		{
		case 'm': enable_alt(); break;
#ifdef REGEN
		case 'c': cdump_keymap(); break;
#endif /* REGEN */
		case 'd': dump_all(); break;
		case 'r': restore_defaults(); inputcount++; break;
		case 'o':
		    (void)strcpy(ofname, *++argv);
		    argc--;
		    goto nextarg;	/* ick... */
		default:
		    (void) fputs("usage: keymap [-mdrv] [-] [files...]\n",
				 stderr);
		    exit(1);
		}
	    continue;
	}
	else
	{
	    FILE	*fp;

	    if (**argv == '-')
	    {
		inputcount++;
		yyin = stdin;
		yyparse();
	    }
	    else if ((fp = fopen(*argv, "r")) == (FILE *)NULL)
	    {
		perror(*argv);
		exit(1);
	    }
	    else	/* O.K., it's a valid input source */
	    {
		int	c = fgetc(fp);	/* peek at first character */

		inputcount++;
		(void) ungetc(c, fp);

		/* if it looks like ascii, run source through interpreter */
		if (isprint(c))
		{
		    yyin = fp;
		    yyparse();
		}
		else if (fread(&kbd,sizeof(kbd_t),1,fp)!=1 || kbd.magic!=KBDHDMAGIC)
		{
		    (void) fprintf(stderr, "keybind: garbled map dump file\n");
		    exit(1);
		}
		else		/* we have a valid dump file */
		{
		    mapmods++;
		    stringmods++;

		    /* massage the loaded fundefs into something useful */
		    for (i = 0, cp = kbd.strmap; *cp; cp += strlen(cp) + 1)
			(void) strcpy(funkeys[i++], cp);
		}
	    }
	}
    nextarg:;
    }

    /* if no file or - arguments, process stdin once */
    if (inputcount == 0)
    {
	yyin = stdin;
	yyparse();
    }
    
    if (ofname[0] == '\0')
	do_changes();
    else
    {
	FILE	*ofp;

	ofp = fopen(ofname, "w");
	(void) fwrite(&kbd, sizeof(kbd_t), 1, ofp);
	(void) fclose(ofp);
    }

    (void) close(ttyfd);
    exit(0);    
}

/* keybind.c ends here */
