/*
 * Micro-X -- an X server for DOS
 * Copyright (C) 1994 StarNet Communications Corp.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 * StarNet Communications Corp.
 * 550 Lakeside Dr. #3
 * Sunnyvale CA 94086 US
 * http://www.starnet.com
 * x-dos@starnet.com
 */

#include "X.h"

#define NEED_EVENTS
#include "Xproto.h"
#include "misc.h"
#include "window.h"
#include "dix.h"
#include "inputstr.h"
#include <dos.h>
#include <conio.h>
#include "ibmkbd.h"
#include "ibmfuncs.h"
#include "keysym.h"
#include "funcs.h"
#include <time.h>
#include <process.h>
#include <fcntl.h>
#include <io.h>

#undef NULL
#include <stdio.h>	/* for debug printf's */

#define KEYINT	9

struct kbd_type {
	int	country_code;
	char	*fname;
};

static struct kbd_type kbd_types[] = {
	  1,	"US.KBD",
	  2,	"CANADA.KBD",
	  3,	"LATINAM.KBD",
	 30,	"GREECE.KBD",
	 31,	"NETHERLA.KBD",
	 32,	"BELGIUM.KBD",
	 33,	"FRANCE.KBD",
	 34,	"SPAIN.KBD",
	 36,	"HUNGARY.KBD",
	 38,	"YUGOSLAV.KBD",
	 39,	"ITALY.KBD",
	 41,	"SWITZERL.KBD",
	 42,	"CZECH.KBD",
	 43,	"AUSTRIA.KBD",
	 44,	"UK.KBD",
	 45,	"DENMARK.KBD",
	 46,	"SWEDEN.KBD",
	 47,	"NORWAY.KBD",
	 48,	"POLAND.KBD",
	 49,	"GERMANY.KBD",
	 55,	"BRAZIL.KBD",
	 61,	"ENGLISH.KBD",		/* International English */
	 62,	"INDONESI.KBD",
	 64,	"NEWZEALA.KBD",
	 65,	"SINGAPOR.KBD",
	 81,	"JAPAN.KBD",
	351,	"PORTUGAL.KBD",
	352,	"LUXEMBOU.KBD",
	353,	"IRELAND.KBD",
	358,	"FINLAND.KBD",
	852,	"HONGKONG.KBD",
	0,	0
};

static KeySymsRec pcKeySyms_File;
extern KeySymsRec pcKeySyms_US;
static KeySymsPtr pcKeySyms = &pcKeySyms_US;

extern u_char input_ready;
extern void stack_dump(int);
int kbd_country(void);
extern void debug_print(void);
extern void fork_shell(void);
static void kbd_abort(void);

static void kbdBell(), kbdControlProc();

extern u_long TimeLastInput;

extern CARD8 pcModMap[];
extern CARD16 keyModifiersList[];

extern u_short mouse_X, mouse_Y;

extern int screenIsSaved;

static DevicePtr	kbd_ptr;

static struct _IBM_Kbd	near k_queue[NKQUEUE];
static int k_head, k_tail;

static unsigned char volatile kbd_ack;
static unsigned char key_down[16];

unsigned kbd_flags = 0;
#define KBD_ON		0x0001
/* #define KBD_CLICK	0x0002 */
/* #define KBD_CLCOUNT	0x0004 */		/* count contains CLICK_COUNT */
#define KBD_REPEAT	0x0008
#define KBD_BELLON	0x0010		/* bell in progress */
#define KBD_CLICKON	0x0020
#define KBD_INIT	0x0040
#define KBD_ATEXIT	0x0080
#define KBD_XT		0x0100

