/* svgalib.c
 * (c) 2000-2002 Karel 'Clock' Kulhavy
 * This file is a part of the Links program, released under GPL.
 *
 * This does graphics driver of svgalib, svgalib mouse.
 * This doesn't do svgalib keyboard.
 */

#include "cfg.h"

#ifdef GRDRV_SVGALIB

#include "links.h"
#include "bits.h"

#include <vga.h>
#include <vgamouse.h>
#include "arrow.inc"

struct irgb {
	unsigned rgb[3];
};

static struct itrm *svgalib_kbd;

extern struct graphics_driver svga_driver;
static int mouse_x, mouse_y, mouse_buttons; /* For tracking the state of the mouse */
static int background_x, background_y; /* Where was the mouse background taken from */
static unsigned char *mouse_buffer, *background_buffer, *new_background_buffer;
static struct graphics_device *mouse_graphics_device;
static int global_mouse_hidden;
static long mouse_black, mouse_white; /* Mouse arrow pointer colors */
static int (* mouse_getscansegment)(unsigned char *, int, int, int);
static int (* mouse_drawscansegment)(unsigned char *, int, int, int);
static unsigned char mouse_works = 0;
static unsigned char *svga_driver_param; /* NULL by default */
static int vga_mode; /* The mode that has been selected */
static int mouse_aggregate_flag, mouse_aggregate_action;
static int flags = 0; /* OR-ed 1: running in background
		*       2: vga_block()-ed
		*/
#ifndef __SPAD__
static struct timer *svgalib_timer_id;
#endif

/*---------------------------LIMITATIONS---------------------------------------*/
/* pixel_set_paged works only for <=8 bytes per pixel.
 * Doesn't work on video cards which have 1 pixel spanning more that 65536 bytes! ( :-) )
 * vga_linewidth%vga_bytes must be zero.
 * The bitmaps have all consecutive data. No vidram mallocing is performed.
 */

/*------------------------STRUCTURES-------------------------------------------*/
struct modeline{
	char *name;
	int number;
};

/*-------------- GLOBAL VARIABLES --------------------------------------------*/

#define NUMBER_OF_DEVICES	10

#define TEST_INACTIVITY if (dev != current_virtual_device || flags) return;

#define TEST_INACTIVITY_0 if (dev != current_virtual_device || flags) return 0;

#define RECTANGLES_INTERSECT(xl0, xh0, xl1, xh1, yl0, yh0, yl1, yh1) (\
				   (xl0)<(xh1)\
				&& (xl1)<(xh0)\
				&& (yl0)<(yh1)\
				&& (yl1)<(yh0))

#define TEST_MOUSE(xl,xh,yl,yh) if (RECTANGLES_INTERSECT(\
					(xl),(xh),\
					background_x,background_x+arrow_width,\
					(yl),(yh),\
					background_y,background_y+arrow_height)\
					&& !global_mouse_hidden){\
					mouse_hidden=1;\
					hide_mouse();\
				}else mouse_hidden=0;

#define END_MOUSE if (mouse_hidden) show_mouse();

/* Actual vga mode definition */
static int vga_linewidth;		/* Prepared out from vga_getmodeinfo */
static int xsize, ysize;		/* Prepared out from vga_getmodeinfo */
static int vga_bytes;			/* Prepared out from vga_getmodeinfo */
static int vga_colors;			/* Prepared out from vga_getmodeinfo */
static int vga_misordered;		/* Prepared out from vga_getmodeinfo */
static int vga_linear;			/* 1 linear mode, 0 nonlinear mode (paged) */
static int palette_depth;		/* 6 for normal VGA, 8 for VGA which supports 8 bit DAC */
static int accel_avail;		/* Which accel fns are available */
static int do_sync;			/* Tells the "normal" memory operations (those
				 * that do not use accelerator) to do
				 * vga_accel(ACCEL_SYNC) before writing into the
				 * memory.
				 */
