#include "X.h"
#define NEED_EVENTS
#include "Xproto.h"
#include "misc.h"
#include "window.h"
#include "dix.h"
#include "input.h"
#include "inputstr.h"
#include <dos.h>
#include <conio.h>
#include "ibmkbd.h"
#include "ibmfuncs.h"
#include "keysym.h"

#include "site.h"

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

#define KEYINT	9

extern u_short near input_ready;
extern void stack_dump(int);

static void kbdBell(), kbdControlProc();
static void _fastcall _near sendCommand(u_char com);

extern u_long near TimeLastInput;
extern u_long near sleepcount;

static struct _IBM_Kbd	near k_queue[NKQUEUE];
static struct _IBM_Kbd	near *k_head, near * near *k_tail, near *k_free;
extern KeySymsRec near pcKeySyms[];
extern CARD8 * near pcModMap[];
extern CARD16 near keyModifiersList[];

extern int near mouse_X, near mouse_Y;

extern int near screenIsSaved;

static DevicePtr	kbd_ptr;

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

static unsigned kbd_flags;
#define KBD_ON		0x0001
#define KBD_CLICK	0x0002
#undef  KBD_CLICK	
#define KBD_CLCOUNT	0x0004		/* count contains CLICK_COUNT */
#define KBD_REPEAT	0x0008
#define KBD_BELLON	0x0010		/* bell in progress */
#define KBD_CLICKON	0x0020

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

int
kbdproc(device, action)
DevicePtr device;
int action;
{
	void interrupt far kbdint();
	struct ibmkbd_priv *priv;
	int i;

	switch(action) {
	    case DEVICE_INIT:
		k_free = 0;
		k_head = 0;
		k_tail = &k_head;
		for (i = 0; i < NKQUEUE; i++) {
			k_queue[i].next = k_free;
			k_free = &k_queue[i];
		}
		_disable();
		oldkbd = _dos_getvect(KEYINT);
		_dos_setvect(KEYINT, kbdint);
		_enable();
		priv = (struct ibmkbd_priv *)malloc(sizeof (struct ibmkbd_priv));
		if (0 == priv)
			return 1;
		device->devicePrivate = (pointer)priv;
		priv->map = pcModMap[0];

		priv->offset = KBD_OFFSET;

		InitKeyboardDeviceStruct(device, &pcKeySyms[0], pcModMap[0],
		    kbdBell, kbdControlProc);
		return Success;

	    case DEVICE_ON:
		kbd_ptr = device;
		kbd_flags |= KBD_ON;
		/* remember LEDs from BIOS */
		i = (u_char)((*(u_char far *)0x00400017L >> 4) & 7);
		NoteLedState((DeviceIntPtr)kbd_ptr, LED_NUM,
			i & (1 << (LED_NUM-1)));
		NoteLedState((DeviceIntPtr)kbd_ptr, LED_CAPS,
			i & (1 << (LED_CAPS-1)));
		NoteLedState((DeviceIntPtr)kbd_ptr, LED_SCROLL,
			i & (1 << (LED_SCROLL-1)));
		sendCommand(0xed);
		sendCommand((u_char)i);
		return Success;

	    case DEVICE_OFF:
		kbd_flags &= KBD_ON;
		return Success;

	    case DEVICE_CLOSE:
		/* Reset LED's from BIOS state. */
		sendCommand(0xed);
		sendCommand((u_char)((*(u_char far *)0x00400017L >> 4) & 7));
		if (oldkbd) {
		    _disable();
		    _dos_setvect(KEYINT, oldkbd);
		    _enable();
		    oldkbd = 0;
		}
		free(device->devicePrivate);
		return Success;
	}
	return 1;
}

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

Bool
LegalModifier(key)
BYTE key;
{
printf("LegalModifier called key=%x\n", key);
return TRUE;
}

static unsigned char bell_duration;
static unsigned bell_count;
#define CLICK_COUNT	1193

#pragma loop_opt(off)
static void
kbdBell(loud, pDevice)
int loud;
DevicePtr pDevice;
{
	u_char t;

	_disable();
	kbd_flags |= KBD_BELLON;
	_enable();
	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();
	}
	outp(0x61, inp(0x61) | 3);
	t = b_clicks() + bell_duration;
	while (b_clicks() != t)
		;
	outp(0x61, inp(0x61) & ~3);
	_disable();
	kbd_flags &= ~KBD_BELLON;
	_enable();
}
#pragma loop_opt()

static void
kbdControlProc(device, ctrl)
DevicePtr device;
KeybdCtrl *ctrl;
{
	int i;

	bell_count = (short)(1193000L / ctrl->bell_pitch);
	i = ctrl->bell_duration / 55;
	if (i > 255)
		i = 255;
	bell_duration = i;

	_disable();
#ifdef KBD_CLICK
	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;
	_enable();

	sendCommand(0xed);
	sendCommand((u_char)((u_char)ctrl->leds & 7));
}

#ifdef SERIALNUMBER
extern u_long _near myserial;
#endif

