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

Return to Castle Wolfenstein single player GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. 

This file is part of the Return to Castle Wolfenstein single player GPL Source Code (RTCW SP Source Code).  

RTCW SP 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 3 of the License, or
(at your option) any later version.

RTCW SP 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 RTCW SP Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 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:
**
** GLimp_EndFrame
** GLimp_Init
** GLimp_Shutdown
** GLimp_SwitchFullscreen
** GLimp_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/tr_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   "Return to Castle Wolfenstein"

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;
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;

// 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 dgamouse = qfalse;
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;

/*
* Find the first occurrence of find in s.
*/
// bk001130 - from cvs1.17 (mkv), const
// bk001130 - made first argument const
static const char *Q_stristr( const char *s, const char *find ) {
	register char c, sc;
	register size_t len;

	if ( ( c = *find++ ) != 0 ) {
		if ( c >= 'a' && c <= 'z' ) {
			c -= ( 'a' - 'A' );
		}
		len = strlen( find );
		do
		{
			do
			{
				if ( ( sc = *s++ ) == 0 ) {
					return NULL;
				}
				if ( sc >= 'a' && sc <= 'z' ) {
					sc -= ( 'a' - 'A' );
				}
			} while ( sc != c );
		} while ( Q_stricmpn( s, find, len ) != 0 );
		s--;
	}
	return s;
}

/*****************************************************************************
** 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;

	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 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
			ri.Printf( PRINT_ALL, "Failed to detect XF86DGA Mouse\n" );
			ri.Cvar_Set( "in_dgamouse", "0" );
		} else
		{
			dgamouse = qtrue;
			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 ( dgamouse ) {
		dgamouse = qfalse;
		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

static void HandleEvents( void ) {
	int b;
	int key;
	XEvent event;
	qboolean dowarp = qfalse;
	char *p;
	int dx, dy;
	int t;

	if ( !dpy ) {
		return;
	}

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

		case KeyRelease:

			// 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( 0, SE_KEY, key, qfalse, 0, NULL );
			break;

		case MotionNotify:
			if ( mouse_active ) {
				if ( dgamouse ) {
					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;
					}
					t = Sys_Milliseconds();
					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;
						t = Sys_Milliseconds();
						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:
			if ( event.xbutton.button == 4 ) {
				Sys_QueEvent( 0, SE_KEY, K_MWHEELUP, qtrue, 0, NULL );
			} else if ( event.xbutton.button == 5 ) {
				Sys_QueEvent( 0, 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( 0, SE_KEY, K_MOUSE1 + b, qtrue, 0, NULL );
			}
			break;

		case ButtonRelease:
			if ( event.xbutton.button == 4 ) {
				Sys_QueEvent( 0, SE_KEY, K_MWHEELUP, qfalse, 0, NULL );
			} else if ( event.xbutton.button == 5 ) {
				Sys_QueEvent( 0, 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( 0, 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 ) );
	}
}

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

	if ( !mouse_active ) {
		install_grabs();
		mouse_active = qtrue;
	}
}

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

	if ( mouse_active ) {
		uninstall_grabs();
		mouse_active = qfalse;
	}
}
/*****************************************************************************/

static qboolean signalcaught = qfalse;;

void Sys_Exit( int ); // bk010104 - abstraction

static void signal_handler( int sig ) { // bk010104 - replace this... (NOTE TTimo huh?)
	if ( signalcaught ) {
		printf( "DOUBLE SIGNAL FAULT: Received signal %d, exiting...\n", sig );
		Sys_Exit( 1 ); // bk010104 - abstraction
	}

	signalcaught = qtrue;
	printf( "Received signal %d, exiting...\n", sig );
	GLimp_Shutdown(); // bk010104 - shouldn't this be CL_Shutdown
	Sys_Exit( 0 ); // bk010104 - abstraction NOTE TTimo send a 0 to avoid DOUBLE SIGNAL FAULT
}

static void InitSig( void ) {
	signal( SIGHUP, signal_handler );
	signal( SIGQUIT, signal_handler );
	signal( SIGILL, signal_handler );
	signal( SIGTRAP, signal_handler );
	signal( SIGIOT, signal_handler );
	signal( SIGBUS, signal_handler );
	signal( SIGFPE, signal_handler );
	signal( SIGSEGV, signal_handler );
	signal( SIGTERM, signal_handler );
}

/*
** GLimp_SetGamma
**
** This routine should only be called if glConfig.deviceSupportsGamma is TRUE
*/
void GLimp_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 );
}

/*
** GLimp_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 GLimp_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
		//   ( show_bug.cgi?id=33 )
		XCloseDisplay( dpy );
	}
	vidmode_active = qfalse;
	dpy = NULL;
	win = 0;
	ctx = NULL;

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

	QGL_Shutdown();
}

/*
** GLimp_LogComment
*/
void GLimp_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

	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;
	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 );

	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)

	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" );
			GLimp_Shutdown();
			return RSERR_INVALID_MODE;
		} else
		{
			ri.Printf( PRINT_ALL, "...using software Mesa (r_allowSoftwareGL==1).\n" );
		}
	}

	return RSERR_OK;
}

