diff --git a/source/.gitignore b/source/.gitignore
index b821e8d..88b15fc 100644
--- a/source/.gitignore
+++ b/source/.gitignore
@@ -3,8 +3,8 @@
 *.obj
 qf-server
 qf-client-x11
+qf-client-ggi
 qf-client-svga
 qf-client-glx
 client.exe
 server.exe
-
diff --git a/source/Makefile b/source/Makefile
index 3809082..9e3f674 100644
--- a/source/Makefile
+++ b/source/Makefile
@@ -9,6 +9,9 @@ CL_SVGA_libs=-lvga
 CL_X11_name=qf-client-x11
 CL_X11_libs=-lX11 -lXext
 
+CL_GGI_name=qf-client-ggi
+CL_GGI_libs=-lggi
+
 CL_GLX_name=qf-client-glx
 CL_GLX_libs=-lX11 -lXext -lGL
 
@@ -170,6 +173,12 @@ CL_X11_sources=\
 CL_X11_dependencies = $(patsubst %,%.d,$(basename $(CL_X11_sources)))
 CL_X11_objects = $(patsubst %.d,%.o,$(CL_X11_dependencies))
 
+CL_GGI_sources=\
+  vid_ggi.c
+
+CL_GGI_dependencies = $(patsubst %,%.d,$(basename $(CL_GGI_sources)))
+CL_GGI_objects = $(patsubst %.d,%.o,$(CL_GGI_dependencies))
+
 CL_GLX_sources=\
   gl_draw.c \
   gl_mesh.c \
@@ -192,7 +201,7 @@ CL_GLX_objects = $(patsubst %.d,%.o,$(CL_GLX_dependencies))
 
 #               *Explicit Rules*
 
-all: $(SV_name) $(CL_SVGA_name) $(CL_X11_name) $(CL_GLX_name)
+all: $(SV_name) $(CL_SVGA_name) $(CL_X11_name) $(CL_GGI_name) $(CL_GLX_name)
 
 $(SV_name): $(COM_objects) $(SV_objects)
 	$(CC) $(LDFLAGS) -o $@ $^ -lm $(SV_libs)
