asm {
NORMAL_KEY_SCAN_DECODE_TABLE::
                DU8         0, CH_ESC,       "1234567890-=", CH_BACKSPACE, '\t';
                DU8         "qwertyuiop[]", '\n', 0, "as";
                DU8         "dfghjkl;'\`", 0, "\\zxcv";
                DU8         "bnm,./", 0, '*', 0, CH_SPACE, 0, 0, 0, 0, 0, 0;
                DU8         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '+', 0;
SHIFT_KEY_SCAN_DECODE_TABLE::
                DU8         0, CH_SHIFT_ESC, "!@#$%^&*()_+", CH_BACKSPACE, '\t';
                DU8         "QWERTYUIOP{}", '\n', 0, "AS";
                DU8         "DFGHJKL:\"~", 0, "|ZXCV";
                DU8         "BNM<>?", 0, '*', 0, CH_SPACE, 0, 0, 0, 0, 0, 0;
                DU8         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '+', 0;
CTRL_KEY_SCAN_DECODE_TABLE::
                DU8         0, CH_ESC,       "1234567890-=", CH_BACKSPACE, '\t';
                DU8         CH_CTRLQ, CH_CTRLW, CH_CTRLE, CH_CTRLR, CH_CTRLT, CH_CTRLY, CH_CTRLU, CH_CTRLI, CH_CTRLO,
                            CH_CTRLP, "[]", '\n', 0, CH_CTRLA, CH_CTRLS;
                DU8         CH_CTRLD, CH_CTRLF, CH_CTRLG, CH_CTRLH, CH_CTRLJ, CH_CTRLK, CH_CTRLL,
                            ";'\`", 0, "\\", CH_CTRLZ, CH_CTRLX, CH_CTRLC, CH_CTRLV;
                DU8         CH_CTRLB, CH_CTRLN, CH_CTRLM, ",./", 0, '*', 0, CH_SPACE, 0, 0, 0, 0, 0, 0;
                DU8         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '+', 0;
}

U0 KbdCmdSend(I64 port, U8 val)
{
    F64 timeout = tS + 0.125;

    while (tS < timeout)
    {
        if (!(InU8(KBD_CTRL) & 2))
        {
            OutU8(port, val);
            return;
        }
    }
    throw;
}

I64 KbdCmdRead()
{
    F64 timeout = tS + 0.125;

    while (tS < timeout)
        if (InU8(KBD_CTRL) & 1)
            return InU8(KBD_PORT);
    throw;
}

U0 KbdCmdFlush()
{
    F64 timeout = tS + 0.03;

    while (tS < timeout)
        InU8(KBD_PORT);
}

U0 KbdLEDsSet(I64 sc)
{
    U8 v = 0;

    BEqual(&v, 0, Bt(&sc, SCf_SCROLL));
    BEqual(&v, 1, Bt(&sc, SCf_NUM));
    BEqual(&v, 2, Bt(&sc, SCf_CAPS));
    try
    {
        KbdCmdSend(KBD_PORT, 0xED);
        KbdCmdSend(KBD_PORT, v);
    }
    catch
        Fs->catch_except = TRUE;
}

U0 KbdMouseCmdAck(...)
{
    I64 i, ack, timeout;

    for (i = 0; i < argc; i++)
    {
        timeout = 5;
        do
        {
            ack = 0;
            try
            {
                KbdCmdSend(KBD_CTRL, 0xD4);
                KbdCmdSend(KBD_PORT, argv[i]);
                ack = KbdCmdRead;
            }
            catch
            {
                KbdCmdFlush;
                Fs->catch_except = TRUE;
            }
        }
        while (ack != 0xFA && --timeout);

        if (!timeout)
            throw;
    }
}

U0 KbdTypeMatic(U8 delay)
{//Set speed of repeated keys.
    try
    {
        KbdCmdSend(KBD_CTRL, 0xA7); //Disable Mouse
        KbdCmdSend(KBD_CTRL, 0xAE); //Enable Keyboard
        KbdCmdSend(KBD_PORT, 0xF3);
        KbdCmdSend(KBD_PORT, delay);//Typematic rate
        KbdCmdSend(KBD_CTRL, 0xA8); //Enable Mouse
    }
    catch
    {
        KbdCmdFlush;
        Fs->catch_except = TRUE;
    }
}

