/* =========================================================================== 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 . 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 #include #ifdef __linux__ #include #include #endif #include #include #include #include #include // bk001204 #include // bk001206 - from my Heretic2 by way of Ryan's Fakk2 // Needed for the new X11_PendingInput() function. #include #include #include #include "../renderer/tr_local.h" #include "../client/client.h" #include "linux_local.h" // bk001130 #include "unix_glw.h" #include #include #include #include #include #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