
// The VGA graphics module:
//

#if defined(linux) && defined (USE_SVGALIB)
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <linux/vt.h>

#include "vga_framebuf.h"
#include "cursor_sprite.h"
#include "keyboard.h"
#include "vga_keys.h"

typedef struct rgb_triple {
	unsigned char red;
	unsigned char green;
	unsigned char blue;
	};

extern "C" {  // Part of SVGAlib (undocumented)
	extern int  mouse_update(void);
	extern void mouse_seteventhandler(void (*handler)(int, int, int));
}

static VGA_FrameBuf *THIS=NULL;
void VGA_MouseHandler(int button, int x, int y)
{
	THIS->MoveMouse(button, x, y);
}


VGA_FrameBuf:: VGA_FrameBuf(char **icon, 
		unsigned short width, unsigned short height, char *title) :
					FrameBuf(icon, width, height, title)
{
	/* Make sure we only have one VGA framebuffer per process */
	if ( THIS ) {
		fprintf(stderr,
			"VGA_FrameBuf: VGA frame buffer already running!\n");
		exit(255);
	}

	/* Verify the width and height */
	if ( (width != 640) || (height != 480) ) {
		fprintf(stderr, 
			"VGA_FrameBuf only supports 640x480 resolution!\n");
		exit(255);
	} else {
		WIDTH = 640;
		HEIGHT = 480;
	}

	/* Make sure we are connected to a linux console */
	if ( (kbd_fd=open("/dev/tty", O_RDONLY, 0)) < 0) {
		perror("VGA_FrameBuf: Can't open tty");
		exit(255);
	}
	if ( ioctl(kbd_fd, KDGKBMODE, &Old_KDmode) < 0 ) {
		if ( errno == EINVAL ) {
			fprintf(stderr,
				"VGA_FrameBuf: Not at a linux console!\n");
		} else {
			perror(
			"VGA_FrameBuf: Can't get console keyboard modes");
		}
		exit(255);
	}

	/* Turn on the SVGA Graphics mode */
	if ( ! vga_hasmode(G640x480x256) ) {
		fprintf(stderr,
	"VGA_FrameBuf: Video hardware doesn't support requested mode!\n");
		exit(255);
	}
	vga_setmousesupport(1);
	if ( vga_setmode(G640x480x256) != 0 ) {
		fprintf(stderr,
			"VGA_FrameBuf: Couldn't initialize graphics!\n");
		exit(255);
	}
	gl_setcontextvga(G640x480x256);
	screen = gl_allocatecontext();
	gl_getcontext(screen);

	/* Make sure we're at 8 bits per pixel... */
	if ( screen->bytesperpixel != 1 ) {
		vga_setmode(TEXT);
		fprintf(stderr,
			"VGA_FrameBuf: SVGA card uses %d bits per pixel!\n",
						screen->bytesperpixel*8);
		exit(255);
	}

	/* Set basic color palette */
	gl_setrgbpalette();

	/* Allocate a screen pixmap, and set it black */
	Black = gl_rgbcolor(0, 0, 0);
	shared_len = (width * height);	// 8 bits per pixel
	backbuf = new unsigned char[shared_len];
	memset(backbuf, Black, shared_len);
	shared_mem = new unsigned char[shared_len];
	memset(shared_mem, Black, shared_len);

	/* Initialize the keyboard 
	   WARNING: if not properly terminated, this will fry the keyboard!
	*/
	if ( ioctl(kbd_fd, KDSKBMODE, K_MEDIUMRAW) < 0 ) {
		vga_setmode(TEXT);
		perror("Can't set console keyboard modes");
		exit(255);
	}
	system("stty raw; stty -echo");

	/* Initialize Event Queues */
	Events = new Stack<XEvent>(ChunkSize);
	key_state = 0;
	Reefer = new Stack<Area>(ChunkSize);

	/* Set mouse handler */
	mouseSprite.width = *((unsigned short *)cursor_sprite);
	mouseSprite.height = *((unsigned short *)&cursor_sprite[2]);
	mouseSprite.pixels = (unsigned char *)&cursor_sprite[4];
	mouseSprite.mask = (unsigned char *)&cursor_sprite[
				(mouseSprite.width*mouseSprite.height)+4];
	behind_mouse = new unsigned char
				[mouseSprite.width*mouseSprite.height];
#ifdef CENTER_MOUSE
	mouseX = WIDTH/2;
	mouseY = HEIGHT/2;
#else
	mouseX = 0;
	mouseY = 0;
#endif
	mouseButtons = 0;
	mouse_accel = 1.0;
	showmouse = 1;
	THIS = this;
	mouse_seteventhandler(VGA_MouseHandler);
	DrawMouse();

	/* Allow switches to other consoles */
	vga_unlockvc();
}

