/*

kbd.c - Keyboard

Includes a keyboard remapping capability from Bill Brendling.

*/

/*...sincludes:0:*/
#include <stdio.h>

#include "types.h"
#include "diag.h"
#include "common.h"
#include "mem.h"
#include "roms.h"
#include "win.h"
#include "kbd.h"

/*...vtypes\46\h:0:*/
/*...vdiag\46\h:0:*/
/*...vcommon\46\h:0:*/
/*...vwin\46\h:0:*/
/*...vkbd\46\h:0:*/
/*...e*/

/*...svars:0:*/
static int kbd_emu = 0;

static int kbd_drive;
static word kbd_sense[8];
#ifndef NO_JOY
static word kbd_sense_joy[8];
#endif
static BOOLEAN kbd_diag = FALSE;

#define	SHIFT_ROW      6
#define	SHIFT_BITPOS_L 0
#define	SHIFT_BITPOS_R 6
#define	SHIFT_BIT_L    (1<<SHIFT_BITPOS_L)
#define	SHIFT_BIT_R    (1<<SHIFT_BITPOS_R)
#define	SHIFT_BITS     (SHIFT_BIT_L|SHIFT_BIT_R)

#define	RETURN_ROW     5
#define	RETURN_BITPOS  6

#define	LT_ROW         6
#define	LT_BITPOS      4

/* Layout of the MTX keyboard arrow pad
       7 PAGE       8 EOL         9 BRK
       4 TAB        5 UP          6 DEL
       1 LEFT       2 HOME        3 RIGHT
       0 INS        . DOWN        ENT CLS
   accessible via (which try to assign keys with similar meaning)
       WK_Page_Up   WK_End        WK_Pause
       WK_Tab       WK_Up         WK_Delete
       WK_Left      WK_Home       WK_Right
       WK_Insert    WK_Down       WK_Page_Down
   and to better support games (which try to preserve the layout)
       WK_Num_Lock  WK_KP_Divide  WK_KP_Multiply
       WK_KP_Home   WK_KP_Up      WK_KP_Page_Up
       WK_KP_Left   WK_KP_Middle  WK_KP_Right
       WK_KP_End    WK_KP_Down    WK_KP_Page_Down */

/* If WK_ value detected, adjust sense bit regardless of shift status */

static int kbd_map_normal[8][10] =
	{
		{            0,0,0,0,0,0,          0,WK_Page_Up  ,WK_Pause    ,WK_F1 },
		{ WK_Escape   ,0,0,0,0,0,          0,WK_End      ,WK_BackSpace,WK_F5 },
		{ WK_Control_L,0,0,0,0,0,          0,WK_Up       ,WK_Tab      ,WK_F2 },
		{            0,0,0,0,0,0,WK_Linefeed,WK_Left     ,WK_Delete   ,WK_F6 },
		{ WK_Caps_Lock,0,0,0,0,0,          0,WK_Right    ,           0,WK_F7 },
		{            0,0,0,0,0,0,WK_Return  ,WK_Home     ,           0,WK_F3 },
		{ WK_Shift_L  ,0,0,0,0,0,WK_Shift_R ,WK_Down     ,           0,WK_F8 },
		{            0,0,0,0,0,0,WK_Insert  ,WK_Page_Down,' '         ,WK_F4 },
	};

static int kbd_map_keypad[8][10] =
	{
		{ 0,0,0,0,0,0,        0,WK_Num_Lock    ,WK_KP_Multiply,0 },
		{ 0,0,0,0,0,0,        0,WK_KP_Divide   ,             0,0 },
		{ 0,0,0,0,0,0,        0,WK_KP_Up       ,WK_KP_Home    ,0 },
		{ 0,0,0,0,0,0,        0,WK_KP_Left     ,WK_KP_Page_Up ,0 },
		{ 0,0,0,0,0,0,        0,WK_KP_Right    ,             0,0 },
		{ 0,0,0,0,0,0,        0,WK_KP_Middle   ,             0,0 },
		{ 0,0,0,0,0,0,        0,WK_KP_Down     ,             0,0 },
		{ 0,0,0,0,0,0,WK_KP_End,WK_KP_Page_Down,             0,0 },
	};