I64 Char2ScanCode(I64 ch, I64 sc_flags=0)
{//ASCII value to scan code (Slow).
    I64 i;
    U8 *table;

    if (sc_flags)
    {
        table = NORMAL_KEY_SCAN_DECODE_TABLE;
        if (sc_flags & SCF_CTRL || ch < 26)
            table = CTRL_KEY_SCAN_DECODE_TABLE;
        else if (sc_flags & SCF_SHIFT || 'A' <= ch <= 'Z')
        {
            if (!(sc_flags & SCF_CAPS))
                table = SHIFT_KEY_SCAN_DECODE_TABLE;
        }
        else
        {
            if (sc_flags & SCF_CAPS)
                table = SHIFT_KEY_SCAN_DECODE_TABLE;
        }
        for (i = 0; i < 0x50; i++)
            if (table[i] == ch)
                return i | sc_flags;
        return sc_flags;
    }
    else
    {
        table = NORMAL_KEY_SCAN_DECODE_TABLE;
        for (i = 0; i < 0x50; i++)
            if (table[i] == ch)
                return i;
        table = SHIFT_KEY_SCAN_DECODE_TABLE;
        for (i = 0; i < 0x50; i++)
            if (table[i] == ch)
                return i | SCF_SHIFT;
        table = CTRL_KEY_SCAN_DECODE_TABLE;
        for (i = 0; i < 0x50; i++)
            if (table[i] == ch)
                return i | SCF_CTRL;
        return 0;
    }
}

U8 ScanCode2Char(I64 sc)
{//Scan code to ASCII value.
    U8 *table = NORMAL_KEY_SCAN_DECODE_TABLE;

    if (sc & SCF_E0_PREFIX)
        return 0;
    if (sc & SCF_CTRL)
        table = CTRL_KEY_SCAN_DECODE_TABLE;
    else if (sc & SCF_SHIFT)
    {
        if (!(sc & SCF_CAPS))
            table = SHIFT_KEY_SCAN_DECODE_TABLE;
    }
    else
    {
        if (sc & SCF_CAPS)
            table = SHIFT_KEY_SCAN_DECODE_TABLE;
    }
    sc &= 0x7F;
    if (sc >= 0x50)
        return 0;
    else
        return table[sc];
}

U8 scan_code_map[0x100] =
{
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, SC_SHIFT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 

    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, SC_ENTER, SC_CTRL, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0x35, 0, 0, SC_ALT, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, SC_HOME, SC_CURSOR_UP, SC_PAGE_UP, 0, SC_CURSOR_LEFT, 0, SC_CURSOR_RIGHT, 0, SC_END, 
    SC_CURSOR_DOWN, SC_PAGE_DOWN, SC_INS, SC_DELETE, 0, 0, 0, 0, 0, 0, 0, 0, SC_GUI, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
};

U8 num_lock_map[0x100] =
{
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, SC_SHIFT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 8, 9, 10, 0, 5, 6, 7, 0, 2, 
    3, 4, 11, 0x34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 

    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, SC_ENTER, SC_CTRL, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0x35, 0, 0, SC_ALT, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, SC_HOME, SC_CURSOR_UP, SC_PAGE_UP, 0, SC_CURSOR_LEFT, 0, SC_CURSOR_RIGHT, 0, SC_END, 
    SC_CURSOR_DOWN, SC_PAGE_DOWN, SC_INS, SC_DELETE, 0, 0, 0, 0, 0, 0, 0, 0, SC_GUI, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};