VGA_FrameBuf:: ~VGA_FrameBuf()
{
	/* Free the video memory */
	delete[] backbuf;
	delete[] shared_mem;
	delete   Reefer;
	delete   Events;

	/* Reset the keyboard and mouse --
		If these fail, what can we do?
	 */
	(void) ioctl(kbd_fd, KDSKBMODE, Old_KDmode);
	(void) system("stty -raw echo");

	/* Reset the display */
	vga_setmode(TEXT);
	THIS = NULL;
}

int
VGA_FrameBuf:: Alloc_Cmap(int polite, Color Cmap[NUM_COLORS])
{
	Alloc_Private_Cmap(Cmap);
}

/* Allocate a private 256 color colormap :) */
int
VGA_FrameBuf:: Alloc_Private_Cmap(Color Cmap[NUM_COLORS])
{
	rgb_triple Colors[NUM_COLORS];
	int        c;

	// Gently refresh a black screen in the new colormap
	memset(Colors, 0, 256*sizeof(rgb_triple));
	gl_setpalette(Colors);
	for ( c=0; c<256; ++c ) {
		Color_Map[c].red   = Cmap[c].red<<8;
		Color_Map[c].green = Cmap[c].green<<8;
		Color_Map[c].blue  = Cmap[c].blue<<8;
		Colors[c].red      = Cmap[c].red>>2;
		Colors[c].green    = Cmap[c].green>>2;
		Colors[c].blue     = Cmap[c].blue>>2;
		Pixel_colors[c]    = c;

		if ( !Cmap[c].red && !Cmap[c].green && !Cmap[c].blue )
			Black = c;
	}
	Colors[Black].red = Colors[0].red;
	Colors[Black].green = Colors[0].green;
	Colors[Black].blue = Colors[0].blue;
	Colors[0].red = Colors[0].green = Colors[0].blue = 0;
	Pixel_colors[Black] = 0;
	Pixel_colors[0] = Black;
	Black = 0;
	memset(backbuf, Black, shared_len);
	memset(shared_mem, Black, shared_len);
	Refresh();
	gl_setpalette(Colors);
}

void
VGA_FrameBuf:: Hide_Cursor(void)
{
	if ( hidden_cursor ==  0 ) {
		showmouse = 0;
		EraseMouse();
		hidden_cursor = 1;
	}
}

void
VGA_FrameBuf:: Show_Cursor(void)
{
	if ( hidden_cursor == 1 ) {
		DrawMouse();
		showmouse = 1;
		hidden_cursor = 0;
	}
}