/* Some host keystrokes (the symbols written on the keys) are in different
   places on the host to where they are on the MTX keyboard.
   In particular, shifted keystrokes are obtained by pressing shift on
   different keys, eg: ( is shift 9 on a PC keyboard, but shift 8 on MTX.
   This is not a problem.

   Host shift keypresses directly correspond to MTX shift keypresses.
   We need this to be the case, because shift is used as a modifier on
   special keys, ie: those in the normal and keypad tables above.

   Unfortunately, some keystrokes are shifted on one, but not the other.
   If we try to lie about the shift state, we get don't get reliable behavior,
   primarily due to the way the sense bits are typically scanned.
   The closest thing to a solution is to have the user make an alternative
   keypress on the host, with the right shift status :-

     keystroke  host keypress  MTX keypress  alternative host keypress
     ---------  -------------  ------------  -------------------------
     ^          shift 6        ^             =
     =          =              shift -       shift 6
     :          shift ;        :             #
     '          '              shift 7       shift '
     @          shift '        @             '
     `          `              shift @       shift `
     #          shift 3 or #   shift 3       shift 3

   Summarised another way :-

     type ^ for =
     type = for ^
     type ' for @
     type @ for '
     type # for :
     type shift ` for `

   Apologies for this, its the best engineering tradeoff I could find.

*/

static int kbd_unmap_unshifted[8][10] =
	{
		{ '1','3','5','7','9','-' ,'\\',0,0,0 },
		{   0,'2','4','6','8','0' ,'=' ,0,0,0 },
		{   0,'w','r','y','i','p' ,'[' ,0,0,0 },
		{ 'q','e','t','u','o','\'',   0,0,0,0 },
		{   0,'s','f','h','k',';' ,']' ,0,0,0 },
		{ 'a','d','g','j','l','#' ,   0,0,0,0 },
		{   0,'x','v','n',',','/' ,   0,0,0,0 },
		{ 'z','c','b','m','.','_' ,   0,0,0,0 },
	};

static int kbd_unmap_shifted[8][10] =
	{
		{ '!','#','%','@',')','^','|',0,0,0 },
		{   0,'"','$','&','(','0','~',0,0,0 },
		{   0,'W','R','Y','I','P','{',0,0,0 },
		{ 'Q','E','T','U','O','`',  0,0,0,0 },
		{   0,'S','F','H','K','+','}',0,0,0 },
		{ 'A','D','G','J','L','*',  0,0,0,0 },
		{   0,'X','V','N','<','?',  0,0,0,0 },
		{ 'Z','C','B','M','>','_',  0,0,0,0 },
	};

static int kbd_unmap_unshifted_autotype[8][10] =
	{
		{ '1','3','5','7','9','-' ,'\\',0,0,0 },
		{   0,'2','4','6','8','0' ,'^' ,0,0,0 },
		{   0,'w','r','y','i','p' ,'[' ,0,0,0 },
		{ 'q','e','t','u','o','@' ,   0,0,0,0 },
		{   0,'s','f','h','k',';' ,']' ,0,0,0 },
		{ 'a','d','g','j','l',':' ,   0,0,0,0 },
		{   0,'x','v','n',',','/' ,   0,0,0,0 },
		{ 'z','c','b','m','.','_' ,   0,0,0,0 },
	};

static int kbd_unmap_shifted_autotype[8][10] =
	{
		{ '!','#','%','\'',')','=','|',0,0,0 },
		{   0,'"','$','&','(','0','~',0,0,0 },
		{   0,'W','R','Y','I','P','{',0,0,0 },
		{ 'Q','E','T','U','O','`',  0,0,0,0 },
		{   0,'S','F','H','K','+','}',0,0,0 },
		{ 'A','D','G','J','L','*',  0,0,0,0 },
		{   0,'X','V','N','<','?',  0,0,0,0 },
		{ 'Z','C','B','M','>','_',  0,0,0,0 },
	};

