/* $Id: svgalib.c,v 1.5 2005/07/30 17:13:52 cegger Exp $
******************************************************************************

   SVGAlib wrapper for LibGGI - the vga_* functions

   Copyright (C) 1997      Jason McMullan	[jmcc@ggi-project.org]
   Copyright (C) 1998      Todd T. Fries	[toddf@acm.org]
   Copyright (C) 1998-1999 Marcus Sundberg	[marcus@ggi-project.org]

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   THE AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

******************************************************************************
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <internal/svgaggi.h>

static struct modeinfo_s {
	int width;
	int height;
	ggi_graphtype gt;
} ModeInfo[] = {
	{  GGI_AUTO, GGI_AUTO,GT_TEXT16}, /* #define TEXT     0 */
	{ 320, 200, GT_4BIT}, /* #define G320x200x16  1 */
	{ 640, 200, GT_4BIT}, /* #define G640x200x16  2 */
	{ 640, 350, GT_4BIT}, /* #define G640x350x16  3 */
	{ 640, 480, GT_4BIT}, /* #define G640x480x16  4 */

	{ 320, 200, GT_8BIT}, /* #define G320x200x256 5 */
	{ 320, 240, GT_8BIT}, /* #define G320x240x256 6 */
	{ 320, 400, GT_8BIT}, /* #define G320x400x256 7 */
	{ 360, 480, GT_8BIT}, /* #define G360x480x256 8 */

	{ 640, 480, GT_1BIT}, /* #define G640x480x2   9 */
  
	{ 640, 480, GT_8BIT}, /* #define G640x480x256 10 */
	{ 800, 600, GT_8BIT}, /* #define G800x600x256 11 */
	{1024, 768, GT_8BIT}, /* #define G1024x768x256 12 */
	{1280,1024, GT_8BIT}, /* #define G1280x1024x256 13 */

	{ 320, 200, GT_15BIT}, /* #define G320x200x32K 14 */
	{ 320, 200, GT_16BIT}, /* #define G320x200x64K 15 */
	{ 320, 200, GT_24BIT}, /* #define G320x200x16M 16 */

	{ 640, 480, GT_15BIT}, /* #define G640x480x32K 17 */
	{ 640, 480, GT_16BIT}, /* #define G640x480x64K 18 */
	{ 640, 480, GT_24BIT}, /* #define G640x480x16M 19 */
  
	{ 800, 600, GT_15BIT}, /* #define G800x600x32K 20 */
	{ 800, 600, GT_16BIT}, /* #define G800x600x64K 21 */
	{ 800, 600, GT_24BIT}, /* #define G800x600x16M 22 */

	{1024, 768, GT_15BIT}, /* #define G1024x768x32K 23 */
	{1024, 768, GT_16BIT}, /* #define G1024x768x64K 24 */
	{1024, 768, GT_24BIT}, /* #define G1024x768x16M 25 */

	{1280, 1024, GT_15BIT}, /* #define G1280x1024x32K 26 */
	{1280, 1024, GT_16BIT}, /* #define G1280x1024x64K 27 */
	{1280, 1024, GT_24BIT}, /* #define G1280x1024x16M 28 */

	{ 800, 600, GT_4BIT},  /* #define G800x600x16 29 */
	{1024, 768, GT_4BIT},  /* #define G1024x768x16 30 */
	{1280,1024, GT_4BIT},  /* #define G1280x1024x16 31 */

	{ 720, 348, GT_1BIT},  /* #define G720x348x2 32	 Hercules emulation mode */

	{ 320, 200, GT_32BIT}, /* #define G320x200x16M32 33 */
	{ 640, 480, GT_32BIT}, /* #define G640x480x16M32 34 */
	{ 800, 600, GT_32BIT}, /* #define G800x600x16M32 35 */
	{1024, 768, GT_32BIT}, /* #define G1024x768x16M32 36 */
	{1280, 1024, GT_32BIT},/* #define G1280x1024x16M32 37 */

	{1152, 864, GT_4BIT},  /* #define G1152x864x16 38 */
	{1152, 864, GT_8BIT},  /* #define G1152x864x256 39 */
	{1152, 864, GT_15BIT}, /* #define G1152x864x32K 40 */
	{1152, 864, GT_16BIT}, /* #define G1152x864x64K 41 */
	{1152, 864, GT_24BIT}, /* #define G1152x864x16M 42 */
	{1152, 864, GT_32BIT}, /* #define G1152x864x16M32 43 */

	{1600,1200, GT_4BIT},  /* #define G1600x1200x16 44 */
	{1600,1200, GT_8BIT},  /* #define G1600x1200x256 45 */
	{1600,1200, GT_15BIT}, /* #define G1600x1200x32K 46 */
	{1600,1200, GT_16BIT}, /* #define G1600x1200x64K 47 */
	{1600,1200, GT_24BIT}, /* #define G1600x1200x16M 48 */
	{1600,1200, GT_32BIT}, /* #define G1600x1200x16M32 49 */
};