void
kbd_finit(x11env)
const char *x11env;
{
    char *name, *cp;
    int i, size;
    struct kbd_type *kp;
    char _fname[16];
    char kbd_fname[128];
    int fd;

    i = kbd_country();

    name = 0;
    for (kp = kbd_types; kp->country_code; kp++)
	if (kp->country_code == i) {
	    name = kp->fname;
	    break;
	}

    if (!name && i > 0 && i < 1000) {
	sprintf(_fname, "%03d.KBD", i);
	name = _fname;
    }
    if (name) {
	printf("Using keyboard file %s\n", name);
	strcpy(kbd_fname, x11env);
	cp = kbd_fname + strlen(kbd_fname);
	if (cp[-1] != '\\')
	    *cp++ = '\\';
	strcpy(cp, name);
	fd = open(kbd_fname, O_RDONLY|O_BINARY);
	if (fd < 0) {
	    perror(kbd_fname);
	} else {
	    if (pcKeySyms_File.map) {
		free(pcKeySyms_File.map);
		pcKeySyms_File.map = 0;
	    }
	    i = read(fd, &pcKeySyms_File, sizeof pcKeySyms_File);
	    if (i != sizeof pcKeySyms_File) {
		fprintf(stderr, "%s: short read\n", kbd_fname);
	    } else {
		size = (pcKeySyms_File.maxKeyCode -
		    pcKeySyms_File.minKeyCode + 1) *
		    pcKeySyms_File.mapWidth * sizeof (KeySym);
		pcKeySyms_File.map = malloc(size);
		if (!pcKeySyms_File.map) {
		    close(fd);
		    return;
		}
		i = read(fd, pcKeySyms_File.map, size);
		if (i != size) {
		    fprintf(stderr, "%s: short read %d\n", kbd_fname, i);
		} else {
		    pcKeySyms = &pcKeySyms_File;
		    if (pcKeySyms_File.mapWidth > 2)
			pcModMap[0x6d] = Mod3Mask; /* XXX */
		}
	    }
	    close(fd);
	}
    }
}

int
kbdproc(device, action)
DevicePtr device;
int action;
{
    struct ibmkbd_priv *priv;
    int i;

    switch(action) {
	case DEVICE_INIT:
	    priv = (struct ibmkbd_priv *)xalloc(sizeof (struct ibmkbd_priv));
	    if (0 == priv) {
		    return 1;
	    }
	    k_head = 0;
	    k_tail = 0;
	    kbd_init();
	    if (!(kbd_flags & KBD_ATEXIT))
		    atexit(kbd_abort);
	    kbd_flags |= (KBD_INIT|KBD_ATEXIT);

	    device->devicePrivate = (pointer)priv;
	    priv->map = pcModMap;

	    priv->offset = KBD_OFFSET;

	    InitKeyboardDeviceStruct(device, pcKeySyms, pcModMap,
		kbdBell, kbdControlProc);
	    return Success;

	case DEVICE_ON:
	    kbd_ptr = device;
	    kbd_flags |= KBD_ON;
	    /* remember LEDs from BIOS */
	    i = (bios[0x417] >> 4) & 7;
	    if (!(kbd_flags & KBD_XT)) {
		NoteLedState((DeviceIntPtr)kbd_ptr, 1, i & LED_SCROLL);
		NoteLedState((DeviceIntPtr)kbd_ptr, 2, i & LED_NUM);
		NoteLedState((DeviceIntPtr)kbd_ptr, 3, i & LED_CAPS);
		sendCommand(0xed);
		sendCommand((u_char)i);
	    }
	    if (i & LED_SCROLL)
		fake_key(KEY_SCROLLLOCK);
#ifdef notdef
	    if (i & LED_NUM)
		fake_key(KEY_PAUSE);
#endif
	    if (i & LED_CAPS)
		fake_key(KEY_CAPSLOCK);
	    return Success;

	case DEVICE_OFF:
	    kbd_flags &= KBD_ON;
	    return Success;

	case DEVICE_CLOSE:
	    if (!(kbd_flags & KBD_XT)) {
		/* Reset LED's from BIOS state. */
		sendCommand(0xed);
		sendCommand((bios[0x417] >> 4) & 7);
	    }
	    kbd_exit();
	    xfree(device->devicePrivate);
	    if (pcKeySyms_File.map) {
		free(pcKeySyms_File.map);
		pcKeySyms_File.map = 0;
	    }
	    return Success;
    }
    return 1;
}

static void
kbd_abort()
{
    kbd_exit();
}

Bool
LegalModifier(key, kbd)
BYTE key;
DeviceIntPtr kbd;
{
	kbd = kbd;
	key = key;
	return TRUE;
}

static int bell_duration;
static unsigned short bell_count;
#define CLICK_COUNT	1193

