/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2006 Robert Beckebans <trebor_7@users.sourceforge.net>

This file is part of XreaL source code.

XreaL source code 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.

XreaL source code 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 XreaL source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
/*
** GLW_IMP.C
**
** This file contains ALL Linux specific stuff having to do with the
** OpenGL refresh.  When a port is being made the following functions
** must be implemented by the port:
**
** GLW_EndFrame
** GLW_Init
** GLW_Shutdown
** GLW_SwitchFullscreen
** GLW_SetGamma
**
*/

#include <termios.h>
#include <sys/ioctl.h>
#ifdef __linux__
#include <sys/stat.h>
#include <sys/vt.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <semaphore.h>

// bk001204
#include <dlfcn.h>

// bk001206 - from my Heretic2 by way of Ryan's Fakk2
// Needed for the new X11_PendingInput() function.
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#include "../renderer/r_local.h"
#include "../client/client.h"
#include "linux_local.h"		// bk001130

#include "unix_glw.h"

#include <GL/glx.h>

#include <X11/keysym.h>
#include <X11/cursorfont.h>

#include <X11/extensions/xf86dga.h>
#include <X11/extensions/xf86vmode.h>

#define	WINDOW_CLASS_NAME	"XreaL"

typedef enum
{
	RSERR_OK,

	RSERR_INVALID_FULLSCREEN,
	RSERR_INVALID_MODE,

	RSERR_UNKNOWN
} rserr_t;

glwstate_t      glw_state;

static Display *dpy = NULL;
static int      scrnum;
static Window   win = 0;
static GLXContext ctx = NULL;

// bk001206 - not needed anymore
// static qboolean autorepeaton = qtrue;

#define KEY_MASK (KeyPressMask | KeyReleaseMask)
#define MOUSE_MASK (ButtonPressMask | ButtonReleaseMask | \
		    PointerMotionMask | ButtonMotionMask )
#define X_MASK (KEY_MASK | MOUSE_MASK | VisibilityChangeMask | StructureNotifyMask )

static qboolean mouse_avail;
static qboolean mouse_active = qfalse;
static int      mwx, mwy;
static int      mx = 0, my = 0;

// Time mouse was reset, we ignore the first 50ms of the mouse to allow settling of events
static int      mouseResetTime = 0;

#define MOUSE_RESET_DELAY 50

static cvar_t  *in_mouse;
static cvar_t  *in_dgamouse;	// user pref for dga mouse
cvar_t         *in_subframe;
cvar_t         *in_nograb;		// this is strictly for developers

// bk001130 - from cvs1.17 (mkv), but not static
cvar_t         *in_joystick = NULL;
cvar_t         *in_joystickDebug = NULL;
cvar_t         *joy_threshold = NULL;

cvar_t         *r_allowSoftwareGL;	// don't abort out if the pixelformat claims software
cvar_t         *r_previousglDriver;

qboolean        vidmode_ext = qfalse;
static int      vidmode_MajorVersion = 0, vidmode_MinorVersion = 0;	// major and minor of XF86VidExtensions

// gamma value of the X display before we start playing with it
static XF86VidModeGamma vidmode_InitialGamma;

static int      win_x, win_y;

static XF86VidModeModeInfo **vidmodes;

//static int default_dotclock_vidmode; // bk001204 - unused
static int      num_vidmodes;
static qboolean vidmode_active = qfalse;

static int      mouse_accel_numerator;
static int      mouse_accel_denominator;
static int      mouse_threshold;

/*****************************************************************************
** KEYBOARD
** NOTE TTimo the keyboard handling is done with KeySyms
**   that means relying on the keyboard mapping provided by X
**   in-game it would probably be better to use KeyCode (i.e. hardware key codes)
**   you would still need the KeySyms in some cases, such as for the console and all entry textboxes
**     (cause there's nothing worse than a qwerty mapping on a french keyboard)
**
** you can turn on some debugging and verbose of the keyboard code with #define KBD_DBG
******************************************************************************/

//#define KBD_DBG

static char    *XLateKey(XKeyEvent * ev, int *key)
{
	static char     buf[64];
	KeySym          keysym;
	int             XLookupRet;

	*key = 0;

	XLookupRet = XLookupString(ev, buf, sizeof buf, &keysym, 0);
#ifdef KBD_DBG
	ri.Printf(PRINT_ALL, "XLookupString ret: %d buf: %s keysym: %x\n", XLookupRet, buf, keysym);
#endif

	switch (keysym)
	{
		case XK_KP_Page_Up:
		case XK_KP_9:
			*key = K_KP_PGUP;
			break;
		case XK_Page_Up:
			*key = K_PGUP;
			break;

		case XK_KP_Page_Down:
		case XK_KP_3:
			*key = K_KP_PGDN;
			break;
		case XK_Page_Down:
			*key = K_PGDN;
			break;

		case XK_KP_Home:
			*key = K_KP_HOME;
			break;
		case XK_KP_7:
			*key = K_KP_HOME;
			break;
		case XK_Home:
			*key = K_HOME;
			break;

		case XK_KP_End:
		case XK_KP_1:
			*key = K_KP_END;
			break;
		case XK_End:
			*key = K_END;
			break;

		case XK_KP_Left:
			*key = K_KP_LEFTARROW;
			break;
		case XK_KP_4:
			*key = K_KP_LEFTARROW;
			break;
		case XK_Left:
			*key = K_LEFTARROW;
			break;

		case XK_KP_Right:
			*key = K_KP_RIGHTARROW;
			break;
		case XK_KP_6:
			*key = K_KP_RIGHTARROW;
			break;
		case XK_Right:
			*key = K_RIGHTARROW;
			break;

		case XK_KP_Down:
		case XK_KP_2:
			*key = K_KP_DOWNARROW;
			break;
		case XK_Down:
			*key = K_DOWNARROW;
			break;

		case XK_KP_Up:
		case XK_KP_8:
			*key = K_KP_UPARROW;
			break;
		case XK_Up:
			*key = K_UPARROW;
			break;

		case XK_Escape:
			*key = K_ESCAPE;
			break;

		case XK_KP_Enter:
			*key = K_KP_ENTER;
			break;
		case XK_Return:
			*key = K_ENTER;
			break;

		case XK_Tab:
			*key = K_TAB;
			break;

		case XK_F1:
			*key = K_F1;
			break;

		case XK_F2:
			*key = K_F2;
			break;

		case XK_F3:
			*key = K_F3;
			break;

		case XK_F4:
			*key = K_F4;
			break;

		case XK_F5:
			*key = K_F5;
			break;

		case XK_F6:
			*key = K_F6;
			break;

		case XK_F7:
			*key = K_F7;
			break;

		case XK_F8:
			*key = K_F8;
			break;

		case XK_F9:
			*key = K_F9;
			break;

		case XK_F10:
			*key = K_F10;
			break;

		case XK_F11:
			*key = K_F11;
			break;

		case XK_F12:
			*key = K_F12;
			break;

			// bk001206 - from Ryan's Fakk2 
			//case XK_BackSpace: *key = 8; break; // ctrl-h
		case XK_BackSpace:
			*key = K_BACKSPACE;
			break;				// ctrl-h

		case XK_KP_Delete:
		case XK_KP_Decimal:
			*key = K_KP_DEL;
			break;
		case XK_Delete:
			*key = K_DEL;
			break;

		case XK_Pause:
			*key = K_PAUSE;
			break;

		case XK_Shift_L:
		case XK_Shift_R:
			*key = K_SHIFT;
			break;

		case XK_Execute:
		case XK_Control_L:
		case XK_Control_R:
			*key = K_CTRL;
			break;

		case XK_Alt_L:
		case XK_Meta_L:
		case XK_Alt_R:
		case XK_Meta_R:
			*key = K_ALT;
			break;

		case XK_KP_Begin:
			*key = K_KP_5;
			break;

		case XK_Insert:
			*key = K_INS;
			break;
		case XK_KP_Insert:
		case XK_KP_0:
			*key = K_KP_INS;
			break;

		case XK_KP_Multiply:
			*key = '*';
			break;
		case XK_KP_Add:
			*key = K_KP_PLUS;
			break;
		case XK_KP_Subtract:
			*key = K_KP_MINUS;
			break;
		case XK_KP_Divide:
			*key = K_KP_SLASH;
			break;

			// bk001130 - from cvs1.17 (mkv)
		case XK_exclam:
			*key = '1';
			break;
		case XK_at:
			*key = '2';
			break;
		case XK_numbersign:
			*key = '3';
			break;
		case XK_dollar:
			*key = '4';
			break;
		case XK_percent:
			*key = '5';
			break;
		case XK_asciicircum:
			*key = '6';
			break;
		case XK_ampersand:
			*key = '7';
			break;
		case XK_asterisk:
			*key = '8';
			break;
		case XK_parenleft:
			*key = '9';
			break;
		case XK_parenright:
			*key = '0';
			break;

			// weird french keyboards ..
			// NOTE: console toggle is hardcoded in cl_keys.c, can't be unbound
			//   cleaner would be .. using hardware key codes instead of the key syms
			//   could also add a new K_KP_CONSOLE
		case XK_twosuperior:
			*key = '~';
			break;

			// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=472
		case XK_space:
		case XK_KP_Space:
			*key = K_SPACE;
			break;

		default:
			if(XLookupRet == 0)
			{
				if(com_developer->value)
				{
					ri.Printf(PRINT_ALL, "Warning: XLookupString failed on KeySym %d\n", keysym);
				}
				return NULL;
			}
			else
			{
				// XK_* tests failed, but XLookupString got a buffer, so let's try it
				*key = *(unsigned char *)buf;
				if(*key >= 'A' && *key <= 'Z')
					*key = *key - 'A' + 'a';
				// if ctrl is pressed, the keys are not between 'A' and 'Z', for instance ctrl-z == 26 ^Z ^C etc.
				// see https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=19
				else if(*key >= 1 && *key <= 26)
					*key = *key + 'a' - 1;
			}
			break;
	}

	return buf;
}

