/* Keyboard library functions */
/* H. Hanemaayer (hhanemaa@cs.ruu.nl) */

/*
 * Keyboard I/O based on showkey.c from kbd-0.84 package.
 *
 * This is an initial version, it isn't very safe yet since it only catches
 * sigsegv.
 */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>
#include <linux/kd.h>
/* linux/keyboard.h defines NR_KEYS and some scancode-like constants, so it */
/* should also be useful for svgalib programs using the keyboard. It misses */
/* a few KERNEL ifdefs around kernel data structures though. */
#include <linux/keyboard.h>
#include <vga.h>
#include "vgakeyboard.h"


static void (*__keyboard_eventhandler)( int, int );


static struct sigaction oldsiga_segv;
static struct termios oldkbdtermios, newkbdtermios;
static int oldkbmode;
static int kbd_fd;
static void (*currentinthandler)();
static void (*currentsegvhandler)();

#if 0
static void inthandler() {
	keyboard_close();
	sigaction(SIGINT, &oldsiga, NULL);
	raise(SIGINT);
}
#endif

static void handle_segv() {
	/* Hope this still works (the critical thing is probably that */
	/* oldkbmode is not overwritten). */
	keyboard_close();
	/* restore old interrupt */
	sigaction(SIGSEGV, &oldsiga_segv, NULL);
	raise(SIGSEGV);
}

static void default_handler( int, int );

int keyboard_init() {
	struct sigaction siga;

	currentinthandler = NULL;
	currentsegvhandler = NULL;

	/* Install default keyboard handler. */
	__keyboard_eventhandler = default_handler;

	if ((kbd_fd = open("/dev/console", O_RDONLY)) < 0) {
		printf("svgalib: cannot open /dev/console.\n");
		return -1;
	}

	if (ioctl(kbd_fd, KDGKBMODE, &oldkbmode)) {
		printf("svgalib: cannot get keyboard mode.\n");
		return -1;
	}

	tcgetattr(kbd_fd, &oldkbdtermios);
	newkbdtermios = oldkbdtermios;

	newkbdtermios.c_lflag = newkbdtermios.c_lflag & ~ (ICANON | ECHO | ISIG);
	newkbdtermios.c_cc[VMIN] = 0;	/* Making these 0 seems to have the */
	newkbdtermios.c_cc[VTIME] = 0;	/* desired effect. */

	tcsetattr(kbd_fd, TCSAFLUSH, &newkbdtermios);

	ioctl(kbd_fd, KDSKBMODE, K_MEDIUMRAW);

	keyboard_clearstate();

	/* Install segv handler. */
	/* We probably need to catch a host of other signals also if */
	/* we don't want the keyboard to die in any circumstances. */
	currentsegvhandler = handle_segv;
	siga.sa_handler = handle_segv;
	siga.sa_flags = 0;
	siga.sa_mask = 0;
	sigaction(SIGSEGV, &siga, &oldsiga_segv);

	return 0;	/* OK. */
}

void keyboard_close() {
	ioctl(kbd_fd, KDSKBMODE, oldkbmode);
	tcsetattr(kbd_fd, 0, &oldkbdtermios);
	close(kbd_fd);
	if (currentsegvhandler != NULL)
		/* Restore old interrupt. */
		sigaction(SIGSEGV, &oldsiga_segv, NULL);
#if 0		
	if (currentinthandler != NULL)
		/* Restore old interrupt. */
		sigaction(SIGINT, &oldsiga, NULL);
#endif
}

/* For now, we assume there's no console switching. */
/* (Actually, there won't be any unless we catch the console switching */
/* keys). */

#define KBDREADBUFFERSIZE 32