U8 *Char2KeyName(I64 ch, Bool include_ctrl=TRUE)
{//ASCII value to key name.
    I64 i;
    U8  buf[STR_LEN];

    if (ch <= CH_SPACE)
    {
        switch [ch]
        {
            case '\n':
                StrCopy(buf, "ENTER");
                break;

            case CH_BACKSPACE:
                StrCopy(buf, "BACKSPACE");
                break;

            case '\t':
                StrCopy(buf, "TAB");
                break;

            case CH_ESC:
                StrCopy(buf, "ESC");
                break;

            case CH_SHIFT_ESC:
                StrCopy(buf, "SHIFT_ESC");
                break;

            case 0: //nobound switch
            case 29:
            case 30:
                *buf = 0;
                break;

            case CH_SPACE:
                StrCopy(buf, "SPACE");
                break;

            default:
                if (include_ctrl)
                    StrCopy(buf, "CTRL ");
                buf[i = StrLen(buf)] = ch - 1 + 'a';
                buf[i + 1] = 0;
                break;
        }
    }
    else if (Bt(char_bmp_printable, ch))
    {
        *buf = ch;
        buf[1] = 0;
    }
    else
        *buf = 0;
    return StrNew(buf);
}

U8 *ScanCode2KeyName(I64 sc)
{//Scan code to key name.
    I64 ch;
    U8  buf[STR_LEN], *st;

    *buf = 0;
    if (sc & SCF_CTRL)
        CatPrint(buf, "CTRL ");
    if (sc & SCF_ALT)
        CatPrint(buf, "ALT ");
    if (sc & SCF_SHIFT)
        CatPrint(buf, "SHIFT ");
    if (sc & SCF_NO_SHIFT)
        CatPrint(buf, "      ");
    if (ch = ScanCode2Char(sc & 255))
    {
        st = Char2KeyName(ch, FALSE);
        StrCopy(buf + StrLen(buf), st);
        Free(st);
    }
    else
    {
        switch (sc & 255)
        {
            case SC_BACKSPACE:      CatPrint(buf, "BACK");          break;
            case SC_CAPS:           CatPrint(buf, "CAPS");          break;
            case SC_NUM:            CatPrint(buf, "NUM");           break;
            case SC_SCROLL:         CatPrint(buf, "SCROLL");        break;
            case SC_CURSOR_UP:      CatPrint(buf, "UP");            break;
            case SC_CURSOR_DOWN:    CatPrint(buf, "DOWN");          break;
            case SC_CURSOR_LEFT:    CatPrint(buf, "LEFT");          break;
            case SC_CURSOR_RIGHT:   CatPrint(buf, "RIGHT");         break;
            case SC_PAGE_UP:        CatPrint(buf, "PAGE_UP");       break;
            case SC_PAGE_DOWN:      CatPrint(buf, "PAGE_DOWN");     break;
            case SC_HOME:           CatPrint(buf, "HOME");          break;
            case SC_END:            CatPrint(buf, "END");           break;
            case SC_INS:            CatPrint(buf, "INS");           break;
            case SC_DELETE:         CatPrint(buf, "DELETE");        break;
            case SC_F1:             CatPrint(buf, "F1");            break;
            case SC_F2:             CatPrint(buf, "F2");            break;
            case SC_F3:             CatPrint(buf, "F3");            break;
            case SC_F4:             CatPrint(buf, "F4");            break;
            case SC_F5:             CatPrint(buf, "F5");            break;
            case SC_F6:             CatPrint(buf, "F6");            break;
            case SC_F7:             CatPrint(buf, "F7");            break;
            case SC_F8:             CatPrint(buf, "F8");            break;
            case SC_F9:             CatPrint(buf, "F9");            break;
            case SC_F10:            CatPrint(buf, "F10");           break;
            case SC_F11:            CatPrint(buf, "F11");           break;
            case SC_F12:            CatPrint(buf, "F12");           break;
            case SC_GUI:            CatPrint(buf, "WINDOWS");       break;
            case SC_PRINTSCREEN1:   CatPrint(buf, "PRINTSCREEN1");  break;
            case SC_PRINTSCREEN2:   CatPrint(buf, "PRINTSCREEN2");  break;
        }
    }
    return StrNew(buf);
}