// ========================================================================
// makes a null cursor
// ========================================================================

static Cursor CreateNullCursor(Display * display, Window root)
{
	Pixmap          cursormask;
	XGCValues       xgc;
	GC              gc;
	XColor          dummycolour;
	Cursor          cursor;

	cursormask = XCreatePixmap(display, root, 1, 1, 1 /*depth */ );
	xgc.function = GXclear;
	gc = XCreateGC(display, cursormask, GCFunction, &xgc);
	XFillRectangle(display, cursormask, gc, 0, 0, 1, 1);
	dummycolour.pixel = 0;
	dummycolour.red = 0;
	dummycolour.flags = 04;
	cursor = XCreatePixmapCursor(display, cursormask, cursormask, &dummycolour, &dummycolour, 0, 0);
	XFreePixmap(display, cursormask);
	XFreeGC(display, gc);
	return cursor;
}

static void install_grabs(void)
{
	// inviso cursor
	XWarpPointer(dpy, None, win, 0, 0, 0, 0, glConfig.vidWidth / 2, glConfig.vidHeight / 2);
	XSync(dpy, False);

	XDefineCursor(dpy, win, CreateNullCursor(dpy, win));

	XGrabPointer(dpy, win,		// bk010108 - do this earlier?
				 False, MOUSE_MASK, GrabModeAsync, GrabModeAsync, win, None, CurrentTime);

	XGetPointerControl(dpy, &mouse_accel_numerator, &mouse_accel_denominator, &mouse_threshold);

	XChangePointerControl(dpy, True, True, 1, 1, 0);

	XSync(dpy, False);

	mouseResetTime = Sys_Milliseconds();

	if(in_dgamouse->value)
	{
		int             MajorVersion, MinorVersion;

		if(!XF86DGAQueryVersion(dpy, &MajorVersion, &MinorVersion))
		{
			// unable to query, probalby not supported, force the setting to 0
			ri.Printf(PRINT_ALL, "Failed to detect XF86DGA Mouse\n");
			ri.Cvar_Set("in_dgamouse", "0");
		}
		else
		{
			XF86DGADirectVideo(dpy, DefaultScreen(dpy), XF86DGADirectMouse);
			XWarpPointer(dpy, None, win, 0, 0, 0, 0, 0, 0);
		}
	}
	else
	{
		mwx = glConfig.vidWidth / 2;
		mwy = glConfig.vidHeight / 2;
		mx = my = 0;
	}

	XGrabKeyboard(dpy, win, False, GrabModeAsync, GrabModeAsync, CurrentTime);

	XSync(dpy, False);
}

static void uninstall_grabs(void)
{
	if(in_dgamouse->value)
	{
		if(com_developer->value)
			ri.Printf(PRINT_ALL, "DGA Mouse - Disabling DGA DirectVideo\n");
		XF86DGADirectVideo(dpy, DefaultScreen(dpy), 0);
	}

	XChangePointerControl(dpy, qtrue, qtrue, mouse_accel_numerator, mouse_accel_denominator, mouse_threshold);

	XUngrabPointer(dpy, CurrentTime);
	XUngrabKeyboard(dpy, CurrentTime);

	XWarpPointer(dpy, None, win, 0, 0, 0, 0, glConfig.vidWidth / 2, glConfig.vidHeight / 2);

	// inviso cursor
	XUndefineCursor(dpy, win);
}

// bk001206 - from Ryan's Fakk2
/**
 * XPending() actually performs a blocking read 
 *  if no events available. From Fakk2, by way of
 *  Heretic2, by way of SDL, original idea GGI project.
 * The benefit of this approach over the quite
 *  badly behaved XAutoRepeatOn/Off is that you get
 *  focus handling for free, which is a major win
 *  with debug and windowed mode. It rests on the
 *  assumption that the X server will use the
 *  same timestamp on press/release event pairs 
 *  for key repeats. 
 */
static qboolean X11_PendingInput(void)
{

	assert(dpy != NULL);

	// Flush the display connection
	//  and look to see if events are queued
	XFlush(dpy);
	if(XEventsQueued(dpy, QueuedAlready))
	{
		return qtrue;
	}

	// More drastic measures are required -- see if X is ready to talk
	{
		static struct timeval zero_time;
		int             x11_fd;
		fd_set          fdset;

		x11_fd = ConnectionNumber(dpy);
		FD_ZERO(&fdset);
		FD_SET(x11_fd, &fdset);
		if(select(x11_fd + 1, &fdset, NULL, NULL, &zero_time) == 1)
		{
			return (XPending(dpy));
		}
	}

	// Oh well, nothing is ready ..
	return qfalse;
}

// bk001206 - from Ryan's Fakk2. See above.
static qboolean repeated_press(XEvent * event)
{
	XEvent          peekevent;
	qboolean        repeated = qfalse;

	assert(dpy != NULL);

	if(X11_PendingInput())
	{
		XPeekEvent(dpy, &peekevent);

		if((peekevent.type == KeyPress) &&
		   (peekevent.xkey.keycode == event->xkey.keycode) && (peekevent.xkey.time == event->xkey.time))
		{
			repeated = qtrue;
			XNextEvent(dpy, &peekevent);	// skip event.
		}						// if
	}							// if

	return (repeated);
}								// repeated_press

int             Sys_XTimeToSysTime(Time xtime);
static void HandleEvents(void)
{
	int             b;
	int             key;
	XEvent          event;
	qboolean        dowarp = qfalse;
	char           *p;
	int             dx, dy;
	int             t = 0;		// default to 0 in case we don't set

	if(!dpy)
		return;

	while(XPending(dpy))
	{
		XNextEvent(dpy, &event);
		switch (event.type)
		{
			case KeyPress:
				t = Sys_XTimeToSysTime(event.xkey.time);
				p = XLateKey(&event.xkey, &key);
				if(key)
				{
					Sys_QueEvent(t, SE_KEY, key, qtrue, 0, NULL);
				}
				if(p)
				{
					while(*p)
					{
						Sys_QueEvent(t, SE_CHAR, *p++, 0, 0, NULL);
					}
				}
				break;

			case KeyRelease:
				t = Sys_XTimeToSysTime(event.xkey.time);
				// bk001206 - handle key repeat w/o XAutRepatOn/Off
				//            also: not done if console/menu is active.
				// From Ryan's Fakk2.
				// see game/q_shared.h, KEYCATCH_* . 0 == in 3d game.  
				if(cls.keyCatchers == 0)
				{				// FIXME: KEYCATCH_NONE
					if(repeated_press(&event) == qtrue)
						continue;
				}				// if
				XLateKey(&event.xkey, &key);

				Sys_QueEvent(t, SE_KEY, key, qfalse, 0, NULL);
				break;

			case MotionNotify:
				t = Sys_XTimeToSysTime(event.xkey.time);
				if(mouse_active)
				{
					if(in_dgamouse->value)
					{
						if(abs(event.xmotion.x_root) > 1)
							mx += event.xmotion.x_root * 2;
						else
							mx += event.xmotion.x_root;
						if(abs(event.xmotion.y_root) > 1)
							my += event.xmotion.y_root * 2;
						else
							my += event.xmotion.y_root;
						if(t - mouseResetTime > MOUSE_RESET_DELAY)
						{
							Sys_QueEvent(t, SE_MOUSE, mx, my, 0, NULL);
						}
						mx = my = 0;
					}
					else
					{
						// If it's a center motion, we've just returned from our warp
						if(event.xmotion.x == glConfig.vidWidth / 2 && event.xmotion.y == glConfig.vidHeight / 2)
						{
							mwx = glConfig.vidWidth / 2;
							mwy = glConfig.vidHeight / 2;
							if(t - mouseResetTime > MOUSE_RESET_DELAY)
							{
								Sys_QueEvent(t, SE_MOUSE, mx, my, 0, NULL);
							}
							mx = my = 0;
							break;
						}

						dx = ((int)event.xmotion.x - mwx);
						dy = ((int)event.xmotion.y - mwy);
						if(abs(dx) > 1)
							mx += dx * 2;
						else
							mx += dx;
						if(abs(dy) > 1)
							my += dy * 2;
						else
							my += dy;

						mwx = event.xmotion.x;
						mwy = event.xmotion.y;
						dowarp = qtrue;
					}
				}
				break;

			case ButtonPress:
				t = Sys_XTimeToSysTime(event.xkey.time);
				if(event.xbutton.button == 4)
				{
					Sys_QueEvent(t, SE_KEY, K_MWHEELUP, qtrue, 0, NULL);
				}
				else if(event.xbutton.button == 5)
				{
					Sys_QueEvent(t, SE_KEY, K_MWHEELDOWN, qtrue, 0, NULL);
				}
				else
				{
					// NOTE TTimo there seems to be a weird mapping for K_MOUSE1 K_MOUSE2 K_MOUSE3 ..
					b = -1;
					if(event.xbutton.button == 1)
					{
						b = 0;	// K_MOUSE1
					}
					else if(event.xbutton.button == 2)
					{
						b = 2;	// K_MOUSE3
					}
					else if(event.xbutton.button == 3)
					{
						b = 1;	// K_MOUSE2
					}
					else if(event.xbutton.button == 6)
					{
						b = 3;	// K_MOUSE4
					}
					else if(event.xbutton.button == 7)
					{
						b = 4;	// K_MOUSE5
					};

					Sys_QueEvent(t, SE_KEY, K_MOUSE1 + b, qtrue, 0, NULL);
				}
				break;

			case ButtonRelease:
				t = Sys_XTimeToSysTime(event.xkey.time);
				if(event.xbutton.button == 4)
				{
					Sys_QueEvent(t, SE_KEY, K_MWHEELUP, qfalse, 0, NULL);
				}
				else if(event.xbutton.button == 5)
				{
					Sys_QueEvent(t, SE_KEY, K_MWHEELDOWN, qfalse, 0, NULL);
				}
				else
				{
					b = -1;
					if(event.xbutton.button == 1)
					{
						b = 0;
					}
					else if(event.xbutton.button == 2)
					{
						b = 2;
					}
					else if(event.xbutton.button == 3)
					{
						b = 1;
					}
					else if(event.xbutton.button == 6)
					{
						b = 3;	// K_MOUSE4
					}
					else if(event.xbutton.button == 7)
					{
						b = 4;	// K_MOUSE5
					};
					Sys_QueEvent(t, SE_KEY, K_MOUSE1 + b, qfalse, 0, NULL);
				}
				break;

			case CreateNotify:
				win_x = event.xcreatewindow.x;
				win_y = event.xcreatewindow.y;
				break;

			case ConfigureNotify:
				win_x = event.xconfigure.x;
				win_y = event.xconfigure.y;
				break;
		}
	}

	if(dowarp)
	{
		XWarpPointer(dpy, None, win, 0, 0, 0, 0, (glConfig.vidWidth / 2), (glConfig.vidHeight / 2));
	}
}