int _ggigsw_async = 0;
int _ggigsw_but2key = 0;
int _ggigsw_modeemu = 0;
int _ggigsw_inggi = -2;   /* Current active entry of the ModeInfo table */
ggi_visual_t _ggigsw_visual;

#define VIS		_ggigsw_visual
#define MODE5SIZE	(320*200)

static int _ggigsw_pflip = 0;
static int _ggigsw_curframe = 0;
static int _ggigsw_pixelsize = 0;
static int _ggigsw_realpixelsize = 0;
static int _ggigsw_linewidth = 0;
static int _ggigsw_bytesperline = 0;
static int _ggigsw_linesperpage = 0;
static int _ggigsw_pageemu = 0;
static int _ggigsw_curpage = 0;
static uint8_t *_ggigsw_convbuf = NULL;
static uint8_t *_ggigsw_convpal = NULL;

static uint8_t *_ggigsw_pagefb = NULL;
static uint8_t *_ggigsw_realfb = NULL;
#if 0
static uint8_t *_ggigsw_realfb2 = NULL;
#endif

static int                      _ggigsw_fbmem = 0;
static int                      _ggigsw_pages = 1;
static ggi_mode                 _ggigsw_mode;
static const ggi_directbuffer  *_ggigsw_dbuf;
#if 0
static const ggi_directbuffer  *_ggigsw_dbuf2;
#endif
static int                      _ggigsw_stride;
static int                      _ggigsw_curmodenum;
static int                      _ggigsw_wantmouse = 0;

/* This is used by the svgalib target to avoid infinite recursion */
#define GSW_MAGIC	(-4711)
int __svgalib_tty_fd = GSW_MAGIC;

/**********************
 * Internal functions *
 **********************/

static int
cando8bitemu(ggi_graphtype gt)
{
	if (GT_SIZE(gt) == 8 || GT_SIZE(gt) == 16 || GT_SIZE(gt) == 32) {
		return 1;
	} else {
		return 0;
	}
}
	
static void
bufconv(void *dest, uint8_t *src, int len)
{
	switch (_ggigsw_realpixelsize) {
	case 1: {
		uint8_t *buf = (uint8_t *)dest;
		uint8_t *pal = (uint8_t *)_ggigsw_convpal;
		int i;
		
		for (i = 0; i < len; i++) {
			buf[i] = pal[src[i]];
		}
	}
	break;
	case 2: {
		uint16_t *buf = (uint16_t *)dest;
		uint16_t *pal = (uint16_t *)_ggigsw_convpal;
		int i;
		
		for (i = 0; i < len; i++) {
			buf[i] = pal[src[i]];
		}
	}
	break;
	case 4: {
		uint32_t *buf = (uint32_t *)dest;
		uint32_t *pal = (uint32_t *)_ggigsw_convpal;
		int i;
		
		for (i = 0; i < len; i++) {
			buf[i] = pal[src[i]];
		}
	}
	break;
	}
}

void
_ggigsw_mode5flush(void)
{
	if (_ggigsw_mode.graphtype == GT_8BIT) {
		ggiPutBox(_ggigsw_visual, 0, 0, 320, 200, _ggigsw_pagefb);
		return;
	}
	bufconv(_ggigsw_convbuf, _ggigsw_pagefb, MODE5SIZE);
	ggiPutBox(_ggigsw_visual, 0, 0, 320, 200, _ggigsw_convbuf);
}


/**********************
 * Exported variables *
 **********************/

unsigned char *graph_mem = NULL;


/****************
 * Mode setting *
 ****************/

