
/*
 *  KEYBOARD.C
 *
 *	(C)Copyright 1987 by Matthew Dillon
 *
 *  Handle keyboard related stuff such as keyboard mappings.  Every time
 *  a key is pressed, KEYCTL() is called with the code.  KEYCTL() remembers
 *  which qualifier keys are currently held down, and when a non-qualifier
 *  key is pressed finds the hash entry for the key.  If no hash entry
 *  exists (e.g. you type a normal 'a') the default keymap is used.
 */

#include "defs.h"
#include <devices/keymap.h>
#include <devices/console.h>

Prototype void	  keyctl	 (IMESS *, int, USHORT);
Prototype void	  dealloc_hash	 (void);
Prototype void	  resethash	 (void);
Prototype int	  returnoveride  (int);
Prototype void	  addhash	 (ubyte, ubyte, ubyte, ubyte, const ubyte *);
Prototype int	  remhash	 (ubyte, ubyte, ubyte);
Prototype char	* keyspectomacro (char *);
Prototype void	  do_map	 (void);
Prototype void	  do_unmap	 (void);
Prototype void	  do_clearmap	 (void);
Prototype void	  do_savemap	 (void);
Prototype void	  keyboard_init  (void);
Prototype ubyte * cqtoa 	 (int, int);
Prototype int	  get_codequal	 (const ubyte *, ubyte *, ubyte *);

typedef struct IOStdReq CIO;

#define QUAL_SHIFT   0x01
#define QUAL_CTRL    0x02
#define QUAL_AMIGA   0x04
#define QUAL_ALT     0x08
#define QUAL_LMB     0x10
#define QUAL_MMB     0x20
#define QUAL_RMB     0x40

#define HASHSIZE  64		    /*	power of 2  */
#define HASHMASK  (HASHSIZE-1)

typedef struct _HASH {
    struct _HASH * next;    /* next hash   */
    ubyte	   code;    /* keycode	   */
    ubyte	   mask;    /* qual. mask  */
    ubyte	   qual;    /* qual. comp  */
    ubyte	   stat;    /* string static? */
    char	 * str;     /* command string */
} HASH;

HASH * Hash[HASHSIZE];

struct Library * ConsoleDevice;

static ubyte ctoa[128];
static ubyte cstoa[128];