void near
kbdProcessInput()
{
	int i, type, keycode;
	xEvent Event;
	struct _IBM_Kbd near *kp;

	if (screenIsSaved == SCREEN_SAVER_ON)
		SaveScreens(SCREEN_SAVER_OFF, ScreenSaverReset);
#ifdef KBD_CLICK
	_disable();
	if (kbd_flags & KBD_CLICKON) {
		outp(0x61, inp(0x61) & ~3);
		kbd_flags &= ~KBD_CLICKON;
	}
	_enable();
#endif
	while (k_head) {
		_disable();
		kp = k_head;
		k_head = k_head->next;
		if (!k_head)
			k_tail = &k_head;
		_enable();

		keycode = (kp->key & 0x7f);
		if (kp->key & 0x80) {
			type = KeyRelease;
			key_down[keycode >> 3] &= ~(1 << (keycode & 7));
		} else {
			if ((key_down[keycode >> 3] & (1 << (keycode & 7))) &&
			    (!(kbd_flags & KBD_REPEAT) ||
			    pcModMap[0][keycode+KBD_OFFSET]))
				goto drop;
			type = KeyPress;
			key_down[keycode >> 3] |= (1 << (keycode & 7));
		}
		Event.u.keyButtonPointer.time = kp->time;
		Event.u.keyButtonPointer.rootX = kp->x;
		Event.u.keyButtonPointer.rootY = kp->y;
		Event.u.u.type = type;
		/* Special case for Keyboard keys */
		keycode += KBD_OFFSET;
		i = pcKeySyms[0].map[(keycode - pcKeySyms[0].minKeyCode) *
		    pcKeySyms[0].mapWidth];
		if (i == XK_Escape &&
		    (((DeviceIntPtr)kbd_ptr)->key->state &
		    (ControlMask|ShiftMask|Mod1Mask)) == (ControlMask|Mod1Mask))
			GiveUp(0);
#ifdef SERIALNUMBER
		if (i == XK_F9 && type == KeyPress &&
		    (((DeviceIntPtr)kbd_ptr)->key->state &
		  (ControlMask|ShiftMask|Mod1Mask)) == (ControlMask|Mod1Mask)) {
			printf("serial %ld\n", myserial - 9);
		}
#endif

		if (i >= XK_KP_Decimal && i <= XK_KP_9 && type == KeyPress &&
	((u_char)((DeviceIntPtr)kbd_ptr)->kbdfeed->ctrl.leds & LED_NUM) == 0 ||
		    i >= XK_A && i <= XK_Z && type == KeyPress &&
	    ((u_char)((DeviceIntPtr)kbd_ptr)->kbdfeed->ctrl.leds & LED_CAPS)){
			Event.u.u.detail = PC_FAKESHIFT;
			(*kbd_ptr->processInputProc)(&Event, kbd_ptr, 1);
			Event.u.u.detail = keycode;
			(*kbd_ptr->processInputProc)(&Event, kbd_ptr, 1);
			Event.u.u.detail = PC_FAKESHIFT;
			Event.u.u.type = KeyRelease;
			(*kbd_ptr->processInputProc)(&Event, kbd_ptr, 1);
		} else {
			Event.u.u.detail = keycode;
			(*kbd_ptr->processInputProc)(&Event, kbd_ptr, 1);
		}
		/* special hack for NumLock */
		if (i == XK_Num_Lock && type == KeyPress) {
			((DeviceIntPtr)kbd_ptr)->kbdfeed->ctrl.leds ^= LED_NUM;
			sendCommand(0xed);
/* debug_print(); */
			sendCommand((u_char)
	    (((u_char)((DeviceIntPtr)kbd_ptr)->kbdfeed->ctrl.leds) & 7));
		}
		if (i == XK_Caps_Lock && type == KeyPress) {
			((DeviceIntPtr)kbd_ptr)->kbdfeed->ctrl.leds ^= LED_CAPS;
			sendCommand(0xed);
			sendCommand((u_char)
	    (((u_char)((DeviceIntPtr)kbd_ptr)->kbdfeed->ctrl.leds) & 7));
		}
    drop:
		_disable();
		kp->next = k_free;
		k_free = kp;
		_enable();
	}
	_disable();
	input_ready &= ~INPUT_KBD;
	TimeLastInput = clock();
	_enable();
}

#pragma check_stack(off)
void interrupt far
kbdint(es, ds, di, si, bp, sp, bx, dx, cx, ax, off, seg, flags)
int es, ds, di, si, bp, sp, bx, dx, cx, ax, off, seg, flags;
{
	static Bool extended_code=0;
	unsigned char c, c2;
	struct _IBM_Kbd near *kp;
	extern near lastRequest;

	_enable();
	c = inp(0x60);
	if (0xf0 == (c & 0xf0)) {
		kbd_ack = c;
		goto out;
	}
	if (!(kbd_flags & KBD_ON))
		goto out;
	if (0xe0 == (c & 0xfe)) {
		extended_code = 1;
		goto out;
	}
	c2 = c & 0x7f;
	if (extended_code) {
		if (c2 >= 0x6f)
			c += 0x07;
		else if (c2 >= 0x45)
			c += 0x22;
		else if (c2 >= 0x35)
			c += 0x2e;
		else if (c2 == 0x2a)
			c += 0x38;
		else
			c += 0x44;
	}
	extended_code = 0;
	kp = k_free;
	if (!kp) {
		if (c == PC_DEL) {
			/* 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);
			printf("my CS= %x, last request %d\n", (u_short)((u_long)kbdint >> 16), lastRequest);
#ifdef notdef
			stack_dump(bp);
#endif
			fclose(stdout);
			GiveUp(TRUE);
		}
		goto out;
	}
	k_free = k_free->next;
	kp->next = 0;
	kp->x = mouse_X;
	kp->y = mouse_Y;
	kp->time = clock();
	kp->key = c;
	*k_tail = kp;
	k_tail = &kp->next;
	input_ready |= INPUT_KBD;
#ifdef KBD_CLICK
	if (c & 0x80)
		goto out;		/* 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
out:
	_disable();
	outp(0x20, 0x20);
}
#pragma check_stack()

static void _fastcall _near
sendCommand(com)
u_char com;
{
	long i=20000;

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