static int vga_page=-1;
static int mode_x;			/* 1 if mode_X organization is set up */
static int fb_pixelsize;		/* Number of bytes per pixel in bitmap */
static unsigned char *my_graph_mem;
static unsigned char *scroll_buffer = NULL; /* For paged scrolling only */
static struct modeline modes[]={
#ifdef G320x200x16
	{"320x200x16", G320x200x16 },
#endif
#ifdef G320x200x256
	{"320x200x256", G320x200x256 },
#endif
#ifdef G320x200x32K
	{"320x200x32K", G320x200x32K },
#endif
#ifdef G320x200x64K
	{"320x200x64K", G320x200x64K },
#endif
#ifdef G320x200x16M
	{"320x200x16M", G320x200x16M },
#endif
#ifdef G320x200x16M32
	{"320x200x16M32", G320x200x16M32 },
#endif

#ifdef G320x240x256
	{"320x240x256", G320x240x256 },
#endif
#ifdef G320x240x32K
	{"320x240x32K", G320x240x32K },
#endif
#ifdef G320x240x64K
	{"320x240x64K", G320x240x64K },
#endif
#ifdef G320x240x16M
	{"320x240x16M", G320x240x16M },
#endif
#ifdef G320x240x16M32
	{"320x240x16M32", G320x240x16M32 },
#endif

#ifdef G320x400x256
	{"320x400x256", G320x400x256 },
#endif
#ifdef G320x400x32K
	{"320x400x32K", G320x400x32K },
#endif
#ifdef G320x400x64K
	{"320x400x64K", G320x400x64K },
#endif
#ifdef G320x400x16M
	{"320x400x16M", G320x400x16M },
#endif
#ifdef G320x400x16M32
	{"320x400x16M32", G320x400x16M32 },
#endif

#ifdef G320x480x256
	{"320x480x256", G320x480x256 },
#endif
#ifdef G320x480x32K
	{"320x480x32K", G320x480x32K },
#endif
#ifdef G320x480x64K
	{"320x480x64K", G320x480x64K },
#endif
#ifdef G320x480x16M
	{"320x480x16M", G320x480x16M },
#endif
#ifdef G320x480x16M32
	{"320x480x16M32", G320x480x16M32 },
#endif

#ifdef G360x480x256
	{"360x480x256", G360x480x256 },
#endif

#ifdef G400x300x256
	{"400x300x256", G400x300x256 },
#endif
#ifdef G400x300x32K
	{"400x300x32K", G400x300x32K },
#endif
#ifdef G400x300x64K
	{"400x300x64K", G400x300x64K },
#endif
#ifdef G400x300x16M
	{"400x300x16M", G400x300x16M },
#endif
#ifdef G400x300x16M32
	{"400x300x16M32", G400x300x16M32 },
#endif

#ifdef G400x600x256
	{"400x600x256", G400x600x256 },
#endif
#ifdef G400x600x32K
	{"400x600x32K", G400x600x32K },
#endif
#ifdef G400x600x64K
	{"400x600x64K", G400x600x64K },
#endif
#ifdef G400x600x16M
	{"400x600x16M", G400x600x16M },
#endif
#ifdef G400x600x16M32
	{"400x600x16M32", G400x600x16M32 },
#endif

#ifdef G512x384x256
	{"512x384x256", G512x384x256 },
#endif
#ifdef G512x384x32K
	{"512x384x32K", G512x384x32K },
#endif
#ifdef G512x384x64K
	{"512x384x64K", G512x384x64K },
#endif
#ifdef G512x384x16M
	{"512x384x16M", G512x384x16M },
#endif
#ifdef G512x384x16M32
	{"512x384x16M32", G512x384x16M32 },
#endif

#ifdef G512x480x256
	{"512x480x256", G512x480x256 },
#endif
#ifdef G512x480x32K
	{"512x480x32K", G512x480x32K },
#endif
#ifdef G512x480x64K
	{"512x480x64K", G512x480x64K },
#endif
#ifdef G512x480x16M
	{"512x480x16M", G512x480x16M },
#endif
#ifdef G512x480x16M32
	{"512x480x16M32", G512x480x16M32 },
#endif

#ifdef G640x200x16
	{"640x200x16", G640x200x16 },
#endif
#ifdef G640x350x16
	{"640x350x16", G640x350x16 },
#endif

#ifdef G640x400x256
	{"640x400x256", G640x400x256 },
#endif
#ifdef G640x400x32K
	{"640x400x32K", G640x400x32K },
#endif
#ifdef G640x400x64K
	{"640x400x64K", G640x400x64K },
#endif
#ifdef G640x400x16M
	{"640x400x16M", G640x400x16M },
#endif
#ifdef G640x400x16M32
	{"640x400x16M32", G640x400x16M32 },
#endif

#ifdef G640x480x16
	{"640x480x16", G640x480x16 },
#endif
#ifdef G640x480x256
	{"640x480x256", G640x480x256 },
#endif
#ifdef G640x480x32K
	{"640x480x32K", G640x480x32K },
#endif
#ifdef G640x480x64K
	{"640x480x64K", G640x480x64K },
#endif
#ifdef G640x480x16M
	{"640x480x16M", G640x480x16M },
#endif
#ifdef G640x480x16M32
	{"640x480x16M32", G640x480x16M32 },
#endif

#ifdef G720x540x256
	{"720x540x256", G720x540x256 },
#endif
#ifdef G720x540x32K
	{"720x540x32K", G720x540x32K },
#endif
#ifdef G720x540x64K
	{"720x540x64K", G720x540x64K },
#endif
#ifdef G720x540x16M
	{"720x540x16M", G720x540x16M },
#endif
#ifdef G720x540x16M32
	{"720x540x16M32", G720x540x16M32 },
#endif

#ifdef G800x600x16
	{"800x600x16", G800x600x16 },
#endif
#ifdef G800x600x256
	{"800x600x256", G800x600x256 },
#endif
#ifdef G800x600x32K
	{"800x600x32K", G800x600x32K },
#endif
#ifdef G800x600x64K
	{"800x600x64K", G800x600x64K },
#endif
#ifdef G800x600x16M
	{"800x600x16M", G800x600x16M },
#endif
#ifdef G800x600x16M32
	{"800x600x16M32", G800x600x16M32 },
#endif

#ifdef G848x480x256
	{"848x480x256", G848x480x256 },
#endif
#ifdef G848x480x32K
	{"848x480x32K", G848x480x32K },
#endif
#ifdef G848x480x64K
	{"848x480x64K", G848x480x64K },
#endif
#ifdef G848x480x16M
	{"848x480x16M", G848x480x16M },
#endif
#ifdef G848x480x16M32
	{"848x480x16M32", G848x480x16M32 },
#endif

#ifdef G960x720x256
	{"960x720x256", G960x720x256 },
#endif
#ifdef G960x720x32K
	{"960x720x32K", G960x720x32K },
#endif
#ifdef G960x720x64K
	{"960x720x64K", G960x720x64K },
#endif
#ifdef G960x720x16M
	{"960x720x16M", G960x720x16M },
#endif
#ifdef G960x720x16M32
	{"960x720x16M32", G960x720x16M32 },
#endif

#ifdef G1024x768x16
	{"1024x768x16", G1024x768x16 },
#endif
#ifdef G1024x768x256
	{"1024x768x256", G1024x768x256 },
#endif
#ifdef G1024x768x32K
	{"1024x768x32K", G1024x768x32K },
#endif
#ifdef G1024x768x64K
	{"1024x768x64K", G1024x768x64K },
#endif
#ifdef G1024x768x16M
	{"1024x768x16M", G1024x768x16M },
#endif
#ifdef G1024x768x16M32
	{"1024x768x16M32", G1024x768x16M32 },
#endif

#ifdef G1072x600x256
	{"1072x600x256", G1072x600x256 },
#endif
#ifdef G1072x600x32K
	{"1072x600x32K", G1072x600x32K },
#endif
#ifdef G1072x600x64K
	{"1072x600x64K", G1072x600x64K },
#endif
#ifdef G1072x600x16M
	{"1072x600x16M", G1072x600x16M },
#endif
#ifdef G1072x600x16M32
	{"1072x600x16M32", G1072x600x16M32 },
#endif

#ifdef G1152x864x16
	{"1152x864x16", G1152x864x16 },
#endif
#ifdef G1152x864x256
	{"1152x864x256", G1152x864x256 },
#endif
#ifdef G1152x864x32K
	{"1152x864x32K", G1152x864x32K },
#endif
#ifdef G1152x864x64K
	{"1152x864x64K", G1152x864x64K },
#endif
#ifdef G1152x864x16M
	{"1152x864x16M", G1152x864x16M },
#endif
#ifdef G1152x864x16M32
	{"1152x864x16M32", G1152x864x16M32 },
#endif

#ifdef G1280x720x256
	{"1280x720x256", G1280x720x256 },
#endif
#ifdef G1280x720x32K
	{"1280x720x32K", G1280x720x32K },
#endif
#ifdef G1280x720x64K
	{"1280x720x64K", G1280x720x64K },
#endif
#ifdef G1280x720x16M
	{"1280x720x16M", G1280x720x16M },
#endif
#ifdef G1280x720x16M32
	{"1280x720x16M32", G1280x720x16M32 },
#endif

#ifdef G1280x1024x16
	{"1280x1024x16", G1280x1024x16 },
#endif
#ifdef G1280x1024x256
	{"1280x1024x256", G1280x1024x256 },
#endif
#ifdef G1280x1024x32K
	{"1280x1024x32K", G1280x1024x32K },
#endif
#ifdef G1280x1024x64K
	{"1280x1024x64K", G1280x1024x64K },
#endif
#ifdef G1280x1024x16M
	{"1280x1024x16M", G1280x1024x16M },
#endif
#ifdef G1280x1024x16M32
	{"1280x1024x16M32", G1280x1024x16M32 },
#endif

#ifdef G1360x768x256
	{"1360x768x256", G1360x768x256 },
#endif
#ifdef G1360x768x32K
	{"1360x768x32K", G1360x768x32K },
#endif
#ifdef G1360x768x64K
	{"1360x768x64K", G1360x768x64K },
#endif
#ifdef G1360x768x16M
	{"1360x768x16M", G1360x768x16M },
#endif
#ifdef G1360x768x16M32
	{"1360x768x16M32", G1360x768x16M32 },
#endif

#ifdef G1600x1200x16
	{"1600x1200x16", G1600x1200x16 },
#endif
#ifdef G1600x1200x256
	{"1600x1200x256", G1600x1200x256 },
#endif
#ifdef G1600x1200x32K
	{"1600x1200x32K", G1600x1200x32K },
#endif
#ifdef G1600x1200x64K
	{"1600x1200x64K", G1600x1200x64K },
#endif
#ifdef G1600x1200x16M
	{"1600x1200x16M", G1600x1200x16M },
#endif
#ifdef G1600x1200x16M32
	{"1600x1200x16M32", G1600x1200x16M32 },
#endif

#ifdef G1800x1012x256
	{"1800x1012x256", G1800x1012x256 },
#endif
#ifdef G1800x1012x32K
	{"1800x1012x32K", G1800x1012x32K },
#endif
#ifdef G1800x1012x64K
	{"1800x1012x64K", G1800x1012x64K },
#endif
#ifdef G1800x1012x16M
	{"1800x1012x16M", G1800x1012x16M },
#endif
#ifdef G1800x1012x16M32
	{"1800x1012x16M32", G1800x1012x16M32 },
#endif

#ifdef G1920x1080x256
	{"1920x1080x256", G1920x1080x256 },
#endif
#ifdef G1920x1080x32K
	{"1920x1080x32K", G1920x1080x32K },
#endif
#ifdef G1920x1080x64K
	{"1920x1080x64K", G1920x1080x64K },
#endif
#ifdef G1920x1080x16M
	{"1920x1080x16M", G1920x1080x16M },
#endif
#ifdef G1920x1080x16M32
	{"1920x1080x16M32", G1920x1080x16M32 },
#endif

#ifdef G1920x1440x256
	{"1920x1440x256", G1920x1440x256 },
#endif
#ifdef G1920x1440x32K
	{"1920x1440x32K", G1920x1440x32K },
#endif
#ifdef G1920x1440x64K
	{"1920x1440x64K", G1920x1440x64K },
#endif
#ifdef G1920x1440x16M
	{"1920x1440x16M", G1920x1440x16M },
#endif
#ifdef G1920x1440x16M32
	{"1920x1440x16M32", G1920x1440x16M32 },
#endif

#ifdef G2048x1152x256
	{"2048x1152x256", G2048x1152x256 },
#endif
#ifdef G2048x1152x32K
	{"2048x1152x32K", G2048x1152x32K },
#endif
#ifdef G2048x1152x64K
	{"2048x1152x64K", G2048x1152x64K },
#endif
#ifdef G2048x1152x16M
	{"2048x1152x16M", G2048x1152x16M },
#endif
#ifdef G2048x1152x16M32
	{"2048x1152x16M32", G2048x1152x16M32 },
#endif

#ifdef G2048x1536x256
	{"2048x1536x256", G2048x1536x256 },
#endif
#ifdef G2048x1536x32K
	{"2048x1536x32K", G2048x1536x32K },
#endif
#ifdef G2048x1536x64K
	{"2048x1536x64K", G2048x1536x64K },
#endif
#ifdef G2048x1536x16M
	{"2048x1536x16M", G2048x1536x16M },
#endif
#ifdef G2048x1536x16M32
	{"2048x1536x16M32", G2048x1536x16M32 },
#endif

};

#include "fbcommon.inc"

/*--------------------------- ROUTINES ---------------------------------------*/

/* Generates these palettes:
 *  7 6 5 4 3 2 1 0
 * +-----+-----+---+
 * |  R  |  G  | B |
 * +-----+-----+---+
 *
 *
 *  3 2 1 0
 * +-+---+-+
 * |R| G |B|
 * +-+---+-+
 */

static void show_mouse(void);
static void hide_mouse(void);
static void redraw_mouse(void);

/* We must perform a quirkafleg
 * This is an empiric magic that ensures
 * Good white purity
 * Correct rounding and dithering prediction
 * And this is the cabbala:
 * 063 021 063
 * 009 009 021
 * 255 085 255
 * 036 036 084
 */
static void generate_palette(struct irgb *palette)
{
	int a;
	unsigned active_colors = (int)vga_colors;
	if ((svga_driver.depth & 0x300) == 0x300)
		active_colors = 216;

	for (a = 0; a < vga_colors; a++) {
		q_palette(active_colors, (unsigned)a, (1U << palette_depth) - 1, palette[a].rgb);
	}
}

static void set_palette(struct irgb *palette)
{
	int r, g, b, c;

	for (c = 0; c < vga_colors; c++) {
		r = (int)palette[c].rgb[0];
		g = (int)palette[c].rgb[1];
		b = (int)palette[c].rgb[2];
		vga_setpalette(c, r, g, b);
	}
}

static void svga_shutdown_driver(void)
{
	if (scroll_buffer) mem_free(scroll_buffer);
	if (mouse_works){
		mem_free(mouse_buffer);
		mem_free(background_buffer);
		mem_free(new_background_buffer);
		svga_driver.shutdown_device(mouse_graphics_device);
		mouse_close();
		mouse_works=0; /* To keep vga_select disabled */
	}
	shutdown_virtual_devices();
	vga_unlockvc();
#ifndef __SPAD__
	kill_timer(svgalib_timer_id);
#endif
	vga_setmode_retry:
	if (vga_setmode(TEXT) < 0) {
		if (out_of_memory(0, NULL, 0))
			goto vga_setmode_retry;
		fatal_exit("ERROR: vga_setmode failed");
	}
	svgalib_free_trm(svgalib_kbd);
	if (svga_driver_param)mem_free(svga_driver_param);
	install_signal_handler(SIGINT, NULL, NULL, 0);
}

static void svga_register_bitmap(struct bitmap *bmp)
{
}

static void svga_unregister_bitmap(struct bitmap *bmp)
{
	mem_free(bmp->data);
}

#define SYNC if (do_sync) vga_accel(ACCEL_SYNC);

/* This assures that x, y, xs, ys, data will be sane according to clipping
 * rectangle. If nothing lies within this rectangle, the current function
 * returns. The data pointer is automatically advanced by this macro to reflect
 * the right position to start with inside the bitmap. */
#define	CLIP_PREFACE \
	int xs, ys;\
	unsigned char *data=bmp->data;\
	int mouse_hidden;\