int vga_init(void)
{
	char *str;

	if (ggiInit() < 0) {
		fprintf(stderr, "SVGAlib wrapper: unable to init LibGGI\n");
		exit(1);
	}

	if ((VIS = ggiOpen(NULL)) == NULL) {
		ggiPanic("SVGAlib wrapper: unable to open default visual\n");
	}
	ggiSetEventMask(VIS, emKeyPress | emKeyRelease | emPointer);

	if ((str = getenv("GSW_ASYNC"))) {
		/* We call ggiFlush() in mouse_update() or keyboard_update() */
		ggiSetFlags(VIS, GGIFLAG_ASYNC);
		if (strcmp(str, "mouse") == 0) {
			_ggigsw_async = GSW_ASYNCMOUSE;
			_GSWPRINT("Using async-mouse mode\n");
		} else {
			_ggigsw_async = GSW_ASYNCKEY;
			_GSWPRINT("Using async-key mode\n");
		}
	}
	if (getenv("GSW_MODEEMU")) {
		_ggigsw_modeemu = GSW_WANTIT;
	}
	if (getenv("GSW_PAGEEMU")) {
		/* Emulate a paged mode */
		_ggigsw_pageemu = GSW_WANTIT;
		_ggigsw_curpage = 0;
		_GSWPRINT("Using page-emulating mode\n");
	}
	if (getenv("GSW_DB")) {
		_ggigsw_pflip = GSW_WANTIT;
	}
	if (getenv("GSW_BUT2KEY")) {
		_ggigsw_but2key = GSW_HAVEIT;
	}

	if (_ggigsw_modeemu || _ggigsw_pageemu) {
		if ((_ggigsw_pagefb = malloc(GSW_PAGESIZE)) == NULL) {
			ggiPanic("malloc() failed!\n");
		}
	}

	_ggigsw_inggi = -1;
	return 0;
}

int vga_setmode(int mode)
{
	int x, y;
	ggi_graphtype gt;
	char buff[1024];

	if (mode < 0 || mode > __GLASTMODE) 
		return -1;

	if (_ggigsw_inggi < -1) vga_init();

	x = ModeInfo[mode].width;
	y = ModeInfo[mode].height;
	gt = ModeInfo[mode].gt;
	_ggigsw_inggi = mode;

	if (gt == GT_TEXT16) {
		ggiSetTextMode(VIS, x, y, GGI_AUTO, GGI_AUTO, 
			       GGI_AUTO, GGI_AUTO, GT_TEXT16);
		return 0;
	} else {
		int frames;
		ggi_mode gmod;

		if (_ggigsw_pflip) {
			_ggigsw_pflip = GSW_WANTIT;
			frames = 2;
		} else {
			frames = GGI_AUTO;
		}
		if (_ggigsw_pageemu) {
			_ggigsw_pageemu = GSW_WANTIT;
		}
		if (_ggigsw_modeemu) {
			_ggigsw_modeemu = GSW_WANTIT;
		}
		if (ggiCheckSimpleMode(VIS, x, y, frames, gt, &gmod)) {
			ggi_mode tmpmode;
			if (ggiCheckSimpleMode(VIS, x, y, GGI_AUTO, gt,
					       &tmpmode) == 0) {
				memcpy(&gmod, &tmpmode, sizeof(ggi_mode));
				goto checkmode_is_done;
			}
			if (_ggigsw_modeemu &&
			    (gmod.graphtype == gt ||
			     (gt == GT_8BIT &&
			      cando8bitemu(gmod.graphtype)))) {
				if (mode == 5) {
					_ggigsw_modeemu = GSW_HAVEMODE5;
				} else {
					_ggigsw_modeemu = GSW_HAVEIT;
				}
				goto checkmode_is_done;
			}
			sprintf(buff, "Can't set graphics mode of "
				"%dx%d in %s colors\n",
				x, y,
				(gt == GT_1BIT) ? "2" :
				(gt == GT_4BIT) ? "16" :
				(gt == GT_8BIT) ? "256" :
				(gt == GT_15BIT) ? "32k" :
				(gt == GT_16BIT) ? "64k" :
				(gt == GT_24BIT) ? "16M"  :
				(gt == GT_32BIT) ? "16M (32 bit)" :
				"an unknown number of");
			ggiPanic(buff);
		}
	  checkmode_is_done:
		if (ggiSetMode(VIS, &gmod) != 0) {
			ggiPanic("Unable to set any mode!\n");
		}
		graph_mem = _ggigsw_realfb = NULL;
		
	}
	ggiGetMode(VIS, &_ggigsw_mode);
	fprintf(stderr, "LibGGI SVGAlib-wrapper: ");
	ggiFPrintMode(stderr, &_ggigsw_mode);
	putc('\n', stderr);

	if (_ggigsw_pflip) {
		if (_ggigsw_mode.frames >= 2) {
			_ggigsw_pflip = GSW_HAVEIT;
		}				
	}

	_ggigsw_realpixelsize = ((GT_SIZE(_ggigsw_mode.graphtype) + 7)/8);
	_ggigsw_pixelsize = ((GT_SIZE(gt) + 7)/8);
	_ggigsw_linewidth = x;
	_ggigsw_bytesperline = x * _ggigsw_pixelsize;
	_ggigsw_fbmem = _ggigsw_bytesperline * y;

	if (_ggigsw_pageemu && _ggigsw_modeemu != GSW_HAVEMODE5) {
		if (_ggigsw_fbmem > GSW_PAGESIZE) {
			graph_mem = _ggigsw_pagefb;
			_ggigsw_pages = (_ggigsw_fbmem+GSW_PAGESIZE-1)
				/ GSW_PAGESIZE;
			_ggigsw_linesperpage
				= GSW_PAGESIZE / _ggigsw_bytesperline;
			_ggigsw_pageemu = GSW_HAVEIT;
		} else {
			_ggigsw_pageemu = GSW_WANTIT;
		}
	}

	_ggigsw_dbuf = NULL;
	if (_ggigsw_pageemu != GSW_HAVEIT
	    && _ggigsw_modeemu != GSW_HAVEMODE5
	    && _ggigsw_mode.visible.x == x
	    && (_ggigsw_dbuf = ggiDBGetBuffer(VIS, 0)) != NULL) {
		if ((_ggigsw_dbuf->type & GGI_DB_SIMPLE_PLB)) {
			_ggigsw_stride = _ggigsw_dbuf->buffer.plb.stride;
			graph_mem = _ggigsw_realfb = _ggigsw_dbuf->write;
		} else {
			_ggigsw_dbuf = NULL;
		}
	}
	if (_ggigsw_dbuf == NULL && _ggigsw_modeemu && mode == 5) {
		_ggigsw_modeemu = GSW_HAVEMODE5;
	}
	
	if (_ggigsw_convbuf != NULL) free(_ggigsw_convbuf);
	_ggigsw_convbuf = NULL;
	if (_ggigsw_convpal != NULL) free(_ggigsw_convpal);
	_ggigsw_convpal = NULL;
 
	if (_ggigsw_modeemu >= GSW_HAVEIT) {
		_ggigsw_convbuf = malloc(GSW_PAGESIZE*_ggigsw_realpixelsize);
		if (_ggigsw_convbuf == NULL) {
			ggiPanic("malloc() failed!\n");
		}
		_ggigsw_convpal = malloc(256*_ggigsw_realpixelsize);
		if (_ggigsw_convpal == NULL) {
			ggiPanic("malloc() failed!\n");
		}
		_ggigsw_dbuf = NULL;
		graph_mem = _ggigsw_pagefb;
	}
		
#if 0
 	if (_ggigsw_pflip == GSW_HAVEIT) {
		if ((_ggigsw_dbuf2 = ggiDBGetBuffer(VIS, 1))
		    != NULL
		    && (_ggigsw_dbuf->type & GGI_DB_SIMPLE_PLB)
		    && _ggigsw_dbuf2->buffer.plb.stride
		    == _ggigsw_stride) {
			_ggigsw_realfb2 = _ggigsw_dbuf2->write;
		} else {
			_ggigsw_pflip = GSW_WANTIT;
		}
	}
#endif

	_ggigsw_curmodenum = mode;

	if (_ggigsw_wantmouse) {
		mouse_setxrange(0, x - 1);
		mouse_setyrange(0, y - 1);
		mouse_setwrap(MOUSE_NOWRAP);
	}

	return 0;
}


