
/*
 *  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"

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 XBITSET(array,bit)   (array[(bit)>>3] |= 1<<((bit)&7))
#define XBITTEST(array,bit)  (array[(bit)>>3] & 1<<((bit)&7))


#define HASHSIZE  64

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

HASH *Hash[HASHSIZE];

static u_char isascii[0x80/8];	/* is printable ascii	*/
static u_char isalpha[0x80/8];	/* is alpha a-z/A-Z	*/
static u_char ctoa[0x80];	/* cvt to character	    */
static u_char cstoa[0x80];	/* cvt to shifted chacter   */

dealloc_hash()
{
    register HASH *hash, *hnext = NULL;
    register 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;
    }
}

resethash()
{
    register short i;

    static struct {
	char *from, *to;
    } defmap[] = {
	"esc",      "esc",
	"return",   "return insline up firstnb down",
	"enter",    "return",
	"up",       "up",
	"down",     "down",
	"right",    "right",
	"left",     "left",
	"bs",       "bs",
	"del",      "del",
	"help",     "newwindow newfile s:DME.DOC escimm `find '",
	"tab",      "tab",
	"s-up",     "top",
	"s-down",   "bottom",
	"s-right",  "last",
	"s-left",   "first",
	"s-tab",    "backtab",
	"s-del",    "deline",
	"s- ",      "` '",              /* shift space to space */
	"c-l",      "wleft",
	"c-r",      "wright",
	"c-i",      "insertmode on",
	"c-o",      "insertmode off",
	"c-j",      "join",
	"c-s",      "split first down",
	"c-del",    "remeol",
	"c-n",      "next",
	"c-p",      "prev",
	"c-/",      "escimm `find '",
	"c-g",      "escimm `goto '",
	"c-up",     "pageup",
	"c-down",   "pagedown",
	"c-q",      "quit",
	"c-f",      "reformat",
	"c-w",      "wordwrap toggle",
	"f1",       "escimm `insfile '",
	"f2",       "escimm `newfile '",
	"f3",       "escimm `newwindow newfile '",
	"f7",       "escimm `bsave '",
	"f8",       "saveold escimm `newfile '",
	"f9",       "saveold",
	"f10",      "saveold quit",
	"c-b",      "block",
	"c-u",      "unblock",
	"a-d",      "bdelete",
	"a-c",      "bcopy",
	"a-m",      "bmove",
	"a-s",      "bsource",
	"a-S",      "unblock block block bsource",
	"L-lmb",    "tomouse",      /*  left button                 */
	"L-mmo",    "tomouse",      /*  mouse move w/left held down */
	"R-rmb",    "iconify",      /*  right button                */
	NULL, NULL
    };

    dealloc_hash();
    loaddefaultkeymap();
    for (i = 0; defmap[i].from; ++i) {
	u_char code, qual;
	if (get_codequal(defmap[i].from, &code, &qual))
	    addhash(code, 1, 0xFF, qual, defmap[i].to);
    }
}

/*
 *  Go through keycodes $00 to $4F and load the ctoa[], cstoa[], and
 *  isalpha[] char tables from the default console keymap.
 */

loaddefaultkeymap()
{
    CIO cio;
    struct KeyMap km;

    /*
     * Note: -1 specification for unit # means that we are not openning
     * a real console.	We can only execute a CD_ASKDEFAULTKEYMAP through
     * it.
     */

    if (!OpenDevice("console.device", -1, &cio, 0)) {
	cio.io_Command = CD_ASKDEFAULTKEYMAP;
	cio.io_Data = (APTR)&km;
	cio.io_Length = sizeof(km);
	DoIO(&cio);
	loadhilo(km.km_LoKeyMapTypes, km.km_LoKeyMap, km.km_LoCapsable, 0, 0x40, 0x00);
	loadhilo(km.km_HiKeyMapTypes, km.km_HiKeyMap, km.km_HiCapsable, 0, 0x10, 0x40);
	CloseDevice(&cio);
    } else {
	if (Output())
	    puts ("Unable to get console keymap");
    }
}