static void
kbdBell(loud, pDevice)
int loud;
DevicePtr pDevice;
{
	u_long t;

	loud = loud;		/* We can't change the bell volume. */
	pDevice = pDevice;	/* We know which device it is. */
#ifdef KBD_CLICK
	if (kbd_flags & KBD_CLCOUNT) {
		_disable();
		outp(0x43, 0xb6);
		outp(0x42, (u_char)bell_count);
		outp(0x42, (u_char)(bell_count >> 8));
		kbd_flags &= ~KBD_CLCOUNT;
		_enable();
	}
#endif
	outp(0x61, inp(0x61) | 3);
	t = n_clicks() + bell_duration;
	while (n_clicks() < t)
		;
	outp(0x61, inp(0x61) & ~3);
}

static void
kbdControlProc(device, ctrl)
DevicePtr device;
KeybdCtrl *ctrl;
{
	u_short count;

	device = device;
	/* convert bell duration to ticks */
	bell_duration = ctrl->bell_duration / 55;

	count = 1193000 / ctrl->bell_pitch;
	if (count != bell_count) {
		bell_count = count;
		outp(0x43, 0xb6);
		outp(0x42, (u_char)bell_count);
		outp(0x42, (u_char)(bell_count >> 8));
	}

#ifdef KBD_CLICK
	kbd_flags &= ~KBD_CLCOUNT;
	if (ctrl->click) {
		kbd_flags |= KBD_CLICK;
	} else
		kbd_flags &= ~KBD_CLICK;
#endif

	if (ctrl->autoRepeat)
		kbd_flags |= KBD_REPEAT;
	else
		kbd_flags &= ~KBD_REPEAT;

	if (!(kbd_flags & KBD_XT)) {
	    sendCommand(0xed);
	    sendCommand((u_char)ctrl->leds & 7);
	}
}

    /* Map extended code keys as follows:
     *  extended   mapped
     *  1c	60	KP_Enter
     *	1d	61	Control_R
     *	2a	62	generated shift
     *  35	63	KP_Divide
     *	37	65	Print Screen
     *  38	66	Alt_R
     *  45	67	Num_Lock
     *  46	68	Break
     *	47	69	Home
     *	48	6a	Up
     *	49	6b	Prior
     *	4b	6d	Left
     *	4d	6f	Right
     *	4f	71	End
     *	50	72	Down
     *	51	73	Next
     *	52	74	Insert
     *	53	75	Delete
     */
static char ext_key[128] = {
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,0x60,0x61,   0,   0,
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,0x62,   0,   0,   0,   0,   0,
   0,   0,   0,   0,   0,0x63,   0,0x65,0x66,   0,   0,   0,   0,   0,   0,   0,
   0,   0,   0,   0,   0,0x67,0x68,0x69,0x6a,0x6b,   0,0x6d,   0,0x6f,   0,0x71,
0x72,0x73,0x74,0x75,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
};

static unsigned long valid_keys[4] = {
0xfffffffe, 0xffffffff, 0x01dfffff, 0x00008000
};
#define KP_START	0x40
#define KP_END		0x60
static char kp_key[32] = {
   0,   0,   0,   0,   0,   0,   0,0x5a,0x5b,0x5c,   0,0x5d,0x5e,0x5f,   0,0x76,
0x77,0x78,0x79,0x7a,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
};