int vga_hasmode(int mode)
{
	int x, y;
	ggi_graphtype gt;
	ggi_mode gmod;

	if (mode < 0 || mode > __GLASTMODE)
		return 0;

	if (_ggigsw_inggi < -1) 
		vga_init();

	x = ModeInfo[mode].width;
	y = ModeInfo[mode].height;
	gt = ModeInfo[mode].gt;

	if (ggiCheckSimpleMode(VIS, x, y, GGI_AUTO, gt, &gmod) == 0
	    || mode == 0
	    || (_ggigsw_modeemu &&
		(gmod.graphtype == gt ||
		 cando8bitemu(gmod.graphtype)))) {
		return 1;
	}

	return 0;
}
  
int vga_setflipchar(int c)
{
	return 0;
}


int vga_clear(void)
{
	ggi_color col = {0, 0, 0};

	ggiSetGCForeground(VIS, ggiMapColor(VIS, &col));
	ggiFillscreen(VIS);

	return 0;
}

int vga_flip(void)
{
	/* BUGBUG - should flip to the text VT, 
	 * or back again to the graphics VT
	 */
	return -1;
}


int vga_getxdim(void)
{
	if (_ggigsw_inggi < 0)
		return 0;

	return ModeInfo[_ggigsw_inggi].width;
}


int vga_getydim(void)
{
	if (_ggigsw_inggi < 0)
		return 0;

	return ModeInfo[_ggigsw_inggi].height;
}

int vga_getcolors(void)
{
	if (_ggigsw_inggi < 0)
		return 0;
   
	return (1 << GT_DEPTH(ModeInfo[_ggigsw_inggi].gt));
}

/*********************************
 *     P a l e t t e  I n f o    *
 *********************************/