void keyctl (IMESS * im, int code, USHORT qual)
{
    ubyte buf[MAXLINELEN];
    ubyte c2;
    short blen = 0;

    code &= 0xFF;
    if (im) {
	uword oldQual = im->Qualifier;

	im->Qualifier &= ~IEQUALIFIER_REPEAT;
	blen = DeadKeyConvert(im, buf+1, 254, NULL);
	im->Qualifier = oldQual;
	if (blen < 0)
	    return;
    }
    c2 = 0;
    if (qual & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
	c2 |= QUAL_SHIFT;
    if (qual & (IEQUALIFIER_CONTROL))
	c2 |= QUAL_CTRL;
    if (qual & (IEQUALIFIER_LCOMMAND|IEQUALIFIER_RCOMMAND))
	c2 |= QUAL_AMIGA;
    if (qual & (IEQUALIFIER_LALT|IEQUALIFIER_RALT))
	c2 |= QUAL_ALT;
    if ((qual & IEQUALIFIER_CAPSLOCK) && blen == 1 && islower (buf[1]))
	c2 |= QUAL_SHIFT;
    if (qual & IEQUALIFIER_LEFTBUTTON)
	c2 |= QUAL_LMB;
    if (qual & IEQUALIFIER_MIDBUTTON)
	c2 |= QUAL_MMB;
    if (qual & (IEQUALIFIER_RBUTTON))
	c2 |= QUAL_RMB;

    {
	HASH * hash;

	for (hash = Hash[code&HASHMASK]; hash; hash = hash->next) {
	    if (hash->code == code && (c2 & hash->mask) == hash->qual)
		break;
	}

	/*
	 *  Use hash entry only if not in command line mode, or if the
	 *  entry does not correspond to an alpha key.
	 */

	if (hash) {
	    if (c2 || !Comlinemode || blen > 1 || !ctoa[code]) {
		strcpy(buf, hash->str);
		do_command(buf);
		return;
	    }
	}
    }

    /*
     *	No hash entry
     */

    if (blen == 1) {
	buf[0] = '\'';
	buf[2] = 0;
    } else {
	buf[0] = '`';
	buf[blen+1] = '\'';
    }

    if (blen)
	do_command(buf);
} /* keyctl */


void dealloc_hash (void)
{
    HASH * hash,
	 * hnext = NULL;
    short  i;

    for (i = 0; i < HASHSIZE; ++i) {
	for (hash = Hash[i]; hash; hash = hnext) {
	    hnext = hash->next;
	    if (!hash->stat)
		FreeMem(hash->str, strlen(hash->str)+1);
	    FreeMem(hash, sizeof(HASH));
	}
	Hash[i] = NULL;
    }
} /* dealloc_hash */


void resethash (void)
{
    short i;
    CIO cio;
    static const struct {
	const char *from, *to;
    } defmap[] = {
	"esc",      "esc",              /* Map some special keys */
	"c-esc",    "recall",
	"enter",    "return",
	"up",       "up",
	"down",     "down",
	"right",    "right",
	"left",     "left",
	"bs",       "bs",
	"del",      "del",
	"tab",      "indent . .ct tab",
	"s-tab",    "indent . -.ct backtab",
	"return",   "return",
	"s-return", "firstnb down",
	"s- ",      "( )",              /* shift space to space */
	"s-del",    "deline",
	"c-c",      "",                 /* break.. map to a nop */
	"help",     "ref",

	/* Follow Styleguide */
	"A-n",      "newwindow arpload",
	"A-o",      "arpload",
	"A-s",      "saveold",
	"A-a",      "arpsave",
	"A-q",      "quit",
	"A-Q",      "quitall",
	"A-z",      "undo",
	"A-x",      "bsave t:xdme_clip bdelete'",
	"A-c",      "bsave t:xdme_clip unblock",
	"A-v",      "insfile t:xdme_clip",
	"A-f",      "escimm (findstr \`) next",
	"A-r",      "escimm (findstr \`) escimm (repstr \`) nextr",
	"A-e",      "escimm (execute \`)",
	"A-b",      "block",
	"A-i",      "iconify",

	"a-up",     "scrollup",
	"a-down",   "scrolldown",
	"a-left",   "wleft",
	"a-right",  "wright",

	"s-up",     "pageup",
	"s-down",   "pagedown",
	"s-right",  "pageright",
	"s-left",   "pageleft",

	"c-up",     "top",
	"c-down",   "bottom",
	"c-right",  "last",
	"c-left",   "set tmp $colno firstnb if x=$tmp first",

	"c-j",      "join",
	"c-i",      "insertmode toggle",
	"c-del",    "remeol",
	"c-w",      "wordwrap toggle",
	"c-g",      "escimm (goto )",
	"c-l",      "((^L))",               /* FF */
	"c-e",      "((^[))",               /* ESC */
	"c-m",      "match",

	"a-l",      "wleft",
	"a-r",      "wright",
	"a-n",      "next",
	"as-n",     "nextr",
	"a-p",      "prev",
	"as-p",     "prevr",
	"a-f",      "reformat",
	"a-i",      "indent b .t",
	"a-I",      "indent b -.t",
	"a-b",      "block",
	"a-c",      "bcopy",
	"a-d",      "bdelete",
	"a-m",      "bmove",
	"a-s",      "barpsave",

	"L-lmb",    "tomouse",      /*  left button                 */
	"L-mmo",    "tomouse",      /*  mouse move w/left held down */
	"R-rmb",    "iconify",      /*  right button                */
	NULL, NULL
    };

    dealloc_hash();

    OpenDevice ("console.device", -1, (struct IORequest *)&cio, 0);
    ConsoleDevice = (struct Library *)cio.io_Device;

    keyboard_init ();

    for (i = 0; defmap[i].from; ++i) {
	ubyte code,
	      qual;

	if (get_codequal (defmap[i].from, &code, &qual))
	    addhash (code, 1, 0xFF, qual, defmap[i].to);
    }
} /* resethash */


int returnoveride (int n)
{
    HASH *hash;
    static ubyte *str;
    static int stat;

    for (hash = Hash[0x44&HASHMASK]; hash; hash = hash->next) {
	if (hash->code == 0x44 && hash->qual == 0) {
	    if (n) {
		str = (ubyte *)hash->str;
		stat= hash->stat;
		hash->str = "return";
		hash->stat = 1;
	    } else {
		if (str == NULL) {
		    remhash(0x44, (ubyte)-1, 0);
		} else {
		    hash->str = (char *)str;
		    hash->stat= stat;
		}
	    }
	    return(0);
	}
    }

    if (n) {
	addhash (0x44, 1, 0xFF, 0, "return");
	str = NULL;
    }

    return (1);
} /* returnoveride */

void addhash (ubyte code, ubyte stat, ubyte mask, ubyte qual, const ubyte * str)
{
    HASH **p, *hash;

    hash = *(p = &Hash[code&HASHMASK]);
    while (hash) {
	if (hash->code == code && hash->qual == qual && hash->mask == mask) {
	    if (!hash->stat)
		FreeMem(hash->str, strlen(hash->str)+1);
	    goto newstr;
	}
	hash = *(p = &hash->next);
    }
    *p = hash = (HASH *)AllocMem(sizeof(HASH), 0);
    hash->next = NULL;
newstr:
    hash->code = code;
    hash->stat = stat;
    hash->mask = mask;
    hash->qual = qual;
    hash->str = (char *)str;

    if (!stat)                  /* if not static */
	hash->str = (char *)strcpy((char *)AllocMem(strlen(str)+1, MEMF_PUBLIC), str);
} /* addhash */


int remhash (ubyte code, ubyte mask, ubyte qual)
{
    HASH *hash, **p;

    hash = *(p = &Hash[code&HASHMASK]);
    while (hash) {
	if (hash->code == code && hash->qual == qual && hash->mask == mask) {
	    if (!hash->stat)
		FreeMem(hash->str, strlen(hash->str)+1);
	    *p = hash->next;
	    FreeMem(hash, sizeof(HASH));
	    return(1);
	}
	hash = *(p = &hash->next);
    }

    return (0);
} /* remhash */


char * keyspectomacro (char * str)
{
    HASH *hash;
    ubyte code, qual;

    if (get_codequal(str, &code, &qual)) {
	for (hash = Hash[code&HASHMASK]; hash; hash = hash->next) {
	    if (hash->code == code) {
		if (hash->qual == (qual & hash->mask)) {
		    return(hash->str);
		}
	    }
	}
    }
    return(NULL);
} /* keyspectomacro */


void do_map (void)
{
    ubyte code, qual;

    if (get_codequal(av[1], &code, &qual)) {
	addhash(code, 0, 0xFF, qual, av[2]);
    } else {
	error ("map:\nUnknown Key '%s'", av[1]);
    }
} /* do_map */


void do_unmap (void)        /* key   */
{
    ubyte code, qual;

    if (get_codequal(av[1], &code, &qual)) {
	remhash(code, (ubyte)-1, qual);
    } else {
	error ("unmap:\nUnknown Key '%s'", av[1]);
    }
} /* do_unmap */


void do_clearmap (void)
{
    resethash();
} /* do_clearmap */


/*
 * SAVEMAP  file
 * SAVESMAP file
 */

void do_savemap (void)
{
    char sysalso;
    char err = 0;
    char buf[MAXLINELEN];
    FILE *fi;
    int i;
    HASH *hash;
    ubyte *ptr;

    fi = fopen(av[1], "w");
    if (fi) {
	sysalso = av[0][4] == 's';
	for (i = 0; i < HASHSIZE; ++i) {
	    for (hash = Hash[i]; hash; hash = hash->next) {
		if (hash->stat == 0 || sysalso) {
		    char soc = '(';
		    char eoc = ')';
		    char ksoc = '(';
		    char keoc = ')';
		    short len;

		    for (ptr = (ubyte *)hash->str; *ptr; ++ptr) {
			if (*ptr == '(')
			    break;
			if (*ptr == '`') {
			    soc = '`';
			    eoc = '\'';
			    break;
			}
		    }
		    len = strlen(ptr = cqtoa(hash->code, hash->qual)) - 1;
		    if (ptr[len] == '(' || ptr[len] == ')') {
			ksoc = '`';
			keoc = '\'';
		    }
		    sprintf(buf, "map %c%s%c %c%s%c\n", ksoc, cqtoa(hash->code, hash->qual), keoc, soc, hash->str, eoc);
		    fputs(buf, fi);
		}
	    }
	}

	fclose(fi);

	if (err)
	    error ("savemap:\nUnable to write to file\n`%s'", av[1]);
	else
	    title ("OK");
    } else {
	error ("savemap:\nUnable to open file\n`%s'", av[1]);
    }
} /* do_savemap */


/*
 *  Nitty Gritty.
 *
 *  keyboard_init:  initialize for get_codequal() and cqtoa()
 *  get_codequal:   convert a qualifier-string combo to a keycode and qual.
 *  cqtoa:	    convert a keycode and qual to a qual & string
 */

#define LN(a,b,c,d)  ((a<<24)|(b<<16)|(c<<8)|d)

static long lname[] = {
    LN('e','s','c', 0  ), LN('f','1', 0 , 0  ), LN('f','2', 0 , 0  ),
    LN('f','3', 0 , 0  ), LN('f','4', 0 , 0  ), LN('f','5', 0 , 0  ),
    LN('f','6', 0 , 0  ), LN('f','7', 0 , 0  ), LN('f','8', 0 , 0  ),
    LN('f','9', 0 , 0  ), LN('f','1','0', 0  ), LN('d','e','l', 0  ),
    LN('b','a','c', 0  ), LN('b','s', 0 , 0  ), LN('t','a','b', 0  ),
    LN('h','e','l', 0  ), LN('r','e','t', 0  ), LN('u','p', 0 , 0  ),
    LN('d','o','w', 0  ), LN('r','i','g', 0  ), LN('l','e','f', 0  ),
    LN('e','n','t', 0  ), LN('n','k','-', 0  ), LN('n','k','.', 0  ),
    LN('n','k','0', 0  ),   /* 24 */
    LN('n','k','1', 0  ), LN('n','k','2', 0  ), LN('n','k','3', 0  ),
    LN('n','k','4', 0  ), LN('n','k','5', 0  ), LN('n','k','6', 0  ),
    LN('n','k','7', 0  ), LN('n','k','8', 0  ), LN('n','k','9', 0  ),
    LN('n','k','[', 0  ), LN('n','k',']', 0  ), LN('n','k','/', 0  ), /*34-36*/
    LN('n','k','*', 0  ), LN('n','k','+', 0  ),
    LN('l','m','b',0xE8), LN('m','m','b',0xEA), LN('r','m','b',0xE9),
    LN('m','m','o',QMOVE),
    0
};


/*
 *  ESC:	x1B
 *  FUNCKEYS:	x9B 30 7E to x9B 39 7E
 *  DEL:	x7E
 *  BS: 	x08
 *  TAB:	x09
 *  RETURN:	x0D
 *  HELP	x9B 3F 7E
 *  UP/D/L/R	x9B 41/42/44/43
 *  NK0-9,-,.,ENTER
 *
 *  Mouse buttons
 */

void keyboard_init (void)
{
    static struct InputEvent ievent = {
	NULL,
	IECLASS_RAWKEY,
    };
    ubyte buf[32];
    short i,
	  len;

    lname[16] |= 0x44;
    lname[21] |= 0x43;

    for (i = 0; i < 128; ++i) {
	ievent.ie_Code = i;
	ievent.ie_Qualifier = 0;
	ievent.ie_position.ie_addr = NULL;

	len = RawKeyConvert (&ievent, buf, 32, NULL);

	switch(len) {
	    case 1:	/*  ESC/DEL/BS/TAB/NKx	*/
		if ((buf[0] & 0x7F) >= 32 && (buf[0] & 0x7F) < 127)
		    ctoa[i] = buf[0];
		switch(buf[0]) {
		case 0x1B:  lname[ 0] |= i; break;
		case 0x7F:  lname[11] |= i; break;
		case 0x09:  lname[14] |= i; break;
		case 0x08:  lname[12] |= i; lname[13] |= i; break;
		case '(': if (i > 0x3A) lname[34] |= i; break;
		case ')': if (i > 0x3A) lname[35] |= i; break;
		case '/': if (i > 0x3A) lname[36] |= i; break;
		case '*': if (i > 0x3A) lname[37] |= i; break;
		case '-': if (i > 0x3A) lname[22] |= i; break;
		case '+': if (i > 0x3A) lname[38] |= i; break;
		case '.': if (i > 0x3A) lname[23] |= i; break;
		default:
		    if (i >= 0x0F && isdigit (buf[0]))
			lname[24+buf[0]-'0'] |= i;
		}
	    break;

	    case 2:	/*  cursor		*/
		if (buf[0] == 0x9B) {
		    switch(buf[1]) {
		    case 0x41:	lname[17] |= i;  break;
		    case 0x42:	lname[18] |= i;  break;
		    case 0x43:	lname[19] |= i;  break;
		    case 0x44:	lname[20] |= i;  break;
		    }
		}
	    break;

	    case 3:	/*  function/help	*/
		if (buf[0] == 0x9B && buf[2] == 0x7E) {
		    if (buf[1] == 0x3F)
			lname[15] |= i;
		    if (buf[1] >= 0x30 && buf[1] <= 0x39)
			lname[buf[1]-0x30+1] |= i;
		}
	    break;
	}
    }

    for (i = 0; i < 128; ++i) {
	ievent.ie_Code = i;
	ievent.ie_Qualifier = IEQUALIFIER_LSHIFT;
	ievent.ie_position.ie_addr = NULL;

	len = RawKeyConvert (&ievent,buf,32,NULL);

	if (len == 1)
	    cstoa[i] = buf[0];
    }

    {
	ubyte code, qual;

	get_codequal ("c", &code, &qual);

	CtlC = code;
    }
} /* keyboard_init */


ubyte * cqtoa (int code, int qual)
{
    static ubyte buf[32];
    ubyte *ptr = buf;
    int i;

    if (qual & QUAL_SHIFT)
	*ptr++ = 's';
    if (qual & QUAL_CTRL)
	*ptr++ = 'c';
    if (qual & QUAL_ALT)
	*ptr++ = 'a';
    if (qual & QUAL_AMIGA)
	*ptr++ = 'A';
    if (qual & QUAL_LMB)
	*ptr++ = 'L';
    if (qual & QUAL_MMB)
	*ptr++ = 'M';
    if (qual & QUAL_RMB)
	*ptr++ = 'R';
    if (qual)
	*ptr++ = '-';
    for (i = 0; i < sizeof(lname)/sizeof(lname[0]); ++i) {
	if ((lname[i]&0xFF) == code) {
	    *ptr++ = (lname[i]>>24);
	    *ptr++ = (lname[i]>>16);
	    *ptr++ = (lname[i]>>8);
	    break;
	}
    }
    if (i == sizeof(lname)/sizeof(lname[0]))
	*ptr++ = ctoa[code];
    *ptr++ = 0;
    return(buf);
} /* cqtoa */


int get_codequal (const ubyte * str, ubyte * pcode, ubyte * pqual)
{
    const char * base = str;
    ubyte qual;
    short i;

    qual = 0;
    if (strlen(str) > 1) {
	for (; *str && *str != '-'; ++str) {
	    if (*str == 's')
		qual |= QUAL_SHIFT;
	    if (*str == 'c')
		qual |= QUAL_CTRL;
	    if (*str == 'a')
		qual |= QUAL_ALT;
	    if (*str == 'A')
		qual |= QUAL_AMIGA;
	    if (*str == 'L')
		qual |= QUAL_LMB;
	    if (*str == 'M')
		qual |= QUAL_MMB;
	    if (*str == 'R')
		qual |= QUAL_RMB;
	    if (!qual)
		goto notqual;
	}
	if (*str == 0) {
	    qual = 0;
	    str = base;
	} else {
	    ++str;
	}
    }
notqual:
    if (strlen(str) != 1) {           /* long name   */
	short shift = 24;
	long mult = 0;
	ubyte c;

	*pqual = qual;
	while ((c = *str) && shift >= 8) {
	    c = tolower (c);
	    mult |= c << shift;
	    shift -= 8;
	    ++str;
	}
	for (i = 0; lname[i]; ++i) {
	    if (mult == (lname[i] & 0xFFFFFF00)) {
		*pcode = lname[i] & 0xFF;
		return(1);
	    }
	}
    } else {		    /*	single character keycap */
	for (i = 0; i < sizeof(ctoa); ++i) {
	    if (*str == ctoa[i]) {
		*pcode = i;
		*pqual = qual;
		return(1);
	    }
	}
	for (i = 0; i < sizeof(cstoa); ++i) {
	    if (*str == cstoa[i]) {
		*pcode = i;
		*pqual = qual|QUAL_SHIFT;
		return(1);
	    }
	}
    }
    return(0);
} /* get_codequal */

