rallyunlimited-engine/code/unix/x11_randr.c

575 lines
14 KiB
C
Raw Normal View History

2024-02-02 16:46:17 +00:00
#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;
}