static int keyboard_getevents( int wait ) {
/* Read keyboard device, and handle events. */
/* If wait == 1, process at least on event and return. */
/* If wait == 0, handle all accumulated events; return 0 if no events */
/* were handled, 1 otherwise. */
/* Wait mode doesn't seem to work very well; the keyboard repeat delay is */
/* present. I don't understand fcntl. */
	static unsigned char buf[KBDREADBUFFERSIZE];
	static int kfdmode = 0;	/* 1 = DELAY, 0 = NDELAY */
	int bytesread, i;
	int eventhandled;

	eventhandled = 0;

again:
	if (kfdmode == 1) {	/* This only happens for wait == 1. */
		struct termios kbdtermios;
		int flags;
		/* We don't want to wait, set NDELAY mode. */
		fcntl(kbd_fd, F_GETFL, &flags);
		fcntl(kbd_fd, F_SETFL, flags | O_NDELAY);

		#if 0
		tcgetattr(kbd_fd, &kbdtermios);
		kbdtermios.c_lflag = kbdtermios.c_lflag & ~ (ICANON | ECHO | ISIG);
		kbdtermios.c_cc[VMIN] = 0;
		kbdtermios.c_cc[VTIME] = 0;
		tcsetattr(kbd_fd, TCSANOW, &kbdtermios);
		#endif

		kfdmode = 0;
	}
	bytesread = read(kbd_fd, buf, KBDREADBUFFERSIZE);

	if (wait == 1 && bytesread < 1) {
		struct termios kbdtermios;
		int flags;
		/* We already handled an event, no need to wait for another. */
		if (eventhandled)
			return 1;
		/* Wait mode, we'll sleep on reads. */
		fcntl(kbd_fd, F_GETFL, &flags);
		fcntl(kbd_fd, F_SETFL, flags & ~O_NDELAY);

		#if 0
		tcgetattr(kbd_fd, &kbdtermios);
		kbdtermios.c_lflag = kbdtermios.c_lflag & ~ (ICANON | ECHO | ISIG);
		kbdtermios.c_cc[VMIN] = 0;
		kbdtermios.c_cc[VTIME] = 0;
		tcsetattr(kbd_fd, TCSANOW, &kbdtermios);
		#endif

		kfdmode = 1;
		bytesread = read(kbd_fd, buf, 1);
	}

	if (wait == 0 && bytesread < 1)
		return eventhandled;

	if (bytesread >= 1)
		eventhandled = 1;

	for (i = 0; i < bytesread; i++)
		__keyboard_eventhandler(buf[i] & 0x7f,
			(buf[i] & 0x80) ? KEY_EVENTRELEASE : KEY_EVENTPRESS);

	/* Handle other events that have accumulated. */
	goto again;
}

int keyboard_update() {
	return keyboard_getevents(0);	/* Don't wait. */
}

void keyboard_waitforupdate() {
	keyboard_getevents(1);		/* Wait for event. */
	return;
}

void keyboard_seteventhandler( void (*handler)( int, int ) ) {
	__keyboard_eventhandler = handler;
}



/* Default event handler. */

void keyboard_setdefaulteventhandler() {
	__keyboard_eventhandler = default_handler;
}

static unsigned char state[NR_KEYS];	/* NR_KEYS is defined in linux/keyboard.h */
static int translatemode = 0;

static int checkscancode( int scancode ) {
	if (scancode < 0 || scancode >= NR_KEYS) {
		printf("svgalib: keyboard scancode out of range (%d).\n",
			scancode);
		return 1;
	}
	return 0;
}

static void default_handler( int scancode, int newstate ) {
	if (checkscancode(scancode))
		return;
	if (translatemode & TRANSLATE_CURSORKEYS)
		/* Map cursor key block to keypad cursor keys. */
		switch (scancode) {
 		case SCANCODE_CURSORBLOCKUP :
			scancode = SCANCODE_CURSORUP; break;
 		case SCANCODE_CURSORBLOCKLEFT :
			scancode = SCANCODE_CURSORLEFT; break;
		case SCANCODE_CURSORBLOCKRIGHT :
			scancode = SCANCODE_CURSORRIGHT; break;
		case SCANCODE_CURSORBLOCKDOWN :
			scancode = SCANCODE_CURSORDOWN; break;
		}
	if (translatemode & TRANSLATE_DIAGONAL) {
		/* Translate diagonal keypad keys to two keypad cursor keys. */
		switch (scancode) {
		case SCANCODE_CURSORUPLEFT :
			state[SCANCODE_CURSORUP] = newstate;
			state[SCANCODE_CURSORLEFT] = newstate;
			return;
		case SCANCODE_CURSORUPRIGHT :
			state[SCANCODE_CURSORUP] = newstate;
			state[SCANCODE_CURSORRIGHT] = newstate;
			return;
		case SCANCODE_CURSORDOWNLEFT :
			state[SCANCODE_CURSORDOWN] = newstate;
			state[SCANCODE_CURSORLEFT] = newstate;
			return;
		case SCANCODE_CURSORDOWNRIGHT :
			state[SCANCODE_CURSORDOWN] = newstate;
			state[SCANCODE_CURSORRIGHT] = newstate;
			return;
		}
	}
	if ((translatemode & TRANSLATE_KEYPADENTER) && scancode ==
	SCANCODE_KEYPADENTER)
		scancode = SCANCODE_ENTER;

#if 0	/* This happens very often. */
	if (state[scancode] == newstate) {
		printf("svgalib: keyboard event does not match (scancode = %d)\n",
			scancode);
		return;
	}
#endif	
	state[scancode] = newstate;
}

void keyboard_clearstate() {
	memset(state, 0, NR_KEYS);
}

int keyboard_keypressed( int scancode ) {
	if (checkscancode(scancode))
		return 0;
	return state[scancode];
}

char *keyboard_getstate() {
	return state;
}

void keyboard_translatekeys( int mode ) {
	translatemode = mode;
}