static int kbd_remap_unshifted[8][10] =
	{
		{ '1','3','5','7','9','-' ,'\\' ,0,0,0 },
		{   0,'2','4','6','8','0' ,'='  ,0,0,0 },
		{   0,'w','r','y','i','p' ,'['  ,0,0,0 },
		{ 'q','e','t','u','o','\'',    0,0,0,0 },
		{   0,'s','f','h','k',';' ,']',0,'`',0 },
		{ 'a','d','g','j','l','#' ,    0,0,0,0 },
		{   0,'x','v','n',',','/' ,    0,0,0,0 },
		{ 'z','c','b','m','.','_' ,    0,0,0,0 },
	};

static int kbd_remap_shifted[8][10] =
	{
		{ '!','#','%',':',')','^','|'  ,0,0,0 },
		{   0,'"','$','&','(','0','~'  ,0,0,0 },
		{   0,'W','R','Y','I','P','{'  ,0,0,0 },
		{ 'Q','E','T','U','O','@',    0,0,0,0 },
		{   0,'S','F','H','K','+','}',0,'`',0 },
		{ 'A','D','G','J','L','*',    0,0,0,0 },
		{   0,'X','V','N','<','?',    0,0,0,0 },
		{ 'Z','C','B','M','>','_',    0,0,0,0 },
};

/* Ought to create remap_*_autotype tables too. Todo. */

static int (*kbd_map_unshifted         )[10] =  kbd_unmap_unshifted;
static int (*kbd_map_shifted           )[10] =  kbd_unmap_shifted;
static int (*kbd_map_unshifted_autotype)[10] =  kbd_unmap_unshifted_autotype;
static int (*kbd_map_shifted_autotype  )[10] =  kbd_unmap_shifted_autotype;

static int kbd_map_pressed[8][10];

/* Normally we use the unmapped tables.
   However, if keyboard remapping is enabled,
   we use different bits for the keys,
   and we have to patch the ROMs using data below. */

static const byte *mtx_base   = (const byte *)
	"z\x00" "a\xa0q\x00\x1b" "1"
	"cxdsew23"
	"bvgftr45"
	"mnjhuy67"
	".,lkoi89"
	"_/:;@p0-"
	"\x15\x00\x0d]\x0a[^\\"
	"\x0c\x0a\x1a\x19\x08\x0b\x05\x1d"
	"\x83\x87\x82\x86\x85\x81\x84\x80"
	" \x00\x00\x00\x7f\x09\x08\x03";

static const byte *mtx_upper  = (const byte *)
	"Z\x00" "A\xa0Q\x00\x1b!"
	"CXDSEW\"#"
	"BVGFTR$%"
	"MNJHUY&'"
	"><LKOI()"
	"_?*+`P0="
	"0\x00\x0d}\x0a{~|"
	"\x0d.231587"
	"\x8b\x8f\x8a\x8e\x8d\x89\x8c\x88"
	" \x00\x00\x00" "64\x08" "9";

static const byte *patch_base = (const byte *)
	"z\x00" "a\xa0q\x00\x1b" "1"
	"cxdsew23"
	"bvgftr45"
	"mnjhuy67"
	".,lkoi89"
	"_/#;'p0-"
	"\x15\x00\x0d]\x0a[=\\"
	"\x0c\x0a\x1a\x19\x08\x0b\x05\x1d"
	"\x83\x87\x82\x86\x85\x81\x84\x80"
	" \x00\x00`\x7f\x09\x08\x03";

static const byte *patch_upper   = (const byte *)
	"Z\x00" "A\xa0Q\x00\x1b!"
	"CXDSEW\"#"
	"BVGFTR$%"
	"MNJHUY&:"
	"><LKOI()"
	"_?*+@P0^"
	"0\x00\x0d}\x0a{~|"
	"\x0d.231587"
	"\x8b\x8f\x8a\x8e\x8d\x89\x8c\x88"
	" \x00\x00\x00" "64\x08" "9";
/*...e*/

/*...skbd_search_map:0:*/
static BOOLEAN kbd_search_map(int wk, int (*map)[10], int *row_found, int *bitpos_found)
	{
	int row, bitpos;
#if defined(WK_PC_Alt_L) && defined(WK_PC_Alt_R)
	if ( wk == WK_PC_Alt_L || wk == WK_PC_Alt_R )
		wk = WK_Home;
#endif
#if defined(WK_Mac_Alt)
	if ( wk == WK_Mac_Alt )
		wk = WK_Home;
#endif
	for ( row = 0; row < 8; row++ )
		for ( bitpos = 0; bitpos < 10; bitpos++ )
			if ( map[row][bitpos] == wk )
				{
				*row_found    = row;
				*bitpos_found = bitpos;
				return TRUE;
				}
	return FALSE;
	}	