int vga_setpalette(int index, int red, int green, int blue)
{
	ggi_color col;

	col.r = red   << COLORSHIFT;
	col.g = green << COLORSHIFT;
	col.b = blue  << COLORSHIFT;

	if (_ggigsw_modeemu >= GSW_HAVEIT
	    && _ggigsw_mode.graphtype != GT_8BIT) {
		switch (_ggigsw_realpixelsize) {
		case 1: {
			uint8_t *pal = (uint8_t *)_ggigsw_convpal;
			pal[index] = ggiMapColor(VIS, &col);
		}
		break;
		case 2: {
			uint16_t *pal = (uint16_t *)_ggigsw_convpal;
			pal[index] = ggiMapColor(VIS, &col);
		}
		break;
		case 4: {
			uint32_t *pal = (uint32_t *)_ggigsw_convpal;
			pal[index] = ggiMapColor(VIS, &col);
		}
		break;
		}
	} else {
		if (ggiSetPalette(VIS, index, 1, &col) != 0) return -1;
	}

	return 0;
}

int vga_getpalette(int index, int *red, int *green, int *blue)
{
	ggi_color col;

	if (_ggigsw_modeemu >= GSW_HAVEIT
	    && _ggigsw_mode.graphtype != GT_8BIT) {
		switch (_ggigsw_realpixelsize) {
		case 1: {
			uint8_t *pal = (uint8_t *)_ggigsw_convpal;
			ggiUnmapPixel(VIS, pal[index], &col);
		}
		break;
		case 2: {
			uint16_t *pal = (uint16_t *)_ggigsw_convpal;
			ggiUnmapPixel(VIS, pal[index], &col);
		}
		break;
		case 4: {
			uint32_t *pal = (uint32_t *)_ggigsw_convpal;
			ggiUnmapPixel(VIS, pal[index], &col);
		}
		break;
		}
	} else {
		if (ggiGetPalette(VIS, index, 1, &col))	return -1;
	}

	*red   = col.r >> COLORSHIFT;
	*green = col.g >> COLORSHIFT;
	*blue  = col.b >> COLORSHIFT;

	return 0;
}


int vga_setpalvec(int start, int num, int *pal)
{
	ggi_color cols[256];
	int i;

	for (i = 0; i < num; i++) {
		cols[i].r = pal[0] << COLORSHIFT;
		cols[i].g = pal[1] << COLORSHIFT;
		cols[i].b = pal[2] << COLORSHIFT;
		pal += 3;
	}
	
	if (_ggigsw_modeemu >= GSW_HAVEIT
	    && _ggigsw_mode.graphtype != GT_8BIT) {
		return ggiPackColors(VIS, _ggigsw_convpal, cols, num) ? -1 : 0;
	} else {
		return (ggiSetPalette(VIS, start, num, cols) ? -1 : 0);
	}
}

int vga_getpalvec(int start, int num, int *pal)
{
	ggi_color cols[256];
	int i;


	if (_ggigsw_modeemu >= GSW_HAVEIT
	    && _ggigsw_mode.graphtype != GT_8BIT) {
		if (ggiUnpackPixels(VIS, _ggigsw_convpal, cols, num)) {
			return -1;
		}
	} else {
		if (ggiGetPalette(VIS, start, num, cols)) {
			return -1;
		}
	}

	for (i = 0;i<num;i++) {
		pal[0] = cols[i].r >> COLORSHIFT;
		pal[1] = cols[i].g >> COLORSHIFT;
		pal[2] = cols[i].b >> COLORSHIFT;
		pal += 3;
	}
	
	return 0;
}


/******************
 * Screen control *
 ******************/

int vga_screenoff(void)
{
	/* BUGBUG - this should 'blank' the screen .. */
	return 0;
}
  
int vga_screenon(void)
{ 
	/* BUGBUG - this should 'unblank' the screen .. */
	return 0;
}


/********************
 * Drawing Commands *
 ********************/


int vga_setcolor(int color)
{
	return (ggiSetGCForeground(VIS, color) ? -1 : 0);
}

int vga_drawpixel(int x, int y)
{
	return (ggiDrawPixel(VIS, x, y) ? -1 : 0);
}

int vga_drawline(int x1, int y1, int x2, int y2)
{
	return (ggiDrawLine(VIS, x1, y1, x2, y2) ? -1 : 0);
}

int vga_drawscanline(int line, unsigned char *colors)
{
	int xlen;

	if (_ggigsw_inggi < 0)
		return -1;

	xlen = ModeInfo[_ggigsw_inggi].width;

	return (ggiPutHLine(VIS, 0, line, xlen, colors)
		? -1 : 0);
}

int vga_drawscansegment(unsigned char *colors, int x, int y, int length)
{
	return (ggiPutHLine(VIS, x, y, length, colors) ? -1 : 0);
}

