cnq3/code/unix/linux_glimp.cpp

1377 lines
33 KiB
C++

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena 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.
Quake III Arena 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 Quake III Arena 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:
**
** GLimp_EndFrame
** GLimp_Init
** GLimp_Shutdown
** GLimp_SwitchFullscreen
**
*/
#include <termios.h>
#include <sys/ioctl.h>
#ifdef __linux__
#include <sys/stat.h>
#include <sys/vt.h>
#endif
#include <dlfcn.h>
#include <signal.h>
#include <pthread.h>
#include <semaphore.h>
#include <GL/glx.h>
#include <X11/keysym.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XInput2.h>
#include "../renderer/tr_local.h"
#include "../client/client.h"
#include "linux_local.h"
#include "unix_glw.h"
static Display *dpy = NULL;
static Window win = 0;
///////////////////////////////////////////////////////////////
struct Mouse {
virtual qbool Init() { return qtrue; }
virtual qbool Activate( qbool active );
virtual void Shutdown() {}
virtual void Read( int* mx, int* my ) = 0;
virtual qbool ProcessEvent( XEvent& event ) { return qfalse; } // returns true if the event was handled
Mouse() : active(qfalse) {}
static const int buttons[];
private:
qbool active;
};
static Mouse* mouse;
const int Mouse::buttons[] = { 0, K_MOUSE1, K_MOUSE3, K_MOUSE2, K_MWHEELUP, K_MWHEELDOWN, 0, 0, K_MOUSE4, K_MOUSE5 };
qbool Mouse::Activate( qbool _active )
{
if (active == _active)
return qfalse;
active = _active;
return qtrue;
}
///////////////////////////////////////////////////////////////
struct xmouse_t : public Mouse {
virtual qbool Activate( qbool active );
virtual void Read( int* mx, int* my );
virtual qbool ProcessEvent( XEvent& event );
int x, y, prev_x, prev_y;
int window_center_x, window_center_y;
};
static xmouse_t xmouse;
qbool xmouse_t::Activate( qbool active )
{
if (!active)
return qtrue;
window_center_x = glConfig.vidWidth / 2;
window_center_y = glConfig.vidHeight / 2;
XWarpPointer( dpy, None, win, 0,0,0,0, window_center_x, window_center_y );
return qtrue;
}
void xmouse_t::Read( int* mx, int* my )
{
// we could just use XQueryPointer here
// but since we're processing events anyway we might as well use the data from those
*mx = x;
*my = y;
x = y = 0;
if (*mx || *my) {
XWarpPointer( dpy, None, win, 0,0,0,0, window_center_x, window_center_y );
}
}
qbool xmouse_t::ProcessEvent( XEvent& event )
{
switch (event.type) {
case MotionNotify: {
// if this is exactly recentering the mouse, it's probably from our warp
// but there's no way to actually handle this problem well in X
if ((event.xmotion.x == window_center_x) && (event.xmotion.y == window_center_y)) {
//Com_Printf( "WARP: mx %d my %d \n", event.xmotion.x, event.xmotion.y );
prev_x = event.xmotion.x;
prev_y = event.xmotion.y;
break;
}
//Com_Printf( "mx %d my %d \n", event.xmotion.x, event.xmotion.y );
// note that accumulating motion events like this is actually wrong
// because button events are processed immediately
// but the windows code has the same bug, and nobody seems upset by it there
x += (event.xmotion.x - prev_x);
y += (event.xmotion.y - prev_y);
prev_x = event.xmotion.x;
prev_y = event.xmotion.y;
return qtrue;
}
break;
case ButtonPress:
case ButtonRelease:
//Com_Printf( "Button %u \n", event.xbutton.button );
if ((event.xbutton.button > 0) && (event.xbutton.button < (sizeof(buttons) / sizeof(buttons[0])))) {
int button = buttons[event.xbutton.button];
if (button) {
Sys_QueEvent( 0, SE_KEY, button, (event.type == ButtonPress), 0, NULL );
}
}
break;
}
return qfalse;
}
///////////////////////////////////////////////////////////////
// this is a total suckfest. debian didn't multiarch xinput for some reason,
// so using it means having to dlsym everything or you can't build both i386 and x64
typedef XIDeviceInfo* (*xiQueryDevice)( Display *display, int deviceid, int *ndevices_return );
typedef void (*xiFreeDeviceInfo)( XIDeviceInfo* );
typedef Status (*xiQueryVersion)( Display *display, int *major_version_inout, int *minor_version_inout );
typedef Status (*xiSelectEvents)( Display *display, Window win, XIEventMask *masks, int num_masks );
static void* libxi = 0;
static xiQueryDevice pxiQueryDevice;
static xiFreeDeviceInfo pxiFreeDeviceInfo;
static xiQueryVersion pxiQueryVersion;
static xiSelectEvents pxiSelectEvents;
static int xiOPCode;
static void* XI_GetProcAddress( const char* symbol )
{
if (!libxi)
return 0;
return dlsym( libxi, symbol );
}
#define XI(F) { pxi##F = (xi##F)XI_GetProcAddress( "XI"#F ); }
struct rawmouse_t : public Mouse {
virtual qbool Init();
//virtual qbool Activate( qbool active );
virtual void Shutdown();
virtual void Read( int* mx, int* my );
virtual qbool ProcessEvent( XEvent& event );
private:
int x, y, prev_x, prev_y;
int mode;
static int FindMouse();
static qbool ValidateMouse( const XIDeviceInfo* info );
};
static rawmouse_t rawmouse;
qbool rawmouse_t::ValidateMouse( const XIDeviceInfo* info )
{
if (!info->enabled)
return qfalse;
const XIAnyClassInfo** classes = (const XIAnyClassInfo**)info->classes;
for (int i = 0; i < info->num_classes; ++i) {
if (classes[i]->type == XIValuatorClass) {
const XIValuatorClassInfo* v = (const XIValuatorClassInfo*)classes[i];
if ((v->mode == XIModeRelative) && !v->resolution)
return qfalse; // invalid combination (eg XTEST mouse)
ri.Printf( PRINT_DEVELOPER, "ValidateMouse: accepted device %d\n", info->deviceid );
rawmouse.mode = v->mode;
return qtrue;
}
}
return qfalse;
}
int rawmouse_t::FindMouse()
{
int n, device = 0;
XIDeviceInfo* info = pxiQueryDevice( dpy, XIAllDevices, &n );
cvar_t* m_device = Cvar_Get( "m_device", "0", 0 );
if (m_device->integer) {
for (int i = 0; i < n; ++i) {
if ((info[i].deviceid == m_device->integer) && ValidateMouse(&info[i])) {
device = info[i].deviceid;
pxiFreeDeviceInfo( info );
return device;
}
}
}
for (int i = 0; i < n; ++i) {
if ((info[i].use == XIMasterPointer) && ValidateMouse(&info[i])) {
device = info[i].deviceid;
break;
}
}
pxiFreeDeviceInfo( info );
return device;
}
qbool rawmouse_t::Init()
{
int event, error;
if (!XQueryExtension(dpy, "XInputExtension", &xiOPCode, &event, &error))
return qfalse;
libxi = dlopen( "libXi.so.6", RTLD_GLOBAL | RTLD_LAZY );
XI(QueryDevice);
XI(FreeDeviceInfo);
XI(QueryVersion);
XI(SelectEvents);
int major = 2, minor = 1;
if (pxiQueryVersion(dpy, &major, &minor) == BadRequest) {
return qfalse;
}
XIEventMask eventmask;
unsigned char mask[4] = { 0 };
eventmask.deviceid = FindMouse();
if (!eventmask.deviceid) {
return qfalse;
}
eventmask.mask_len = sizeof(mask);
eventmask.mask = mask;
XISetMask( mask, XI_RawMotion );
XISetMask( mask, XI_RawButtonPress );
XISetMask( mask, XI_RawButtonRelease );
if (pxiSelectEvents( dpy, DefaultRootWindow(dpy), &eventmask, 1 ) == BadRequest) {
return qfalse;
}
XSelectInput( dpy, win, KeyPressMask | KeyReleaseMask | FocusChangeMask );
return qtrue;
}
void rawmouse_t::Shutdown()
{
if (libxi) {
dlclose( libxi );
libxi = 0;
}
}
void rawmouse_t::Read( int* mx, int* my )
{
*mx = x;
*my = y;
x = y = 0;
}
qbool rawmouse_t::ProcessEvent( XEvent& event )
{
if ((event.xcookie.type != GenericEvent) || (event.xcookie.extension != xiOPCode))
return qfalse;
if (!XGetEventData(dpy, &event.xcookie))
return qfalse;
const XIRawEvent* raw = (const XIRawEvent*)event.xcookie.data;
switch (event.xcookie.evtype) {
case XI_RawMotion: {
int mx = 0, my = 0;
const double* val = raw->raw_values;
for (int i = 0; i < raw->valuators.mask_len * 8; ++i) {
if (XIMaskIsSet(raw->valuators.mask, i)) {
//Com_Printf( "RawMotion on axis %d: %lf \n", i, *val );
if (i == 0)
mx += *val;
else if (i == 1)
my += *val;
++val;
}
}
if (mode == XIModeRelative) {
x += mx;
y += my;
}
// workaround for virtualbox bugs
if (mode == XIModeAbsolute) {
//Com_Printf( "mx %d my %d dx %d dy %d \n", mx, my, (mx - prev_x), (my - prev_y) );
if (mx) { x += (mx - prev_x) * 1920 / 0x8000; prev_x = mx; }
if (my) { y += (my - prev_y) * 1200 / 0x8000; prev_y = my; }
}
}
break;
case XI_RawButtonPress:
case XI_RawButtonRelease:
//Com_Printf( "XI_RawButton %u \n", raw->detail );
if ((raw->detail > 0) && (raw->detail < (sizeof(buttons) / sizeof(buttons[0])))) {
int button = buttons[raw->detail];
if (button) {
Sys_QueEvent( 0, SE_KEY, button, (event.xcookie.evtype == XI_RawButtonPress), 0, NULL );
}
}
break;
}
XFreeEventData(dpy, &event.xcookie);
return qtrue;
}
///////////////////////////////////////////////////////////////
/*
** 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)
*/
static const char* TranslateKey( XKeyEvent* ev, int* key )
{
static char raw[2], translated[2];
KeySym keysym;
int len;
*key = 0;
// get the normal interpretation of the key without messing with shifts, for SE_CHAR
XLookupString( ev, translated, sizeof(translated), &keysym, 0 );
// then get the keysym that we actually want with no shifts at all, for SE_KEY
ev->state = 0;
len = XLookupString( ev, raw, sizeof(raw), &keysym, 0 );
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;
case XK_BackSpace: *key = K_BACKSPACE; break;
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_Add: *key = K_KP_PLUS; break;
case XK_KP_Divide: *key = K_KP_SLASH; break;
case XK_KP_Multiply: *key = K_KP_STAR; break;
case XK_KP_Subtract: *key = K_KP_MINUS; 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 (len) {
*key = raw[0];
} else {
ri.Printf( PRINT_DEVELOPER, "XLookupString failed on KeySym %d\n", keysym );
return NULL;
}
break;
}
return translated;
}
///////////////////////////////////////////////////////////////
static qboolean mouse_avail;
static qboolean mouse_active = qfalse;
static int mouse_accel_numerator;
static int mouse_accel_denominator;
static int mouse_threshold;
#define KEY_MASK (KeyPressMask | KeyReleaseMask)
#define MOUSE_MASK (ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ButtonMotionMask )
#define X_MASK (KEY_MASK | MOUSE_MASK | VisibilityChangeMask | StructureNotifyMask | FocusChangeMask )
cvar_t* in_nograb;
static Cursor CreateNullCursor()
{
Pixmap cursormask = XCreatePixmap( dpy, win, 1, 1, 1 );
XGCValues xgc;
xgc.function = GXclear;
GC gc = XCreateGC( dpy, cursormask, GCFunction, &xgc );
XFillRectangle( dpy, cursormask, gc, 0, 0, 1, 1 );
XColor colour;
colour.pixel = 0;
colour.red = 0;
colour.flags = DoRed;
Cursor cursor = XCreatePixmapCursor( dpy, cursormask, cursormask, &colour, &colour, 0, 0 );
XFreePixmap( dpy, cursormask );
XFreeGC( dpy, gc );
return cursor;
}
static void install_grabs()
{
XSync( dpy, False );
XDefineCursor( dpy, win, CreateNullCursor() );
XGrabPointer( dpy, win, 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 );
XWarpPointer( dpy, None, win, 0, 0, 0, 0, glConfig.vidWidth / 2, glConfig.vidHeight / 2 );
XGrabKeyboard( dpy, win, False, GrabModeAsync, GrabModeAsync, CurrentTime );
XSync( dpy, False );
}
static void uninstall_grabs()
{
XChangePointerControl( dpy, True, True, mouse_accel_numerator, mouse_accel_denominator, mouse_threshold );
XUngrabPointer( dpy, CurrentTime );
XUngrabKeyboard( dpy, CurrentTime );
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;
}
// X sends Release/Press pairs for auto-repeat even though the key hasn't actually been released
static qboolean KeyRepeat( const XEvent* event )
{
if (!X11_PendingInput())
return qfalse;
XEvent peek;
XPeekEvent( dpy, &peek );
if ( (peek.type == KeyPress) && (peek.xkey.keycode == event->xkey.keycode) && (peek.xkey.time == event->xkey.time) ) {
XNextEvent( dpy, &peek ); // discard the CURRENT event, which is the RELEASE
return qtrue;
}
return qfalse;
}
static void HandleEvents()
{
XEvent event;
int key;
const char* p;
if (!dpy)
return;
while (XPending(dpy))
{
XNextEvent(dpy, &event);
if (mouse && mouse->ProcessEvent(event))
continue;
switch (event.type)
{
case KeyPress:
p = TranslateKey( &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:
if (!cls.keyCatchers && KeyRepeat(&event)) {
continue;
}
TranslateKey( &event.xkey, &key );
if (key) {
Sys_QueEvent( 0, SE_KEY, key, qfalse, 0, NULL );
}
break;
case FocusIn:
case FocusOut:
// reset all modifiers on focus change
Sys_QueEvent( 0, SE_KEY, K_ALT, qfalse, 0, NULL );
Sys_QueEvent( 0, SE_KEY, K_CTRL, qfalse, 0, NULL );
Sys_QueEvent( 0, SE_KEY, K_SHIFT, qfalse, 0, NULL );
break;
}
}
}
static void IN_ActivateMouse()
{
if (!mouse_avail || !dpy || !win)
return;
if (!mouse_active) {
if (!in_nograb->value)
install_grabs();
mouse_active = qtrue;
}
mouse->Activate( qtrue );
}
static void IN_DeactivateMouse()
{
if (!mouse_avail || !dpy || !win)
return;
if (mouse_active) {
if (!in_nograb->value)
uninstall_grabs();
mouse_active = qfalse;
}
}
///////////////////////////////////////////////////////////////
#define WINDOW_CLASS_NAME "CNQ3"
// OpenGL driver
#define OPENGL_DRIVER_NAME "libGL.so.1"
typedef enum
{
RSERR_OK,
RSERR_INVALID_FULLSCREEN,
RSERR_INVALID_MODE,
RSERR_UNKNOWN
} rserr_t;
glwstate_t glw_state;
static GLXContext ctx = NULL;
static cvar_t *in_mouse;
// 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_fullscreen;
static qboolean vidmode_active = qfalse;
static int scrnum;
/*
** 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;
ri.Printf( PRINT_DEVELOPER, "Shutting down OpenGL subsystem\n" );
// IN_DeactivateMouse();
qglXDestroyContext(dpy, ctx);
ctx = NULL;
if (win) {
XDestroyWindow(dpy, win);
win = 0;
}
// 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);
dpy = NULL;
vidmode_active = qfalse;
QGL_Shutdown();
memset( &glConfig, 0, sizeof( glConfig ) );
memset( &glState, 0, sizeof( glState ) );
}
static rserr_t GLW_SetMode( qboolean fullscreen )
{
int attrib[] = {
GLX_RGBA,
GLX_RED_SIZE, 8,
GLX_GREEN_SIZE, 8,
GLX_BLUE_SIZE, 8,
GLX_DOUBLEBUFFER,
GLX_DEPTH_SIZE, 24,
GLX_STENCIL_SIZE, 8,
None
};
XVisualInfo *visinfo;
XSetWindowAttributes attr;
XSizeHints sizehints;
unsigned long mask;
int actualWidth, actualHeight;
int i;
ri.Printf( PRINT_ALL, "Initializing OpenGL\n" );
if (!(dpy = XOpenDisplay(NULL))) {
ri.Error( ERR_FATAL, "GLW_SetMode - Couldn't open the X display" );
return RSERR_INVALID_MODE;
}
scrnum = DefaultScreen(dpy);
Window root = RootWindow(dpy, scrnum);
if ( !R_GetModeInfo( &glConfig.vidWidth, &glConfig.vidHeight, &glConfig.windowAspect ) ) {
glConfig.vidWidth = XDisplayWidth(dpy, scrnum);
glConfig.vidHeight = XDisplayHeight(dpy, scrnum);
glConfig.windowAspect = (float)glConfig.vidWidth / glConfig.vidHeight;
vidmode_active = qtrue;
fullscreen = qfalse;
}
ri.Printf( PRINT_DEVELOPER, "...setting mode %dx%d %s\n", glConfig.vidWidth, glConfig.vidHeight, fullscreen ? "FS" : "W" );
actualWidth = glConfig.vidWidth;
actualHeight = glConfig.vidHeight;
visinfo = qglXChooseVisual(dpy, scrnum, attrib);
if (!visinfo)
{
ri.Printf( PRINT_ALL, "Couldn't get a visual\n" );
return RSERR_INVALID_MODE;
}
glConfig.colorBits = 32;
glConfig.depthBits = 24;
glConfig.stencilBits = 8;
/* 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)
XFree( visinfo );
qglXMakeCurrent(dpy, win, ctx);
ri.Printf( PRINT_ALL, "GL_RENDERER: %s\n", (const char*)qglGetString( GL_RENDERER ) );
return RSERR_OK;
}
static qboolean GLW_StartDriverAndSetMode( qboolean fullscreen )
{
rserr_t err;
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( 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\n" );
return qfalse;
default:
break;
}
return qtrue;
}
static void GLW_InitExtensions()
{
ri.Printf( PRINT_ALL, "Initializing OpenGL extensions\n" );
int maxAnisotropy = 0;
if ( strstr( glConfig.extensions_string, "GL_EXT_texture_filter_anisotropic" ) )
{
if ( r_ext_max_anisotropy->integer > 1 )
{
qglGetIntegerv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy );
if ( maxAnisotropy <= 0 ) {
ri.Printf( PRINT_DEVELOPER, "...GL_EXT_texture_filter_anisotropic not properly supported!\n" );
maxAnisotropy = 0;
}
else
{
ri.Printf( PRINT_DEVELOPER, "...using GL_EXT_texture_filter_anisotropic (max: %i)\n", maxAnisotropy );
}
}
else
{
ri.Printf( PRINT_DEVELOPER, "...ignoring GL_EXT_texture_filter_anisotropic\n" );
}
}
else
{
ri.Printf( PRINT_DEVELOPER, "...GL_EXT_texture_filter_anisotropic not found\n" );
}
Cvar_Set( "r_ext_max_anisotropy", va("%i", maxAnisotropy) );
}
/*
** GLW_LoadOpenGL
**
** GLimp_win.c internal function that that attempts to load and use
** a specific OpenGL DLL.
*/
static qboolean GLW_LoadOpenGL( void )
{
qboolean fullscreen;
ri.Printf( PRINT_ALL, "...loading %s: ", OPENGL_DRIVER_NAME );
// load the QGL layer
if ( QGL_Init( OPENGL_DRIVER_NAME ) )
{
fullscreen = r_fullscreen->integer;
// create the window and set up the context
if ( !GLW_StartDriverAndSetMode( fullscreen ) )
{
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;
}
void QGL_SwapInterval( Display *dpy, Window win, int interval );
/*
** GLimp_Init
**
** This routine is responsible for initializing the OS specific portions
** of OpenGL.
*/
void GLimp_Init( void )
{
qboolean attemptedlibGL = qfalse;
qboolean success = qfalse;
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("GLimp_Init() - XInitThreads() failed, disabling r_smp\n");
ri.Cvar_Set( "r_smp", "0" );
}
#endif
// set up our custom error handler for X failures
XSetErrorHandler(&qXErrorHandler);
in_nograb = Cvar_Get( "in_nograb", "0", 0 );
// load appropriate DLL and initialize subsystem
if (!GLW_LoadOpenGL() )
ri.Error( ERR_FATAL, "GLimp_Init()->GLW_LoadOpenGL() - could not load OpenGL subsystem (using '%s')\n", OPENGL_DRIVER_NAME );
// get our config strings
Q_strncpyz( glConfig.vendor_string, (char *)qglGetString (GL_VENDOR), sizeof( glConfig.vendor_string ) );
Q_strncpyz( glConfig.renderer_string, (char *)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, (char *)qglGetString (GL_VERSION), sizeof( glConfig.version_string ) );
Q_strncpyz( glConfig.extensions_string, (char *)qglGetString (GL_EXTENSIONS), sizeof( glConfig.extensions_string ) );
ri.Cvar_Set( "r_lastValidRenderer", glConfig.renderer_string );
// initialize extensions
GLW_InitExtensions();
if ( !GLW_InitGL2() || !QGL_InitGL2() )
ri.Error( ERR_FATAL, "GLimp_Init - could not find or initialize a suitable OpenGL 2 subsystem\n" );
QGL_SwapInterval( dpy, win, r_swapInterval->integer );
IN_Init();
}
/*
** 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)
{
// update the swap interval
if ( r_swapInterval->modified )
{
r_swapInterval->modified = qfalse;
QGL_SwapInterval( dpy, win, r_swapInterval->integer );
}
qglXSwapBuffers( dpy, win );
}
#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
static void IN_StartupMouse()
{
assert( !mouse );
mouse = 0;
cvar_t* in_mouse = Cvar_Get( "in_mouse", "1", CVAR_ARCHIVE|CVAR_LATCH );
in_mouse->modified = qfalse;
if (!in_mouse->integer) {
Com_Printf( "Mouse not active.\n" );
return;
}
if (in_mouse->integer == 1) {
if (rawmouse.Init()) {
mouse = &rawmouse;
Com_Printf( "Using XInput2\n" );
return;
}
Com_Printf( "XInput2 mouse initialization failed\n" );
}
mouse = &xmouse;
mouse->Init();
Com_Printf( "Using XWindows mouse input\n" );
}
void IN_Init()
{
QSUBSYSTEM_INIT_START( "Input" );
//IN_InitKeyboard();
IN_StartupMouse();
in_mouse = Cvar_Get( "in_mouse", "1", CVAR_ARCHIVE|CVAR_LATCH );
// 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
// fix this: it's crap AND wrong: the mouse is what decides if the mouse is available or not
if (in_mouse->value)
mouse_avail = qtrue;
else
mouse_avail = qfalse;
IN_StartupJoystick();
QSUBSYSTEM_INIT_DONE( "Input" );
}
void IN_Shutdown(void)
{
mouse_avail = qfalse;
//IN_Activate( qfalse );
if (mouse) {
mouse->Shutdown();
mouse = 0;
}
}
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
if ( Cvar_VariableValue ("r_fullscreen") == 0 )
{
IN_DeactivateMouse ();
return;
}
}
IN_ActivateMouse();
if (!mouse)
return;
int mx, my;
mouse->Read( &mx, &my );
if ( !mx && !my )
return;
Sys_QueEvent( 0, SE_MOUSE, mx, my, 0, NULL );
}
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__ ) || defined( __sun)) // rb010123
void IN_StartupJoystick( void ) {}
void IN_JoyMove( void ) {}
#endif