@@ -203,6 +212,9 @@ $(CL_SVGA_name): $(COM_objects) $(CL_objects) $(CL_SW_objects) $(CL_SVGA_objects
 $(CL_X11_name): $(COM_objects) $(CL_objects) $(CL_SW_objects) $(CL_X11_objects)
 	$(CC) $(LDFLAGS) -o $@ $^ -lm $(CL_X11_libs)
 
+$(CL_GGI_name): $(COM_objects) $(CL_objects) $(CL_SW_objects) $(CL_GGI_objects)
+	$(CC) $(LDFLAGS) -o $@ $^ -lm $(CL_GGI_libs)
+
 $(CL_GLX_name): $(COM_objects) $(CL_objects) $(CL_GL_objects) $(CL_GLX_objects)
 	$(CC) $(LDFLAGS) -o $@ $^ -lm $(CL_GLX_libs)
 
diff --git a/source/vid_ggi.c b/source/vid_ggi.c
new file mode 100644
index 0000000..2e231a7
--- /dev/null
+++ b/source/vid_ggi.c
@@ -0,0 +1,968 @@
+/*
+	vid_ggi.c
+
+	general LibGGI video driver
+
+	Copyright (C) 1996-1997  Id Software, Inc.
+	Copyright (C) 1999       Marcus Sundberg [mackan@stacken.kth.se]
+	Copyright (C) 1999,2000  contributors of the QuakeForge project
+	Please see the file "AUTHORS" for a list of contributors
+
+	This program is free software; you can redistribute it and/or
+	modify it under the terms of the GNU General Public License
+	as published by the Free Software Foundation; either version 2
+	of the License, or (at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+	See the GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to:
+
+		Free Software Foundation, Inc.
+		59 Temple Place - Suite 330
+		Boston, MA  02111-1307, USA
+
+	$Id$
+*/
+
+#define _BSD
+
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ggi/ggi.h>
+
+#include "sys.h"
+#include "quakedef.h"
+#include "d_local.h"
+
+extern viddef_t        vid; // global video state
+unsigned short	d_8to16table[256];
+
+cvar_t		*m_filter;
+cvar_t		*_windowed_mouse;
+
+/* Unused */
+int		VGA_width, VGA_height, VGA_rowbytes, VGA_bufferrowbytes, VGA_planar;
+byte	*VGA_pagebase;
+
+#define NUM_STDBUTTONS	3
+#define NUM_BUTTONS	10
+
+static qboolean	mouse_avail;
+static float	mouse_x, mouse_y;
+static float	old_mouse_x, old_mouse_y;
+static int		p_mouse_x, p_mouse_y;
+static float	old_windowed_mouse;
+
+static ggi_visual_t		ggivis = NULL;
+static ggi_mode			mode;
+static const ggi_directbuffer	*dbuf1 = NULL, *dbuf2 = NULL;
+
+static uint8	*drawptr = NULL;
+static void	*frameptr[2] = { NULL, NULL };
+static void	*oneline = NULL;
+static void	*palette = NULL;
+static int	curframe = 0;
+
+static int	realwidth, realheight;
+static int	doublebuffer;
+static int	scale;
+static int	stride, drawstride;
+static int	pixelsize;
+static int	usedbuf, havedbuf;
+
+static long GGI_highhunkmark, GGI_buffersize;
+
+static int	vid_surfcachesize;
+static void	*vid_surfcache;
+
+int	VID_options_items = 1;
+
+static void
+do_scale8(int xsize, int ysize, uint8 *dest, uint8 *src)
+{
+	int i, j, destinc = stride*2-xsize*2;
+	for (j = 0; j < ysize; j++) {
+		for (i = 0; i < xsize; /* i is incremented below */) {
+			register uint32 pix1 = src[i++], pix2 = src[i++];
+#ifdef GGI_LITTLE_ENDIAN
+			*((uint32 *) (dest + stride))
+				= *((uint32 *) dest)
+				= (pix1 | (pix1 << 8)
+				   | (pix2 << 16) | (pix2 << 24));
+#else
+			*((uint32 *) (dest + stride))
+				= *((uint32 *) dest)
+				= (pix2 | (pix2 << 8)
+				   | (pix1 << 16) | (pix1 << 24));
+#endif
+			dest += 4;
+		}
+		dest += destinc;
+		src += xsize;
+	}
+}
+
+static void
+do_scale16(int xsize, int ysize, uint8 *dest, uint8 *src)
+{
+	int i, j, destinc = stride*2-xsize*4;
+	uint16 *palptr = palette;
+	for (j = 0; j < ysize; j++) {
+		for (i = 0; i < xsize; /* i is incremented below */) {
+			register uint32 pixel = palptr[src[i++]];
+			*((uint32 *) (dest + stride))
+				= *((uint32 *) dest)
+				= pixel | (pixel << 16);
+			dest += 4;
+		}
+		dest += destinc;
+		src += xsize;
+	}
+}
+
+static void
+do_scale32(int xsize, int ysize, uint8 *dest, uint8 *src)
+{
+	int i, j, destinc = stride*2-xsize*8;
+	uint32 *palptr = palette;
+	for (j = 0; j < ysize; j++) {
+		for (i = 0; i < xsize; /* i is incremented below */) {
+			register uint32 pixel = palptr[src[i++]];
+			*((uint32 *) (dest + stride))
+				= *((uint32 *) (dest)) = pixel;
+			dest += 4;
+			*((uint32 *) (dest + stride))
+				= *((uint32 *) (dest)) = pixel;
+			dest += 4;
+		}
+		dest += destinc;
+		src += xsize;
+	}
+}
+
+
+static void
+do_copy8(int xsize, int ysize, uint8 *dest, uint8 *src)
+{
+	int i, j;
+	uint8 *palptr = palette;
+
+	for (j = 0; j < ysize; j++) {
+		for (i = 0; i < xsize; i++) {
+			dest[i] = palptr[src[i]];
+		}
+		dest += stride;
+		src += xsize;
+	}
+}
+
+static void
+do_copy16(int xsize, int ysize, void *destptr, uint8 *src)
+{
+	int i, j, destinc = (stride/2 - xsize)/2;
+	uint16 *palptr = palette;
+	uint32 *dest = destptr;
+
+	for (j = 0; j < ysize; j++) {
+		for (i = 0; i < xsize;  /* i is incremented below */) {
+			register uint32 pixel = palptr[src[i++]];
+#ifdef GGI_LITTLE_ENDIAN
+			*(dest++) = pixel | (palptr[src[i++]] << 16);
+#else
+			*(dest++) = (palptr[src[i++]] << 16) | pixel;
+#endif
+		}
+		dest += destinc;
+		src += xsize;
+	}
+}
+
+static void
+do_copy32(int xsize, int ysize, uint32 *dest, uint8 *src)
+{
+	int i, j, destinc = stride/4;
+	uint32 *palptr = palette;
+
+	for (j = 0; j < ysize; j++) {
+		for (i = 0; i < xsize; i++) {
+			dest[i] = palptr[src[i]];
+		}
+		dest += destinc;
+		src += xsize;
+	}
+}
+
+
+// ========================================================================
+// Tragic death handler
+// ========================================================================
+
+void ResetFrameBuffer(void)
+{
+	if (d_pzbuffer)
+	{
+		D_FlushCaches ();
+		Hunk_FreeToHighMark (GGI_highhunkmark);
+		d_pzbuffer = NULL;
+	}
+	GGI_highhunkmark = Hunk_HighMark ();
+
+// alloc an extra line in case we want to wrap, and allocate the z-buffer
+	GGI_buffersize = vid.width * vid.height * sizeof (*d_pzbuffer);
+
+	vid_surfcachesize = D_SurfaceCacheForRes (vid.width, vid.height);
+
+	GGI_buffersize += vid_surfcachesize;
+
+	d_pzbuffer = Hunk_HighAllocName (GGI_buffersize, "video");
+	if (d_pzbuffer == NULL)
+		Sys_Error ("Not enough memory for video mode\n");
+
+	vid_surfcache = (byte *) d_pzbuffer
+		+ vid.width * vid.height * sizeof (*d_pzbuffer);
+
+	D_InitCaches(vid_surfcache, vid_surfcachesize);
+}
+
+
+
+// Called at startup to set up translation tables, takes 256 8 bit RGB values
+// the palette data will go away after the call, so it must be copied off if
+// the video driver will need it again
+
+void	VID_Init(unsigned char *pal)
+{
+	int pnum;
+
+	vid.width = GGI_AUTO;
+	vid.height = GGI_AUTO;
+
+	srandom(getpid());
+
+	if (ggiInit() < 0) {
+		Sys_Error("VID: Unable to init LibGGI\n");
+	}
+	ggivis = ggiOpen(NULL);
+	if (!ggivis) {
+		Sys_Error("VID: Unable to open default visual\n");
+	}
+
+	/* Go into async mode */
+	ggiSetFlags(ggivis, GGIFLAG_ASYNC);
+
+	/* check for command-line window size */
+	if ((pnum=COM_CheckParm("-winsize")))
+	{
+		if (pnum >= com_argc-2)
+			Sys_Error("VID: -winsize <width> <height>\n");
+		vid.width = Q_atoi(com_argv[pnum+1]);
+		vid.height = Q_atoi(com_argv[pnum+2]);
+		if (!vid.width || !vid.height)
+			Sys_Error("VID: Bad window width/height\n");
+	}
+	if ((pnum=COM_CheckParm("-width"))) {
+		if (pnum >= com_argc-1)
+			Sys_Error("VID: -width <width>\n");
+		vid.width = Q_atoi(com_argv[pnum+1]);
+		if (!vid.width)
+			Sys_Error("VID: Bad window width\n");
+	}
+	if ((pnum=COM_CheckParm("-height"))) {
+		if (pnum >= com_argc-1)
+			Sys_Error("VID: -height <height>\n");
+		vid.height = Q_atoi(com_argv[pnum+1]);
+		if (!vid.height)
+			Sys_Error("VID: Bad window height\n");
+	}
+
+	scale = COM_CheckParm("-scale");
+
+	/* specify a LibGGI mode */
+	if ((pnum=COM_CheckParm("-ggimode")))
+	{
+		if (pnum >= com_argc-1)
+			Sys_Error("VID: -ggimode <mode>\n");
+		ggiParseMode(com_argv[pnum+1], &mode);
+	} else {
+		/* This will give the default mode */
+		ggiParseMode("", &mode);
+		/* Now put in any parameters given above */
+		mode.visible.x = vid.width;
+		mode.visible.y = vid.height;
+	}
+
+	if (scale) {
+		mode.visible.x *= 2;
+		mode.visible.y *= 2;
+	}
+
+	/* We prefer 8 bit mode unless otherwise specified */
+	if (mode.graphtype == GT_AUTO) mode.graphtype = GT_8BIT;
+
+	/* We want double buffering if possible */
+	if (mode.frames == GGI_AUTO) {
+		ggi_mode tmpmode = mode;
+
+		tmpmode.frames = 2;
+		if (ggiCheckMode(ggivis, &tmpmode) == 0) {
+			mode = tmpmode;
+		} else {
+			tmpmode.frames = 2;
+			if (ggiCheckMode(ggivis, &tmpmode) == 0) {
+				mode = tmpmode;
+			}
+		}
+	}
+
+	if (ggiSetMode(ggivis, &mode) != 0) {
+		/* Try again with suggested mode */
+		if (ggiSetMode(ggivis, &mode) != 0) {
+			Sys_Error("VID: LibGGI can't set any modes!\n");
+		}
+	}
+
+	/* Pixel size must be 1, 2 or 4 bytes */
+	if (GT_SIZE(mode.graphtype) != 8 &&
+	    GT_SIZE(mode.graphtype) != 16 &&
+	    GT_SIZE(mode.graphtype) != 32) {
+		if (GT_SIZE(mode.graphtype) == 24) {
+			Sys_Error("VID: 24 bits per pixel not supported - try using the palemu target.\n");
+		} else {
+			Sys_Error("VID: %d bits per pixel not supported by GGI Quake.\n",
+				  GT_SIZE(mode.graphtype));
+		}
+	}
+
+	realwidth  = mode.visible.x;
+	realheight = mode.visible.y;
+	if (scale) {
+		vid.width  = realwidth / 2;
+		vid.height = realheight / 2;
+	} else {
+		vid.width  = realwidth;
+		vid.height = realheight;
+	}
+
+	if (mode.frames >= 2) doublebuffer = 1;
+	else doublebuffer = 0;
+
+	pixelsize = (GT_SIZE(mode.graphtype)+7) / 8;
+	if (mode.graphtype != GT_8BIT) {
+		if ((palette = malloc(pixelsize*256)) == NULL) {
+			Sys_Error("VID: Unable to allocate palette table\n");
+		}
+	}
+
+	VID_SetPalette(pal);
+
+	usedbuf = havedbuf = 0;
+	drawstride = vid.width;
+	stride = realwidth*pixelsize;
+	if ((dbuf1 = ggiDBGetBuffer(ggivis, 0)) != NULL &&
+	    (dbuf1->type & GGI_DB_SIMPLE_PLB)) {
+		havedbuf = 1;
+		stride = dbuf1->buffer.plb.stride;
+		if (doublebuffer) {
+			if  ((dbuf2 = ggiDBGetBuffer(ggivis, 1)) == NULL ||
+			     !(dbuf2->type & GGI_DB_SIMPLE_PLB)) {
+				/* Only one DB? No double buffering then */
+				doublebuffer = 0;
+			}
+		}
+		if (doublebuffer) {
+			fprintf(stderr,	"VID: Got two DirectBuffers\n");
+		} else {
+			fprintf(stderr,	"VID: Got one DirectBuffer\n");
+		}
+		if (doublebuffer && !scale && !palette) {
+			usedbuf = 1;
+			drawstride = stride;
+			frameptr[0] = dbuf1->write;
+			if (doublebuffer) {
+				frameptr[1] = dbuf2->write;
+			} else {
+				frameptr[1] = frameptr[0];
+			}
+			drawptr = frameptr[0];
+			fprintf(stderr,	"VID: Drawing into DirectBuffer\n");
+		}
+	}
+
+	if (!usedbuf) {
+		if ((drawptr = malloc(vid.width * vid.height)) == NULL) {
+			Sys_Error("VID: Unable to allocate draw buffer\n");
+		}
+		if (!havedbuf && (scale || palette)) {
+			int linesize = pixelsize*realwidth;
+			if (scale) linesize *= 4;
+			if ((oneline = malloc(linesize)) == NULL) {
+				Sys_Error("VID: Unable to allocate line buffer\n");
+			}
+		}
+		fprintf(stderr,
+			"VID: Drawing into offscreen memory\n");
+	}
+
+	ResetFrameBuffer();
+
+	curframe = 0;
+	vid.maxwarpwidth = WARP_WIDTH;
+	vid.maxwarpheight = WARP_HEIGHT;
+	vid.numpages = doublebuffer ? 2 : 1;
+	vid.colormap = host_colormap;
+	vid.buffer = drawptr;
+	vid.rowbytes = drawstride;
+	vid.direct = drawptr;
+	vid.conbuffer = vid.buffer;
+	vid.conrowbytes = vid.rowbytes;
+	vid.conwidth = vid.width;
+	vid.conheight = vid.height;
+	vid.aspect = ((float)vid.height / (float)vid.width) * (320.0 / 240.0);
+	vid.fullbright = 256 - LittleLong (*((int *)vid.colormap + 2048));
+}
+
+void VID_ShiftPalette(unsigned char *pal)
+{
+	VID_SetPalette(pal);
+}
+
+
+
+void VID_SetPalette(unsigned char *pal)
+{
+
+	int i;
+	ggi_color colors[256];
+
+	for (i=0 ; i<256 ; i++) {
+		colors[i].r = pal[i*3] * 257;
+		colors[i].g = pal[i*3+1] * 257;
+		colors[i].b = pal[i*3+2] * 257;
+	}
+	if (palette) {
+		ggiPackColors(ggivis, palette, colors, 256);
+	} else {
+		ggiSetPalette(ggivis, 0, 256, colors);
+	}
+}
+
+// Called at shutdown
+
+void	VID_Shutdown (void)
+{
+	Con_Printf("VID_Shutdown\n");
+
+	if (!usedbuf) {
+		free(drawptr);
+		drawptr = NULL;
+	}
+	if (oneline) {
+		free(oneline);
+		oneline = NULL;
+	}
+	if (palette) {
+		free(palette);
+		palette = NULL;
+	}
+	if (ggivis) {
+		ggiClose(ggivis);
+		ggivis = NULL;
+	}
+	ggiExit();
+}
+
+
+// flushes the given rectangles from the view buffer to the screen
+
+void	VID_Update(vrect_t *rects)
+{
+	int height = 0;
+
+#if 0
+// if the window changes dimension, skip this frame
+
+	if (config_notify)
+	{
+		fprintf(stderr, "config notify\n");
+		config_notify = 0;
+		vid.width = config_notify_width & ~7;
+		vid.height = config_notify_height;
+		if (doShm)
+			ResetSharedFrameBuffers();
+		else
+			ResetFrameBuffer();
+		vid.rowbytes = x_framebuffer[0]->bytes_per_line;
+		vid.buffer = x_framebuffer[curframe]->data;
+		vid.conbuffer = vid.buffer;
+		vid.conwidth = vid.width;
+		vid.conheight = vid.height;
+		vid.conrowbytes = vid.rowbytes;
+		vid.recalc_refdef = 1;				// force a surface cache flush
+		Con_CheckResize();
+		Con_Clear_f();
+		return;
+	}
+
+	// force full update if not 8bit
+	if (x_visinfo->depth != 8) {
+		extern int scr_fullupdate;
+
+		scr_fullupdate = 0;
+	}
+#endif
+
+	while (rects) {
+		int y = rects->y + rects->height;
+		if (y > height) height = y;
+		rects = rects->pnext;
+	}
+
+	if (!usedbuf) {
+		int	i;
+
+		if (havedbuf) {
+			if (ggiResourceAcquire(dbuf1->resource,
+					       GGI_ACTYPE_WRITE) != 0 ||
+			    (doublebuffer ?
+			     ggiResourceAcquire(dbuf2->resource,
+						GGI_ACTYPE_WRITE) != 0
+			     : 0)) {
+				ggiPanic("Unable to acquire DirectBuffer!\n");
+			}
+			/* ->write is allowed to change at acquire time */
+			frameptr[0] = dbuf1->write;
+			if (doublebuffer) {
+				frameptr[1] = dbuf2->write;
+			} else {
+				frameptr[1] = frameptr[0];
+			}
+		}
+		if (scale) {
+			switch (pixelsize) {
+			case 1:	if (havedbuf) {
+				do_scale8(vid.width, height,
+					  frameptr[curframe], drawptr);
+			} else {
+				uint8 *buf = drawptr;
+				for (i=0; i < height; i++) {
+					do_scale8(vid.width, 1, oneline,buf);
+					ggiPutBox(ggivis, 0, i*2, realwidth,
+						  2, oneline);
+					buf += vid.width;
+				}
+			}
+			break;
+			case 2: if (havedbuf) {
+				do_scale16(vid.width, height,
+					   frameptr[curframe], drawptr);
+			} else {
+				uint8 *buf = drawptr;
+				for (i=0; i < height; i++) {
+					do_scale16(vid.width, 1,
+						   oneline, buf);
+					ggiPutBox(ggivis, 0, i*2, realwidth,
+						  2, oneline);
+					buf += vid.width;
+				}
+			}
+			break;
+			case 4: if (havedbuf) {
+				do_scale32(vid.width, height,
+					   frameptr[curframe], drawptr);
+			} else {
+				uint8 *buf = drawptr;
+				for (i=0; i < height; i++) {
+					do_scale32(vid.width, 1,
+						   oneline, buf);
+					ggiPutBox(ggivis, 0, i*2, realwidth,
+						  2, oneline);
+					buf += vid.width;
+				}
+			}
+			break;
+			}
+		} else if (palette) {
+			switch (pixelsize) {
+			case 1:	if (havedbuf) {
+				do_copy8(vid.width, height,
+					 frameptr[curframe], drawptr);
+			} else {
+				uint8 *buf = drawptr;
+				for (i=0; i < height; i++) {
+					do_copy8(vid.width, 1, oneline,buf);
+					ggiPutBox(ggivis, 0, i, realwidth,
+						  1, oneline);
+					buf += vid.width;
+				}
+			}
+			break;
+			case 2: if (havedbuf) {
+				do_copy16(vid.width, height,
+					  frameptr[curframe], drawptr);
+			} else {
+				uint8 *buf = drawptr;
+				for (i=0; i < height; i++) {
+					do_copy16(vid.width, 1,
+						  oneline, buf);
+					ggiPutBox(ggivis, 0, i, realwidth,
+						  1, oneline);
+					buf += vid.width;
+				}
+			}
+			break;
+			case 4: if (havedbuf) {
+				do_copy32(vid.width, height,
+					  frameptr[curframe], drawptr);
+			} else {
+				uint8 *buf = drawptr;
+				for (i=0; i < height; i++) {
+					do_copy32(vid.width, 1,
+						  oneline, buf);
+					ggiPutBox(ggivis, 0, i, realwidth,
+						  1, oneline);
+					buf += vid.width;
+				}
+			}
+			break;
+			}
+		} else {
+			ggiPutBox(ggivis, 0, 0, vid.width, height,
+				  drawptr);
+		}
+		if (havedbuf) {
+			ggiResourceRelease(dbuf1->resource);
+			if (doublebuffer) {
+				ggiResourceRelease(dbuf2->resource);
+			}
+		}
+
+	}
+
+	if (doublebuffer) {
+		ggiSetDisplayFrame(ggivis, curframe);
+		curframe = !curframe;
+		if (usedbuf) {
+			vid.buffer = vid.conbuffer = vid.direct
+				= drawptr = frameptr[curframe];
+		}
+		ggiSetWriteFrame(ggivis, curframe);
+	}
+
+#if 0
+	if (GT_SIZE(mode.graphtype) == 16) {
+		do_copy16(vid.width, height,
+			  (uint16*)frameptr, drawptr);
+	} else if (GT_SIZE(mode.graphtype) == 32) {
+		do_copy32(vid.width, height,
+			  (uint32*)frameptr, drawptr);
+	}
+#endif
+
+	ggiFlush(ggivis);
+}
+
+void D_BeginDirectRect(int x, int y, byte *pbitmap, int width, int height)
+{
+// direct drawing of the "accessing disk" icon isn't supported under Linux
+}
+
+void D_EndDirectRect (int x, int y, int width, int height)
+{
+// direct drawing of the "accessing disk" icon isn't supported under Linux
+}
+
+
+/*
+***************************************************************************
+  Input handling
+***************************************************************************
+*/
+
+static int XLateKey(ggi_key_event *ev)
+{
+	int key = 0;
+
+	if (GII_KTYP(ev->label) == GII_KT_DEAD) {
+		ev->label = GII_KVAL(ev->label);
+	}
+	switch(ev->label) {
+	case GIIK_P9:		key = KP_PGUP; break;
+	case GIIK_PageUp:	key = K_PGUP; break;
+
+	case GIIK_P3:		key = KP_PGDN; break;
+	case GIIK_PageDown:	key = K_PGDN; break;
+
+	case GIIK_P7:		key = KP_HOME; break;
+	case GIIK_Home:		key = K_HOME; break;
+
+	case GIIK_P1:		key = KP_END; break;
+	case GIIK_End:		key = K_END; break;
+
+	case GIIK_P4:		key = KP_LEFTARROW; break;
+	case GIIK_Left:		key = K_LEFTARROW; break;
+
+	case GIIK_P6:		key = KP_RIGHTARROW; break;
+	case GIIK_Right:	key = K_RIGHTARROW; break;
+
+	case GIIK_P2:		key = KP_DOWNARROW; break;
+	case GIIK_Down:		key = K_DOWNARROW; break;
+
+	case GIIK_P8:		key = KP_UPARROW; break;
+	case GIIK_Up:		key = K_UPARROW; break;
+
+	case GIIK_P5:		key = KP_5; break;
+	case GIIK_PBegin:	key = K_AUX32; break;
+
+	case GIIK_P0:		key = KP_INS; break;
+	case GIIK_Insert:	key = K_INS; break;
+
+	case GIIK_PSeparator:
+	case GIIK_PDecimal:	key = KP_DEL; break;
+	case GIIUC_Delete:	key = K_DEL; break;
+
+	case GIIK_PStar:	key = KP_MULTIPLY; break;
+	case GIIK_PPlus:	key = KP_PLUS; break;
+	case GIIK_PMinus:	key = KP_MINUS; break;
+	case GIIK_PSlash:	key = KP_DIVIDE; break;
+
+	case GIIK_PEnter:	key = KP_ENTER; break;
+	case GIIUC_Return:	key = K_ENTER; break;
+
+	case GIIUC_Escape:	key = K_ESCAPE;	break;
+
+	case GIIUC_Tab:		key = K_TAB; break;
+
+	case GIIK_F1:		key = K_F1; break;
+	case GIIK_F2:		key = K_F2; break;
+	case GIIK_F3:		key = K_F3; break;
+	case GIIK_F4:		key = K_F4; break;
+	case GIIK_F5:		key = K_F5; break;
+	case GIIK_F6:		key = K_F6; break;
+	case GIIK_F7:		key = K_F7; break;
+	case GIIK_F8:		key = K_F8; break;
+	case GIIK_F9:		key = K_F9; break;
+	case GIIK_F10:		key = K_F10; break;
+	case GIIK_F11:		key = K_F11; break;
+	case GIIK_F12:		key = K_F12; break;
+
+	case GIIUC_BackSpace:	key = K_BACKSPACE; break;
+
+	case GIIK_ShiftL:
+	case GIIK_ShiftR:		key = K_SHIFT; break;
+
+	case GIIK_Execute:
+	case GIIK_CtrlL:
+	case GIIK_CtrlR:		key = K_CTRL; break;
+
+	case GIIK_AltL:
+	case GIIK_MetaL:
+	case GIIK_AltR:
+	case GIIK_MetaR:
+	case GIIK_AltGr:
+	case GIIK_ModeSwitch:	key = K_ALT; break;
+
+	case GIIK_Caps:			key = K_CAPSLOCK; break;
+	case GIIK_PrintScreen:	key = K_PRNTSCR; break;
+	case GIIK_ScrollLock:	key = K_SCRLCK; break;
+	case GIIK_Pause:		key = K_PAUSE; break;
+	case GIIK_NumLock:		key = KP_NUMLCK; break;
+
+	case GIIUC_Comma: case GIIUC_Minus: case GIIUC_Period:
+		key = ev->label;
+		break;
+	case GIIUC_Section:	key = '~'; break;
+
+	default:
+		if (ev->label >= 0 && ev->label <= 9) return ev->label;
+		if (ev->label >= 'A' && ev->label <= 'Z') {
+			return ev->label - 'A' + 'a';
+		}
+		if (ev->label >= 'a' && ev->label <= 'z') return ev->label;
+
+		if (ev->sym <= 0x7f) {
+			key = ev->sym;
+			if (key >= 'A' && key <= 'Z') {
+				key = key - 'A' + 'a';
+			}
+			return key;
+		}
+		if (ev->label <= 0x7f) {
+			return ev->label;
+		}
+		break;
+	}
+
+	return key;
+}
+
+static void GetEvent(void)
+{
+	ggi_event ev;
+	uint32 b;
+
+	ggiEventRead(ggivis, &ev, emAll);
+	switch(ev.any.type) {
+	case evKeyPress:
+		Key_Event(XLateKey(&ev.key), true);
+		break;
+
+	case evKeyRelease:
+		Key_Event(XLateKey(&ev.key), false);
+		break;
+
+	case evPtrRelative:
+		mouse_x += (float) ev.pmove.x;
+		mouse_y += (float) ev.pmove.y;
+		break;
+
+	case evPtrAbsolute:
+		mouse_x += (float) (ev.pmove.x-p_mouse_x);
+		mouse_y += (float) (ev.pmove.y-p_mouse_y);
+		p_mouse_x = ev.pmove.x;
+		p_mouse_y = ev.pmove.y;
+		break;
+
+	case evPtrButtonPress:
+		if (!mouse_avail) return;
+
+		b = ev.pbutton.button - 1;
+
+		if (b < NUM_STDBUTTONS) {
+			Key_Event(K_MOUSE1 + b, true);
+		} else if (b < NUM_BUTTONS) {
+			Key_Event(K_AUX32 - NUM_BUTTONS + b, true);
+		}
+		break;
+
+	case evPtrButtonRelease:
+		if (!mouse_avail) return;
+
+		b = ev.pbutton.button - 1;
+
+		if (b < NUM_STDBUTTONS) {
+			Key_Event(K_MOUSE1 + b, false);
+		} else if (b < NUM_BUTTONS) {
+			Key_Event(K_AUX32 - NUM_BUTTONS + b, false);
+		}
+		break;
+
+#if 0
+	case ConfigureNotify:
+//printf("config notify\n");
+		config_notify_width = ev.xconfigure.width;
+		config_notify_height = ev.xconfigure.height;
+		config_notify = 1;
+		break;
+
+	default:
+#endif
+	}
+}
+
+
+void Sys_SendKeyEvents(void)
+{
+	/* Get events from LibGGI */
+	if (ggivis) {
+		struct timeval t = {0,0};
+
+		if (ggiEventPoll(ggivis, emAll, &t)) {
+			int i = ggiEventsQueued(ggivis, emAll);
+			while (i--) GetEvent();
+		}
+	}
+}
+
+
+void
+IN_Init(void)
+{
+	_windowed_mouse = Cvar_Get("_windowed_mouse", "0", CVAR_ARCHIVE, "None");
+	old_windowed_mouse = -1; /* Force update */
+	m_filter = Cvar_Get("m_filter", "0", CVAR_ARCHIVE, "None");
+	if (COM_CheckParm ("-nomouse")) return;
+
+	mouse_x = mouse_y = 0.0;
+	mouse_avail = 1;
+}
+
+
+void
+IN_Shutdown(void)
+{
+	mouse_avail = 0;
+}
+
+
+void
+IN_Commands (void)
+{
+	/* Only supported by LibGII 0.7 or later. */
+#ifdef GII_CMDCODE_PREFER_RELPTR
+	if (old_windowed_mouse != _windowed_mouse->value) {
+		gii_event ev;
+
+		old_windowed_mouse = _windowed_mouse->value;
+
+		ev.cmd.size = sizeof(gii_cmd_nodata_event);
+		ev.cmd.type = evCommand;
+		ev.cmd.target = GII_EV_TARGET_ALL;
+		ev.cmd.code = (int)_windowed_mouse->value ? GII_CMDCODE_PREFER_RELPTR
+			: GII_CMDCODE_PREFER_ABSPTR;
+
+		ggiEventSend(ggivis, &ev);
+	}
+#endif
+}
+
+
+void
+IN_Move(usercmd_t *cmd)
+{
+	if (!mouse_avail) return;
+
+	if (m_filter->value) {
+		mouse_x = (mouse_x + old_mouse_x) * 0.5;
+		mouse_y = (mouse_y + old_mouse_y) * 0.5;
+	}
+
+	old_mouse_x = mouse_x;
+	old_mouse_y = mouse_y;
+
+	mouse_x *= sensitivity->value;
+	mouse_y *= sensitivity->value;
+
+	if ( (in_strafe.state & 1) || (lookstrafe->value && (in_mlook.state & 1) ))
+		cmd->sidemove += m_side->value * mouse_x;
+	else
+		cl.viewangles[YAW] -= m_yaw->value * mouse_x;
+	if (in_mlook.state & 1)
+		V_StopPitchDrift ();
+
+	if ( (in_mlook.state & 1) && !(in_strafe.state & 1)) {
+		cl.viewangles[PITCH] += m_pitch->value * mouse_y;
+		if (cl.viewangles[PITCH] > 80)
+			cl.viewangles[PITCH] = 80;
+		if (cl.viewangles[PITCH] < -70)
+			cl.viewangles[PITCH] = -70;
+	} else {
+		if ((in_strafe.state & 1) && noclip_anglehack)
+			cmd->upmove -= m_forward->value * mouse_y;
+		else
+			cmd->forwardmove -= m_forward->value * mouse_y;
+	}
+	mouse_x = mouse_y = 0.0;
+}
+
+
+void VID_InitCvars(void)	{}
+void VID_LockBuffer(void)	{}
+void VID_UnlockBuffer(void) {}