U0 KbdBuildSC(U8 raw_byte, Bool in_irq, U8 *_last_raw_byte, I64 *_last_sc)
{
    I64  ch, sc_flags, sc, sc2, sc_raw, new_key_f;
    Bool set_LEDs = FALSE;

    if (raw_byte == 0xE0)
    {
        *_last_sc &= ~0x1FF;
        *_last_raw_byte = raw_byte;
        return;
    }
    sc = raw_byte;
    BEqual(&sc, SCf_E0_PREFIX, *_last_raw_byte == 0xE0);
    BEqual(&sc, SCf_KEY_UP, raw_byte & 0x80);
    *_last_raw_byte = raw_byte;

    sc_flags = _last_sc->u32[0] & ~0x1FF;
    sc_raw   = sc;

    if (sc_flags & SCF_NUM)
    {
        if (sc2 = num_lock_map[sc.u8[0]])
            sc.u8[0] = sc2;
    }
    else
    {
        if (sc2 = scan_code_map[sc.u8[0]])
            sc.u8[0] = sc2;
    }

    new_key_f = SCF_NEW_KEY;
    if (sc & SCF_KEY_UP)
        switch (sc & ~SCF_KEY_UP)
        {
            case SC_SHIFT:  sc_flags &= ~SCF_SHIFT;                 break;
            case SC_CTRL:   sc_flags &= ~SCF_CTRL;                  break;
            case SC_ALT:    sc_flags &= ~SCF_ALT;                   break;
            case SC_DELETE: sc_flags &= ~SCF_DELETE;                break;
            case SC_INS:    sc_flags &= ~SCF_INS;                   break;
            case SC_CAPS:   sc_flags ^= SCF_CAPS;   set_LEDs=TRUE;  break;
            case SC_NUM:    sc_flags ^= SCF_NUM;    set_LEDs=TRUE;  break;
            case SC_SCROLL: sc_flags ^= SCF_SCROLL; set_LEDs=TRUE;  break;
        }
    else
        switch (sc)
        {
            case SC_SHIFT:
                if (Bts(&sc_flags, SCf_SHIFT))
                    new_key_f = 0;
                break;

            case SC_CTRL:
                if (Bts(&sc_flags, SCf_CTRL))
                    new_key_f = 0;
                break;

            case SC_ALT:
                if (Bts(&sc_flags, SCf_ALT))
                    new_key_f = 0;
                break;

            case SC_DELETE:
                sc_flags |= SCF_DELETE;
                break;

            case SC_INS:
                sc_flags |= SCF_INS;
                break;
        }

    sc_flags |= new_key_f;
    sc = sc_flags | sc | (sc_flags | sc_raw) << 32;
    if (sc_flags & SCF_CTRL && sc_flags & SCF_ALT)
    {
        if (!(sc & SCF_KEY_UP))
        {
            if (sc & 255 == SC_DELETE && !(sc_flags & SCF_SHIFT))
                CtrlAltDel(sc);
            else
            {
                if (sc & 255 == SC_ESC)
                    ch = 't';
                else if (sc & 255 == SC_TAB)
                    ch = 'n';
                else
                    ch = ScanCode2Char(sc & 255);
                if ('a' <= ch <= 'z')
                {
                    sc &= ~(SCF_NEW_KEY | SCF_NEW_KEY << 32);
                    ch -= 'a';
                    kbd.last_down_scan_code = sc;
                    if (keydev.fp_ctrl_alt_cbs[ch] &&
                            Bt(&keydev.ctrl_alt_in_irq_flags, ch) == in_irq &&
                            (!(sc_flags & SCF_SHIFT) &&
                            keydev.ctrl_alt_no_shift_descs[ch]) || sc_flags & SCF_SHIFT && keydev.ctrl_alt_shift_descs[ch])
                        (*keydev.fp_ctrl_alt_cbs[ch])(sc);
                }
            }
        }
    }
    if (set_LEDs && !in_irq)
        KbdLEDsSet(sc);
    *_last_sc = sc;
}