// NOTE TTimo for the tty console input, we didn't rely on those .. 
//   it's not very surprising actually cause they are not used otherwise
void KBD_Init(void)
{
}

void KBD_Close(void)
{
}

void IN_ActivateMouse(void)
{
	if(!mouse_avail || !dpy || !win)
		return;

	if(!mouse_active)
	{
		if(!in_nograb->value)
			install_grabs();
		else if(in_dgamouse->value)	// force dga mouse to 0 if using nograb
			ri.Cvar_Set("in_dgamouse", "0");
		mouse_active = qtrue;
	}
}

void IN_DeactivateMouse(void)
{
	if(!mouse_avail || !dpy || !win)
		return;

	if(mouse_active)
	{
		if(!in_nograb->value)
			uninstall_grabs();
		else if(in_dgamouse->value)	// force dga mouse to 0 if using nograb
			ri.Cvar_Set("in_dgamouse", "0");
		mouse_active = qfalse;
	}
}

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

/*
** GLW_SetGamma
**
** This routine should only be called if glConfig.deviceSupportsGamma is TRUE
*/
void GLW_SetGamma(unsigned char red[256], unsigned char green[256], unsigned char blue[256])
{
	// NOTE TTimo we get the gamma value from cvar, because we can't work with the s_gammatable
	//   the API wasn't changed to avoid breaking other OSes
	float           g = Cvar_Get("r_gamma", "1.0", 0)->value;
	XF86VidModeGamma gamma;

	assert(glConfig.deviceSupportsGamma);
	gamma.red = g;
	gamma.green = g;
	gamma.blue = g;
	XF86VidModeSetGamma(dpy, scrnum, &gamma);
}

/*
** GLW_Shutdown
**
** This routine does all OS specific shutdown procedures for the OpenGL
** subsystem.  Under OpenGL this means NULLing out the current DC and
** HGLRC, deleting the rendering context, and releasing the DC acquired
** for the window.  The state structure is also nulled out.
**
*/
void GLW_Shutdown(void)
{
	if(!ctx || !dpy)
		return;
	IN_DeactivateMouse();
	// bk001206 - replaced with H2/Fakk2 solution
	// XAutoRepeatOn(dpy);
	// autorepeaton = qfalse; // bk001130 - from cvs1.17 (mkv)
	if(dpy)
	{
		if(ctx)
			qglXDestroyContext(dpy, ctx);
		if(win)
			XDestroyWindow(dpy, win);
		if(vidmode_active)
			XF86VidModeSwitchToMode(dpy, scrnum, vidmodes[0]);
		if(glConfig.deviceSupportsGamma)
		{
			XF86VidModeSetGamma(dpy, scrnum, &vidmode_InitialGamma);
		}
		// NOTE TTimo opening/closing the display should be necessary only once per run
		//   but it seems QGL_Shutdown gets called in a lot of occasion
		//   in some cases, this XCloseDisplay is known to raise some X errors
		//   ( https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=33 )
		XCloseDisplay(dpy);
	}
	vidmode_active = qfalse;
	dpy = NULL;
	win = 0;
	ctx = NULL;

	memset(&glConfig, 0, sizeof(glConfig));
	memset(&glConfig2, 0, sizeof(glConfig2));
	memset(&glState, 0, sizeof(glState));

	QGL_Shutdown();
}

/*
** GLW_LogComment
*/
void GLW_LogComment(char *comment)
{
	if(glw_state.log_fp)
	{
		fprintf(glw_state.log_fp, "%s", comment);
	}
}

/*
** GLW_StartDriverAndSetMode
*/
// bk001204 - prototype needed
int             GLW_SetMode(const char *drivername, int mode, qboolean fullscreen);
static qboolean GLW_StartDriverAndSetMode(const char *drivername, int mode, qboolean fullscreen)
{
	rserr_t         err;

	// don't ever bother going into fullscreen with a voodoo card
#if 1							// JDC: I reenabled this
	if(Q_stristr(drivername, "Voodoo"))
	{
		ri.Cvar_Set("r_fullscreen", "0");
		r_fullscreen->modified = qfalse;
		fullscreen = qfalse;
	}
#endif

	if(fullscreen && in_nograb->value)
	{
		ri.Printf(PRINT_ALL, "Fullscreen not allowed with in_nograb 1\n");
		ri.Cvar_Set("r_fullscreen", "0");
		r_fullscreen->modified = qfalse;
		fullscreen = qfalse;
	}

	err = GLW_SetMode(drivername, mode, fullscreen);

	switch (err)
	{
		case RSERR_INVALID_FULLSCREEN:
			ri.Printf(PRINT_ALL, "...WARNING: fullscreen unavailable in this mode\n");
			return qfalse;
		case RSERR_INVALID_MODE:
			ri.Printf(PRINT_ALL, "...WARNING: could not set the given mode (%d)\n", mode);
			return qfalse;
		default:
			break;
	}
	return qtrue;
}