/*...e*/

/*...skbd_win_keypress:0:*/
#ifdef ALT_KEYPRESS
extern BOOLEAN ALT_KEYPRESS(int wk);
#endif

void kbd_win_keypress(int wk)
	{
	int row, bitpos;
#ifdef ALT_KEYPRESS
	if ( ALT_KEYPRESS(wk) )
		return;
#endif
	if ( kbd_diag )
		diag_control(wk);
	else if ( kbd_search_map(wk, kbd_map_normal, &row, &bitpos) )
		kbd_sense[row] &= ~(1<<bitpos);
	else if ( kbd_search_map(wk, kbd_map_keypad, &row, &bitpos) )
		kbd_sense[row] &= ~(1<<bitpos);
	else
		{
		BOOLEAN shift = ( (kbd_sense[SHIFT_ROW]&SHIFT_BITS) != SHIFT_BITS );
		if ( !shift && kbd_search_map(wk, kbd_map_unshifted, &row, &bitpos) )
			{
			kbd_sense[row] &= ~(1<<bitpos);
			kbd_map_pressed[row][bitpos] = wk;
			}
		else if ( shift && kbd_search_map(win_shifted_wk(wk), kbd_map_shifted, &row, &bitpos) )
			{
			kbd_sense[row] &= ~(1<<bitpos);
			kbd_map_pressed[row][bitpos] = wk;
			}
		else			
			switch ( wk )
				{
				case WK_F9:
					kbd_diag = TRUE;
					break;
				case WK_F10:
				case WK_F11:
				case WK_F12:
					break;
				default:
					diag_message(DIAG_KBD_WIN_KEY, "kbd_win_keypress unknown wk=0x%04x", wk);
					break;
				}
		}
	}
/*...e*/
/*...skbd_win_keyrelease:0:*/
#ifdef ALT_KEYRELEASE
extern BOOLEAN ALT_KEYRELEASE(int wk);
#endif

void kbd_win_keyrelease(int wk)
	{
	int row, bitpos;
#ifdef ALT_KEYRELEASE
	if ( ALT_KEYRELEASE (wk) ) return;
#endif
	if ( kbd_search_map(wk, kbd_map_normal, &row, &bitpos) )
		kbd_sense[row] |= (1<<bitpos);
	else if ( kbd_search_map(wk, kbd_map_keypad, &row, &bitpos) )
		kbd_sense[row] |= (1<<bitpos);
	else if ( kbd_search_map(wk, kbd_map_pressed, &row, &bitpos) )
		{
		kbd_sense[row] |= (1<<bitpos);
		kbd_map_pressed[row][bitpos] = 0;
		}
	else
		switch ( wk )
			{
			case WK_F9:
				kbd_diag = FALSE;
				break;
			case WK_F10:
			case WK_F11:
			case WK_F12:
				break;
			default:
				diag_message(DIAG_KBD_WIN_KEY, "kbd_win_keyrelease unknown wk=0x%04x", wk);
				break;
			}
	}
/*...e*/

/*...sknd_find_grid:0:*/
/* We can map most characters to keyboard grip row and bit position.
   However, some keys don't correspond to ASCII characters.
   So we have a table mapping from names row and bit position. */

/* Key Name and Grid position */
typedef struct
	{
	const char *name;
	int row;
	int bitpos;
	} KNG;

