#include "../client/client.h" #include "unix_glw.h" #include #include #include #include #include #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; }