/* This function was adapted from 'xscreensaver', by Jamie Zawinski 

	-- Thanks! :)
*/
void
VGA_FrameBuf:: Fade(int steps)
{
	static int state = XFADE_IN;
	static rgb_triple orig_colors[NUM_COLORS];
	rgb_triple Colors[NUM_COLORS];
	int        i, c;

	state = ( (state == XFADE_IN) ? XFADE_OUT : XFADE_IN );

	if ( state == XFADE_OUT ) {
		gl_getpalette(orig_colors);
		for ( i=steps; i > 0; i-- ) {
			for (c = 0; c < NUM_COLORS; c++) {
				Colors[c].red   =
					(orig_colors[c].red   * i / steps);
				Colors[c].green =
					(orig_colors[c].green * i / steps);
				Colors[c].blue  =
					(orig_colors[c].blue  * i / steps);
			}
			gl_setpalette(Colors);
			usleep(5);
		}
	} else {
		for ( i=0; i < steps; i++ ) {
			for (c = 0; c < NUM_COLORS; c++) {
				Colors[c].red   =
					(orig_colors[c].red   * i / steps);
				Colors[c].green =
					(orig_colors[c].green * i / steps);
				Colors[c].blue  =
					(orig_colors[c].blue  * i / steps);
			}
			gl_setpalette(Colors);
			usleep(5);
		}
	}
	
	if ( state == XFADE_IN ) {
		/* Restore the original colormap */
		for (c = 0; c < NUM_COLORS; c++) {
			Colors[c].red   = orig_colors[c].red;
			Colors[c].green = orig_colors[c].green;
			Colors[c].blue  = orig_colors[c].blue;
		}
		gl_setpalette(Colors);
	}
}
	
void
VGA_FrameBuf:: RefreshArea(int x0, int y0, int width, int height)
{
	Area area;

	area.x = x0;
	area.y = y0;
	area.width = width;
	area.height = height;
	Reefer->Add(&area);
}

void
VGA_FrameBuf:: Refresh(void)
{
	Reefer->Flush();
	gl_putbox(0, 0, WIDTH, HEIGHT, shared_mem);
	if ( showmouse )
		DrawMouse();
}

void
VGA_FrameBuf:: FlushEvents(void)
{
	int    nevents;
	XEvent event;

	for ( nevents=NumEvents(); nevents; --nevents )
		GetEvent(&event);
}


/* Stuff to help map keycodes to ascii */
#define RSHIFT_BIT	0x01
#define LSHIFT_BIT	0x02
#define RCTRL_BIT	0x04
#define LCTRL_BIT	0x08
#define RALT_BIT	0x10
#define LALT_BIT	0x20
#define CAPSLK_BIT	0x40
#define INSERT_BIT	0x80
#define STATE_SHIFT(X)	( (X&CAPSLK_BIT) ? (!(X&(RSHIFT_BIT|LSHIFT_BIT))) : \
						(X&(RSHIFT_BIT|LSHIFT_BIT)) )
#define STATE_CTRL(X)	(X&(RCTRL_BIT|LCTRL_BIT))
#define STATE_ALT(X)	(X&(RALT_BIT|LALT_BIT))

#define UP_BIT	0x80
int
VGA_FrameBuf:: NumEvents(void)
{
	XEvent event;
	struct timeval tv;
	fd_set fdset;
	int    i, len;
	unsigned char key, buffer[1024];

	/* Check for mouse events (before calling select()!) */
	mouse_update();

	/* Check for keyboard events */
	tv.tv_sec = 0;
	tv.tv_usec = 0;
	FD_ZERO(&fdset);
	FD_SET(kbd_fd, &fdset);

	if ( select(kbd_fd+1, &fdset, NULL, NULL, &tv) != 1 )
		return(Events->Size());

	if ( (len=read(kbd_fd, buffer, 1024)) < 0 ) {
		perror("VGA_FrameBuf: read event error");
		return(Events->Size());
	}

	for ( i=0; i<len; ++i ) {
		key = buffer[i];

		if ( key & UP_BIT ) {
			key &= ~UP_BIT;
			if ( pressed_keys[key] ) {
				event.type = KeyRelease;
				switch (vga_keys[key]) {
					case XK_Caps_Lock:
						key_state &= ~CAPSLK_BIT;
						break;
					case XK_Shift_L:
						key_state &= ~LSHIFT_BIT;
						break;
					case XK_Shift_R:
						key_state &= ~RSHIFT_BIT;
						break;
					case XK_Control_L:
						key_state &= ~LCTRL_BIT;
						break;
					case XK_Control_R:
						key_state &= ~RCTRL_BIT;
						break;
					case XK_Alt_L:
						key_state &= ~LALT_BIT;
						break;
					case XK_Alt_R:
						key_state &= ~RALT_BIT;
						break;
					default:
						break;
				}
				event.xkey.state = key_state;
				event.xkey.keycode = key;
				Events->Add(&event);
				pressed_keys[key] = 0;
			}
		} else {
			if ( ! pressed_keys[key] ) {
				event.type = KeyPress;
				switch (vga_keys[key]) {
					case XK_Caps_Lock:
						key_state |= CAPSLK_BIT;
						break;
					case XK_Shift_L:
						key_state |= LSHIFT_BIT;
						break;
					case XK_Shift_R:
						key_state |= RSHIFT_BIT;
						break;
					case XK_Control_L:
						key_state |= LCTRL_BIT;
						break;
					case XK_Control_R:
						key_state |= RCTRL_BIT;
						break;
					case XK_Alt_L:
						key_state |= LALT_BIT;
						break;
					case XK_Alt_R:
						key_state |= RALT_BIT;
						break;
					// Check for console switch
					case XK_F1:
					case XK_F2:
					case XK_F3:
					case XK_F4:
					case XK_F5:
					case XK_F6:
					case XK_F7:
					case XK_F8:
						if ( STATE_ALT(key_state) ) {
							Switch(vga_keys[key]-
								XK_F1+1);
							continue;
						}
						break;
					default:
						break;
				}
				event.xkey.state = key_state;
				event.xkey.keycode = key;
				Events->Add(&event);
				pressed_keys[key] = 1;
			}
		}
	}
	return(Events->Size());
}