/*
 *  Decode plain and shifted keys only.  Ignore strings larger than
 *  a single character (thus things like the cursor keys do not get
 *  mapped)
 */

loadhilo(types, map, caps, is, ie, ia)
u_char *types;
u_char *caps;
long *map;
{
    register long n;
    register int idx;
    register u_char *ptr;
    u_char c;

    for (; is < ie; ++is) {
	n = map[is];
	if (n && (types[is] & 0x60)) {	    /*	STRING 0x40 or BUG 0x20 */
	    ptr = (u_char *)n;
	    n = 0;
	    if (ptr[0] == 0)
		n = ptr[1];
	    if (ptr[0] == 1)
		n = ptr[ptr[1]];
	    if (types[is] & KCF_SHIFT) {
		if (ptr[2] == 0)
		    n |= ptr[3] << 8;
		if (ptr[2] == 1)
		    n |= ptr[ptr[3]] << 8;
	    }
	}
	idx = is + ia;
	c = n;
	ctoa[idx] = c;
	cstoa[idx] = (n>>8) & 0xFF;
	if (caps[is>>3] & (1 << (is&7)))
	    XBITSET(isalpha,idx);
	if (c >= 32 && c != 0x7F && ctoa[idx])
	    XBITSET(isascii,idx);
    }
}


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

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



addhash(code, stat, mask, qual, str)
u_char code, stat, mask, qual;
u_char *str;
{
    register HASH **p, *hash;

    hash = *(p = &Hash[code % HASHSIZE]);
    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(AllocMem(strlen(str)+1, 0), str);
}


remhash(code, mask, qual)
u_char code, mask, qual;
{
    register HASH *hash, **p;

    hash = *(p = &Hash[code % HASHSIZE]);
    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);
}


keyctl(code, qual)
register USHORT qual;
{
    register u_char c, c2;
    register HASH *hash;

    code &= 0xFF;

    if ((code & 0x78) == 0x60)	    /*	forget qualifier keys	*/
	return(0);
    if (code & 0x80)		    /*	forget upstrokes	*/
	return(0);

    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) && (code <= 0x37) && XBITTEST(isalpha,code))
	c2 |= QUAL_SHIFT;
    if (qual & IEQUALIFIER_LEFTBUTTON)
	c2 |= QUAL_LMB;
    if (qual & IEQUALIFIER_MIDBUTTON)
	c2 |= QUAL_MMB;
    if (qual & (IEQUALIFIER_RBUTTON))
	c2 |= QUAL_RMB;
    for (hash = Hash[code % HASHSIZE]; hash; hash = hash->next) {
	if (hash->code == code) {
	    if ((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) {
	char buf[256];
	/*printf ("c2 %lx BIT: %lx\n", c2, XBITTEST(isascii,code));*/
	if (c2 || !ComLineMode || !XBITTEST(isascii,code)) {
	    strcpy(buf, hash->str);
	    do_command(buf);
	    return(0);
	}
    }

    if (code < 0x50) {
	c = (c2 & QUAL_SHIFT) ? cstoa[code] : ctoa[code];
	if (c2 & QUAL_CTRL)
	    c &= 0x1F;
	if (c && (c2 & QUAL_ALT))
	    c |= 0x80;
	if (c && (c2 & QUAL_AMIGA))
	    c |= 0xC0;
	if (c) {
	    u_char buf[3];
	    buf[0] = '\'';
	    buf[1] = c;
	    buf[2] = 0;
	    do_command(buf);
	}
    }
}

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

long lname[] = {
    LN('e','s','c',0x45), LN('f','1', 0 ,0x50), LN('f','2', 0 ,0x51),
    LN('f','3', 0 ,0x52), LN('f','4', 0 ,0x53), LN('f','5', 0 ,0x54),
    LN('f','6', 0 ,0x55), LN('f','7', 0 ,0x56), LN('f','8', 0 ,0x57),
    LN('f','9', 0 ,0x58), LN('f','1','0',0x59), LN('d','e','l',0x46),
    LN('b','a','c',0x41), LN('b','s', 0 ,0x41), LN('t','a','b',0x42),
    LN('h','e','l',0x5F), LN('r','e','t',0x44), LN('u','p', 0 ,0x4C),
    LN('d','o','w',0x4D), LN('r','i','g',0x4E), LN('l','e','f',0x4F),
    LN('e','n','t',0x43), LN('n','k','-',0x4A), LN('n','k','.',0x3C),
    LN('n','k','0',0x0F),
    LN('n','k','1',0x1D), LN('n','k','2',0x1E), LN('n','k','3',0x1F),
    LN('n','k','4',0x2D), LN('n','k','5',0x2E), LN('n','k','6',0x2F),
    LN('n','k','7',0x3D), LN('n','k','8',0x3E), LN('n','k','9',0x3F),
    LN('l','m','b',0x68), LN('m','m','b',0x6A), LN('r','m','b',0x69),
    LN('m','m','o',QMOVE),
    0
};


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

    if (get_codequal(str, &code, &qual)) {
	for (hash = Hash[code % HASHSIZE]; hash; hash = hash->next) {
	    if (hash->code == code) {
		if (hash->qual == (qual & hash->mask))
		    return(hash->str);
	    }
	}
    }
    title ("Bad command or unmapped key");
    return(NULL);
}


get_codequal(str, pcode, pqual)
u_char *pcode, *pqual;
u_char *str;
{
    u_char qual;
    register 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)
	    ++str;
    }