void
kbdProcessInput()
{
    static Bool extended_code=0;
    static u_long control = 0;
    int i;
    xEvent Event;
    unsigned char key;
    static char last_alt, last_ctl;	/* keycode of last ALT key pressed */
    struct _IBM_Kbd *kp;

    if (screenIsSaved == SCREEN_SAVER_ON)
	    SaveScreens(SCREEN_SAVER_OFF, ScreenSaverReset);
    _disable();
    input_ready &= ~INPUT_KBD;
    _enable();
#ifdef KBD_CLICK
    _disable();
    if (kbd_flags & KBD_CLICKON) {
	    outp(0x61, inp(0x61) & ~3);
	    kbd_flags &= ~KBD_CLICKON;
    }
    _enable();
#endif
    while (k_head != k_tail) {
	_disable();
	kp = &k_queue[k_head];
	k_head = (k_head + 1) % NKQUEUE;
	key = kp->key;
	Event.u.keyButtonPointer.time = kp->time;
	Event.u.keyButtonPointer.rootX = kp->x;
	Event.u.keyButtonPointer.rootY = kp->y;
	_enable();
	if (0xe0 == (key & 0xfe)) {
		extended_code = 1;
		continue;
	}
	if (key & 0x80)
		Event.u.u.type = KeyRelease;
	else
		Event.u.u.type = KeyPress;
	/*
	 * The 101 and 102 keyboards generate a control-NumLock when Pause
	 * is pressed.
	 */
	if (key == KEY_CONTROL && extended_code)
	    control = Event.u.keyButtonPointer.time;
	else {
	    if (Event.u.keyButtonPointer.time > control + 1)
		control = 0;
	    if ((key & 0x7f) == KEY_PAUSE) {
		if (!control)
		    extended_code = 1;
	    }
	    control = 0;
	}
	key &= 0x7f;
	if (extended_code) {
	    extended_code = 0;
	    key = ext_key[key];
	    if (!key)
		continue;
	} else {
	    if (!(valid_keys[key >> 5] & (1L << (key & 0x1f))))
		continue;
	    /* special hack for "Macro" key */
	    if (key == 0x6f)
		key = 0x59;
	    /* special hack for KP keys */
	    if ((((DeviceIntPtr)kbd_ptr)->kbdfeed->ctrl.leds & LED_NUM) &&
		key >= KP_START && key < KP_END && kp_key[key - KP_START])
		    key = kp_key[key - KP_START];
	}

	Event.u.u.detail = key + KBD_OFFSET;

	/*
	 * Handle toggle keys
	 */
	switch (key) {
	    case KEY_CAPSLOCK:
		i = LED_CAPS;
		goto toggle;
	    case KEY_NUMLOCK:
		if (Event.u.u.type == KeyRelease)
		    continue;
	        ((DeviceIntPtr)kbd_ptr)->kbdfeed->ctrl.leds ^= LED_NUM;
		goto send;
	    case KEY_SCROLLLOCK:
		i = LED_SCROLL;
	    toggle:
		if (Event.u.u.type == KeyRelease)
		    continue;
		if (key_down[key >> 3] & (1 << (key & 7))) {
		    Event.u.u.type = KeyRelease;
		    ((DeviceIntPtr)kbd_ptr)->kbdfeed->ctrl.leds &= ~i;
		} else {
		    ((DeviceIntPtr)kbd_ptr)->kbdfeed->ctrl.leds |= i;
		}
	    send:
		if (!(kbd_flags & KBD_XT)) {
		    sendCommand(0xed);
		    sendCommand((u_char)
		  (((u_char)((DeviceIntPtr)kbd_ptr)->kbdfeed->ctrl.leds) & 7));
		}
		if (key == KEY_NUMLOCK)
		    return;
	}
	if (Event.u.u.type == KeyPress) {
	    if ((key_down[key >> 3] & (1 << (key & 7))) &&
		(!(kbd_flags & KBD_REPEAT) || pcModMap[Event.u.u.detail]))
		    continue;
	    key_down[key >> 3] |= (1 << (key & 7));
	    if (pcModMap[Event.u.u.detail] & Mod1Mask)
		last_alt = key;
	    else if (pcModMap[Event.u.u.detail] & ControlMask)
		last_ctl = key;
	} else {
		key_down[key >> 3] &= ~(1 << (key & 7));
	}

	i = pcKeySyms->map[(Event.u.u.detail - pcKeySyms->minKeyCode) *
	    pcKeySyms->mapWidth];

	/* Check for special Ctrl+Alt keys. */
	if (Event.u.u.type == KeyPress && (((DeviceIntPtr)kbd_ptr)->key->state &
	    (ControlMask|ShiftMask|Mod1Mask)) == (ControlMask|Mod1Mask)) {

	    if (i == XK_Escape)
		GiveUp(0);

#ifdef SERIALNUMBER
	    if (i == XK_F9)
		printf("serial %ld\n", myserial - 9);
#endif

	    if (i == XK_Execute) {
		fork_shell();
		/* Now cause Alt and Control to be released */
		key_down[last_alt >> 3] &= ~(1 << (last_alt & 7));
		key_down[last_ctl >> 3] &= ~(1 << (last_ctl & 7));

		Event.u.u.type = KeyRelease;
		Event.u.u.detail = last_alt+KBD_OFFSET;
		(*kbd_ptr->processInputProc)(&Event, kbd_ptr, 1);
		Event.u.u.detail = last_ctl+KBD_OFFSET;
		(*kbd_ptr->processInputProc)(&Event, kbd_ptr, 1);
		continue;
	    }
	}
	(*kbd_ptr->processInputProc)(&Event, kbd_ptr, 1);
#ifdef notdef
	/* special hack for Ctrl-NumLock == Pause */
	if (i == XK_Num_Lock &&
	    (((DeviceIntPtr)kbd_ptr)->key->state & ControlMask))
		i = XK_Pause;
#endif
    }
    TimeLastInput = *(u_long *)0x46c * 55;
}