/*
** GLW_InitExtensions
*/
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->value ) {
			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 ) dlsym( glw_state.OpenGLLib, "glMultiTexCoord2fARB" );
			qglActiveTextureARB = ( PFNGLACTIVETEXTUREARBPROC ) dlsym( glw_state.OpenGLLib, "glActiveTextureARB" );
			qglClientActiveTextureARB = ( PFNGLCLIENTACTIVETEXTUREARBPROC ) dlsym( glw_state.OpenGLLib, "glClientActiveTextureARB" );

			if ( qglActiveTextureARB ) {
				qglGetIntegerv( GL_MAX_ACTIVE_TEXTURES_ARB, &glConfig.maxActiveTextures );

				if ( glConfig.maxActiveTextures > 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_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 ) )dlsym( glw_state.OpenGLLib, "glLockArraysEXT" );
			qglUnlockArraysEXT = ( void ( APIENTRY * )( void ) )dlsym( glw_state.OpenGLLib, "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_NV_fog_distance
	if ( Q_stristr( glConfig.extensions_string, "GL_NV_fog_distance" ) ) {
		if ( r_ext_NV_fog_dist->integer ) {
			glConfig.NVFogAvailable = qtrue;
			ri.Printf( PRINT_ALL, "...using GL_NV_fog_distance\n" );
		} else {
			ri.Printf( PRINT_ALL, "...ignoring GL_NV_fog_distance\n" );
			ri.Cvar_Set( "r_ext_NV_fog_dist", "0" );
		}
	} else {
		ri.Printf( PRINT_ALL, "...GL_NV_fog_distance not found\n" );
		ri.Cvar_Set( "r_ext_NV_fog_dist", "0" );
	}

}

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
**
** GLimp_win.c internal 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;
}

/*
** GLimp_Init
**
** This routine is responsible for initializing the OS specific portions
** of OpenGL.
*/
void GLimp_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 );
	// cvar_t	*cv; // bk001204 - unused

	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
		// show_bug.cgi?id=455
		// removing outdated Voodoo GL loading
		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, "GLimp_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, "banshee" ) || Q_stristr( buf, "Voodoo_Graphics" ) ) {
		glConfig.hardwareType = GLHW_3DFX_2D3D;
	} else if ( Q_stristr( buf, "rage pro" ) || Q_stristr( buf, "RagePro" ) ) {
		glConfig.hardwareType = GLHW_RAGEPRO;
	} else if ( Q_stristr( buf, "permedia2" ) ) {
		glConfig.hardwareType = GLHW_PERMEDIA2;
	} else if ( Q_stristr( buf, "riva 128" ) ) {
		glConfig.hardwareType = GLHW_RIVA128;
	} else if ( Q_stristr( buf, "riva tnt " ) ) {
	}

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

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

	InitSig();

	return;
}


/*
** GLimp_EndFrame
**
** Responsible for doing a swapbuffers and possibly for other stuff
** as yet to be determined.  Probably better not to make this a GLimp
** function and instead do a call to GLimp_SwapBuffers.
*/
void GLimp_EndFrame( void ) {
	// don't flip if drawing to front buffer
	if ( Q_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

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

sem_t renderCommandsEvent;
sem_t renderCompletedEvent;
sem_t renderActiveEvent;

void ( *glimpRenderThread )( void );

void *GLimp_RenderThreadWrapper( void *stub ) {
	glimpRenderThread();
	return NULL;
}


/*
=======================
GLimp_SpawnRenderThread
=======================
*/
pthread_t renderThreadHandle;
qboolean GLimp_SpawnRenderThread( void ( *function )( void ) ) {

	sem_init( &renderCommandsEvent, 0, 0 );
	sem_init( &renderCompletedEvent, 0, 0 );
	sem_init( &renderActiveEvent, 0, 0 );

	glimpRenderThread = function;

	if ( pthread_create( &renderThreadHandle, NULL,
						 GLimp_RenderThreadWrapper, NULL ) ) {
		return qfalse;
	}

	return qtrue;
}

static void  *smpData;
//static	int		glXErrors; // bk001204 - unused

void *GLimp_RendererSleep( void ) {
	void  *data;

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

	sem_wait( &renderCommandsEvent );

	data = smpData;

	// after this, the main thread can exit GLimp_WakeRenderer
	sem_post( &renderActiveEvent );

	return data;
}


void GLimp_FrontEndSleep( void ) {
	sem_wait( &renderCompletedEvent );
}


void GLimp_WakeRenderer( void *data ) {
	smpData = data;

	// after this, the renderer can continue through GLimp_RendererSleep
	sem_post( &renderCommandsEvent );

	sem_wait( &renderActiveEvent );
}

#else

void GLimp_RenderThreadWrapper( void *stub ) {}
qboolean GLimp_SpawnRenderThread( void ( *function )( void ) ) {
	return qfalse;
}
void *GLimp_RendererSleep( void ) {
	return NULL;
}
void GLimp_FrontEndSleep( void ) {}
void GLimp_WakeRenderer( void *data ) {}

#endif

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

void IN_Init( void ) {
	// mouse variables
	in_mouse = Cvar_Get( "in_mouse", "1", CVAR_ARCHIVE );
	in_dgamouse = Cvar_Get( "in_dgamouse", "1", CVAR_ARCHIVE );

	// 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)
}

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