static KNG kbd_kngs[] =
	{
	"<ESC>",	1,	0,
	"<BS>",		1,	8,
	"<PAGE>",	0,	7,
	"<EOL>",	1,	7,
	"<BRK>",	0,	8,
	"<F1>",		0,	9,
	"<F5>",		1,	9,
	"<CTRL>",	2,	0,
	"<LINEFEED>",	3,	6,
	"<TAB>",	2,	8,
	"<UP>",		2,	7,
	"<DEL>",	3,	8,
	"<F2>",		2,	9,
	"<F6>",		3,	9,
	"<ALPHALOCK>",	4,	0,
	"<RET>",	5,	6,
	"<LEFT>",	3,	7,
	"<HOME>",	5,	7,
	"<RIGHT>",	4,	7,
	"<F3>",		4,	9,
	"<F7>",		5,	9,
	"<LSHIFT>",	6,	0,
	"<RSHIFT>",	6,	6,
	"<INS>",	7,	6,
	"<DOWN>",	6,	7,
	"<ENTCLS>",	7,	7,
	"<F4>",		6,	9,
	"<F8>",		7,	9,
	};

/* Lookup special keyname and return grid pos */
static BOOLEAN kbd_find_grid_special(const char **p, int *row, int *bitpos)
	{
	int i;
	if ( **p != '<' )
		return FALSE;
	for ( i = 0; i < sizeof(kbd_kngs)/sizeof(kbd_kngs[0]); i++ )
		{
		KNG *kng = &(kbd_kngs[i]);
		if ( !strncmp(*p, kng->name, strlen(kng->name)) )
			{
			*row     = kng->row;
			*bitpos  = kng->bitpos;
			(*p)    += strlen(kng->name);
			return TRUE;
			}
		}
	return FALSE;
	}

/* Lookup keyname and return grid pos and shift */
BOOLEAN kbd_find_grid(const char **p, int *row, int *bitpos, BOOLEAN *shifted)
	{
	if ( **p == '\0' )
		return FALSE;
	if ( !strncmp(*p, "<LT>", 4) )
		{
		*row     = LT_ROW;
		*bitpos  = LT_BITPOS;
		*shifted = TRUE;
		(*p) += 4;
		return TRUE;
		}
	if ( kbd_find_grid_special(p, row, bitpos) )
		{
		*shifted = FALSE;
		return TRUE; 
		}
	if ( kbd_search_map(**p, kbd_map_normal, row, bitpos) )
		{
		*shifted = FALSE;
		++(*p);
		return TRUE;
		}
	if ( kbd_search_map(**p, kbd_map_unshifted_autotype, row, bitpos) )
		{
		*shifted = FALSE;
		++(*p);
		return TRUE;
		}
	if ( kbd_search_map(**p, kbd_map_shifted_autotype, row, bitpos) )
		{
		*shifted = TRUE;
		++(*p);
		return TRUE;
		}
	return FALSE;
	}
/*...e*/
/*...squeue of key events:0:*/
#define	QKE_DELAY	0
#define	QKE_PRESS	1
#define	QKE_RELEASE	2

typedef struct _QKE QKE;

struct _QKE
	{
	QKE *next;
	int action;
	int value1;
	int value2;
	};

static QKE *kbd_qke_first = NULL;
static QKE *kbd_qke_last  = NULL;

static void kbd_qke_enqueue(int action, int value1, int value2)
	{
	QKE *qke = (QKE *) emalloc(sizeof(QKE));
	qke->action = action;
	qke->value1 = value1;
	qke->value2 = value2;
	qke->next   = NULL;
	if ( kbd_qke_first == NULL )
		{
		kbd_qke_first = qke;
		kbd_qke_last  = qke;
		}
	else
		{
		kbd_qke_last->next = qke;
		kbd_qke_last       = qke;
		}
	}

static void kbd_qke_dequeue(void)
	{
	QKE *qke = kbd_qke_first;
	if ( (kbd_qke_first = kbd_qke_first->next) == NULL )
		kbd_qke_last = NULL;
	free(qke);
	}
/*...e*/
/*...sauto typing:0:*/
/* Cope with things like this
     <Wait20>LOAD ""<RET>
     <Wait20><ALPHALOCK><AutoShift>10 PRINT "Hello"
     <Press><HOME><LEFT><RIGHT><Release><LEFT><RIGHT><HOME><PressAndRelease>
     <Wait200><AutoShift>dir A:<RET>
*/