int vga_getscansegment(unsigned char *colors, int x, int y, int length)
{
	return (ggiGetHLine(VIS, x, y, length, colors) ? -1 : 0);
}

int vga_getpixel(int x, int y)
{
	int pixel;

	return (ggiGetPixel(VIS, x, y, &pixel) ? pixel : -1);
}


int vga_getch(void)
{
	return ggiGetc(VIS);
}


int vga_dumpregs(void)
{
	/* BUGBUG - We should dump the mode-info here... */
	return 0;
}


/***************************
 * VGA Lib v1.2 extensions *
 ***************************/



vga_modeinfo *vga_getmodeinfo(int mode)
{
	static vga_modeinfo minfo;
	char *env;

	memset(&minfo, 0, sizeof(vga_modeinfo));

	if (mode < 0 || mode > __GLASTMODE)
		return NULL;
  
	minfo.width = ModeInfo[mode].width;
	minfo.height = ModeInfo[mode].height;
	minfo.bytesperpixel = GT_SIZE(ModeInfo[mode].gt)/8;
	minfo.colors = 1 << GT_DEPTH(ModeInfo[mode].gt);
	if ((env = getenv("GSW_WIDTH")) != NULL) {
		minfo.linewidth = atoi(env);
	} else {
		minfo.linewidth = minfo.width * minfo.bytesperpixel;
	}
	minfo.maxlogicalwidth = minfo.linewidth;
	minfo.maxpixels = 1600*1200; /* Just a random number */

	minfo.flags |= CAPABLE_LINEAR; /* We have no way of knowing without
					  actually setting the mode, so we set
					  the flag and hope that the app is
					  smart enough to check the return
					  value from vga_setlinearaddresing()
				       */
	if (mode == _ggigsw_curmodenum && _ggigsw_dbuf) {
		minfo.flags |= IS_LINEAR; /* A linear capable mode is always
					     linear when running on LibGGI
					  */
	}
	return &minfo;
}

int vga_getdefaultmode(void)
{
	return (_ggigsw_inggi < 0) ? G320x200x256 : _ggigsw_inggi;
}

int vga_getcurrentmode(void)
{
	return (_ggigsw_inggi < 0) ? -1 : _ggigsw_inggi;
}


int vga_getcurrentchipset(void)
{
	return UNDEFINED;
}

char *vga_getmodename(int mode)
{
	static char buff[128];
	int m;

	m = ModeInfo[mode].gt;

	if (ModeInfo[mode].gt == GT_TEXT16)
		sprintf(buff, "TEXT16");
	else
		sprintf(buff, "G%dx%dx%s",
			ModeInfo[mode].width, ModeInfo[mode].height,
			(m == GT_1BIT) ? "2" :
			(m == GT_4BIT) ? "16" :
			(m == GT_8BIT) ? "256" :
			(m == GT_15BIT) ? "32K" :
			(m == GT_16BIT) ? "64K" :
			(m == GT_24BIT) ? "16M"  :
			(m == GT_32BIT) ? "16M32" : "??");
  
	return buff;
}


/* Some applications think that using internal functions are good,
   so we have to implement this.
*/
int __svgalib_name2number(char *name);

int __svgalib_name2number(char *name)
{
	int i, x, y;
	unsigned int m;
	char buff[1024];

	/* Make sure we don't overrun buff */
	if (strlen(name) >= 1024) return -1;

	if (sscanf(name,"G%dx%dx%s", &x, &y, buff) != 3)
		return -1;

	if (strcmp(buff, "2") == 0) 
		m = GT_1BIT;
	else if (strcmp(buff, "16") == 0)
		m = GT_4BIT;
	else if (strcmp(buff, "256") == 0)
		m = GT_8BIT;
	else if (strcmp(buff, "32K") == 0)
		m = GT_15BIT;
	else if (strcmp(buff, "64K") == 0)
		m = GT_16BIT;
	else if (strcmp(buff, "16M") == 0)
		m = GT_24BIT;
	else if (strcmp(buff, "16M32") == 0)
		m = GT_32BIT;
	else
		return -1;

	for (i = 0; i <= __GLASTMODE;i++) {
		if (ModeInfo[i].width == x &&
		    ModeInfo[i].height == y &&
		    ModeInfo[i].gt == m)
			return i;
	}

	return -1;
}


int vga_getmodenumber(char *name)
{
	int i;

	i = __svgalib_name2number(name);
	if (i > 0) return i;

	for (i = G320x200x16; i <= GLASTMODE; i++) {
		char tmpstr[4];

		sprintf(tmpstr, "%d", i);
		if (strcasecmp(name, tmpstr) == 0) return i;
	}

	if (strcasecmp(name, "PROMPT") == 0) return -1;

	fprintf(stderr, "Invalid graphics mode \'%s\'.\n", name);
	return -1;
}