notqual:
    *pqual = qual;
    if (strlen(str) != 1) {	      /* long name   */
	register short shift = 24;
	register long mult = 0;

	while (*str && shift >= 8) {
	    if (*str >= 'A' && *str <= 'Z')
		*str = *str - 'A' + 'a';
	    mult |= *str << shift;
	    shift -= 8;
	    ++str;
	}
	for (i = 0; lname[i]; ++i) {
	    if (mult == (lname[i] & 0xFFFFFF00)) {
		*pcode = lname[i] & 0x7F;
		return(1);
	    }
	}
    } else {			     /* short name  */
	for (i = 0; i < sizeof(ctoa); ++i) {
	    if (*str == ctoa[i]) {
		*pcode = i;
		return(1);
	    }
	}
	for (i = 0; i < sizeof(cstoa); ++i) {
	    if (*str == cstoa[i]) {
		*pcode = i;
		*pqual |= QUAL_SHIFT;
		return(1);
	    }
	}
    }
    return(0);
}

u_char *
cqtoa(code, qual)
{
    static u_char buf[32];
    register u_char *ptr = buf;
    register 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);
}


do_map()
{
    u_char code, qual;

    if (get_codequal(av[1], &code, &qual)) {
	addhash(code, 0, 0xFF, qual, av[2]);
    } else {
	title("Unknown Key");
    }
}

do_unmap()	  /* key   */
{
    u_char code, qual;

    if (get_codequal(av[1], &code, &qual)) {
	remhash(code, -1, qual);
    } else {
	title("Unknown Command");
    }
}

do_clearmap()
{
    resethash();
}

/*
 * SAVEMAP  file
 * SAVESMAP file
 */

do_savemap()
{
    char sysalso;
    char err = 0;
    u_char buf[256];
    long fi;
    register int i;
    register HASH *hash;

    fi = xopen(av[1], "w", 512);
    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) {
		    sprintf(buf, "map `%s' `%s'", cqtoa(hash->code, hash->qual), hash->str);
		    xputs(fi, buf);
		}
	    }
	}
	xclose(fi);
	if (err)
	    title ("Unable to Write");
	else
	    title ("OK");
    } else {
	title("Unable to open file");
    }
}