static BOOLEAN kbd_do_press      = TRUE;
static BOOLEAN kbd_do_release    = TRUE;
static int     kbd_time_press    = 1;
static int     kbd_time_release  = 1;
static BOOLEAN kbd_do_auto_shift = TRUE;
#define	SS_UNKNOWN   0
#define	SS_SHIFTED   1
#define	SS_UNSHIFTED 2
static int     kbd_shift_state = SS_UNKNOWN;
static int     kbd_time_return = 25;

void kbd_add_events(const char *p)
	{
	diag_message(DIAG_KBD_AUTO_TYPE, "auto-type %s", p);
	while ( *p != '\0' )
		{
		char buf[50+1];
		/* Simple delay */
		if ( sscanf(p, "<Wait%[0-9]>", buf) == 1 )
			{
			int wait;
			sscanf(buf, "%d", &wait);
			p += ( 6 + strlen(buf) );
			kbd_qke_enqueue(QKE_DELAY, wait, 0);
			}
		/* Controlling of button pushing behavior */
		else if ( !strncmp(p, "<Press>", 7) )
			{ kbd_do_press = TRUE; kbd_do_release = FALSE; p += 7; }
		else if ( !strncmp(p, "<Release>", 9) )
			{ kbd_do_press = FALSE; kbd_do_release = TRUE; p += 9; }
		else if ( !strncmp(p, "<PressAndRelease>", 17) )
			{ kbd_do_press = FALSE; kbd_do_release = TRUE; p += 17; }
		else if ( sscanf(p, "<PressTime%[0-9]>", buf) == 1 )
			{
			sscanf(buf, "%d", &kbd_time_press);
			p += ( 11 + strlen(buf) );
			}
		else if ( sscanf(p, "<ReleaseTime%[0-9]>", buf) == 1 )
			{
			sscanf(buf, "%d", &kbd_time_release);
			p += ( 13 + strlen(buf) );
			}
		else if ( sscanf(p, "<ReturnTime%[0-9]>", buf) == 1 )
			{
			sscanf(buf, "%d", &kbd_time_return);
			p += ( 12 + strlen(buf) );
			}
		else if ( !strncmp(p, "<AutoShift>", 11) )
			{
			kbd_do_auto_shift = TRUE;
			p += 11;
			}
		else if ( !strncmp(p, "<NoAutoShift>", 13) )
			{
			if ( kbd_do_auto_shift )
				{
				p += 13;
				if ( kbd_shift_state == SS_SHIFTED )
					{
					kbd_qke_enqueue(QKE_RELEASE, SHIFT_ROW, SHIFT_BITPOS_L);
					kbd_qke_enqueue(QKE_DELAY, kbd_time_release, 0);
					kbd_shift_state = SS_UNKNOWN;
					}
				kbd_do_auto_shift = FALSE;
				}
			}
		/* Button push */
		else
			{
			int row, bitpos;
			BOOLEAN shifted;
			if ( kbd_find_grid(&p, &row, &bitpos, &shifted) )
				{
				if ( kbd_do_press )
					{
					if ( kbd_do_auto_shift )
						{
						if ( shifted && kbd_shift_state != SS_SHIFTED )
							{
							kbd_qke_enqueue(QKE_PRESS, SHIFT_ROW, SHIFT_BITPOS_L);
							kbd_qke_enqueue(QKE_DELAY, kbd_time_press, 0);
							kbd_shift_state = SS_SHIFTED;
							}
						else if ( ! shifted && kbd_shift_state != SS_UNSHIFTED )
							{
							kbd_qke_enqueue(QKE_RELEASE, SHIFT_ROW, SHIFT_BITPOS_L);
							kbd_qke_enqueue(QKE_DELAY, kbd_time_release, 0);
							kbd_shift_state = SS_UNSHIFTED;
							}
						}
					kbd_qke_enqueue(QKE_PRESS, row, bitpos);
					kbd_qke_enqueue(QKE_DELAY, kbd_time_press, 0);
					}
				if ( kbd_do_release )
					{
					kbd_qke_enqueue(QKE_RELEASE, row, bitpos);
					kbd_qke_enqueue(QKE_DELAY, kbd_time_release, 0);
					if ( kbd_time_return != 0 &&
					     row == RETURN_ROW && bitpos == RETURN_BITPOS )
						kbd_qke_enqueue(QKE_DELAY, kbd_time_return, 0);
					}
				}
			else
				++p; /* Skip the junk */
			}
		}
	}