int vga_lastmodenumber()
{
	return __GLASTMODE;
}


/******************
 *  Memory access *
 ******************/

unsigned char *vga_getgraphmem(void)
{
	return graph_mem;
}

void vga_setpage(int p)
{
	uint8_t *fbptr;
	_GSWPRINT_I("GSW: vga_setpage() - %d\n", p);

	if (_ggigsw_pageemu != GSW_HAVEIT) {
		return;
	}
	if (_ggigsw_modeemu >= GSW_HAVEIT
	    && _ggigsw_mode.graphtype != GT_8BIT) {
		bufconv(_ggigsw_convbuf, _ggigsw_pagefb, GSW_PAGESIZE);
		fbptr = _ggigsw_convbuf;
	} else {
		fbptr = _ggigsw_pagefb;
	}
	if (GSW_PAGESIZE % _ggigsw_linewidth == 0) {
		int lines = (_ggigsw_linesperpage*(_ggigsw_curpage+1)
			     > _ggigsw_mode.visible.y)
			? _ggigsw_mode.visible.y % _ggigsw_linesperpage
			: _ggigsw_linesperpage;
		ggiPutBox(VIS, 0, _ggigsw_linesperpage*_ggigsw_curpage,
			  _ggigsw_linewidth, lines,
			  fbptr);
	} else {
		int startoff = GSW_PAGESIZE * _ggigsw_curpage;
		int startline = startoff / _ggigsw_bytesperline;
		int start = startoff % _ggigsw_bytesperline;
		int endwidth = (startoff+GSW_PAGESIZE) % _ggigsw_bytesperline;
		int endline;

		if (start) {
			start /= _ggigsw_pixelsize;
			ggiPutHLine(VIS, start, startline,
				    _ggigsw_linewidth - start, fbptr);
			fbptr += (_ggigsw_linewidth - start)
				* _ggigsw_realpixelsize;
			startline++;
		}

		endline = (startoff+GSW_PAGESIZE) / _ggigsw_bytesperline;
		ggiPutBox(VIS, 0, startline, _ggigsw_linewidth,
			  endline - startline, fbptr);
		fbptr += _ggigsw_linewidth * _ggigsw_realpixelsize
			* (endline - startline);
#if 0
		} else {
			for (; startline < endline; startline++) {
				ggiPutHLine(VIS, 0, startline,
					    _ggigsw_linewidth, fbptr);
				fbptr += _ggigsw_linewidth
					* _ggigsw_realpixelsize;
			}
		}
#endif
		if (endwidth) {
			endwidth /= _ggigsw_pixelsize;
			ggiPutHLine(VIS, 0, endline, endwidth, fbptr);
		}
	}

	if (_ggigsw_pflip == GSW_HAVEIT) {
		if (p == 0) {
			if (_ggigsw_curframe == 0) {	
				ggiSetDisplayFrame(VIS, 0);
				ggiSetReadFrame(VIS, 1);
				ggiSetWriteFrame(VIS, 1);
				_ggigsw_curframe = 1;
			} else {
				ggiSetDisplayFrame(VIS, 1);
				ggiSetReadFrame(VIS, 0);
				ggiSetWriteFrame(VIS, 0);
				_ggigsw_curframe = 0;
			}
		}
	}
	_ggigsw_curpage = p;
}


/* Not supported */
void vga_setreadpage(int p)  { }
void vga_setwritepage(int p) { }

void vga_setlogicalwidth(int w)
{
	/* BUGBUG - is set virtualsize. Should be done with modesetting in GGI. */
}

void vga_setdisplaystart(int a)
{
	/* BUGBUG - not supported */
}

void vga_waitretrace(void)
{
	/* BUGBUG - not supported */
}

int vga_claimvideomemory(int m)
{
	if (m > _ggigsw_stride * _ggigsw_mode.virt.y) {
		return -1;
	}
	return 0;
}

void vga_disabledriverreport(void)
{
	/* BUGBUG - not supported */
}

int vga_setmodeX(void)
{
	/* BUGBUG - not supported */
	return -1;
}


int vga_getmonitortype(void)
{
	return MON1024_72;
}

/******************
 * Event handling *
 ******************/

int vga_getmousetype(void)
{
	return MOUSE_MOUSESYSTEMS;
}

void vga_setmousesupport(int s)
{
	_ggigsw_wantmouse = 2;
}

/* Not supported */
void vga_lockvc(void)   { }
void vga_unlockvc(void) { }


int vga_getkey(void)
{
	return ggiKbhit(VIS);
}