U0 KbdPacketRead()
{
    static U8  last_raw_byte = 0;
    static I64 last_sc = 0;
    U8         raw_byte;

    if (TSCGet>kbd.timestamp + counts.time_stamp_freq >> 3)
        FifoU8Flush(kbd.fifo);
    kbd.timestamp = TSCGet;
    raw_byte = InU8(KBD_PORT);
    KbdBuildSC(raw_byte, TRUE, &last_raw_byte, &last_sc);
    if (!FifoU8Count(kbd.fifo))
    {
        FifoU8Insert(kbd.fifo, raw_byte);
        if (raw_byte != 0xE0)
        {
            while (FifoU8Remove(kbd.fifo, &raw_byte))
                FifoU8Insert(kbd.fifo2, raw_byte);
        }
    }
    else
    {
        FifoU8Insert(kbd.fifo, raw_byte);
        while (FifoU8Remove(kbd.fifo, &raw_byte))
            FifoU8Insert(kbd.fifo2, raw_byte);
    }
}

interrupt U0 IRQKbd()
{
    CLD
    OutU8(PIC_1, PIC_EOI);
    kbd.irqs_working = TRUE;
    if (mouse_hard.install_in_progress)
    {
        kbd.reset = TRUE;
        return;
    }
    keydev.ctrl_alt_ret_addr = RBPGet()(I64) + 8;
    KbdPacketRead;
}

U0 KbdInit()
{
    try
    {
        KbdCmdFlush;
        KbdCmdSend(KBD_CTRL, 0xA7); //Disable Mouse
        KbdCmdSend(KBD_CTRL, 0xAE); //Enable Keyboard
        KbdCmdSend(KBD_PORT, 0xF0);
        KbdCmdSend(KBD_PORT, 0x02);
        KbdLEDsSet(kbd.scan_code);
    }
    catch
    {
        KbdCmdFlush;
        Fs->catch_except = TRUE;
    }
    IntEntrySet(0x21, &IRQKbd);
    OutU8(PIC_1_DATA, InU8(PIC_1_DATA) & ~2);
}

U0 KbdHandler()
{
    static U8 last_raw_byte = 0;
    U8        raw_byte;

    FifoU8Remove(kbd.fifo2, &raw_byte);
    KbdBuildSC(raw_byte, FALSE, &last_raw_byte, &kbd.scan_code);
    if (raw_byte == 0xE0)
    {
        FifoU8Remove(kbd.fifo2, &raw_byte);
        KbdBuildSC(raw_byte, FALSE, &last_raw_byte, &kbd.scan_code);
    }
    if (Btr(&kbd.scan_code, SCf_NEW_KEY))
    {
        kbd.new_key_timestamp = kbd.timestamp;
        Btr(&kbd.scan_code, 32 + SCf_NEW_KEY);
        FifoI64Ins(kbd.scan_code_fifo, kbd.scan_code);
        kbd.count++;
        if (!(kbd.scan_code & SCF_KEY_UP))
        {
            kbd.last_down_scan_code = kbd.scan_code;
            Bts(kbd.down_bitmap,  kbd.scan_code.u8[0]);
            Bts(kbd.down_bitmap2, kbd.scan_code.u8[4]);
        }
        else
        {
            Btr(kbd.down_bitmap,  kbd.scan_code.u8[0]);
            Btr(kbd.down_bitmap2, kbd.scan_code.u8[4]);
        }
    }
}

I64 KbdMessagesQueue()
{
    I64      arg1, arg2, message_code = MESSAGE_NULL;
    CTask   *task_focus;
    if (task_focus = sys_focus_task)
    {
        while (FifoI64Remove(kbd.scan_code_fifo, &arg2))
        {
            arg1 = ScanCode2Char(arg2);
            if (arg2 & SCF_KEY_UP)
            {
                TaskMessage(task_focus, 0, MESSAGE_KEY_UP, arg1, arg2, 0);
                message_code = MESSAGE_KEY_UP;
            }
            else
            {
                TaskMessage(task_focus, 0, MESSAGE_KEY_DOWN, arg1, arg2, 0);
                message_code = MESSAGE_KEY_DOWN;
            }
        }
    }
    return message_code;
}

I64 KbdMouseEventTime()
{//Timestamp of last key or mouse event.
    if (mouse_hard.timestamp > kbd.timestamp)
        return mouse_hard.timestamp;
    else
        return kbd.new_key_timestamp;
}