\
	TEST_INACTIVITY\
	CLIP_DRAW_BITMAP\
	xs = bmp->x;\
	ys = bmp->y;\
	if (x+xs>dev->clip.x2) xs=dev->clip.x2-x;\
	if (y+ys>dev->clip.y2) ys=dev->clip.y2-y;\
	if (dev->clip.x1-x>0){\
		xs-=(dev->clip.x1-x);\
		data+=fb_pixelsize*(dev->clip.x1-x);\
		x=dev->clip.x1;\
	}\
	if (dev->clip.y1-y>0){\
		ys-=(dev->clip.y1-y);\
		data+=bmp->skip*(dev->clip.y1-y);\
		y=dev->clip.y1;\
	}\
	/* xs, ys: how much pixels to paint\
	 * data: where to start painting from\
	 */\
	TEST_MOUSE(x,x+xs,y,y+ys)

static inline void draw_bitmap_accel(struct graphics_device *dev,
	struct bitmap *bmp, int x, int y)
{
	CLIP_PREFACE

	if (xs*fb_pixelsize==bmp->skip) vga_accel(ACCEL_PUTIMAGE,x,y,xs,ys,data);
	else for(;ys;ys--){
		vga_accel(ACCEL_PUTIMAGE,x,y,xs,1,data);
		data+=bmp->skip;
		y++;
	}
	END_MOUSE
}

static inline void my_setpage(int page)
{
	if (vga_page!=page){
		vga_page=page;
		vga_setpage(page);
	}
}

static inline void paged_memcpy(int lina, unsigned char *src, int len)
{
	int page = lina >> 16;
	int paga = lina & 0xffff;
	int remains;

	my_setpage(page);
	remains = 65536 - paga;
	again:
	if (remains >= len) {
		memcpy_to_fb_inline(my_graph_mem + paga, src, len, 0);
		vga_page = page;
		return;
	}else{
		memcpy_to_fb(my_graph_mem + paga, src, remains, 0);
		paga = 0;
		src += remains;
		len -= remains;
		remains = 65536;
		vga_setpage(++page);
		goto again;
	}
}


static inline void draw_bitmap_drawscansegment(struct graphics_device *dev, struct bitmap *bmp, int x, int y)
{
	int ys0;

	CLIP_PREFACE
	SYNC
	for (ys0=ys;ys0;ys0--){
		vga_drawscansegment(data,x,y,xs);
		y++;
		data+=bmp->skip;
	}
	END_MOUSE
}

static inline void draw_bitmap_paged(struct graphics_device *dev, struct bitmap *bmp, int x, int y)
{
	int scr_start;
	size_t copy_len;

	CLIP_PREFACE
	SYNC
	scr_start = y * vga_linewidth + x * vga_bytes;
	copy_len = xs * fb_pixelsize;
	for(; ys; ys--) {
		paged_memcpy(scr_start, data, copy_len);
		data += bmp->skip;
		scr_start += vga_linewidth;
	}
	END_MOUSE
}

static inline void draw_bitmap_linear(struct graphics_device *dev,struct bitmap *bmp, int x, int y)
{
	unsigned char *scr_start;
	size_t copy_len;

	CLIP_PREFACE
	SYNC
	scr_start = my_graph_mem + y * vga_linewidth + x * vga_bytes;
	copy_len = xs * fb_pixelsize;
	for (; ys; ys--) {
		memcpy_to_fb_inline(scr_start, data, copy_len, 0);
		data += bmp->skip;
		scr_start += vga_linewidth;
	}
	END_MOUSE
}

/* fill_area: 5,5,10,10 fills in 25 pixels! */

/* This assures that x1, x2, y1, y2 will be sane according to the
 * clipping rectangle set up by set_clip_area. If empty region
 * results, return from current function occurs. */
#define FILL_CLIP_PREFACE \
	int mouse_hidden;\
	TEST_INACTIVITY\
	CLIP_FILL_AREA\
	TEST_MOUSE(x1,x2,y1,y2)


static void fill_area_accel_box(struct graphics_device *dev, int x1, int y1, int x2, int y2, long color)
{
	FILL_CLIP_PREFACE

	vga_accel(ACCEL_SETFGCOLOR,color);
	vga_accel(ACCEL_FILLBOX,x1,y1,x2-x1,y2-y1);
	END_MOUSE
}

static void fill_area_accel_lines(struct graphics_device *dev, int x1, int y1, int x2, int y2, long color)
{
	int y;

	FILL_CLIP_PREFACE
	vga_accel(ACCEL_SETFGCOLOR,color);
	for (y=y1;y<y2;y++) vga_accel(ACCEL_DRAWLINE,x1,y,x2-1,y);
	END_MOUSE
}

static void fill_area_linear(struct graphics_device *dev, int x1, int y1, int x2, int y2, long color)
{
	unsigned char *dest;
	int y;

	FILL_CLIP_PREFACE
	SYNC
	dest=my_graph_mem+y1*vga_linewidth+x1*vga_bytes;
	for (y=y2-y1;y;y--){
		pixel_set(dest,(x2-x1)*vga_bytes,&color);
		dest+=vga_linewidth;
	}
	END_MOUSE
}

/* Params are exactly the same as in pixel_set except lina, which is offset from
 * my_graph_mem in bytes.  Works for every vga_bytes. len is in bytes. len must
 * be a whole number of pixels.
 */
static void pixel_set_paged(int lina, int len, void * color)
{
	int page=lina>>16; /* Page number */
	int paga=lina&0xffff; /* 16-bit address within a page */
	int remains=65536-paga; /* How many bytes remain within the page*/
	int offset=0; /* Offset inside the pixel */
	unsigned char color0[15];

	memcpy(color0,color,vga_bytes);
	memcpy(color0+vga_bytes,color,vga_bytes-1);

	my_setpage(page);
	again:
	if (remains>=len){
		int done=len-len%vga_bytes;
		pixel_set(my_graph_mem+paga,done,color0+offset);
		paga+=done;
		if (done<len)
			memcpy_to_fb(my_graph_mem+paga,color0+offset,len-done, 0);
		vga_page=page;
		return;
	}else{
		int done=remains-remains%vga_bytes;
		pixel_set(my_graph_mem+paga,done,color0+offset);
		paga+=done;
		if (done<remains)
			memcpy_to_fb(my_graph_mem+paga,color0+offset,remains-done, 0);
		offset+=(remains-done);
		if (offset>=vga_bytes) offset-=vga_bytes;
		len-=remains;
		remains=65536;
		vga_setpage(++page);
		paga=0;
		goto again;
	}
}

static void fill_area_paged(struct graphics_device *dev, int x1, int y1, int x2, int y2, long color)
{
	int dest;
	int y;
	int len;
	FILL_CLIP_PREFACE
	SYNC
	len=(x2-x1)*vga_bytes;
	dest=y1*vga_linewidth+x1*vga_bytes;
	for (y=y2-y1;y;y--){
		pixel_set_paged(dest,len,&color);
		dest+=vga_linewidth;
	}
	END_MOUSE
}

#define HLINE_CLIP_PREFACE \
	int mouse_hidden;\
	TEST_INACTIVITY\
	CLIP_DRAW_HLINE\
	TEST_MOUSE(x1,x2,y,y+1)

#define VLINE_CLIP_PREFACE \
	int mouse_hidden;\
	TEST_INACTIVITY\
	CLIP_DRAW_VLINE\
	TEST_MOUSE(x,x+1,y1,y2)

static void draw_hline_accel_line(struct graphics_device *dev, int x1, int y, int x2, long color)
{
	HLINE_CLIP_PREFACE

	vga_accel(ACCEL_SETFGCOLOR,color);
	vga_accel(ACCEL_DRAWLINE,x1,y,x2-1,y);
	END_MOUSE
}

static void draw_hline_accel_box(struct graphics_device *dev, int x1, int y, int x2, long color)
{
	HLINE_CLIP_PREFACE

	vga_accel(ACCEL_SETFGCOLOR,color);
	vga_accel(ACCEL_FILLBOX,x1,y,x2-x1,1);
	END_MOUSE
}

static void draw_vline_accel_line(struct graphics_device *dev, int x, int y1, int y2, long color)
{
	VLINE_CLIP_PREFACE

	vga_accel(ACCEL_SETFGCOLOR,color);
	vga_accel(ACCEL_DRAWLINE,x,y1,x,y2-1);
	END_MOUSE
}

static void draw_vline_accel_box(struct graphics_device *dev, int x, int y1, int y2, long color)
{
	VLINE_CLIP_PREFACE

	vga_accel(ACCEL_SETFGCOLOR,color);
	vga_accel(ACCEL_FILLBOX,x,y1,1,y2-y1);
	END_MOUSE
}

static void draw_hline_linear(struct graphics_device *dev, int x1, int y, int x2, long color)
{
	unsigned char *dest;
	HLINE_CLIP_PREFACE
	SYNC
	dest=my_graph_mem+y*vga_linewidth+x1*vga_bytes;
	pixel_set(dest,(x2-x1)*vga_bytes,&color);
	END_MOUSE
}

static void draw_vline_linear(struct graphics_device *dev, int x, int y1, int y2, long color)
{
	unsigned char *dest;
	int y;
	VLINE_CLIP_PREFACE
	SYNC
	dest=my_graph_mem+y1*vga_linewidth+x*vga_bytes;
	for (y=(y2-y1);y;y--){
		memcpy_to_fb(dest, (unsigned char *)&color, vga_bytes, 0);
		dest+=vga_linewidth;
	}
	END_MOUSE
}

static void draw_hline_paged(struct graphics_device *dev, int x1, int y, int x2, long color)
{
	int dest;
	int len;
	HLINE_CLIP_PREFACE
	SYNC
	len=(x2-x1)*vga_bytes;

	dest=y*vga_linewidth+x1*vga_bytes;
	pixel_set_paged(dest,len,&color);
	END_MOUSE
}

/* Works only for pixel length = 1 */
static void draw_vline_paged_1(struct graphics_device *dev, int x, int y1, int y2, long color)
{
	int dest,n, page,paga,remains;
	int byte=*(unsigned char *)&color;
	VLINE_CLIP_PREFACE;
	SYNC
	dest=y1*vga_linewidth+x;
	n=y2-y1;
	page=dest>>16;
	my_setpage(page);
	again:
	paga=dest&0xffff;
	remains=(65535-paga)/vga_linewidth+1;
	if (remains>=n){
		for (;n;n--){
			my_graph_mem[paga]=byte;
			paga+=vga_linewidth;
		}
		vga_page=page;
		END_MOUSE
		return;
	}else{
		dest+=remains*vga_linewidth;
		n-=remains;
		for (;remains;remains--){
			my_graph_mem[paga]=byte;
			paga+=vga_linewidth;
		}
		vga_setpage(++page);
		goto again;
	}
}