void
VGA_FrameBuf:: GetEvent(XEvent *event)
{
	Flush(0);
	while ( ! NumEvents() )
		;
	Events->Pop(event);
}

/* Macro to 'controlify' a lower case letter */
#define toctrl(X)	(toupper(X)-'@')

int
VGA_FrameBuf:: KeyToAscii(XEvent *event, char *buf, int buflen, KeySym *key)
{
	*key = vga_keys[event->xkey.keycode];
	for ( int i=0; keycodes[i].name; ++i ) {
		if ( *key == keycodes[i].key ) {
			if ( (strlen(keycodes[i].ascii)+1) > buflen ) {
				strncpy(buf, keycodes[i].ascii, buflen-1);
				buf[buflen-1] = '\0';
			} else
				strcpy(buf, keycodes[i].ascii);

			/* We rely on function strings starting with ESC */
			if ( STATE_CTRL(event->xkey.state) ) {
				if ( isalpha(*buf) )
					*buf = toctrl(*buf);
			} else if ( STATE_SHIFT(event->xkey.state) ) {
				if ( isalnum(*buf) )
					*buf = toupper(*buf);
			}
			return(strlen(buf));
		}
	}
	return(0);
}

void
VGA_FrameBuf:: Flush(int sync)
{
	int nreefs;
	Area area;

	for ( nreefs = Reefer->Size(); nreefs; --nreefs ) {
		Reefer->Pop(&area);
		gl_putboxpart(area.x, area.y, area.width, area.height,
				WIDTH, HEIGHT, shared_mem, area.x, area.y);
	}
	if ( showmouse )
		DrawMouse();
}

/* Routine that switches to another console and suspends
   the current process until the console is switched back.
*/
void
VGA_FrameBuf:: Switch(int which)
{
	struct vt_stat vtstate;

	/* Find out what the current console is. */
	if ( ioctl(kbd_fd, VT_GETSTATE, &vtstate) < 0 ) {
		perror("ioctl(VT_GETSTATE)");
		return;
	}
	if ( vtstate.v_active == which )
		return;

	/* Switch to a different console and wait for switch back */
	vga_runinbackground(0);
	if ( ioctl(kbd_fd, VT_ACTIVATE, which) < 0 ) {
		perror("ioctl(VT_ACTIVATE)");
		return;
	}
}

// Miscellaneous function:
/* Tell whether we are on the console... */
int On_Console(void)
{
	struct vt_stat vtstate;

	return((ioctl(2, VT_GETSTATE, &vtstate) == 0) ? 1 : 0);
}
#else
int On_Console(void)
{
	return(0);
}
#endif /* linux && USE_SVGALIB */

