mirror of
https://github.com/Q3Rally-Team/rallyunlimited-engine.git
synced 2024-11-28 23:12:01 +00:00
574 lines
14 KiB
C
574 lines
14 KiB
C
#include "../client/client.h"
|
|
#include "unix_glw.h"
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/Xos.h>
|
|
|
|
#include <X11/extensions/Xrandr.h>
|
|
#include <X11/extensions/Xrender.h>
|
|
|
|
#define MAX_MONITORS 16
|
|
|
|
typedef struct
|
|
{
|
|
int x, y;
|
|
int w, h;
|
|
RROutput outputn;
|
|
RRCrtc crtcn;
|
|
RRMode curMode;
|
|
RRMode oldMode;
|
|
char name[32];
|
|
} monitor_t;
|
|
|
|
monitor_t monitors[ MAX_MONITORS ];
|
|
monitor_t *current_monitor;
|
|
monitor_t desktop_monitor;
|
|
|
|
unsigned short old_gamma[3][4096]; // backup
|
|
int old_gamma_size;
|
|
|
|
static qboolean BackupMonitorGamma( void );
|
|
|
|
// we resolve all functions dynamically
|
|
|
|
static void *r_lib = NULL;
|
|
|
|
Bool (*_XRRQueryExtension)( Display *dpy, int *event_base_return, int *error_base_return );
|
|
Status (*_XRRQueryVersion)( Display *dpy, int *major_version_return, int *minor_version_return );
|
|
XRRScreenResources* (*_XRRGetScreenResources)( Display *dpy, Window window );
|
|
void (*_XRRFreeScreenResources)( XRRScreenResources *resources );
|
|
XRROutputInfo *(*_XRRGetOutputInfo)( Display *dpy, XRRScreenResources *resources, RROutput output );
|
|
void (*_XRRFreeOutputInfo)( XRROutputInfo *outputInfo );
|
|
XRRCrtcInfo *(*_XRRGetCrtcInfo)( Display *dpy, XRRScreenResources *resources, RRCrtc crtc );
|
|
void (*_XRRFreeCrtcInfo)( XRRCrtcInfo *crtcInfo );
|
|
Status (*_XRRSetCrtcConfig)( Display *dpy, XRRScreenResources *resources, RRCrtc crtc,
|
|
Time timestamp, int x, int y, RRMode mode, Rotation rotation,
|
|
RROutput *outputs, int noutputs );
|
|
int (*_XRRGetCrtcGammaSize)( Display *dpy, RRCrtc crtc );
|
|
XRRCrtcGamma *(*_XRRGetCrtcGamma)( Display *dpy, RRCrtc crtc );
|
|
XRRCrtcGamma *(*_XRRAllocGamma)( int size );
|
|
void (*_XRRSetCrtcGamma)( Display *dpy, RRCrtc crtc, XRRCrtcGamma *gamma );
|
|
void (*_XRRFreeGamma)( XRRCrtcGamma *gamma );
|
|
|
|
static sym_t r_list[] =
|
|
{
|
|
{ (void**)&_XRRQueryExtension, "XRRQueryExtension" },
|
|
{ (void**)&_XRRQueryVersion, "XRRQueryVersion" },
|
|
{ (void**)&_XRRGetScreenResources, "XRRGetScreenResources" },
|
|
{ (void**)&_XRRFreeScreenResources, "XRRFreeScreenResources" },
|
|
{ (void**)&_XRRGetOutputInfo, "XRRGetOutputInfo" },
|
|
{ (void**)&_XRRFreeOutputInfo, "XRRFreeOutputInfo" },
|
|
{ (void**)&_XRRGetCrtcInfo, "XRRGetCrtcInfo" },
|
|
{ (void**)&_XRRFreeCrtcInfo, "XRRFreeCrtcInfo" },
|
|
{ (void**)&_XRRSetCrtcConfig, "XRRSetCrtcConfig" },
|
|
{ (void**)&_XRRGetCrtcGammaSize, "XRRGetCrtcGammaSize" },
|
|
{ (void**)&_XRRGetCrtcGamma, "XRRGetCrtcGamma" },
|
|
{ (void**)&_XRRAllocGamma, "XRRAllocGamma" },
|
|
{ (void**)&_XRRSetCrtcGamma, "XRRSetCrtcGamma" },
|
|
{ (void**)&_XRRFreeGamma, "XRRFreeGamma" },
|
|
};
|
|
|
|
|
|
static qboolean monitor_in_list( int x, int y, int w, int h, RROutput outputn, RRCrtc crtcn )
|
|
{
|
|
int i;
|
|
|
|
for ( i = 0; i < glw_state.monitorCount; i++ )
|
|
{
|
|
if ( monitors[i].x != x || monitors[i].y != y )
|
|
continue;
|
|
if ( monitors[i].w != w || monitors[i].h != h )
|
|
continue;
|
|
if ( monitors[i].outputn != outputn )
|
|
continue;
|
|
if ( monitors[i].crtcn != crtcn )
|
|
continue;
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
void monitor_add( int x, int y, int w, int h, const char *name, RROutput outputn, RRCrtc crtcn, RRMode mode )
|
|
{
|
|
monitor_t *m;
|
|
|
|
if ( glw_state.monitorCount >= MAX_MONITORS )
|
|
return;
|
|
|
|
if ( monitor_in_list( x, y, w, h, outputn, crtcn ) )
|
|
return;
|
|
|
|
m = monitors + glw_state.monitorCount;
|
|
|
|
m->x = x; m->y = y;
|
|
m->w = w; m->h = h;
|
|
|
|
Q_strncpyz( m->name, name, sizeof( m->name ) );
|
|
|
|
m->outputn = outputn;
|
|
m->crtcn = crtcn;
|
|
|
|
m->curMode = mode;
|
|
m->oldMode = mode;
|
|
|
|
glw_state.monitorCount++;
|
|
}
|
|
|
|
|
|
static int getRefreshRate( const XRRModeInfo *mode_info )
|
|
{
|
|
if ( mode_info->hTotal && mode_info->vTotal )
|
|
return ( (double)mode_info->dotClock / ( (double)mode_info->hTotal * (double)mode_info->vTotal ) );
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const XRRModeInfo* getModeInfo( const XRRScreenResources* sr, RRMode id )
|
|
{
|
|
int i;
|
|
|
|
for ( i = 0; i < sr->nmode; i++ )
|
|
if ( sr->modes[ i ].id == id )
|
|
return sr->modes + i;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
qboolean RandR_SetMode( int *width, int *height, int *rate )
|
|
{
|
|
monitor_t *m = &desktop_monitor;
|
|
XRRScreenResources *sr;
|
|
const XRRModeInfo *mode_info;
|
|
XRROutputInfo *output_info;
|
|
XRRCrtcInfo *crtc_info;
|
|
RRMode newMode;
|
|
int best_fit, best_dist, best_rate;
|
|
int dist, r, rr;
|
|
int x, y, w, h;
|
|
int n;
|
|
|
|
glw_state.randr_active = qfalse;
|
|
|
|
if ( !glw_state.randr_ext )
|
|
return glw_state.randr_active;
|
|
|
|
if ( *width == m->w && *height == m->h )
|
|
{
|
|
Com_Printf( "...using desktop display mode\n" );
|
|
glw_state.randr_active = qtrue;
|
|
return glw_state.randr_active;
|
|
}
|
|
|
|
sr = _XRRGetScreenResources( dpy, DefaultRootWindow( dpy ) );
|
|
|
|
output_info = _XRRGetOutputInfo( dpy, sr, m->outputn );
|
|
crtc_info = _XRRGetCrtcInfo( dpy, sr, m->crtcn );
|
|
|
|
best_rate = 999999999;
|
|
best_dist = 999999999;
|
|
best_fit = -1;
|
|
|
|
// find best-matching mode from available
|
|
for ( n = 0; n < output_info->nmode; n++ )
|
|
{
|
|
mode_info = getModeInfo( sr, output_info->modes[ n ] );
|
|
|
|
if ( !mode_info || ( mode_info->modeFlags & RR_Interlace ) )
|
|
continue;
|
|
|
|
// change original policy, i.e. allow selecting lower resolution modes
|
|
// as it is very unlikely that current mode is lower than mode you want to set
|
|
if ( mode_info->width > *width || mode_info->height > *height )
|
|
continue;
|
|
x = *width - mode_info->width;
|
|
y = *height - mode_info->height;
|
|
dist = ( x * x ) + ( y * y );
|
|
|
|
if ( *rate ) {
|
|
r = *rate - getRefreshRate( mode_info );
|
|
r = ( r * r );
|
|
} else {
|
|
r = best_rate;
|
|
}
|
|
|
|
if ( dist < best_dist || ( dist == best_dist && r < best_rate ) )
|
|
{
|
|
best_dist = dist;
|
|
best_rate = r;
|
|
best_fit = n;
|
|
newMode = output_info->modes[ n ];
|
|
w = mode_info->width; // save adjusted with
|
|
h = mode_info->height; // save adjusted height
|
|
rr = getRefreshRate( mode_info );
|
|
|
|
}
|
|
//fprintf( stderr, "mode[%i]: %i x %i @ %iHz.\n", i, mode_info->width, mode_info->height, getRefreshRate( mode_info ) );
|
|
}
|
|
|
|
if ( best_fit != -1 )
|
|
{
|
|
//Com_Printf( "...setting new mode 0x%x via xrandr \n", (int)newMode );
|
|
_XRRSetCrtcConfig( dpy, sr, m->crtcn, CurrentTime, crtc_info->x, crtc_info->y,
|
|
newMode, crtc_info->rotation, crtc_info->outputs, crtc_info->noutput );
|
|
|
|
m->curMode = newMode;
|
|
glw_state.randr_active = qtrue;
|
|
*width = w;
|
|
*height = h;
|
|
*rate = rr;
|
|
}
|
|
|
|
_XRRFreeCrtcInfo( crtc_info );
|
|
_XRRFreeOutputInfo( output_info );
|
|
_XRRFreeScreenResources( sr );
|
|
|
|
return glw_state.randr_active;
|
|
}
|
|
|
|
|
|
void RandR_RestoreMode( void )
|
|
{
|
|
monitor_t *m = &desktop_monitor;
|
|
XRRScreenResources *sr;
|
|
XRROutputInfo *output_info;
|
|
XRRCrtcInfo *crtc_info;
|
|
|
|
if ( !glw_state.randr_ext || !glw_state.randr_active || !dpy )
|
|
return;
|
|
|
|
glw_state.randr_active = qfalse;
|
|
|
|
if ( m->curMode == m->oldMode )
|
|
return;
|
|
|
|
Com_Printf( "...restoring desktop display mode\n" );
|
|
|
|
sr = _XRRGetScreenResources( dpy, DefaultRootWindow( dpy ) );
|
|
|
|
output_info = _XRRGetOutputInfo( dpy, sr, m->outputn );
|
|
crtc_info = _XRRGetCrtcInfo( dpy, sr, m->crtcn );
|
|
|
|
_XRRSetCrtcConfig( dpy, sr, m->crtcn, CurrentTime, crtc_info->x, crtc_info->y,
|
|
m->oldMode, crtc_info->rotation, crtc_info->outputs, crtc_info->noutput );
|
|
|
|
_XRRFreeCrtcInfo( crtc_info );
|
|
_XRRFreeOutputInfo( output_info );
|
|
_XRRFreeScreenResources( sr );
|
|
|
|
m->curMode = m->oldMode;
|
|
|
|
current_monitor = NULL;
|
|
}
|
|
|
|
|
|
static void BuildMonitorList( void )
|
|
{
|
|
XRRScreenResources *sr;
|
|
XRRCrtcInfo *crtc_info;
|
|
XRROutputInfo *info;
|
|
int outn;
|
|
|
|
glw_state.monitorCount = 0;
|
|
|
|
sr = _XRRGetScreenResources( dpy, DefaultRootWindow( dpy ) );
|
|
|
|
if ( !sr )
|
|
return;
|
|
|
|
for ( outn = 0; outn < sr->noutput; outn++ )
|
|
{
|
|
info = _XRRGetOutputInfo( dpy, sr, sr->outputs[ outn ] );
|
|
if ( info )
|
|
{
|
|
if ( info->connection == RR_Connected && info->crtc != None )
|
|
{
|
|
crtc_info = _XRRGetCrtcInfo( dpy, sr, info->crtc );
|
|
if ( crtc_info )
|
|
{
|
|
//fprintf( stderr, "%ix%i @%ix%i outn:%i (crtc:%i) %s\n",
|
|
// crtc_info->width, crtc_info->height,
|
|
// crtc_info->x, crtc_info->y,
|
|
// (int)outn, (int)info->crtc, info->name );
|
|
if ( crtc_info->width && crtc_info->height )
|
|
{
|
|
monitor_add( crtc_info->x, crtc_info->y, crtc_info->width, crtc_info->height,
|
|
info->name, sr->outputs[ outn ], info->crtc, crtc_info->mode );
|
|
}
|
|
_XRRFreeCrtcInfo( crtc_info );
|
|
}
|
|
}
|
|
_XRRFreeOutputInfo( info );
|
|
}
|
|
}
|
|
_XRRFreeScreenResources( sr );
|
|
}
|
|
|
|
|
|
static monitor_t *FindNearestMonitor( int x, int y, int w, int h )
|
|
{
|
|
monitor_t *m, *found, *list[ MAX_MONITORS ];
|
|
unsigned long dx, dy, dist, nearest;
|
|
int cx, cy;
|
|
int i, cnt, minx, maxx, slen;
|
|
|
|
cx = x + w/2;
|
|
|
|
cy = y + h/2;
|
|
|
|
cnt = 0;
|
|
for ( i = 0; i < glw_state.monitorCount; i++ )
|
|
{
|
|
m = &monitors[ i ];
|
|
// window center intersection
|
|
if ( cx >= m->x && cx < (m->x + m->w) && cy >= m->y && cy < (m->y + m->h) )
|
|
list[ cnt++ ] = m;
|
|
}
|
|
|
|
if ( cnt == 1 ) // single monitor found
|
|
{
|
|
return list[ 0 ];
|
|
}
|
|
|
|
if ( cnt > 1 )
|
|
{
|
|
// divide screen width on segments
|
|
minx = 999999999;
|
|
maxx = 0;
|
|
for ( i = 0; i < cnt ; i++ )
|
|
{
|
|
m = list[ i ];
|
|
if ( m->x < minx )
|
|
minx = m->x;
|
|
if ( m->x + m->w > maxx )
|
|
maxx = m->x + m->w;
|
|
}
|
|
slen = ( maxx - minx ) / cnt;
|
|
|
|
return list[ cx / slen ];
|
|
}
|
|
|
|
// search by nearest distance to window center
|
|
found = NULL;
|
|
nearest = 0xFFFFFFFF;
|
|
|
|
for ( i = 0; i < glw_state.monitorCount; i++ )
|
|
{
|
|
m = &monitors[ i ];
|
|
// nearest distance
|
|
//dx = MIN( abs( m->x - ( x + w ) ), abs( x - ( m->x + m->w ) ) );
|
|
//dy = MIN( abs( m->y - ( y + h ) ), abs( y - ( m->y + m->h ) ) );
|
|
// nearest distance from window center to screen center
|
|
dx = (m->x + m->w/2) - cx;
|
|
dy = (m->y + m->h/2) - cy;
|
|
dist = ( dx * dx ) + ( dy * dy );
|
|
if ( nearest > dist )
|
|
{
|
|
nearest = dist;
|
|
found = m;
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
|
|
void RandR_UpdateMonitor( int x, int y, int w, int h )
|
|
{
|
|
monitor_t *cm;
|
|
// int i;
|
|
|
|
if ( !glw_state.monitorCount || glw_state.cdsFullscreen )
|
|
return;
|
|
|
|
// try to find monitor to which input coordinates belongs to
|
|
cm = FindNearestMonitor( x, y, w, h );
|
|
|
|
if ( !cm )
|
|
return;
|
|
|
|
if ( cm != current_monitor )
|
|
{
|
|
qboolean gammaSet = glw_state.gammaSet;
|
|
|
|
if ( glw_state.randr_gamma && gammaSet )
|
|
{
|
|
RandR_RestoreGamma();
|
|
}
|
|
|
|
// save new monitor
|
|
current_monitor = cm;
|
|
memcpy( &desktop_monitor, cm, sizeof( desktop_monitor ) );
|
|
|
|
glw_state.desktop_x = cm->x;
|
|
glw_state.desktop_y = cm->y;
|
|
glw_state.desktop_width = cm->w;
|
|
glw_state.desktop_height = cm->h;
|
|
|
|
glw_state.desktop_ok = qtrue;
|
|
|
|
Com_Printf( "...current monitor: %ix%i@%i,%i %s\n",
|
|
glw_state.desktop_width, glw_state.desktop_height,
|
|
glw_state.desktop_x, glw_state.desktop_y,
|
|
desktop_monitor.name );
|
|
|
|
BackupMonitorGamma();
|
|
|
|
if ( glw_state.randr_gamma && gammaSet && re.SetColorMappings )
|
|
{
|
|
re.SetColorMappings();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static qboolean BackupMonitorGamma( void )
|
|
{
|
|
XRRCrtcGamma* gamma;
|
|
int gammaRampSize;
|
|
|
|
if ( !glw_state.monitorCount || !glw_state.randr_gamma )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
gammaRampSize = _XRRGetCrtcGammaSize( dpy, desktop_monitor.crtcn );
|
|
if ( gammaRampSize < 256 || gammaRampSize > 4096 )
|
|
{
|
|
glw_state.randr_gamma = qfalse;
|
|
fprintf( stderr, "...unsupported gamma ramp size: %i\n", gammaRampSize );
|
|
return qfalse;
|
|
}
|
|
|
|
gamma = _XRRGetCrtcGamma( dpy, desktop_monitor.crtcn );
|
|
|
|
if ( gamma )
|
|
{
|
|
memcpy( old_gamma[0], gamma->red, gammaRampSize * sizeof( unsigned short ) );
|
|
memcpy( old_gamma[1], gamma->green, gammaRampSize * sizeof( unsigned short ) );
|
|
memcpy( old_gamma[2], gamma->blue, gammaRampSize * sizeof( unsigned short ) );
|
|
old_gamma_size = gammaRampSize;
|
|
|
|
_XRRFreeGamma( gamma );
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
static void SetMonitorGamma( unsigned short *red, unsigned short *green, unsigned short *blue, int size )
|
|
{
|
|
XRRCrtcGamma* gamma;
|
|
|
|
gamma = _XRRAllocGamma( size );
|
|
if ( gamma )
|
|
{
|
|
memcpy( gamma->red, red, size * sizeof( unsigned short ) );
|
|
memcpy( gamma->green, green, size * sizeof( unsigned short ) );
|
|
memcpy( gamma->blue, blue, size * sizeof( unsigned short ) );
|
|
_XRRSetCrtcGamma( dpy, desktop_monitor.crtcn, gamma );
|
|
_XRRFreeGamma( gamma );
|
|
}
|
|
}
|
|
|
|
void RandR_RestoreGamma( void )
|
|
{
|
|
if ( glw_state.randr_gamma && old_gamma_size )
|
|
SetMonitorGamma( old_gamma[0], old_gamma[1], old_gamma[2], old_gamma_size );
|
|
|
|
old_gamma_size = 0;
|
|
}
|
|
|
|
|
|
void RandR_SetGamma( unsigned char red[256], unsigned char green[256], unsigned char blue[256] )
|
|
{
|
|
unsigned short table[3][4096];
|
|
|
|
if ( BuildGammaRampTable( red, green, blue, old_gamma_size, table ) )
|
|
{
|
|
SetMonitorGamma( table[0], table[1], table[2], old_gamma_size );
|
|
glw_state.gammaSet = qtrue;
|
|
}
|
|
}
|
|
|
|
|
|
qboolean RandR_Init( int x, int y, int w, int h )
|
|
{
|
|
int event_base, error_base;
|
|
int ver_major = 1, ver_minor = 2;
|
|
int i;
|
|
|
|
glw_state.randr_ext = qfalse;
|
|
glw_state.randr_active = qfalse;
|
|
glw_state.randr_gamma = qfalse;
|
|
|
|
glw_state.monitorCount = 0;
|
|
current_monitor = NULL;
|
|
memset( monitors, 0, sizeof( monitors ) );
|
|
memset( &desktop_monitor, 0, sizeof( desktop_monitor ) );
|
|
|
|
if ( r_lib == NULL )
|
|
{
|
|
r_lib = Sys_LoadLibrary( "libXrandr.so.2" );
|
|
if ( r_lib == NULL )
|
|
{
|
|
r_lib = Sys_LoadLibrary( "libXrandr.so" );
|
|
}
|
|
if ( r_lib == NULL )
|
|
{
|
|
Com_Printf( "...error loading libXrandr\n" );
|
|
goto __fail;
|
|
}
|
|
}
|
|
|
|
for ( i = 0 ; i < ARRAY_LEN( r_list ); i++ )
|
|
{
|
|
*r_list[ i ].symbol = Sys_LoadFunction( r_lib, r_list[ i ].name );
|
|
if ( *r_list[ i ].symbol == NULL )
|
|
{
|
|
Com_Printf( "...couldn't find '%s' in libXrandr\n", r_list[ i ].name );
|
|
goto __fail;
|
|
}
|
|
}
|
|
|
|
if ( !_XRRQueryExtension( dpy, &event_base, &error_base ) || !_XRRQueryVersion( dpy, &ver_major, &ver_minor ) )
|
|
{
|
|
Com_Printf( "...RandR extension is not available.\n" );
|
|
goto __fail;
|
|
}
|
|
|
|
Com_Printf( "...RandR extension version %i.%i detected.\n", ver_major, ver_minor );
|
|
|
|
glw_state.randr_ext = qtrue;
|
|
|
|
glw_state.randr_gamma = qtrue; // this will be reset by BackupMonitorGamma() if gamma is not available
|
|
|
|
BuildMonitorList();
|
|
|
|
RandR_UpdateMonitor( x, y, w, h );
|
|
|
|
return qtrue;
|
|
|
|
__fail:
|
|
RandR_Done();
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
void RandR_Done( void )
|
|
{
|
|
if ( r_lib )
|
|
{
|
|
Sys_UnloadLibrary( r_lib );
|
|
r_lib = NULL;
|
|
}
|
|
glw_state.randr_ext = qfalse;
|
|
glw_state.randr_gamma = qfalse;
|
|
}
|