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