static void
fake_key(c)
unsigned char c;
{
    struct _IBM_Kbd *kp;

    _disable();
    kp = &k_queue[k_tail];
    k_tail = (k_tail + 1) % NKQUEUE;
    kp->x = mouse_X;
    kp->y = mouse_Y;
    kp->time = *(u_long *)0x46c * 55;
    kp->key = c;
    input_ready |= INPUT_KBD;
    _enable();
}

#pragma check_stack(off)
void __interrupt
kbdint()
{
    unsigned char c;
    struct _IBM_Kbd *kp;
    extern near lastRequest;

    while (inp(0x64) & 0x01) {
	c = inp(0x60);
	if (0xf0 == (c & 0xf0)) {
		kbd_ack = c;
		continue;
	}
	if (!(kbd_flags & KBD_ON))
		continue;
	if ((k_tail + 1) % NKQUEUE == k_head) {
#ifdef notdef
		if (c == PC_DEL) {
			_enable();
			/* Unfortunately, this is a bad time to do a
			 * printf because this is an interrupt routine
			 * and DOS is not re-entrant.  I'll try anyway.
			 */
			printf("kbdint called by %x:%x\n", seg, off, flags);
#ifdef notdef
			stack_dump(bp);
#endif
			fflush(stdout);
			GiveUp(TRUE);
		}
#endif
		continue;
	}
	kp = &k_queue[k_tail];
	k_tail = (k_tail + 1) % NKQUEUE;
	kp->x = mouse_X;
	kp->y = mouse_Y;
	kp->time = *(u_long *)0x46c * 55;
	kp->key = c;
	input_ready |= INPUT_KBD;
#ifdef KBD_CLICK
	if (c & 0x80)
		continue;		/* don't click on key up */
	if ((kbd_flags & (KBD_CLICK|KBD_BELLON)) == KBD_CLICK) {

		if (pcModMap[0][(c & 0x7f)+KBD_OFFSET])
			goto out;	/* don't click control keys */

		if (!(kbd_flags & KBD_CLCOUNT)) {
			outp(0x43, 0xb6);
			outp(0x42, (u_char)CLICK_COUNT);
			outp(0x42, (u_char)(CLICK_COUNT >> 8));
			kbd_flags |= KBD_CLCOUNT;
		}
		outp(0x61, inp(0x61) | 3);
		kbd_flags |= KBD_CLICKON;
		/* The right thing to do here is set a timer to turn
		 * the click tone off after click_time.  */
	}
#endif
    }
    outp(0x20, 0x20);
}
#pragma check_stack()

static void
sendCommand(com)
u_char com;
{
	long i=200000;

rexmit:
	kbd_ack = 0;
	_disable();
	while (i-- && (inp(0x64) & 0x02))
		;
	outp(0x60, com);
	_enable();
if (i == -1)
printf("Failed to get kbd ready\n");
	i = 200000;
	while (i-- && !kbd_ack)
		;
if (i == -1)
printf("kbd_ack=%x\n", kbd_ack);
	if (kbd_ack == 0xfe)
		goto rexmit;
}

static void (interrupt far *oldkbd)() = 0;

void
kbd_init()
{
    _disable();
    oldkbd = _dos_getvect(KEYINT);
    _dos_setvect(KEYINT, kbdint);
    _enable();
}

void
kbd_exit()
{
    if (oldkbd) {
	_disable();
	_dos_setvect(KEYINT, oldkbd);
	_enable();
	oldkbd = 0;
    }
}