#ifdef t2c
/* Works only for pixel length 2 */
static void draw_vline_paged_2(struct graphics_device *dev, int x, int y1, int y2, long color)
{
	int dest,page,n,paga,remains;
	int word=*(t2c *)memory_barrier((void *)&color);
	VLINE_CLIP_PREFACE;
	SYNC
	dest=y1*vga_linewidth+(x<<1);
	n=y2-y1;
	page=dest>>16;
	my_setpage(page);
	again:
	paga=dest&0xffff;
	remains=(65534-paga)/vga_linewidth+1;
	if (remains>=n){
		for (;n;n--){
			*(t2c *)(my_graph_mem+paga)=word;
			paga+=vga_linewidth;
		}
		vga_page=page;
		END_MOUSE
		return;
	}else{
		dest+=remains*vga_linewidth;
		n-=remains;
		for (;remains;remains--){
			*(t2c *)(my_graph_mem+paga)=word;
			paga+=vga_linewidth;
		}
		vga_setpage(++page);
		goto again;
	}
}
#endif /* #ifdef t2c */

#ifdef t4c
/* Works only for pixel length 4 */
static void draw_vline_paged_4(struct graphics_device *dev, int x, int y1, int y2, long color)
{
	unsigned long dest,page,paga,remains,n;
	t4c val=*(t4c *)memory_barrier((void *)&color);

	VLINE_CLIP_PREFACE;
	SYNC
	dest=y1*(unsigned long)vga_linewidth+(x<<2);
	n=y2-y1;
	page=dest>>16;
	my_setpage(page);
	again:
	paga=dest&0xffffUL;
	remains=(65532-paga)/vga_linewidth+1;
	if (remains>=n){
		for (;n;n--){
			*(t4c *)(my_graph_mem+paga)=val;
			paga+=vga_linewidth;
		}
		vga_page=page;
		END_MOUSE
		return;
	}else{
		dest+=remains*vga_linewidth;
		n-=remains;
		for (;remains;remains--){
			*(t4c *)(my_graph_mem+paga)=color;
			paga+=vga_linewidth;
		}
		vga_setpage(++page);
		goto again;
	}
}
#endif /*t4c*/

/* Works only for pixel lengths power of two */
static void draw_vline_paged_aligned(struct graphics_device *dev, int x, int y1, int y2, long color)
{
	int dest,page,paga,remains,n;
	VLINE_CLIP_PREFACE;
	SYNC
	dest=y1*vga_linewidth+x*vga_bytes;
	n=y2-y1;
	page=dest>>16;
	my_setpage(page);
	again:
	paga=dest&0xffff;
	remains=(65536-paga-vga_bytes)/vga_linewidth+1;
	if (remains >= n){
		for (; n; n--){
			memcpy_to_fb(my_graph_mem + paga, (unsigned char *)&color, vga_bytes, 0);
			paga += vga_linewidth;
		}
		vga_page = page;
		END_MOUSE
		return;
	}else{
		dest += remains * vga_linewidth;
		n -= remains;
		for (; remains; remains--) {
			memcpy_to_fb(my_graph_mem + paga, (unsigned char *)&color, vga_bytes, 0);
			paga += vga_linewidth;
		}
		vga_setpage(++page);
		goto again;
	}
}

/* Works for any pixel length */
static void draw_vline_paged(struct graphics_device *dev, int x, int y1, int y2, long color)
{
	int lina,page,paga,remains,n;
	/* lina: linear address withing the screen
	 * page: page number
	 * paga: 16-bit address within the page
	 * remains: how many bytes remain in the current page
	 * n: how many pixels remain to be drawn
	 */
	VLINE_CLIP_PREFACE;
	SYNC
	lina=y1*vga_linewidth+x*vga_bytes;
	n=y2-y1;
	page=lina>>16;
	my_setpage(page);
	again:
	/* Invariant here: n>=1
	 * lina points to a begin of pixel
	 * page is set to page
	 */
	paga=lina&0xffff;
	remains=65536-paga;
	if (remains<vga_bytes){
		memcpy_to_fb(my_graph_mem + paga, (unsigned char *)&color, remains, 0);
		vga_setpage(++page);
		memcpy_to_fb(my_graph_mem, (unsigned char *)&color + remains, vga_bytes - remains, 0);
		lina+=vga_linewidth;
		n--;
		if (!n) goto end;
		goto again;
	}
	remains=(remains-vga_bytes)/vga_linewidth+1;
	if (remains>=n){
		for (;n;n--){
			memcpy_to_fb(my_graph_mem + paga, (unsigned char *)&color, vga_bytes, 0);
			paga+=vga_linewidth;
		}
end:
		vga_page=page;
		END_MOUSE
		return;
	}else{
		lina+=remains*vga_linewidth;
		n-=remains;
		for (;remains;remains--){
			memcpy_to_fb(my_graph_mem + paga, (unsigned char *)&color, vga_bytes, 0);
			paga+=vga_linewidth;
		}
		if (paga>=65536)vga_setpage(++page);
		goto again;
	}
}

#define SCROLL_CLIP_PREFACE \
	int mouse_hidden;\
	TEST_INACTIVITY_0\
	TEST_MOUSE(dev->clip.x1, dev->clip.x2, dev->clip.y1, dev->clip.y2)


/* When scx is <0, moves the data x1. Scrolls the whole clip window */
static int scroll_accel(struct graphics_device *dev, struct rect_set **ignore, int scx, int scy)
{
	SCROLL_CLIP_PREFACE

	vga_accel(ACCEL_SCREENCOPY,
		dev->clip.x1 - (scx < 0 ? scx : 0),
		dev->clip.y1 - (scy < 0 ? scy : 0),
		dev->clip.x1 + (scx >= 0 ? scx : 0),
		dev->clip.y1 + (scy >= 0 ? scy : 0),
		dev->clip.x2 - dev->clip.x1 - abs(scx),
		dev->clip.y2 - dev->clip.y1 - abs(scy));

	END_MOUSE
	return 1;
}

static int scroll_scansegment(struct graphics_device *dev, struct rect_set **ignore, int scx, int scy)
{
	int y, len, x_src, x_dest;
	SCROLL_CLIP_PREFACE
	SYNC
	len = dev->clip.x2 - dev->clip.x1 - abs(scx);
	x_src = dev->clip.x1 - (scx < 0 ? scx : 0);
	x_dest = dev->clip.x1 + (scx >= 0 ? scx : 0);
	if (scy > 0) {
		/* Down */
		for (y = dev->clip.y2 - 1; y >= dev->clip.y1 + scy; y--) {
			vga_getscansegment(scroll_buffer, x_src, y - scy, len);
			vga_drawscansegment(scroll_buffer, x_dest, y, len);
		}
	} else {
		/* Up */
		for (y = dev->clip.y1 - scy; y < dev->clip.y2; y++) {
			vga_getscansegment(scroll_buffer, x_src, y, len);
			vga_drawscansegment(scroll_buffer, x_dest, y + scy, len);
		}
	}
	END_MOUSE
	return 1;
}

static int scroll_linear(struct graphics_device *dev, struct rect_set **ignore, int scx, int scy)
{
	unsigned char *dest, *src;
	int y, len, dest_off, src_off;
	SCROLL_CLIP_PREFACE
	SYNC
	if (scx >= 0) {
		len = (dev->clip.x2 - dev->clip.x1 - scx) * vga_bytes;
		dest_off = scx * vga_bytes;
		src_off = 0;
	} else {
		len = (dev->clip.x2 - dev->clip.x1 + scx) * vga_bytes;
		dest_off = 0;
		src_off = -scx * vga_bytes;
	}

	if (scy > 0) {
		/* Down */
		dest = my_graph_mem + (dev->clip.y2 - 1) * vga_linewidth + dev->clip.x1 * vga_bytes;
		src = dest - vga_linewidth * scy;
		dest += dest_off;
		src += src_off;
		for (y = dev->clip.y2 - dev->clip.y1 - scy; y; y--) {
			memcpy_to_fb(dest, src, len, 1);
			dest -= vga_linewidth;
			src -= vga_linewidth;
		}
	} else {
		/* Up */
		dest = my_graph_mem + dev->clip.y1 * vga_linewidth + dev->clip.x1 * vga_bytes;
		src = dest - vga_linewidth * scy;
		dest += dest_off;
		src += src_off;
		for (y = dev->clip.y2 - dev->clip.y1 + scy; y; y--) {
			if (scy)
				memcpy_to_fb(dest, src, len, 1);
			else
				memmove_in_fb(dest, src, len);
			dest += vga_linewidth;
			src += vga_linewidth;
		}
	}
	END_MOUSE
	return 1;
}

static inline void get_row(unsigned char *bptr, int lina, int len)
{
	int page=lina>>16;
	int paga=lina&0xffff;
	int remains;

	my_setpage(page);
	remains=65536-paga;
	again:
	if (remains>=len){
		memcpy(bptr,my_graph_mem+paga,len);
		vga_page=page;
		return;
	}else{
		memcpy(bptr,my_graph_mem+paga,remains);
		paga=0;
		bptr+=remains;
		len-=remains;
		remains=65536;
		vga_setpage(++page);
		goto again;
	}
}

static int scroll_paged(struct graphics_device *dev, struct rect_set **ignore, int scx, int scy)
{
	int dest, src, y, len, dest_off, src_off;
	SCROLL_CLIP_PREFACE
	SYNC
	if (scx >= 0) {
		len = (dev->clip.x2 - dev->clip.x1 - scx) * vga_bytes;
		dest_off = scx * vga_bytes;
		src_off = 0;
	} else {
		len = (dev->clip.x2 - dev->clip.x1 + scx) * vga_bytes;
		dest_off = 0;
		src_off = -scx * vga_bytes;
	}
	if (scy > 0) {
		/* Down */
		dest = (dev->clip.y2 - 1) * vga_linewidth + dev->clip.x1 * vga_bytes;
		src = dest - vga_linewidth * scy;
		dest += dest_off;
		src += src_off;
		for (y = dev->clip.y2 - dev->clip.y1 - scy; y; y--) {
			get_row(scroll_buffer, src, len);
			paged_memcpy(dest, scroll_buffer, len);
			dest -= vga_linewidth;
			src -= vga_linewidth;
		}
	} else {
		/* Up */
		dest = dev->clip.y1 * vga_linewidth + dev->clip.x1 * vga_bytes;
		src = dest - vga_linewidth * scy;
		dest += dest_off;
		src += src_off;
		for (y = dev->clip.y2 - dev->clip.y1 + scy; y; y--) {
			get_row(scroll_buffer, src, len);
			paged_memcpy(dest, scroll_buffer, len);
			dest += vga_linewidth;
			src += vga_linewidth;
		}
	}
	END_MOUSE
	return 1;
}