/*
** GLW_SetMode
*/
int GLW_SetMode(const char *drivername, int mode, qboolean fullscreen)
{
	int             attrib[] = {
		GLX_RGBA,				// 0
		GLX_RED_SIZE, 4,		// 1, 2
		GLX_GREEN_SIZE, 4,		// 3, 4
		GLX_BLUE_SIZE, 4,		// 5, 6
		GLX_DOUBLEBUFFER,		// 7
		GLX_DEPTH_SIZE, 1,		// 8, 9
		GLX_STENCIL_SIZE, 1,	// 10, 11
		None
	};
	// these match in the array
#define ATTR_RED_IDX 2
#define ATTR_GREEN_IDX 4
#define ATTR_BLUE_IDX 6
#define ATTR_DEPTH_IDX 9
#define ATTR_STENCIL_IDX 11
	Window          root;
	XVisualInfo    *visinfo;
	XSetWindowAttributes attr;
	XSizeHints      sizehints;
	unsigned long   mask;
	int             colorbits, depthbits, stencilbits;
	int             tcolorbits, tdepthbits, tstencilbits;
	int             dga_MajorVersion, dga_MinorVersion;
	int             actualWidth, actualHeight;
	int             i;
	const char     *glstring;	// bk001130 - from cvs1.17 (mkv)

	ri.Printf(PRINT_ALL, "Initializing OpenGL display\n");

	ri.Printf(PRINT_ALL, "...setting mode %d:", mode);

	if(!R_GetModeInfo(&glConfig.vidWidth, &glConfig.vidHeight, &glConfig.windowAspect, mode))
	{
		ri.Printf(PRINT_ALL, " invalid mode\n");
		return RSERR_INVALID_MODE;
	}
	ri.Printf(PRINT_ALL, " %d %d\n", glConfig.vidWidth, glConfig.vidHeight);

	if(!(dpy = XOpenDisplay(NULL)))
	{
		fprintf(stderr, "Error couldn't open the X display\n");
		return RSERR_INVALID_MODE;
	}

	scrnum = DefaultScreen(dpy);
	root = RootWindow(dpy, scrnum);

	actualWidth = glConfig.vidWidth;
	actualHeight = glConfig.vidHeight;

	// Get video mode list
	if(!XF86VidModeQueryVersion(dpy, &vidmode_MajorVersion, &vidmode_MinorVersion))
	{
		vidmode_ext = qfalse;
	}
	else
	{
		ri.Printf(PRINT_ALL, "Using XFree86-VidModeExtension Version %d.%d\n", vidmode_MajorVersion, vidmode_MinorVersion);
		vidmode_ext = qtrue;
	}

	// Check for DGA  
	dga_MajorVersion = 0, dga_MinorVersion = 0;
	if(in_dgamouse->value)
	{
		if(!XF86DGAQueryVersion(dpy, &dga_MajorVersion, &dga_MinorVersion))
		{
			// unable to query, probalby not supported
			ri.Printf(PRINT_ALL, "Failed to detect XF86DGA Mouse\n");
			ri.Cvar_Set("in_dgamouse", "0");
		}
		else
		{
			ri.Printf(PRINT_ALL, "XF86DGA Mouse (Version %d.%d) initialized\n", dga_MajorVersion, dga_MinorVersion);
		}
	}

	if(vidmode_ext)
	{
		int             best_fit, best_dist, dist, x, y;

		XF86VidModeGetAllModeLines(dpy, scrnum, &num_vidmodes, &vidmodes);

		// Are we going fullscreen?  If so, let's change video mode
		if(fullscreen)
		{
			best_dist = 9999999;
			best_fit = -1;

			for(i = 0; i < num_vidmodes; i++)
			{
				if(glConfig.vidWidth > vidmodes[i]->hdisplay || glConfig.vidHeight > vidmodes[i]->vdisplay)
					continue;

				x = glConfig.vidWidth - vidmodes[i]->hdisplay;
				y = glConfig.vidHeight - vidmodes[i]->vdisplay;
				dist = (x * x) + (y * y);
				if(dist < best_dist)
				{
					best_dist = dist;
					best_fit = i;
				}
			}

			if(best_fit != -1)
			{
				actualWidth = vidmodes[best_fit]->hdisplay;
				actualHeight = vidmodes[best_fit]->vdisplay;

				// change to the mode
				XF86VidModeSwitchToMode(dpy, scrnum, vidmodes[best_fit]);
				vidmode_active = qtrue;

				// Move the viewport to top left
				XF86VidModeSetViewPort(dpy, scrnum, 0, 0);

				ri.Printf(PRINT_ALL, "XFree86-VidModeExtension Activated at %dx%d\n", actualWidth, actualHeight);

			}
			else
			{
				fullscreen = 0;
				ri.Printf(PRINT_ALL, "XFree86-VidModeExtension: No acceptable modes found\n");
			}
		}
		else
		{
			ri.Printf(PRINT_ALL, "XFree86-VidModeExtension:  Ignored on non-fullscreen/Voodoo\n");
		}
	}


	if(!r_colorbits->value)
		colorbits = 24;
	else
		colorbits = r_colorbits->value;

	if(!Q_stricmp(r_glDriver->string, _3DFX_DRIVER_NAME))
		colorbits = 16;

	if(!r_depthbits->value)
		depthbits = 24;
	else
		depthbits = r_depthbits->value;
	stencilbits = r_stencilbits->value;

	for(i = 0; i < 16; i++)
	{
		// 0 - default
		// 1 - minus colorbits
		// 2 - minus depthbits
		// 3 - minus stencil
		if((i % 4) == 0 && i)
		{
			// one pass, reduce
			switch (i / 4)
			{
				case 2:
					if(colorbits == 24)
						colorbits = 16;
					break;
				case 1:
					if(depthbits == 24)
						depthbits = 16;
					else if(depthbits == 16)
						depthbits = 8;
				case 3:
					if(stencilbits == 24)
						stencilbits = 16;
					else if(stencilbits == 16)
						stencilbits = 8;
			}
		}

		tcolorbits = colorbits;
		tdepthbits = depthbits;
		tstencilbits = stencilbits;

		if((i % 4) == 3)
		{						// reduce colorbits
			if(tcolorbits == 24)
				tcolorbits = 16;
		}

		if((i % 4) == 2)
		{						// reduce depthbits
			if(tdepthbits == 24)
				tdepthbits = 16;
			else if(tdepthbits == 16)
				tdepthbits = 8;
		}

		if((i % 4) == 1)
		{						// reduce stencilbits
			if(tstencilbits == 24)
				tstencilbits = 16;
			else if(tstencilbits == 16)
				tstencilbits = 8;
			else
				tstencilbits = 0;
		}

		if(tcolorbits == 24)
		{
			attrib[ATTR_RED_IDX] = 8;
			attrib[ATTR_GREEN_IDX] = 8;
			attrib[ATTR_BLUE_IDX] = 8;
		}
		else
		{
			// must be 16 bit
			attrib[ATTR_RED_IDX] = 4;
			attrib[ATTR_GREEN_IDX] = 4;
			attrib[ATTR_BLUE_IDX] = 4;
		}

		attrib[ATTR_DEPTH_IDX] = tdepthbits;	// default to 24 depth
		attrib[ATTR_STENCIL_IDX] = tstencilbits;

		visinfo = qglXChooseVisual(dpy, scrnum, attrib);
		if(!visinfo)
		{
			continue;
		}

		ri.Printf(PRINT_ALL, "Using %d/%d/%d Color bits, %d depth, %d stencil display.\n",
				  attrib[ATTR_RED_IDX], attrib[ATTR_GREEN_IDX], attrib[ATTR_BLUE_IDX],
				  attrib[ATTR_DEPTH_IDX], attrib[ATTR_STENCIL_IDX]);

		glConfig.colorBits = tcolorbits;
		glConfig.depthBits = tdepthbits;
		glConfig.stencilBits = tstencilbits;
		break;
	}

	if(!visinfo)
	{
		ri.Printf(PRINT_ALL, "Couldn't get a visual\n");
		return RSERR_INVALID_MODE;
	}

	/* window attributes */
	attr.background_pixel = BlackPixel(dpy, scrnum);
	attr.border_pixel = 0;
	attr.colormap = XCreateColormap(dpy, root, visinfo->visual, AllocNone);
	attr.event_mask = X_MASK;
	if(vidmode_active)
	{
		mask = CWBackPixel | CWColormap | CWSaveUnder | CWBackingStore | CWEventMask | CWOverrideRedirect;
		attr.override_redirect = True;
		attr.backing_store = NotUseful;
		attr.save_under = False;
	}
	else
		mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;

	win = XCreateWindow(dpy, root, 0, 0, actualWidth, actualHeight, 0, visinfo->depth, InputOutput, visinfo->visual, mask, &attr);

	XStoreName(dpy, win, WINDOW_CLASS_NAME);

	/* GH: Don't let the window be resized */
	sizehints.flags = PMinSize | PMaxSize;
	sizehints.min_width = sizehints.max_width = actualWidth;
	sizehints.min_height = sizehints.max_height = actualHeight;

	XSetWMNormalHints(dpy, win, &sizehints);

	XMapWindow(dpy, win);

	if(vidmode_active)
		XMoveWindow(dpy, win, 0, 0);

	XFlush(dpy);
	XSync(dpy, False);			// bk001130 - from cvs1.17 (mkv)
	ctx = qglXCreateContext(dpy, visinfo, NULL, True);
	XSync(dpy, False);			// bk001130 - from cvs1.17 (mkv)

	/* GH: Free the visinfo after we're done with it */
	XFree(visinfo);

	qglXMakeCurrent(dpy, win, ctx);

	// bk001130 - from cvs1.17 (mkv)
	glstring = qglGetString(GL_RENDERER);
	ri.Printf(PRINT_ALL, "GL_RENDERER: %s\n", glstring);

	// bk010122 - new software token (Indirect)
	if(!Q_stricmp(glstring, "Mesa X11") || !Q_stricmp(glstring, "Mesa GLX Indirect"))
	{
		if(!r_allowSoftwareGL->integer)
		{
			ri.Printf(PRINT_ALL, "\n\n***********************************************************\n");
			ri.Printf(PRINT_ALL, " You are using software Mesa (no hardware acceleration)!   \n");
			ri.Printf(PRINT_ALL, " Driver DLL used: %s\n", drivername);
			ri.Printf(PRINT_ALL, " If this is intentional, add\n");
			ri.Printf(PRINT_ALL, "       \"+set r_allowSoftwareGL 1\"\n");
			ri.Printf(PRINT_ALL, " to the command line when starting the game.\n");
			ri.Printf(PRINT_ALL, "***********************************************************\n");
			GLW_Shutdown();
			return RSERR_INVALID_MODE;
		}
		else
		{
			ri.Printf(PRINT_ALL, "...using software Mesa (r_allowSoftwareGL==1).\n");
		}
	}

	return RSERR_OK;
}

/*
** GLW_InitExtensions
*/
// Tr3B - use this function like those other GLW_InitExtensions implementations
void *qwglGetProcAddress(char *symbol);