int vga_waitevent(int which, fd_set *in, fd_set *out, fd_set *except,
		  struct timeval *timeout)
{
	ggi_event_mask mask = 0;
	int retval;

	if (which & VGA_MOUSEEVENT) {
		mask |= emKeyPress | emKeyRelease;
	}
	if (which & VGA_KEYEVENT) {
		mask |= emPointer;
	}

	/* GRR, this braindead vga_waitevent() doesn't take the n argument of
	   select() */
	retval = ggiEventSelect(VIS, &mask, FD_SETSIZE, in, out, except,
				timeout);
	if (retval < 0) {
		return -1;
	}

	retval = 0;
	if (mask & emPointer) {
		retval |= VGA_MOUSEEVENT;
		mouse_update();
	}
	if (mask & (emKeyPress | emKeyRelease)) {
		retval |= VGA_KEYEVENT;
		keyboard_update();
	}
	
	return retval;
}


/*****************
 * Writing stuff *
 *****************/

int vga_oktowrite(void)
{
	return 1;
}

void vga_copytoplanar256(unsigned char *virtualp, int pitch,
			 int voffset, int vpitch, int w, int h)
{
	/* BUGBUG - not supported */
	return;
}

void vga_copytoplanar16(unsigned char *virtualp, int pitch,
			int voffset, int vpitch, int w, int h)
{
	/* BUGBUG - not supported */
	return;
}

void vga_copytoplane(unsigned char *virtualp, int pitch,
		     int voffset, int vpitch, int w, int h, int plane)
{
	/* BUGBUG - not supported */
	return;
}

int vga_setlinearaddressing(void)
{
	if (_ggigsw_dbuf != NULL) {
		return _ggigsw_stride * _ggigsw_mode.virt.y;
	} else {
		return -1;
	}
}

/* Not supported */
void vga_safety_fork(void (*dummy) (void)) { }


/* Not supported */
void vga_setchipset(int c) { }
void vga_setchipsetandfeatures(int c, int par1, int par2) { }
void vga_gettextfont(void *font) { }
void vga_puttextfont(void *font) { }
void vga_settextmoderegs(void *regs) { }
void vga_gettextmoderegs(void *regs) { }


/*
***************************************************************************
 Acceleration - unsupported
***************************************************************************
*/

int vga_white(void)
{
	ggi_color col = { (1<<16)-1, (1<<16)-1, (1<<16)-1 };

	if (_ggigsw_inggi < 0) return ~0;

	return ggiMapColor(VIS, &col);
}

int vga_setrgbcolor(int r, int g, int b)
{
	ggi_color col;
	ggi_pixel pixval;

	if (_ggigsw_inggi < 1) return -1;

	col.r = r;
	col.g = g;
	col.b = b;

	pixval = ggiMapColor(VIS, &col);
	ggiSetGCForeground(VIS, pixval);

	return pixval;
}

int vga_setegacolor(int c)
{
	/* EGA colors */
	static const unsigned char egared[16] =
	{0,   0,   0,   0, 168, 168, 168, 168, 84,  84,  84,  84, 255, 255, 255, 255};
	static const unsigned char egagreen[16] =
	{0,   0, 168, 168,   0,   0,  84, 168, 84,  84, 255, 255,  84,  84, 255, 255};
	static const unsigned char egablue[16] =
	{0, 168,   0, 168,   0, 168,   0, 168, 84, 255,  84, 255,  84, 255,  84, 255};

	if (_ggigsw_inggi < 0) return -1;

	if (c < 0) c = 0;
	else if (c > 15) c = 15;

	switch (ModeInfo[_ggigsw_inggi].gt) {
	case GT_15BIT:
	case GT_16BIT:
	case GT_24BIT:
	case GT_32BIT:
		return vga_setrgbcolor(egared[c], egagreen[c], egablue[c]);
	default:
		break;
	}

	ggiSetGCForeground(VIS, c);
	return c;
}


/*
***************************************************************************
 Unsupported
***************************************************************************
*/

void vga_bitblt(int srcaddr, int destaddr, int w, int h, int pitch)     { }
void vga_imageblt(void *srcaddr, int destaddr, int w, int h, int pitch) { }
void vga_fillblt(int destaddr, int w, int h, int pitch, int c)          { }
void vga_hlinelistblt(int ymin, int n, int *xmin, int *xmax, int pitch, int c)
{ }
void vga_blitwait(void) { }
int vga_ext_set(unsigned what,...)    { return -1; }
int vga_accel(unsigned operation,...) {	return -1; }

/* Not supported */
void vga_runinbackground(int stat, ...) { }

int vga_runinbackground_version(void)
{
	/* Tell apps not to use vga_runinbackground() */
	return 0;
}
