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


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;

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

void keyctl(struct IntuiMessage *im, int code, unsigned short qual)
{
    ubyte buf[256];
    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 && buf[1] >= 'a' && buf[1] <= 'z')
        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.
         */


     /* Problem with rawkey-code 0 fixed: Wasn't able to enter `-sign */
     /* was: if (hash) {                                              */

        if (hash && code > 0) {
            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);
}

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;
    }
}

void resethash(void)
{
    short i;
    CIO cio;
    static const struct {
        char *from, *to;   /* const char *from,*to; */
    } defmap[] = {
        "esc",      "esc",
        "c-esc",    "recall",
        "return",   "split return set oldcol \$(colno)",
        "enter",    "firstnb downadd insline set oldcol \$(colno)",
        "up",       "up col \$(oldcol) if r (last)",
        "down",     "down col \$(oldcol) if r (last)",
        "right",    "ifelse r (down first) (right) set oldcol \$(colno)",
        "left",     "ifelse l (up last) (left) set oldcol \$(colno)",
        "bs",       "ifelse l (up last join2) (bs) set oldcol \$(colno)",
        "del",      "ifelse r (join2) (del) set oldcol \$(colno)",
        "tab",      "tab",
        "a-up",     "upfast col \$(oldcol) if r (last)",
        "a-down",   "downfast col \$(oldcol) if r (last)",
        "a-r",      "nextr",
        "a-u",      "while cl (tlate -32 right)",
        "a-l",      "while cu (tlate +32 right)",
        "s-up",     "pageup col \$(oldcol) if r (last)",
        "s-down",   "pagedown col \$(oldcol) if r (last)",
        "s-right",  "wright set oldcol \$(colno)",
        "s-left",   "wleft set oldcol \$(colno)",
        "s-tab",    "backtab",
        "s-del",    "deline",
        "s- ",      "( )",              /* shift space to space */
        "c-1",      "goto block",
        "c-c",      "",                 /* break.. map to a nop */
        "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-]",      "ref",
        "c-[",      "ctags",
        "c-g",      "escimm (goto )",
        "c-up",     "top first set oldcol \$(colno)",
        "c-down",   "bottom last set oldcol \$(colno)",
        "c-right",  "last set oldcol \$(colno)",
        "c-left",   "firstnb set oldcol \$(colno)",
        "c-q",      "quit",
        "c-f",      "reformat",
        "c-w",      "wordwrap toggle",
        "f1",       "escimm (insfile )",
        "f2",       "escimm (newfile )",
        "f3",       "escimm (newwindow newfile )",
        "f6",       "saveold iconify",
        "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 if r (last) set oldcol \$(colno)",
        "L-mmo",    "tomouse if r (last) set oldcol \$(colno)",
        "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);
    }
}

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;
    }
}

void addhash(ubyte code, ubyte stat, ubyte mask, ubyte qual, 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);
}


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);
}

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);
}

void do_map(void)
{
    ubyte code, qual;

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

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

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

void do_clearmap(void)
{
    resethash();
}

/*
 * SAVEMAP  file
 * SAVESMAP file
 */

void do_savemap(void)
{
    char sysalso;
    char err = 0;
    char buf[256];
    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)
            title ("Unable to Write");
        else
            title ("OK");
    } else {
        title("Unable to open file");
    }
}

/*
 *  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 && buf[0] >= '0' && buf[0] <= '9')
                    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;
    }
}


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);
}


int get_codequal(ubyte *str, ubyte *pcode, ubyte *pqual)
{
    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) {
            if (c >= 'A' && c <= 'Z')
                c = c - 'A' + 'a';
            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);
}