static void GLW_InitExtensions(void)
{
	if(!r_allowExtensions->integer)
	{
		ri.Printf(PRINT_ALL, "*** IGNORING OPENGL EXTENSIONS ***\n");
		return;
	}

	ri.Printf(PRINT_ALL, "Initializing OpenGL extensions\n");

	// GL_S3_s3tc
	if(Q_stristr(glConfig.extensions_string, "GL_S3_s3tc"))
	{
		if(r_ext_compressed_textures->integer == 1)
		{
			glConfig.textureCompression = TC_S3TC;
			ri.Printf(PRINT_ALL, "...using GL_S3_s3tc\n");
		}
		else
		{
			glConfig.textureCompression = TC_NONE;
			ri.Printf(PRINT_ALL, "...ignoring GL_S3_s3tc\n");
		}
	}
	else
	{
		glConfig.textureCompression = TC_NONE;
		ri.Printf(PRINT_ALL, "...GL_S3_s3tc not found\n");
	}

	// GL_EXT_texture_env_add
	glConfig.textureEnvAddAvailable = qfalse;
	if(Q_stristr(glConfig.extensions_string, "EXT_texture_env_add"))
	{
		if(r_ext_texture_env_add->integer)
		{
			glConfig.textureEnvAddAvailable = qtrue;
			ri.Printf(PRINT_ALL, "...using GL_EXT_texture_env_add\n");
		}
		else
		{
			glConfig.textureEnvAddAvailable = qfalse;
			ri.Printf(PRINT_ALL, "...ignoring GL_EXT_texture_env_add\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_EXT_texture_env_add not found\n");
	}

	// GL_ARB_multitexture
	qglMultiTexCoord2fARB = NULL;
	qglActiveTextureARB = NULL;
	qglClientActiveTextureARB = NULL;
	if(Q_stristr(glConfig.extensions_string, "GL_ARB_multitexture"))
	{
		if(r_ext_multitexture->value)
		{
			qglMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC) qwglGetProcAddress("glMultiTexCoord2fARB");
			qglActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC) qwglGetProcAddress("glActiveTextureARB");
			qglClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC) qwglGetProcAddress("glClientActiveTextureARB");

			if(qglActiveTextureARB)
			{
				qglGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &glConfig.maxTextureUnits);

				if(glConfig.maxTextureUnits > 1)
				{
					ri.Printf(PRINT_ALL, "...using GL_ARB_multitexture\n");
				}
				else
				{
					qglMultiTexCoord2fARB = NULL;
					qglActiveTextureARB = NULL;
					qglClientActiveTextureARB = NULL;
					ri.Printf(PRINT_ALL, "...not using GL_ARB_multitexture, < 2 texture units\n");
				}
			}
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_ARB_multitexture\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_ARB_multitexture not found\n");
	}

	// GL_ARB_transpose_matrix
	qglLoadTransposeMatrixfARB = NULL;
	if(Q_stristr(glConfig.extensions_string, "GL_ARB_transpose_matrix"))
	{
		if(r_ext_transpose_matrix->value)
		{
			qglLoadTransposeMatrixfARB = (PFNGLLOADTRANSPOSEMATRIXFARBPROC) qwglGetProcAddress("glLoadTransposeMatrixfARB");
			ri.Printf(PRINT_ALL, "...using GL_ARB_transpose_matrix\n");
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_ARB_transpose_matrix\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_ARB_transpose_matrix not found\n");
	}
	
	// GL_ARB_texture_cube_map
	glConfig2.textureCubeAvailable = qfalse;
	if(Q_stristr(glConfig.extensions_string, "GL_ARB_texture_cube_map"))
	{
		if(r_ext_texture_cube_map->integer)
		{
			qglGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB, &glConfig2.maxCubeMapTextureSize);
			glConfig2.textureCubeAvailable = qtrue;
			ri.Printf(PRINT_ALL, "...using GL_ARB_texture_cube_map\n");
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_ARB_texture_cube_map\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_ARB_texture_cube_map\n");
	}
	
	// GL_ARB_depth_texture
	glConfig2.depthTextureAvailable = qfalse;
	if(Q_stristr(glConfig.extensions_string, "GL_ARB_depth_texture"))
	{
		if(r_ext_depth_texture->integer)
		{
			glConfig2.depthTextureAvailable = qtrue;
			ri.Printf(PRINT_ALL, "...using GL_ARB_depth_texture\n");
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_ARB_depth_texture\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_ARB_depth_texture\n");
	}

	// GL_ARB_vertex_program
	glConfig2.vertexProgramAvailable = qfalse;
	qglVertexAttribPointerARB = NULL;
	qglEnableVertexAttribArrayARB = NULL;
	qglDisableVertexAttribArrayARB = NULL;
	if(Q_stristr(glConfig.extensions_string, "GL_ARB_vertex_program"))
	{
		if(r_ext_vertex_program->value)
		{
			qglVertexAttribPointerARB = (PFNGLVERTEXATTRIBPOINTERARBPROC) qwglGetProcAddress("glVertexAttribPointerARB");
			qglEnableVertexAttribArrayARB =
				(PFNGLENABLEVERTEXATTRIBARRAYARBPROC) qwglGetProcAddress("glEnableVertexAttribArrayARB");
			qglDisableVertexAttribArrayARB =
				(PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) qwglGetProcAddress("glDisableVertexAttribArrayARB");
			glConfig2.vertexProgramAvailable = qtrue;
			ri.Printf(PRINT_ALL, "...using GL_ARB_vertex_program\n");
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_ARB_vertex_program\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_ARB_vertex_program not found\n");
	}
	
	// GL_ARB_vertex_buffer_object
	glConfig2.vertexBufferObjectAvailable = qfalse;
	qglBindBufferARB = NULL;
	qglDeleteBuffersARB = NULL;
	qglGenBuffersARB = NULL;
	qglIsBufferARB = NULL;
	qglBufferDataARB = NULL;
	qglBufferSubDataARB = NULL;
	qglGetBufferSubDataARB = NULL;
	qglMapBufferARB = NULL;
	qglUnmapBufferARB = NULL;
	qglGetBufferParameterivARB = NULL;
	qglGetBufferPointervARB = NULL;
	if(Q_stristr(glConfig.extensions_string, "GL_ARB_vertex_buffer_object"))
	{
		if(r_ext_vertex_buffer_object->value)
		{
			qglBindBufferARB = (PFNGLBINDBUFFERARBPROC) qwglGetProcAddress("glBindBufferARB");
			qglDeleteBuffersARB = (PFNGLDELETEBUFFERSARBPROC) qwglGetProcAddress("glDeleteBuffersARB");
			qglGenBuffersARB = (PFNGLGENBUFFERSARBPROC) qwglGetProcAddress("glGenBuffersARB");
			qglIsBufferARB = (PFNGLISBUFFERARBPROC) qwglGetProcAddress("glIsBufferARB");
			qglBufferDataARB = (PFNGLBUFFERDATAARBPROC) qwglGetProcAddress("glBufferDataARB");
			qglBufferSubDataARB = (PFNGLBUFFERSUBDATAARBPROC) qwglGetProcAddress("glBufferSubDataARB");
			qglGetBufferSubDataARB = (PFNGLGETBUFFERSUBDATAARBPROC) qwglGetProcAddress("glGetBufferSubDataARB");
			qglMapBufferARB = (PFNGLMAPBUFFERARBPROC) qwglGetProcAddress("glMapBufferARB");
			qglUnmapBufferARB = (PFNGLUNMAPBUFFERARBPROC) qwglGetProcAddress("glUnmapBufferARB");
			qglGetBufferParameterivARB = (PFNGLGETBUFFERPARAMETERIVARBPROC) qwglGetProcAddress("glGetBufferParameterivARB");
			qglGetBufferPointervARB = (PFNGLGETBUFFERPOINTERVARBPROC) qwglGetProcAddress("glGetBufferPointervARB");
			glConfig2.vertexBufferObjectAvailable = qtrue;
			ri.Printf(PRINT_ALL, "...using GL_ARB_vertex_buffer_object\n");
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_ARB_vertex_buffer_object\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_ARB_vertex_buffer_object not found\n");
	}
	
	// GL_ARB_occlusion_query
	glConfig2.occlusionQueryAvailable = qfalse;
	glConfig2.occlusionQueryBits = 0;
	qglGenQueriesARB = NULL;
	qglDeleteQueriesARB = NULL;
	qglIsQueryARB = NULL;
	qglBeginQueryARB = NULL;
	qglEndQueryARB = NULL;
	qglGetQueryivARB = NULL;
	qglGetQueryObjectivARB = NULL;
	qglGetQueryObjectuivARB = NULL;
	if(Q_stristr(glConfig.extensions_string, "GL_ARB_occlusion_query"))
	{
		if(r_ext_occlusion_query->value)
		{
			qglGenQueriesARB = (PFNGLGENQUERIESARBPROC) qwglGetProcAddress("glGenQueriesARB");
			qglDeleteQueriesARB = (PFNGLDELETEQUERIESARBPROC) qwglGetProcAddress("glDeleteQueriesARB");
			qglIsQueryARB = (PFNGLISQUERYARBPROC) qwglGetProcAddress("glIsQueryARB");
			qglBeginQueryARB = (PFNGLBEGINQUERYARBPROC) qwglGetProcAddress("glBeginQueryARB");
			qglEndQueryARB = (PFNGLENDQUERYARBPROC) qwglGetProcAddress("glEndQueryARB");
			qglGetQueryivARB = (PFNGLGETQUERYIVARBPROC) qwglGetProcAddress("glGetQueryivARB");
			qglGetQueryObjectivARB = (PFNGLGETQUERYOBJECTIVARBPROC) qwglGetProcAddress("glGetQueryObjectivARB");
			qglGetQueryObjectuivARB = (PFNGLGETQUERYOBJECTUIVARBPROC) qwglGetProcAddress("glGetQueryObjectuivARB");
			glConfig2.occlusionQueryAvailable = qtrue;
			qglGetQueryivARB(GL_SAMPLES_PASSED, GL_QUERY_COUNTER_BITS, &glConfig2.occlusionQueryBits); 
			ri.Printf(PRINT_ALL, "...using GL_ARB_occlusion_query\n");
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_ARB_occlusion_query\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_ARB_occlusion_query not found\n");
	}

	// GL_ARB_shader_objects
	glConfig2.shaderObjectsAvailable = qfalse;
	qglDeleteObjectARB = NULL;
	qglGetHandleARB = NULL;
	qglDetachObjectARB = NULL;
	qglCreateShaderObjectARB = NULL;
	qglShaderSourceARB = NULL;
	qglCompileShaderARB = NULL;
	qglCreateProgramObjectARB = NULL;
	qglAttachObjectARB = NULL;
	qglLinkProgramARB = NULL;
	qglUseProgramObjectARB = NULL;
	qglValidateProgramARB = NULL;
	qglUniform1fARB = NULL;
	qglUniform2fARB = NULL;
	qglUniform3fARB = NULL;
	qglUniform4fARB = NULL;
	qglUniform1iARB = NULL;
	qglUniform2iARB = NULL;
	qglUniform3iARB = NULL;
	qglUniform4iARB = NULL;
	qglUniform2fvARB = NULL;
	qglUniform3fvARB = NULL;
	qglUniform4fvARB = NULL;
	qglUniform2ivARB = NULL;
	qglUniform3ivARB = NULL;
	qglUniform4ivARB = NULL;
	qglUniformMatrix2fvARB = NULL;
	qglUniformMatrix3fvARB = NULL;
	qglUniformMatrix4fvARB = NULL;
	qglGetObjectParameterfvARB = NULL;
	qglGetObjectParameterivARB = NULL;
	qglGetInfoLogARB = NULL;
	qglGetAttachedObjectsARB = NULL;
	qglGetUniformLocationARB = NULL;
	qglGetActiveUniformARB = NULL;
	qglGetUniformfvARB = NULL;
	qglGetUniformivARB = NULL;
	qglGetShaderSourceARB = NULL;
	if(Q_stristr(glConfig.extensions_string, "GL_ARB_shader_objects"))
	{
		if(r_ext_shader_objects->value)
		{
			qglDeleteObjectARB = (PFNGLDELETEOBJECTARBPROC) qwglGetProcAddress("glDeleteObjectARB");
			qglGetHandleARB = (PFNGLGETHANDLEARBPROC) qwglGetProcAddress("glGetHandleARB");
			qglDetachObjectARB = (PFNGLDETACHOBJECTARBPROC) qwglGetProcAddress("glDetachObjectARB");
			qglCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC) qwglGetProcAddress("glCreateShaderObjectARB");
			qglShaderSourceARB = (PFNGLSHADERSOURCEARBPROC) qwglGetProcAddress("glShaderSourceARB");
			qglCompileShaderARB = (PFNGLCOMPILESHADERARBPROC) qwglGetProcAddress("glCompileShaderARB");
			qglCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC) qwglGetProcAddress("glCreateProgramObjectARB");
			qglAttachObjectARB = (PFNGLATTACHOBJECTARBPROC) qwglGetProcAddress("glAttachObjectARB");
			qglLinkProgramARB = (PFNGLLINKPROGRAMARBPROC) qwglGetProcAddress("glLinkProgramARB");
			qglUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC) qwglGetProcAddress("glUseProgramObjectARB");
			qglValidateProgramARB = (PFNGLVALIDATEPROGRAMARBPROC) qwglGetProcAddress("glValidateProgramARB");
			qglUniform1fARB = (PFNGLUNIFORM1FARBPROC) qwglGetProcAddress("glUniform1fARB");
			qglUniform2fARB = (PFNGLUNIFORM2FARBPROC) qwglGetProcAddress("glUniform2fARB");
			qglUniform3fARB = (PFNGLUNIFORM3FARBPROC) qwglGetProcAddress("glUniform3fARB");
			qglUniform4fARB = (PFNGLUNIFORM4FARBPROC) qwglGetProcAddress("glUniform4fARB");
			qglUniform1iARB = (PFNGLUNIFORM1IARBPROC) qwglGetProcAddress("glUniform1iARB");
			qglUniform2iARB = (PFNGLUNIFORM2IARBPROC) qwglGetProcAddress("glUniform2iARB");
			qglUniform3iARB = (PFNGLUNIFORM3IARBPROC) qwglGetProcAddress("glUniform3iARB");
			qglUniform4iARB = (PFNGLUNIFORM4IARBPROC) qwglGetProcAddress("glUniform4iARB");
			qglUniform2fvARB = (PFNGLUNIFORM2FVARBPROC) qwglGetProcAddress("glUniform2fvARB");
			qglUniform3fvARB = (PFNGLUNIFORM3FVARBPROC) qwglGetProcAddress("glUniform3fvARB");
			qglUniform4fvARB = (PFNGLUNIFORM4FVARBPROC) qwglGetProcAddress("glUniform4fvARB");
			qglUniform2ivARB = (PFNGLUNIFORM2IVARBPROC) qwglGetProcAddress("glUniform2ivARB");
			qglUniform3ivARB = (PFNGLUNIFORM3IVARBPROC) qwglGetProcAddress("glUniform3ivARB");
			qglUniform4ivARB = (PFNGLUNIFORM4IVARBPROC) qwglGetProcAddress("glUniform4ivARB");
			qglUniformMatrix2fvARB = (PFNGLUNIFORMMATRIX2FVARBPROC) qwglGetProcAddress("glUniformMatrix2fvARB");
			qglUniformMatrix3fvARB = (PFNGLUNIFORMMATRIX3FVARBPROC) qwglGetProcAddress("glUniformMatrix3fvARB");
			qglUniformMatrix4fvARB = (PFNGLUNIFORMMATRIX4FVARBPROC) qwglGetProcAddress("glUniformMatrix4fvARB");
			qglGetObjectParameterfvARB = (PFNGLGETOBJECTPARAMETERFVARBPROC) qwglGetProcAddress("glGetObjectParameterfvARB");
			qglGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC) qwglGetProcAddress("glGetObjectParameterivARB");
			qglGetInfoLogARB = (PFNGLGETINFOLOGARBPROC) qwglGetProcAddress("glGetInfoLogARB");
			qglGetAttachedObjectsARB = (PFNGLGETATTACHEDOBJECTSARBPROC) qwglGetProcAddress("glGetAttachedObjectsARB");
			qglGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC) qwglGetProcAddress("glGetUniformLocationARB");
			qglGetActiveUniformARB = (PFNGLGETACTIVEUNIFORMARBPROC) qwglGetProcAddress("glGetActiveUniformARB");
			qglGetUniformfvARB = (PFNGLGETUNIFORMFVARBPROC) qwglGetProcAddress("glGetUniformfvARB");
			qglGetUniformivARB = (PFNGLGETUNIFORMIVARBPROC) qwglGetProcAddress("glGetUniformivARB");
			qglGetShaderSourceARB = (PFNGLGETSHADERSOURCEARBPROC) qwglGetProcAddress("glGetShaderSourceARB");
			glConfig2.shaderObjectsAvailable = qtrue;
			ri.Printf(PRINT_ALL, "...using GL_ARB_shader_objects\n");
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_ARB_shader_objects\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_ARB_shader_objects not found\n");
	}

	// GL_ARB_vertex_shader
	glConfig2.vertexShaderAvailable = qfalse;
	qglBindAttribLocationARB = NULL;
	qglGetActiveAttribARB = NULL;
	qglGetAttribLocationARB = NULL;
	if(Q_stristr(glConfig.extensions_string, "GL_ARB_vertex_shader"))
	{
		if(r_ext_vertex_shader->value)
		{
			qglBindAttribLocationARB = (PFNGLBINDATTRIBLOCATIONARBPROC) qwglGetProcAddress("glBindAttribLocationARB");
			qglGetActiveAttribARB = (PFNGLGETACTIVEATTRIBARBPROC) qwglGetProcAddress("glGetActiveAttribARB");
			qglGetAttribLocationARB = (PFNGLGETATTRIBLOCATIONARBPROC) qwglGetProcAddress("glGetAttribLocationARB");
			glConfig2.vertexShaderAvailable = qtrue;
			ri.Printf(PRINT_ALL, "...using GL_ARB_vertex_shader\n");
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_ARB_vertex_shader\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_ARB_vertex_shader not found\n");
	}

	// GL_ARB_fragment_shader
	glConfig2.fragmentShaderAvailable = qfalse;
	if(Q_stristr(glConfig.extensions_string, "GL_ARB_fragment_shader"))
	{
		if(r_ext_fragment_shader->value)
		{
			glConfig2.fragmentShaderAvailable = qtrue;
			ri.Printf(PRINT_ALL, "...using GL_ARB_fragment_shader\n");
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_ARB_fragment_shader\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_ARB_fragment_shader not found\n");
	}

	// GL_ARB_shading_language_100
	glConfig2.shadingLanguage100Available = qfalse;
	if(Q_stristr(glConfig.extensions_string, "GL_ARB_shading_language_100"))
	{
		if(r_ext_shading_language_100->value)
		{
			glConfig2.shadingLanguage100Available = qtrue;
			ri.Printf(PRINT_ALL, "...using GL_ARB_shading_language_100\n");
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_ARB_shading_language_100\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_ARB_shading_language_100 not found\n");
	}

	// GL_EXT_compiled_vertex_array
	if(Q_stristr(glConfig.extensions_string, "GL_EXT_compiled_vertex_array"))
	{
		if(r_ext_compiled_vertex_array->value)
		{
			ri.Printf(PRINT_ALL, "...using GL_EXT_compiled_vertex_array\n");
			qglLockArraysEXT = (void (APIENTRY *) (int, int))qwglGetProcAddress("glLockArraysEXT");
			qglUnlockArraysEXT = (void (APIENTRY *) (void))qwglGetProcAddress("glUnlockArraysEXT");
			if(!qglLockArraysEXT || !qglUnlockArraysEXT)
			{
				ri.Error(ERR_FATAL, "bad getprocaddress");
			}
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_EXT_compiled_vertex_array\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_EXT_compiled_vertex_array not found\n");
	}
	
	// GL_EXT_stencil_wrap
	glConfig2.stencilWrapAvailable = qfalse;
	if(Q_stristr(glConfig.extensions_string, "GL_EXT_stencil_wrap"))
	{
		if(r_ext_stencil_wrap->value)
		{
			glConfig2.stencilWrapAvailable = qtrue;
			ri.Printf(PRINT_ALL, "...using GL_EXT_stencil_wrap\n");
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_EXT_stencil_wrap\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_EXT_stencil_wrap not found\n");
	}
	
	// GL_EXT_texture_filter_anisotropic
	glConfig2.textureAnisotropyAvailable = qfalse;
	if(Q_stristr(glConfig.extensions_string, "GL_EXT_texture_filter_anisotropic"))
	{
		qglGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &glConfig2.maxTextureAnisotropy);
		
		if(r_ext_texture_filter_anisotropic->value)
		{
			glConfig2.textureAnisotropyAvailable = qtrue;
			ri.Printf(PRINT_ALL, "...using GL_EXT_texture_filter_anisotropic\n");
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_EXT_texture_filter_anisotropic\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_EXT_texture_filter_anisotropic not found\n");
	}
	
	// GL_EXT_stencil_two_side
	qglActiveStencilFaceEXT = NULL;
	if(Q_stristr(glConfig.extensions_string, "GL_EXT_stencil_two_side"))
	{
		if(r_ext_stencil_two_side->value)
		{
			qglActiveStencilFaceEXT = (void (APIENTRY *) (GLenum))qwglGetProcAddress("glActiveStencilFaceEXT");
			ri.Printf(PRINT_ALL, "...using GL_EXT_stencil_two_side\n");
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_EXT_stencil_two_side\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_EXT_stencil_two_side not found\n");
	}
	
	// GL_EXT_depth_bounds_test
	qglDepthBoundsEXT = NULL;
	if(Q_stristr(glConfig.extensions_string, "GL_EXT_depth_bounds_test"))
	{
		if(r_ext_depth_bounds_test->value)
		{
			qglDepthBoundsEXT = (PFNGLDEPTHBOUNDSEXTPROC)qwglGetProcAddress("glDepthBoundsEXT");
			ri.Printf(PRINT_ALL, "...using GL_EXT_depth_bounds_test\n");
		}
		else
		{
			ri.Printf(PRINT_ALL, "...ignoring GL_EXT_depth_bounds_test\n");
		}
	}
	else
	{
		ri.Printf(PRINT_ALL, "...GL_EXT_depth_bounds_test not found\n");
	}
}

static void GLW_InitGamma()
{
	/* Minimum extension version required */
#define GAMMA_MINMAJOR 2
#define GAMMA_MINMINOR 0

	glConfig.deviceSupportsGamma = qfalse;

	if(vidmode_ext)
	{
		if(vidmode_MajorVersion < GAMMA_MINMAJOR ||
		   (vidmode_MajorVersion == GAMMA_MINMAJOR && vidmode_MinorVersion < GAMMA_MINMINOR))
		{
			ri.Printf(PRINT_ALL, "XF86 Gamma extension not supported in this version\n");
			return;
		}
		XF86VidModeGetGamma(dpy, scrnum, &vidmode_InitialGamma);
		ri.Printf(PRINT_ALL, "XF86 Gamma extension initialized\n");
		glConfig.deviceSupportsGamma = qtrue;
	}
}

/*
** GLW_LoadOpenGL
**
** Function that that attempts to load and use a specific OpenGL DLL.
*/
static qboolean GLW_LoadOpenGL(const char *name)
{
	qboolean        fullscreen;

	ri.Printf(PRINT_ALL, "...loading %s: ", name);

	// disable the 3Dfx splash screen and set gamma
	// we do this all the time, but it shouldn't hurt anything
	// on non-3Dfx stuff
	putenv("FX_GLIDE_NO_SPLASH=0");

	// Mesa VooDoo hacks
	putenv("MESA_GLX_FX=fullscreen\n");

	// load the QGL layer
	if(QGL_Init(name))
	{
		fullscreen = r_fullscreen->integer;

		// create the window and set up the context
		if(!GLW_StartDriverAndSetMode(name, r_mode->integer, fullscreen))
		{
			if(r_mode->integer != 3)
			{
				if(!GLW_StartDriverAndSetMode(name, 3, fullscreen))
				{
					goto fail;
				}
			}
			else
				goto fail;
		}

		return qtrue;
	}
	else
	{
		ri.Printf(PRINT_ALL, "failed\n");
	}
  fail:

	QGL_Shutdown();

	return qfalse;
}

/*
** XErrorHandler
**   the default X error handler exits the application
**   I found out that on some hosts some operations would raise X errors (GLXUnsupportedPrivateRequest)
**   but those don't seem to be fatal .. so the default would be to just ignore them
**   our implementation mimics the default handler behaviour (not completely cause I'm lazy)
*/
int qXErrorHandler(Display * dpy, XErrorEvent * ev)
{
	static char     buf[1024];

	XGetErrorText(dpy, ev->error_code, buf, 1024);
	ri.Printf(PRINT_ALL, "X Error of failed request: %s\n", buf);
	ri.Printf(PRINT_ALL, "  Major opcode of failed request: %d\n", ev->request_code, buf);
	ri.Printf(PRINT_ALL, "  Minor opcode of failed request: %d\n", ev->minor_code);
	ri.Printf(PRINT_ALL, "  Serial number of failed request: %d\n", ev->serial);
	return 0;
}

/*
** GLW_Init
**
** This routine is responsible for initializing the OS specific portions
** of OpenGL.  
*/
void GLW_Init(void)
{
	qboolean        attemptedlibGL = qfalse;
	qboolean        attempted3Dfx = qfalse;
	qboolean        success = qfalse;
	char            buf[1024];
	cvar_t         *lastValidRenderer = ri.Cvar_Get("r_lastValidRenderer", "(uninitialized)", CVAR_ARCHIVE);

	// guarded, as this is only relevant to SMP renderer thread
#ifdef SMP
	if(!XInitThreads())
	{
		Com_Printf("GLW_Init() - XInitThreads() failed, disabling r_smp\n");
		ri.Cvar_Set("r_smp", "0");
	}
#endif

	r_allowSoftwareGL = ri.Cvar_Get("r_allowSoftwareGL", "0", CVAR_LATCH);

	r_previousglDriver = ri.Cvar_Get("r_previousglDriver", "", CVAR_ROM);

	InitSig();

	// Hack here so that if the UI 
	if(*r_previousglDriver->string)
	{
		// The UI changed it on us, hack it back
		// This means the renderer can't be changed on the fly
		ri.Cvar_Set("r_glDriver", r_previousglDriver->string);
	}

	// set up our custom error handler for X failures
	XSetErrorHandler(&qXErrorHandler);

	//
	// load and initialize the specific OpenGL driver
	//
	if(!GLW_LoadOpenGL(r_glDriver->string))
	{
		if(!Q_stricmp(r_glDriver->string, OPENGL_DRIVER_NAME))
		{
			attemptedlibGL = qtrue;
		}
		else if(!Q_stricmp(r_glDriver->string, _3DFX_DRIVER_NAME))
		{
			attempted3Dfx = qtrue;
		}

#if 0
		// TTimo
		// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=455
		// old legacy load code, was confusing people who had a bad OpenGL setup
		if(!attempted3Dfx && !success)
		{
			attempted3Dfx = qtrue;
			if(GLW_LoadOpenGL(_3DFX_DRIVER_NAME))
			{
				ri.Cvar_Set("r_glDriver", _3DFX_DRIVER_NAME);
				r_glDriver->modified = qfalse;
				success = qtrue;
			}
		}
#endif

		// try ICD before trying 3Dfx standalone driver
		if(!attemptedlibGL && !success)
		{
			attemptedlibGL = qtrue;
			if(GLW_LoadOpenGL(OPENGL_DRIVER_NAME))
			{
				ri.Cvar_Set("r_glDriver", OPENGL_DRIVER_NAME);
				r_glDriver->modified = qfalse;
				success = qtrue;
			}
		}

		if(!success)
			ri.Error(ERR_FATAL, "GLW_Init() - could not load OpenGL subsystem\n");

	}

	// Save it in case the UI stomps it
	ri.Cvar_Set("r_previousglDriver", r_glDriver->string);

	// This values force the UI to disable driver selection
	glConfig.driverType = GLDRV_ICD;
	glConfig.hardwareType = GLHW_GENERIC;

	// get our config strings
	Q_strncpyz(glConfig.vendor_string, qglGetString(GL_VENDOR), sizeof(glConfig.vendor_string));
	Q_strncpyz(glConfig.renderer_string, qglGetString(GL_RENDERER), sizeof(glConfig.renderer_string));
	if(*glConfig.renderer_string && glConfig.renderer_string[strlen(glConfig.renderer_string) - 1] == '\n')
		glConfig.renderer_string[strlen(glConfig.renderer_string) - 1] = 0;
	Q_strncpyz(glConfig.version_string, qglGetString(GL_VERSION), sizeof(glConfig.version_string));
	Q_strncpyz(glConfig.extensions_string, qglGetString(GL_EXTENSIONS), sizeof(glConfig.extensions_string));

	//
	// chipset specific configuration
	//
	strcpy(buf, glConfig.renderer_string);
	strlwr(buf);

	//
	// NOTE: if changing cvars, do it within this block.  This allows them
	// to be overridden when testing driver fixes, etc. but only sets
	// them to their default state when the hardware is first installed/run.
	//
	if(Q_stricmp(lastValidRenderer->string, glConfig.renderer_string))
	{
		glConfig.hardwareType = GLHW_GENERIC;

		ri.Cvar_Set("r_textureMode", "GL_LINEAR_MIPMAP_NEAREST");

		// VOODOO GRAPHICS w/ 2MB
		if(Q_stristr(buf, "voodoo graphics/1 tmu/2 mb"))
		{
			ri.Cvar_Set("r_picmip", "2");
			ri.Cvar_Get("r_picmip", "1", CVAR_ARCHIVE | CVAR_LATCH);
		}
		else
		{
			ri.Cvar_Set("r_picmip", "1");

			if(Q_stristr(buf, "rage 128") || Q_stristr(buf, "rage128"))
			{
				ri.Cvar_Set("r_finish", "0");
			}
			// Savage3D and Savage4 should always have trilinear enabled
			else if(Q_stristr(buf, "savage3d") || Q_stristr(buf, "s3 savage4"))
			{
				ri.Cvar_Set("r_texturemode", "GL_LINEAR_MIPMAP_LINEAR");
			}
		}
	}

	//
	// this is where hardware specific workarounds that should be
	// detected/initialized every startup should go.
	//
	if(Q_stristr(buf, "radeon"))
	{
		glConfig.hardwareType = GLHW_ATI;
	}

	ri.Cvar_Set("r_lastValidRenderer", glConfig.renderer_string);

	// initialize extensions
	GLW_InitExtensions();
	GLW_InitGamma();

	InitSig();					// not clear why this is at begin & end of function

	return;
}


/*
** GLW_SwapBuffers
** 
** Responsible for doing a swapbuffers and possibly for other stuff
** as yet to be determined.
*/
void GLW_SwapBuffers(void)
{
	// don't flip if drawing to front buffer
	if(stricmp(r_drawBuffer->string, "GL_FRONT") != 0)
	{
		qglXSwapBuffers(dpy, win);
	}

	// check logging
	QGL_EnableLogging((qboolean) r_logFile->integer);	// bk001205 - was ->value
}

#ifdef SMP
/*
===========================================================

SMP acceleration

===========================================================
*/

static pthread_mutex_t smpMutex = PTHREAD_MUTEX_INITIALIZER;

static pthread_cond_t renderCommandsEvent = PTHREAD_COND_INITIALIZER;
static pthread_cond_t renderCompletedEvent = PTHREAD_COND_INITIALIZER;

static void     (*glimpRenderThread) (void);

static void    *GLimp_RenderThreadWrapper(void *arg)
{
	Com_Printf("Render thread starting\n");

	glimpRenderThread();

	qglXMakeCurrent(dpy, None, NULL);

	Com_Printf("Render thread terminating\n");

	return arg;
}

qboolean GLimp_SpawnRenderThread(void (*function) (void))
{
	pthread_t       renderThread;
	int             ret;

	pthread_mutex_init(&smpMutex, NULL);

	pthread_cond_init(&renderCommandsEvent, NULL);
	pthread_cond_init(&renderCompletedEvent, NULL);

	glimpRenderThread = function;

	ret = pthread_create(&renderThread, NULL,	// attributes
						 GLimp_RenderThreadWrapper, NULL);	// argument
	if(ret)
	{
		ri.Printf(PRINT_ALL, "pthread_create returned %d: %s", ret, strerror(ret));
		return qfalse;
	}
	else
	{
		ret = pthread_detach(renderThread);
		if(ret)
		{
			ri.Printf(PRINT_ALL, "pthread_detach returned %d: %s", ret, strerror(ret));
		}
	}

	return qtrue;
}

static volatile void *smpData = NULL;
static volatile qboolean smpDataReady;

void           *GLimp_RendererSleep(void)
{
	void           *data;

	qglXMakeCurrent(dpy, None, NULL);

	pthread_mutex_lock(&smpMutex);
	{
		smpData = NULL;
		smpDataReady = qfalse;

		// after this, the front end can exit GLimp_FrontEndSleep
		pthread_cond_signal(&renderCompletedEvent);

		while(!smpDataReady)
		{
			pthread_cond_wait(&renderCommandsEvent, &smpMutex);
		}

		data = (void *)smpData;
	}
	pthread_mutex_unlock(&smpMutex);

	qglXMakeCurrent(dpy, win, ctx);

	return data;
}

void GLimp_FrontEndSleep(void)
{
	pthread_mutex_lock(&smpMutex);
	{
		while(smpData)
		{
			pthread_cond_wait(&renderCompletedEvent, &smpMutex);
		}
	}
	pthread_mutex_unlock(&smpMutex);

	qglXMakeCurrent(dpy, win, ctx);
}

void GLimp_WakeRenderer(void *data)
{
	qglXMakeCurrent(dpy, None, NULL);

	pthread_mutex_lock(&smpMutex);
	{
		assert(smpData == NULL);
		smpData = data;
		smpDataReady = qtrue;

		// after this, the renderer can continue through GLimp_RendererSleep
		pthread_cond_signal(&renderCommandsEvent);
	}
	pthread_mutex_unlock(&smpMutex);
}

#else

void GLimp_RenderThreadWrapper(void *stub)
{
}
qboolean GLimp_SpawnRenderThread(void (*function) (void))
{
	ri.Printf(PRINT_WARNING, "ERROR: SMP support was disabled at compile time\n");
	return qfalse;
}
void           *GLimp_RendererSleep(void)
{
	return NULL;
}
void GLimp_FrontEndSleep(void)
{
}
void GLimp_WakeRenderer(void *data)
{
}

#endif

/*****************************************************************************/
/* MOUSE                                                                     */
/*****************************************************************************/

void IN_Init(void)
{
	Com_Printf("\n------- Input Initialization -------\n");
	// mouse variables
	in_mouse = Cvar_Get("in_mouse", "1", CVAR_ARCHIVE);
	in_dgamouse = Cvar_Get("in_dgamouse", "1", CVAR_ARCHIVE);

	// turn on-off sub-frame timing of X events
	in_subframe = Cvar_Get("in_subframe", "1", CVAR_ARCHIVE);

	// developer feature, allows to break without loosing mouse pointer
	in_nograb = Cvar_Get("in_nograb", "0", 0);

	// bk001130 - from cvs.17 (mkv), joystick variables
	in_joystick = Cvar_Get("in_joystick", "0", CVAR_ARCHIVE | CVAR_LATCH);
	// bk001130 - changed this to match win32
	in_joystickDebug = Cvar_Get("in_debugjoystick", "0", CVAR_TEMP);
	joy_threshold = Cvar_Get("joy_threshold", "0.15", CVAR_ARCHIVE);	// FIXME: in_joythreshold

	if(in_mouse->value)
		mouse_avail = qtrue;
	else
		mouse_avail = qfalse;

	IN_StartupJoystick();		// bk001130 - from cvs1.17 (mkv)
	Com_Printf("------------------------------------\n");
}

void IN_Shutdown(void)
{
	mouse_avail = qfalse;
}

void IN_Frame(void)
{

	// bk001130 - from cvs 1.17 (mkv)
	IN_JoyMove();				// FIXME: disable if on desktop?

	if(cls.keyCatchers & KEYCATCH_CONSOLE)
	{
		// temporarily deactivate if not in the game and
		// running on the desktop
		// voodoo always counts as full screen
		if(Cvar_VariableValue("r_fullscreen") == 0 && strcmp(Cvar_VariableString("r_glDriver"), _3DFX_DRIVER_NAME))
		{
			IN_DeactivateMouse();
			return;
		}
	}

	IN_ActivateMouse();
}

void IN_Activate(void)
{
}

// bk001130 - cvs1.17 joystick code (mkv) was here, no linux_joystick.c

void Sys_SendKeyEvents(void)
{
	// XEvent event; // bk001204 - unused

	if(!dpy)
		return;
	HandleEvents();
}


// bk010216 - added stubs for non-Linux UNIXes here
// FIXME - use NO_JOYSTICK or something else generic

#if defined( __FreeBSD__ )		// rb010123
void IN_StartupJoystick(void)
{
}
void IN_JoyMove(void)
{
}
#endif