#define	L_LINE 300

void kbd_add_events_file(const char *fn)
	{
	FILE *fp;
	char line[L_LINE+1];
	diag_message(DIAG_KBD_AUTO_TYPE, "auto-type from file %s", fn);
	fp = efopen(fn, "r");
	while ( fgets(line, L_LINE, fp) != NULL )
		{
		int len = (int)strlen(line);
		if ( len > 0 && line[len-1] == '\n' )
			{
			line[len-1] = '\0';
			kbd_add_events(line);
			kbd_add_events("<RET>");
			}
		else
			kbd_add_events(line);
		}
	fclose(fp);
	}

void kbd_add_events_done(void)
	{
	if ( kbd_do_auto_shift )
		{
		if ( kbd_shift_state == SS_SHIFTED )
			{
			kbd_qke_enqueue(QKE_RELEASE, SHIFT_ROW, SHIFT_BITPOS_L);
			kbd_qke_enqueue(QKE_DELAY, kbd_time_release, 0);
			}
		kbd_do_auto_shift = FALSE;
		kbd_shift_state = SS_UNKNOWN;
		}
	}

/* Called here every 50th of a second */
void kbd_periodic(void)
	{
	while ( kbd_qke_first != NULL )
		{
		switch ( kbd_qke_first->action )
			{
			case QKE_DELAY:
				if ( (kbd_qke_first->value1)-- > 0 )
					{
					diag_message(DIAG_KBD_AUTO_TYPE, "auto-type delay %d", kbd_qke_first->value1+1);
					return;
					}
				break;
			case QKE_PRESS:
				{
				int row    = kbd_qke_first->value1;
				int bitpos = kbd_qke_first->value2;
				diag_message(DIAG_KBD_AUTO_TYPE, "auto-type press %d %d", row, bitpos);
				kbd_sense[row] &= ~(1<<bitpos);
				kbd_map_pressed[row][bitpos] = 0;
				}
				break;
			case QKE_RELEASE:
				{
				int row    = kbd_qke_first->value1;
				int bitpos = kbd_qke_first->value2;
				diag_message(DIAG_KBD_AUTO_TYPE, "auto-type release %d %d", row, bitpos);
				kbd_sense[row] |= (1<<bitpos);
				kbd_map_pressed[row][bitpos] = 0;
				}
				break;
			}
		kbd_qke_dequeue();
		}
	}
/*...e*/

#ifndef NO_JOY
/*...skbd_grid_press:0:*/
void kbd_grid_press(int row, int bitpos)
	{
	kbd_sense_joy[row] &= ~(1<<bitpos);
	}
/*...e*/
/*...skbd_grid_release:0:*/
void kbd_grid_release(int row, int bitpos)
	{
	kbd_sense_joy[row] |= (1<<bitpos);
	}
/*...e*/
#endif

/*...skbd_out5:0:*/
/* "Drive" */
void kbd_out5(byte val)
	{
	kbd_drive = val;
	diag_message(DIAG_KBD_DRIVE, "kbd_out5 0x%02x", kbd_drive);
	}
/*...e*/
/*...skbd_in5:0:*/
/* "Sense1" */
#ifdef ALT_KBD_SENSE1
extern word ALT_KBD_SENSE1 (word drive);
#endif

byte kbd_in5(void)
	{
	int i;
	word result = 0x00ff;
	for ( i = 0; i < 8; i++ )
		if ( (kbd_drive&(1<<i)) == 0 )
			{
			result &= kbd_sense    [i];
#ifndef NO_JOY
			result &= kbd_sense_joy[i];
#endif
			}
#ifdef ALT_KBD_SENSE1
	result &= ALT_KBD_SENSE1 (kbd_drive);
#endif
	diag_message(DIAG_KBD_SENSE, "kbd_in5 0x%02x returns 0x%02x", kbd_drive, result);
	return (byte) result;
	}