/* For modes where video memory is not directly accessible through svgalib */
static inline void fill_area_drawscansegment(struct graphics_device *dev, int x1, int y1, int x2, int y2, long color)
{
	int xs;
	int col=*(unsigned char *)&color;

	FILL_CLIP_PREFACE
	SYNC
	xs=x2-x1;
	memset(scroll_buffer,col,xs);
	for (;y1<y2;y1++){
		vga_drawscansegment(scroll_buffer,x1,y1,xs);
	}
	END_MOUSE
}

/* Emulates horizontal line by calling fill_area */
static void draw_hline_fill_area(struct graphics_device *dev, int x1, int y, int x2, long color)
{
	svga_driver.fill_area(dev,x1,y,x2,y+1,color);
}


/* Emulates vline by fill_area */
static void draw_vline_fill_area(struct graphics_device *dev, int x, int y1, int y2, long color)
{
	svga_driver.fill_area(dev,x,y1,x+1,y2, color);
}

/* This does no clipping and is used only by the mouse code
 * length is in bytes, not in pixels
 */
static int drawscansegment_linear(unsigned char *colors, int x, int y, int length)
{
	unsigned char *ptr = my_graph_mem + vga_linewidth * y + vga_bytes * x;

	memcpy_to_fb(ptr, colors, length, 0);
	return 0;
}

/* This does no clipping and is used only by the mouse code
 * length is in bytes, not in pixels
 */
static int getscansegment_linear(unsigned char *colors, int x, int y, int length)
{
	unsigned char *ptr = my_graph_mem + vga_linewidth * y + vga_bytes*x;

	memcpy_to_fb(colors, ptr, length, 1);
	return 0;
}

/* This does no clipping and is used only by the mouse code
 * length is in bytes, not in pixels
 */
static int drawscansegment_paged(unsigned char *colors, int x, int y, int length)
{
	int lina = vga_linewidth * y + vga_bytes * x;

	paged_memcpy(lina, colors, length);
	return 0;
}

/* This does no clipping and is used only by the mouse code
 * length is in the bytes, not in pixels
 */
static int getscansegment_paged(unsigned char *colors, int x, int y, int length)
{
	int lina=vga_linewidth*y+vga_bytes*x;

	get_row(colors, lina, length);
	return 0;
}

static void alloc_scroll_buffer(void)
{
	if (!scroll_buffer) {
		if ((unsigned)xsize > (unsigned)MAXINT / fb_pixelsize) overalloc();
		scroll_buffer=mem_alloc(xsize*fb_pixelsize);
	}
}

static void setup_functions(void)
{
	svga_driver.flags &= ~GD_DONT_USE_SCROLL;

	if (accel_avail&ACCELFLAG_SETMODE){
		do_sync=1;
		vga_accel(ACCEL_SETMODE, BLITS_IN_BACKGROUND);
	}else do_sync=0;

	svga_driver.get_color = get_color_fn(svga_driver.depth);
	if (!svga_driver.get_color) internal_error("Unknown bit depth %x", svga_driver.depth);
	switch (vga_colors) {
		case 2:
		internal_error("2-color modes are not supported by links as they are buggy in svgalib and incapable of colors");

		case 16:
		alloc_scroll_buffer();
		svga_driver.draw_bitmap = draw_bitmap_drawscansegment;
		svga_driver.scroll = scroll_scansegment;
		svga_driver.flags |= GD_DONT_USE_SCROLL;
		svga_driver.fill_area = fill_area_drawscansegment;
		svga_driver.draw_hline = draw_hline_fill_area;
		svga_driver.draw_vline = draw_vline_fill_area;
		mouse_getscansegment = vga_getscansegment;
		mouse_drawscansegment = vga_drawscansegment;
		break;

		default:
		mouse_getscansegment = vga_getscansegment;
		mouse_drawscansegment = vga_drawscansegment;
		if (accel_avail & ACCELFLAG_PUTIMAGE) {
			svga_driver.draw_bitmap = draw_bitmap_accel;
		} else if (vga_linear) {
			svga_driver.draw_bitmap = draw_bitmap_linear;
		} else if (mode_x) {
			svga_driver.draw_bitmap = draw_bitmap_drawscansegment;
		} else {
			svga_driver.draw_bitmap = draw_bitmap_paged;
		}

		if (accel_avail & ACCELFLAG_FILLBOX) svga_driver.fill_area = fill_area_accel_box;
		else if (accel_avail & ACCELFLAG_DRAWLINE) svga_driver.fill_area = fill_area_accel_lines;
		else if (vga_linear) svga_driver.fill_area = fill_area_linear;
		else if (mode_x) svga_driver.fill_area = fill_area_drawscansegment;
		else svga_driver.fill_area = fill_area_paged;

		if (accel_avail & ACCELFLAG_DRAWLINE) {
			svga_driver.draw_hline = draw_hline_accel_line;
			svga_driver.draw_vline = draw_vline_accel_line;
		} else if (accel_avail & ACCELFLAG_FILLBOX) {
			svga_driver.draw_hline = draw_hline_accel_box;
			svga_driver.draw_vline = draw_vline_accel_box;
		} else if (vga_linear) {
			svga_driver.draw_hline = draw_hline_linear;
			svga_driver.draw_vline = draw_vline_linear;
		} else if (mode_x) {
			svga_driver.draw_hline = draw_hline_fill_area;
			svga_driver.draw_vline = draw_vline_fill_area;
		} else {
			/* Paged memory access */
			svga_driver.draw_hline = draw_hline_paged;
			switch (vga_bytes)
			{
				case 1:
				svga_driver.draw_vline = draw_vline_paged_1;
				break;
#ifdef t2c
				case 2:
				svga_driver.draw_vline = draw_vline_paged_2;
				break;
#endif /* #ifdef t2c */

#ifdef t4c
				case 4:
				svga_driver.draw_vline = draw_vline_paged_4;
				break;
#endif /* #ifdef t4c */

				default:
				if (vga_bytes & (vga_bytes - 1))
					svga_driver.draw_vline = draw_vline_paged;
				else
					svga_driver.draw_vline = draw_vline_paged_aligned;
				break;
			}
		}

		if (vga_colors >= 256) {
			if (vga_linear) {
				mouse_drawscansegment = drawscansegment_linear;
				mouse_getscansegment = getscansegment_linear;
			} else if (!mode_x) {
				mouse_drawscansegment = drawscansegment_paged;
				mouse_getscansegment = getscansegment_paged;
			}
		}

		if (accel_avail & ACCELFLAG_SCREENCOPY) {
			svga_driver.scroll = scroll_accel;
		} else if (vga_linear) {
			svga_driver.scroll = scroll_linear;
			svga_driver.flags |= GD_DONT_USE_SCROLL;
		} else if (mode_x) {
			alloc_scroll_buffer();
			svga_driver.scroll = scroll_scansegment;
			svga_driver.flags |= GD_DONT_USE_SCROLL;
		} else {
			alloc_scroll_buffer();
			svga_driver.scroll = scroll_paged;
			svga_driver.flags |= GD_DONT_USE_SCROLL;
		}
	}

}

#if 0
void dump_mode_info_into_file(vga_modeinfo* i)
{
 FILE *f;

 f=fopen(".links_svga_modeinfo","w");
 if (!f) return;
 fprintf(f,"Resolution %d*%d\n",i->width,i->height);
 fprintf(f,"%d bytes per screen pixel\n",i->bytesperpixel);
 fprintf(f,"%d colors\n",i->colors);
 fprintf(f,"Linewidth %d bytes\n",i->linewidth);
 fprintf(f,"Maximum logical width %d bytes\n",i->maxlogicalwidth);
 fprintf(f,"Start address rangemask 0x%x\n",i->startaddressrange);
 fprintf(f,"Max. pixels per logical screen %d\n",i->maxpixels);
 fprintf(f,"bitblt %s\n",i->haveblit&HAVE_BITBLIT?"yes":"no");
 fprintf(f,"fillblt %s\n",i->haveblit&HAVE_FILLBLIT?"yes":"no");
 fprintf(f,"imageblt %s\n",i->haveblit&HAVE_IMAGEBLIT?"yes":"no");
 fprintf(f,"hlinelistblt %s\n",i->haveblit&HAVE_HLINELISTBLIT?"yes":"no");
 fprintf(f,"read/write page %s\n",i->flags&HAVE_RWPAGE?"yes":"no");
 fprintf(f,"Interlaced %s\n",i->flags&IS_INTERLACED?"yes":"no");
 fprintf(f,"Mode X layout %s\n",i->flags&IS_MODEX?"yes":"no");
 fprintf(f,"Dynamically loaded %s\n",i->flags&IS_DYNAMICMODE?"yes":"no");
 fprintf(f,"Linear: %s\n",vga_linear?"yes":"no");
 fprintf(f,"Misordered %s\n",i->flags&RGB_MISORDERED?"yes":"no");
 if (!i->flags&EXT_INFO_AVAILABLE){
	 fprintf(f,"Old svgalib, extended info is not available\n");
 }else{
	 fprintf(f,"Chiptype 0x%x\n",i->chiptype);
	 fprintf(f,"Memory %dKB\n",i->memory);
	 fprintf(f,"Linewidth Unit %d\n",i->linewidth_unit);
	 fprintf(f,"Aperture size %d\n",i->aperture_size);
 }
 fprintf(f,"Accelerated putimage: %s\n",svga_driver.draw_bitmap==draw_bitmap_accel?"yes":"no");
 fclose(f);
}
#endif

static void svgalib_key_in(struct itrm *p, unsigned char *ev_, int size)
{
	struct links_event *ev = (struct links_event *)(void *)ev_;
	if (size != sizeof(struct links_event)) return;
	if (ev->ev == EV_ABORT) terminate_loop = 1;
	if (ev->ev != EV_KBD) return;
	if (ev->y & KBD_PASTING) goto skip;
	if ((ev->y & (KBD_CTRL | KBD_ALT)) == KBD_ALT && ev->x >= '0' && ev->x <= '9') {
		switch_virtual_device((ev->x - '1' + 10) % 10);
		return;
	}
skip:
	if (g_kbd_codepage(&svga_driver) != utf8_table && ev->x >= 128 && ev->x <= 255)
		if ((ev->x = cp2u(ev->x, g_kbd_codepage(&svga_driver))) == -1) return;
	if (current_virtual_device && current_virtual_device->keyboard_handler) current_virtual_device->keyboard_handler(current_virtual_device, ev->x, ev->y);
}

#ifndef MOUSE_FOURTHBUTTON
#define MOUSE_FOURTHBUTTON	0
#endif

#ifndef MOUSE_FIFTHBUTTON
#define MOUSE_FIFTHBUTTON	0
#endif

#ifndef MOUSE_SIXTHBUTTON
#define MOUSE_SIXTHBUTTON	0
#endif

#define BUTTON_MASK (MOUSE_RIGHTBUTTON | MOUSE_MIDDLEBUTTON | MOUSE_LEFTBUTTON | MOUSE_FOURTHBUTTON | MOUSE_FIFTHBUTTON /*| MOUSE_SIXTHBUTTON*/)

static inline void mouse_aggregate_flush(void)
{
	if (!mouse_aggregate_flag) return;
	mouse_aggregate_flag=0;
	if (current_virtual_device && current_virtual_device->mouse_handler) current_virtual_device->mouse_handler(current_virtual_device, mouse_x, mouse_y, mouse_aggregate_action);
}

/* Only calls appropriate callbacks, doesn't draw anything. */
#ifdef HAVE_MOUSE_GETPOSITION_6D
static void mouse_event_handler(int button, int dx, int dy, int dz, int drx, int dry, int drz)
{
#else
static void mouse_event_handler(int button, int dx, int dy)
{
	int drx = 0, dry = 0;
#endif
	int moved, old_mouse_x, old_mouse_y;
	void (*mh)(struct graphics_device *, int, int, int);
	struct graphics_device *cd = current_virtual_device;

	mh = cd ? cd->mouse_handler : (void(*)(struct graphics_device *dev, int x, int y, int buttons))NULL;
	old_mouse_x = mouse_x;
	old_mouse_y = mouse_y;

	mouse_x += dx;
	if (mouse_x >= xsize) mouse_x = xsize - 1;
	else if (mouse_x < 0) mouse_x = 0;

	mouse_y += dy;
	if (mouse_y >= ysize) mouse_y = ysize - 1;
	else if (mouse_y < 0) mouse_y = 0;

	redraw_mouse();

	moved = (old_mouse_x != mouse_x || old_mouse_y != mouse_y);

	/* Test movement without buttons */
	if (!(mouse_buttons & BUTTON_MASK) && moved) {
		mouse_aggregate_flag=1;
		mouse_aggregate_action=B_MOVE;
	}

	/* Test presses */
	if ((button&MOUSE_LEFTBUTTON)&&!(mouse_buttons&MOUSE_LEFTBUTTON)){
		mouse_aggregate_flush();
		if (mh) mh(cd,mouse_x, mouse_y,B_LEFT|B_DOWN);
	}
	if ((button&MOUSE_MIDDLEBUTTON)&&!(mouse_buttons&MOUSE_MIDDLEBUTTON)){
		mouse_aggregate_flush();
		if (mh) mh(cd,mouse_x, mouse_y,B_MIDDLE|B_DOWN);
	}
	if ((button&MOUSE_RIGHTBUTTON)&&!(mouse_buttons&MOUSE_RIGHTBUTTON)){
		mouse_aggregate_flush();
		if (mh) mh(cd,mouse_x, mouse_y,B_RIGHT|B_DOWN);
	}
	if ((button&MOUSE_FOURTHBUTTON)&&!(mouse_buttons&MOUSE_FOURTHBUTTON)){
		mouse_aggregate_flush();
		if (mh) mh(cd,mouse_x, mouse_y,B_FOURTH|B_DOWN);
	}
	if ((button&MOUSE_FIFTHBUTTON)&&!(mouse_buttons&MOUSE_FIFTHBUTTON)){
		mouse_aggregate_flush();
		if (mh) mh(cd,mouse_x, mouse_y,B_FIFTH|B_DOWN);
	}
	if ((button&MOUSE_SIXTHBUTTON)&&!(mouse_buttons&MOUSE_SIXTHBUTTON)){
		mouse_aggregate_flush();
		/*if (mh) mh(cd,mouse_x, mouse_y,B_SIXTH|B_DOWN);*/
		switch_virtual_device(VD_NEXT);
	}

	/* Test releases */
	if (!(button&MOUSE_LEFTBUTTON)&&(mouse_buttons&MOUSE_LEFTBUTTON)){
		mouse_aggregate_flush();
		if (mh) mh(cd,mouse_x, mouse_y,B_LEFT|B_UP);
	}
	if (!(button&MOUSE_MIDDLEBUTTON)&&(mouse_buttons&MOUSE_MIDDLEBUTTON)){
		mouse_aggregate_flush();
		if (mh) mh(cd,mouse_x, mouse_y,B_MIDDLE|B_UP);
	}
	if (!(button&MOUSE_RIGHTBUTTON)&&(mouse_buttons&MOUSE_RIGHTBUTTON)){
		mouse_aggregate_flush();
		if (mh) mh(cd,mouse_x, mouse_y,B_RIGHT|B_UP);
	}
	if (!(button&MOUSE_FOURTHBUTTON)&&(mouse_buttons&MOUSE_FOURTHBUTTON)){
		mouse_aggregate_flush();
		if (mh) mh(cd,mouse_x, mouse_y,B_FOURTH|B_UP);
	}
	if (!(button&MOUSE_FIFTHBUTTON)&&(mouse_buttons&MOUSE_FIFTHBUTTON)){
		mouse_aggregate_flush();
		if (mh) mh(cd,mouse_x, mouse_y,B_FIFTH|B_UP);
	}
	if (!(button&MOUSE_SIXTHBUTTON)&&(mouse_buttons&MOUSE_SIXTHBUTTON)){
		mouse_aggregate_flush();
		/*if (mh) mh(cd,mouse_x, mouse_y,B_SIXTH|B_UP);*/
	}

	if (drx < 0 && mh) mh(cd, mouse_x, mouse_y, B_MOVE | B_WHEELUP);
	if (drx > 0 && mh) mh(cd, mouse_x, mouse_y, B_MOVE | B_WHEELDOWN);

	if (dry < 0 && mh) mh(cd, mouse_x, mouse_y, B_MOVE | B_WHEELLEFT);
	if (dry > 0 && mh) mh(cd, mouse_x, mouse_y, B_MOVE | B_WHEELRIGHT);

	/* Test drag */
	if (! ((button^mouse_buttons) & BUTTON_MASK ) && moved && (button &
		BUTTON_MASK)){
		mouse_aggregate_flag=1;
		mouse_aggregate_action=(
				      button&MOUSE_LEFTBUTTON?B_LEFT:
				      button&MOUSE_RIGHTBUTTON?B_RIGHT:
				      button&MOUSE_MIDDLEBUTTON?B_MIDDLE:
				      button&MOUSE_FOURTHBUTTON?B_FOURTH:
				      button&MOUSE_FIFTHBUTTON?B_FIFTH:
				      /*button&MOUSE_SIXTHBUTTON?B_SIXTH:*/
				      0) | B_DRAG;
	}
	mouse_buttons=button;
}

#undef BUTTON_MASK

/* Flushes the background_buffer onscreen where it was originally taken from. */
static void place_mouse_background(void)
{
	struct bitmap bmp;
	struct graphics_device *current_virtual_device_backup;

	bmp.x = arrow_width;
	bmp.y = arrow_height;
	bmp.skip = arrow_width * fb_pixelsize;
	bmp.data = background_buffer;

	current_virtual_device_backup = current_virtual_device;
	current_virtual_device = mouse_graphics_device;
	svga_driver.draw_bitmap(mouse_graphics_device, &bmp, background_x, background_y);
	current_virtual_device = current_virtual_device_backup;
}

/* Only when the old and new mouse don't interfere. Using it on interfering mouses would
 * cause a flicker.
 */
static void hide_mouse(void)
{

	global_mouse_hidden = 1;
	place_mouse_background();
}

/* Gets background from the screen (clipping provided only right and bottom) to the
 * passed buffer.
 */
static void get_mouse_background(unsigned char *buffer_ptr)
{
	int width,height,skip,x,y;

	skip=arrow_width*fb_pixelsize;

	x=mouse_x;
	y=mouse_y;

	width=fb_pixelsize*(arrow_width+x>xsize?xsize-x:arrow_width);
	height=arrow_height+y>ysize?ysize-y:arrow_height;

	SYNC
	for (;height;height--){
		mouse_getscansegment(buffer_ptr,x,y,width);
		buffer_ptr+=skip;
		y++;
	}
}

/* Overlays the arrow's image over the mouse_buffer
 * Doesn't draw anything into the screen
 */
static void render_mouse_arrow(void)
{
	int x,y, reg0, reg1;
	unsigned char *mouse_ptr=mouse_buffer;
	const unsigned *arrow_ptr=arrow;

	for (y=arrow_height;y;y--){
		reg0=*arrow_ptr;
		reg1=arrow_ptr[1];
		arrow_ptr+=2;
		for (x=arrow_width;x;)
		{
			int mask=1<<(--x);

			if (reg0&mask)
				memcpy (mouse_ptr, &mouse_black, fb_pixelsize);
			else if (reg1&mask)
				memcpy (mouse_ptr, &mouse_white, fb_pixelsize);
			mouse_ptr+=fb_pixelsize;
		}
	}
}

static void place_mouse(void)
{
	struct bitmap bmp;
	struct graphics_device *current_graphics_device_backup;

	bmp.x = arrow_width;
	bmp.y = arrow_height;
	bmp.skip = arrow_width * fb_pixelsize;
	bmp.data = mouse_buffer;

	current_graphics_device_backup = current_virtual_device;
	current_virtual_device = mouse_graphics_device;
	/* We do need to worry about SYNC because draw_bitmap already
	 * does it (if necessary)
	 */
	svga_driver.draw_bitmap(mouse_graphics_device, &bmp, mouse_x, mouse_y);
	current_virtual_device = current_graphics_device_backup;

	global_mouse_hidden=0;
}

/* Only when the old and the new mouse positions do not interfere. Using this routine
 * on interfering positions would cause a flicker.
 */
static void show_mouse(void)
{

	get_mouse_background(background_buffer);
	background_x=mouse_x;
	background_y=mouse_y;
	memcpy(mouse_buffer,background_buffer,fb_pixelsize*arrow_area);
	render_mouse_arrow();
	place_mouse();
}

/* Doesn't draw anything into the screen
 */
static void put_and_clip_background_buffer_over_mouse_buffer(void)
{
	unsigned char *bbufptr=background_buffer, *mbufptr=mouse_buffer;
	int left=background_x-mouse_x;
	int top=background_y-mouse_y;
	int right,bottom;
	int bmpixelsizeL=fb_pixelsize;
	int number_of_bytes;
	int byte_skip;

	right=left+arrow_width;
	bottom=top+arrow_height;

	if (left<0){
		bbufptr-=left*bmpixelsizeL;
		left=0;
	}
	if (right>arrow_width) right=arrow_width;
	if (top<0){
		bbufptr-=top*bmpixelsizeL*arrow_width;
		top=0;
	}
	if (bottom>arrow_height) bottom=arrow_height;
	mbufptr+=bmpixelsizeL*(left+arrow_width*top);
	byte_skip=arrow_width*bmpixelsizeL;
	number_of_bytes=bmpixelsizeL*(right-left);
	for (;top<bottom;top++){
		memcpy(mbufptr,bbufptr,number_of_bytes);
		mbufptr+=byte_skip;
		bbufptr+=byte_skip;
	}
}

/* This draws both the contents of background_buffer and mouse_buffer in a scan
 * way (left-right, top-bottom), so the flicker is reduced.
 */
static inline void place_mouse_composite(void)
{
	int mouse_left=mouse_x;
	int mouse_top=mouse_y;
	int background_left=background_x;
	int background_top=background_y;
	int mouse_right=mouse_left+arrow_width;
	int mouse_bottom=mouse_top+arrow_height;
	int background_right=background_left+arrow_width;
	int background_bottom=background_top+arrow_height;
	int skip=arrow_width*fb_pixelsize;
	int background_length,mouse_length;
	unsigned char *mouse_ptr=mouse_buffer,*background_ptr=background_buffer;
	int yend;

	/* First let's sync to the beam - wait for the beginning of vertical retrace
	 * (it would be better to wait for the beginning of the blank, however,
	 * svgalib doesn't provide it as VGA and SVGA cards don't provide it
	 */

	/* This will probably not make any good anyway.
	if (vga_colors >= 256)
		vga_waitretrace();
	*/

	if (mouse_bottom>ysize) mouse_bottom=ysize;
	if (background_bottom>ysize) background_bottom=ysize;

	SYNC
	/* We have to sync because mouse_drawscansegment does not wait for
	 * the accelerator to finish. But we never waste time here because
	 * mouse_drawscansegment is never accelerated.
	 */
	/* Let's do the top part */
	if (background_top<mouse_top){
		/* Draw the background */
		background_length=background_right>xsize?xsize-background_left
			:arrow_width;
		for (;background_top<mouse_top;background_top++){
			mouse_drawscansegment(background_ptr,background_left
				,background_top,background_length*fb_pixelsize);
			background_ptr+=skip;
		}

	}else if (background_top>mouse_top){
		/* Draw the mouse */
		mouse_length=mouse_right>xsize
			?xsize-mouse_left:arrow_width;
		for (;mouse_top<background_top;mouse_top++){
			mouse_drawscansegment(mouse_ptr,mouse_left,mouse_top,mouse_length*fb_pixelsize);
			mouse_ptr+=skip;
		}
	}

	/* Let's do the middle part */
	yend=mouse_bottom<background_bottom?mouse_bottom:background_bottom;
	if (background_left<mouse_left){
		/* Draw background, mouse */
		mouse_length=mouse_right>xsize?xsize-mouse_left:arrow_width;
		for (;mouse_top<yend;mouse_top++){
			mouse_drawscansegment(background_ptr,background_left,mouse_top
				,(mouse_left-background_left)*fb_pixelsize);
			mouse_drawscansegment(mouse_ptr,mouse_left,mouse_top,mouse_length*fb_pixelsize);
			mouse_ptr+=skip;
			background_ptr+=skip;
		}

	}else{
		int l1, l2, l3;

		/* Draw mouse, background */
		mouse_length=mouse_right>xsize?xsize-mouse_left:arrow_width;
		background_length=background_right-mouse_right;
		if (background_length+mouse_right>xsize)
			background_length=xsize-mouse_right;
		l1=mouse_length*fb_pixelsize;
		l2=(mouse_right-background_left)*fb_pixelsize;
		l3=background_length*fb_pixelsize;
		for (;mouse_top<yend;mouse_top++){
			mouse_drawscansegment(mouse_ptr,mouse_left,mouse_top,l1);
			if (background_length>0)
				mouse_drawscansegment(
					background_ptr + l2,
					mouse_right,mouse_top, l3);
			mouse_ptr+=skip;
			background_ptr+=skip;
		}
	}

	if (background_bottom<mouse_bottom){
		/* Count over bottoms! tops will be invalid! */
		/* Draw mouse */
		mouse_length=mouse_right>xsize?xsize-mouse_left
			:arrow_width;
		for (;background_bottom<mouse_bottom;background_bottom++){
			mouse_drawscansegment(mouse_ptr,mouse_left,background_bottom
				,mouse_length*fb_pixelsize);
			mouse_ptr+=skip;
		}
	}else{
		/* Draw background */
		background_length=background_right>xsize?xsize-background_left
			:arrow_width;
		for (;mouse_bottom<background_bottom;mouse_bottom++){
			mouse_drawscansegment(background_ptr,background_left,mouse_bottom
				,background_length*fb_pixelsize);
			background_ptr+=skip;
		}
	}
}

/* This moves the mouse a sophisticated way when the old and new position of the
 * cursor overlap.
 */
static inline void redraw_mouse_sophisticated(void)
{
	int new_background_x, new_background_y;

	get_mouse_background(mouse_buffer);
	put_and_clip_background_buffer_over_mouse_buffer();
	memcpy(new_background_buffer,mouse_buffer,fb_pixelsize*arrow_area);
	new_background_x=mouse_x;
	new_background_y=mouse_y;
	render_mouse_arrow();
	place_mouse_composite();
	memcpy(background_buffer,new_background_buffer,fb_pixelsize*arrow_area);
	background_x=new_background_x;
	background_y=new_background_y;
}

static void redraw_mouse(void)
{
	if (flags) return; /* We are not drawing */
	if (mouse_x!=background_x||mouse_y!=background_y){
		if (RECTANGLES_INTERSECT(
			background_x, background_x+arrow_width,
			mouse_x, mouse_x+arrow_width,
			background_y, background_y+arrow_height,
			mouse_y, mouse_y+arrow_height)){
			redraw_mouse_sophisticated();
		}else{
			/* Do a normal hide/show */
			get_mouse_background(mouse_buffer);
			memcpy(new_background_buffer,
				mouse_buffer,arrow_area*fb_pixelsize);
			render_mouse_arrow();
			hide_mouse();
			place_mouse();
			memcpy(background_buffer,new_background_buffer
				,arrow_area*fb_pixelsize);
			background_x=mouse_x;
			background_y=mouse_y;
		}
	}
}


static unsigned char *svga_get_driver_param(void)
{
	return svga_driver_param;
}

static void generate_palette_outer(void)
{
	if (vga_colors == 16 || vga_colors == 256) {
		struct irgb *palette;
		palette = mem_alloc(vga_colors * sizeof(*palette));
		generate_palette(palette);
		set_palette(palette);
		mem_free(palette);
		/* Palette in SVGAlib will be always color cube */
	}
}

static void vga_update_palette(void)
{
	if (!svga_driver.param->palette_mode)
		svga_driver.depth |= 0x300;
	else
		svga_driver.depth &= ~0x300;

	svga_driver.get_color = get_color_fn(svga_driver.depth);
	if (!svga_driver.get_color) internal_error("Unknown bit depth %x", svga_driver.depth);

	mouse_black = svga_driver.get_color(0);
	mouse_white = svga_driver.get_color(0xffffff);

	generate_palette_outer();
}

/* This is to be called after vga_setmode and sets up accelerator,
 * svgalib functions */
static void setup_mode(int mode)
{
	vga_modeinfo *i;
	int sig;

	accel_avail=vga_ext_set(VGA_EXT_AVAILABLE,VGA_AVAIL_ACCEL);
	if (vga_ext_set(VGA_EXT_AVAILABLE, VGA_AVAIL_SET)&VGA_CLUT8){
		vga_ext_set(VGA_EXT_SET,VGA_CLUT8);
		palette_depth=8;
	}else palette_depth=6;
	i=vga_getmodeinfo(mode);
	vga_bytes=i->bytesperpixel;
	fb_pixelsize=vga_bytes?vga_bytes:1;
	vga_misordered=!!(i->flags&RGB_MISORDERED);
	mode_x=!!(i->flags&IS_MODEX);
	vga_linear=!!(i->flags&IS_LINEAR);
	/*
	if (!vga_linear && i->flags&CAPABLE_LINEAR && 0<=vga_setlinearaddressing()) vga_linear=1;
	*/
	my_graph_mem = vga_getgraphmem();
	svga_driver.x = xsize = i->width;
	svga_driver.y = ysize = i->height;
	vga_colors = i->colors;
	if (xsize == 320 && ysize == 200 && i->colors == 256) vga_linear = 1; /* The mode
		does not need to page :-) */
	vga_linewidth = i->linewidth;
	if (!vga_linear) {
		vga_page=-1;
	}
	vga_misordered = !!(i->flags & RGB_MISORDERED);

	svga_driver.flags &= ~GD_SELECT_PALETTE;

	/*dump_mode_info_into_file(i);*/
	svga_driver.depth = 0;
	svga_driver.depth |= vga_misordered << 8;
	switch (vga_colors) {
		case 16:
			sig = 4;
			break;
		case 256:
			sig = 8;
			if (!svga_driver.param->palette_mode)
				svga_driver.depth |= 0x300;
			svga_driver.flags |= GD_SELECT_PALETTE;
			break;
		case 32768:
			sig = 15;
			break;
		case 65536:
			sig = 16;
			break;
		case 16777216:
			sig = 24;
			break;
		default:
			sig = 0; /* Only to suppress warning */
			break;
	}
	svga_driver.depth |= sig << 3;
	svga_driver.depth |= fb_pixelsize;

	/* setup_functions uses depth. */
	setup_functions();
	generate_palette_outer();
}

#ifndef __SPAD__
static void vtswitch_handler(void *nothing)
{
	int oktowrite;

	vga_unlockvc();
	vga_lockvc();
	oktowrite = vga_oktowrite();
	if (flags == 1 && oktowrite) {
		flags = 0;
		setup_mode(vga_mode);
		if (mouse_works) {
			mouse_seteventhandler((void *)mouse_event_handler);
		}
		if (current_virtual_device) current_virtual_device->redraw_handler(current_virtual_device, &current_virtual_device->size);
	}
	flags = (flags & ~1) | !oktowrite;
	svgalib_timer_id = install_timer(100, vtswitch_handler, NULL);
}
#endif

static void svga_ctrl_c(void *i_)
{
	kbd_ctrl_c();
}

/* Param: one string which is to be compared with one from modes.
 * Copies the svga_driver into gr_driver.
 * Returns:	0 OK
 *		1 Passed mode unknown by svga_driver
 *		2 Passed mode unknown by svgalib
 * mikulas: Change: Returns:	NULL: OK
 *				non-null: poiner to string with error
 *					  description, string must be freed
 */
static unsigned char *svga_init_driver(unsigned char *param, unsigned char *display)
{
	int j;

	kbd_set_raw = 0;

	vga_init();
	svga_driver.flags |= GD_NEED_CODEPAGE;
	j = 0;

	svga_driver_param = NULL;
	if (!param || !*param) goto not_found;
	svga_driver_param=stracpy(param);
	for (j = 0; (size_t)j < array_elements(modes); j++) {
		if (!casestrcmp(cast_uchar modes[j].name, param)) {
			goto found;
		}
	}
	j = 1;
	not_found:
	{
		unsigned char *m = init_str();
		int l = 0;
		int f = 0;
		if (j) {
			add_to_str(&m, &l, cast_uchar "Video mode ");
			add_to_str(&m, &l, param);
			add_to_str(&m, &l,
				j == 1 ? cast_uchar " not supported by svgalib" :
				j == 2 ? cast_uchar " not supported by your video card" :
				cast_uchar " could not be set");
			add_to_str(&m, &l, cast_uchar ".\n");
		} else add_to_str(&m, &l, cast_uchar "There is no default video mode.\n");
		for (j=0;(size_t)j<sizeof(modes)/sizeof(*modes);j++) if (vga_hasmode(modes[j].number)) {
			if (f) add_to_str(&m, &l, cast_uchar ", ");
			else f = 1, add_to_str(&m, &l, cast_uchar "The following modes are supported:\n");
			add_to_str(&m, &l, cast_uchar modes[j].name);
		}
		if (f) add_to_str(&m, &l, cast_uchar "\nUse -mode switch to set video mode.\n");
		else add_to_str(&m, &l, cast_uchar "There are no supported video modes. Links can't run on svgalib.\n");
		if(svga_driver_param)mem_free(svga_driver_param),svga_driver_param=NULL;
		return m;
	}
	found:
	if (!vga_hasmode(modes[j].number)) {
		j = 2;
		goto not_found;
	}
	init_virtual_devices(&svga_driver, NUMBER_OF_DEVICES);
#ifdef MOUSE_NONE
	if ((vga_getmousetype() & MOUSE_TYPE_MASK) == MOUSE_NONE) {
		vga_setmousesupport(0);
		mouse_works = 0;
	} else
#endif
	{
		vga_setmousesupport(1);
		mouse_works = 1;
	}
	vga_lockvc();
#ifndef __SPAD__
	svgalib_timer_id = install_timer(100, vtswitch_handler, NULL);
#ifdef HAVE_VGA_RUNINBACKGROUND_VERSION
	if (vga_runinbackground_version() >= 1)
#endif
		vga_runinbackground(1);
#endif
	vga_setmode_retry:
	if (vga_setmode(modes[j].number) < 0) {
		if (out_of_memory(0, NULL, 0))
			goto vga_setmode_retry;
#ifndef __SPAD__
		kill_timer(svgalib_timer_id);
#endif
		vga_unlockvc();
		shutdown_virtual_devices();
		j = 3;
		goto not_found;
	}
	vga_mode=modes[j].number;
	setup_mode(modes[j].number);
	svgalib_kbd = handle_svgalib_keyboard(svgalib_key_in);

	if (mouse_works){
		if ((unsigned)arrow_area > (unsigned)MAXINT / fb_pixelsize) overalloc();
		mouse_buffer=mem_alloc(fb_pixelsize*arrow_area);
		background_buffer=mem_alloc(fb_pixelsize*arrow_area);
		new_background_buffer=mem_alloc(fb_pixelsize*arrow_area);
		mouse_black=svga_driver.get_color(0);
		mouse_white=svga_driver.get_color(0xffffff);
		mouse_graphics_device=svga_driver.init_device();
		virtual_devices[0] = NULL;
		global_mouse_hidden=1;
		background_x=mouse_x=xsize>>1;
		background_y=mouse_y=ysize>>1;
		show_mouse();
		mouse_seteventhandler((void *)mouse_event_handler);
	}else{
		global_mouse_hidden=1;
		/* To ensure hide_mouse and show_mouse will do nothing */
	}
	ignore_signals();
	errno = 0;
	while (signal(SIGTSTP, SIG_IGN) == SIG_ERR && errno == EINTR) errno = 0;
	install_signal_handler(SIGINT, svga_ctrl_c, svgalib_kbd, 0);
	return NULL;
}

/* Return value:	0 alloced on heap
 *			1 alloced in vidram
 *			2 alloced in X server shm
 */
static int svga_get_empty_bitmap(struct bitmap *dest)
{
	if (dest->x && (unsigned)dest->x * (unsigned)dest->y / (unsigned)dest->x != (unsigned)dest->y) overalloc();
	if ((unsigned)dest->x * (unsigned)dest->y > (unsigned)MAXINT / fb_pixelsize) overalloc();
	dest->data=mem_alloc(dest->x*dest->y*fb_pixelsize);
	dest->skip=dest->x*fb_pixelsize;
	dest->flags=0;
	return 0;
}

static int vga_block(struct graphics_device *dev)
{
	int overridden;

	overridden = (flags >> 1) & 1;
	if (!overridden) {
		svgalib_block_itrm(svgalib_kbd);
		if (mouse_works) {
			hide_mouse();
			/* mouse_close(); This is not necessary as it is
				handled by vga_setmode(TEXT). */
		}
		vga_setmode_retry:
		if (vga_setmode(TEXT) < 0) {
			if (out_of_memory(0, NULL, 0))
				goto vga_setmode_retry;
			fatal_exit("ERROR: vga_setmode failed");
		}
	}
	flags |= 2;
	return overridden;
}

static int vga_unblock(struct graphics_device *dev)
{
#ifdef DEBUG
	if (!(flags & 2)) {
		internal_error("vga_unblock called without vga_block");
		return 0;
	}
#endif /* #ifdef DEBUG */
	flags &= ~2;
	vga_setmousesupport(1);
	vga_setmode_retry:
	if (vga_setmode(vga_mode) < 0) {
		if (out_of_memory(0, NULL, 0))
			goto vga_setmode_retry;
		fatal_exit("ERROR: vga_setmode failed");
	}
	setup_mode(vga_mode);
	if (mouse_works) {
		show_mouse();
		mouse_seteventhandler((void *)mouse_event_handler);
	}
	svgalib_unblock_itrm(svgalib_kbd);
	if (current_virtual_device) current_virtual_device->redraw_handler(current_virtual_device, &current_virtual_device->size);
	return 0;
}

static void *svga_prepare_strip(struct bitmap *bmp, int top, int lines)
{
	return (unsigned char *)bmp->data + bmp->skip * top;
}


static void svga_commit_strip(struct bitmap *bmp, int top, int lines)
{
	return;
}

int vga_select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
{
	int retval,i;

	if (drv != &svga_driver) return select(n, readfds, writefds, exceptfds, timeout);

	/* The second flag here is to suppress mouse wait
	 * in blocked state */
	retval = vga_waitevent((mouse_works && !(flags & 2) ? VGA_MOUSEEVENT : 0)
#ifdef VGA_REDRAWEVENT
		| VGA_REDRAWEVENT
#endif
			, readfds, writefds, exceptfds, timeout);
	if (retval < 0) return retval;
#ifdef VGA_REDRAWEVENT
	if (retval & VGA_REDRAWEVENT) {
		if (current_virtual_device) current_virtual_device->redraw_handler(current_virtual_device, &current_virtual_device->size);
		check_bottom_halves();
	}
#endif
	if (retval & VGA_MOUSEEVENT){
		mouse_aggregate_flush();
		/*redraw_mouse(); mikulas: dal jsem to do mouse_event_handler,
		  aby ukazatel mysi nezustaval pozadu za scrollbarem */
		check_bottom_halves();
	}
	retval = 0;
	for (i = 0; i < n; i++){
		if (readfds && FD_ISSET(i, readfds)) retval++;
		if (writefds && FD_ISSET(i, writefds)) retval++;
		if (exceptfds && FD_ISSET(i, exceptfds)) retval++;
	}
	return retval;
}

struct graphics_driver svga_driver = {
	cast_uchar "svgalib",
	svga_init_driver,
	init_virtual_device,
	shutdown_virtual_device,
	svga_shutdown_driver,
	NULL,
	NULL,
	svga_get_driver_param,
	NULL,
	NULL,
	NULL,
	svga_get_empty_bitmap,
	svga_register_bitmap,
	svga_prepare_strip,
	svga_commit_strip,
	svga_unregister_bitmap,
	NULL, /* svga_draw_bitmap */
	NULL, /* get_color */
	NULL, /* fill_area */
	NULL, /* draw_hline */
	NULL, /* draw_vline */
	NULL, /* scroll */
	NULL, /* set_clip_area */
	NULL, /* flush */
	vga_block, /* block */
	vga_unblock, /* unblock */
	vga_update_palette, /* set_palette */
	NULL, /* get_real_colors */
	NULL, /* set_title */
	NULL, /* exec */
	NULL, /* set_clipboard_text */
	NULL, /* get_clipboard_text */
	0,				/* depth */
	0, 0,				/* size */
	GD_NO_LIBEVENT
#ifndef SPAD
		| GD_NOAUTO
#endif
		,			/* flags */
	NULL,				/* param */
};

#endif /* GRDRV_SVGALIB */