/*...e*/
/*...skbd_in6:0:*/
/* "Sense2", the two extra rows.
   D3 and D2 are two bits of the country code
   (00=English, 01=France, 02=German, 03=Swedish).
   Interestingly, the BASIC ROM has tables for English, then France,
   then German, then Swedish, then Denmark (commented out), then Spain!
   D7 to D4 are all zeros (per Claus Baekkel observation, but I suspect that
   on real hardware you get is what ever happens to be last on the bus. */
#ifdef ALT_KBD_SENSE2
extern word ALT_KBD_SENSE2(word drive);
#endif

byte kbd_in6(void)
	{
	int i;
	word result = 0x03ff;
	for ( i = 0; i < 8; i++ )
		if ( (kbd_drive&(1<<i)) == 0 )
			{
			result &= kbd_sense[i];
#ifndef NO_JOY
			result &= kbd_sense_joy[i];
#endif
			}
	result >>= 8;
	result   |= ( kbd_emu & KBDEMU_COUNTRY );
#ifdef ALT_KBD_SENSE2
	result &= ALT_KBD_SENSE2 (kbd_drive);
#endif
	diag_message(DIAG_KBD_SENSE, "kbd_in6 0x%02x returns 0x%02x", kbd_drive, result);
	return (byte) result;
	}
/*...e*/

/*...skbd_apply_remap:0:*/
static void kbd_patch_roms (const byte *old_lower, const byte *old_upper, const byte *new_lower, const byte *new_upper)
	{
	int rom;
	byte *ptr;
	/*
	{
	char s[256];
	int i, j;
	
	ptr = mem_rom_ptr(0) + 0x1773;
	diag_message(DIAG_KBD_REMAP, "Expected upper table in ROM 0:");
	for ( j = 0; j < 10; ++j )
		{
		for ( i = 0; i < 8; ++i )
			{
			sprintf (&s[3*i], "%02x ", ptr[i+8*j]);
			sprintf (&s[3*i+25], "%02x ", old_upper[i+8*j]);
			}
		s[24] = '\x09';
		diag_message (DIAG_KBD_REMAP, s);
		}
	}
	*/
	for ( rom = 0; rom < 8; ++rom )
		{
		/* diag_message (DIAG_KBD_REMAP, "Searching ROM%d for keyboard mappings", rom); */
		ptr = mem_rom_ptr(rom);
		if ( memcmp(ptr, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) )
			{
			int addr;
			for ( addr = ROM_SIZE; addr < ( 2 * ROM_SIZE - 80 ); ++addr )
				{
				if ( !memcmp(ptr, old_lower, 80) )
					{
					memcpy(ptr, new_lower, 80);
					diag_message (DIAG_KBD_REMAP, "Remapped unshifted keys at %d:0x%04x", rom, addr);
					}
				else if ( !memcmp (ptr, old_upper, 80) )
					{
					memcpy(ptr, new_upper, 80);
					diag_message(DIAG_KBD_REMAP, "Remapped shifted keys at %d:0x%04x", rom, addr);
					}
				++ptr;
				}
			}
		}
	}

void kbd_apply_remap (void)
	{
	kbd_patch_roms (mtx_base, mtx_upper, patch_base, patch_upper);
	kbd_map_unshifted = kbd_remap_unshifted;
	kbd_map_shifted	  = kbd_remap_shifted  ;
	kbd_emu	|= KBDEMU_REMAP;
	}

void kbd_apply_unmap (void)
	{
	kbd_patch_roms (patch_base, patch_upper, mtx_base, mtx_upper);
	kbd_map_unshifted = kbd_unmap_unshifted;
	kbd_map_shifted	  = kbd_unmap_shifted  ;
	kbd_emu &= ~KBDEMU_REMAP;
	}
/*...e*/

/*...skbd_init:0:*/
void kbd_init(int emu)
	{
	int i;

	kbd_emu = emu;

	for ( i = 0; i < 8; i++ )
		{
		kbd_sense    [i] = 0xffff;
#ifndef NO_JOY
		kbd_sense_joy[i] = 0xffff;
#endif
		}

	if ( kbd_emu & KBDEMU_REMAP )
		kbd_apply_remap();
	}

int kbd_get_emu (void)
	{
	return kbd_emu;
	}
/*...e*/
/*...skbd_term:0:*/
void kbd_term(void)
	{
	kbd_emu = 0;
	}
/*...e*/
