RetroArch/deps/switchres/custom_video_xrandr.cpp
Subs 62e6439329
[CRT] Update switchres (new PR) (#15526)
* Remove deps/switchres to update it right after

* Squashed 'deps/switchres/' content from commit 4df022c68a

git-subtree-dir: deps/switchres
git-subtree-split: 4df022c68a43b6481e18d5aa8e0ea27481291d1a
2023-07-28 01:39:39 +02:00

1205 lines
40 KiB
C++

/**************************************************************
custom_video_xrandr.cpp - Linux XRANDR video management layer
---------------------------------------------------------
Switchres Modeline generation engine for emulation
License GPL-2.0+
Copyright 2010-2021 Chris Kennedy, Antonio Giner,
Alexandre Wodarczyk, Gil Delescluse
**************************************************************/
#include <stdio.h>
#include <exception>
#include <dlfcn.h>
#include <string.h>
#include "custom_video_xrandr.h"
#include "log.h"
//============================================================
// library functions
//============================================================
#define XRRAddOutputMode p_XRRAddOutputMode
#define XRRConfigCurrentConfiguration p_XRRConfigCurrentConfiguration
#define XRRCreateMode p_XRRCreateMode
#define XRRDeleteOutputMode p_XRRDeleteOutputMode
#define XRRDestroyMode p_XRRDestroyMode
#define XRRFreeCrtcInfo p_XRRFreeCrtcInfo
#define XRRFreeOutputInfo p_XRRFreeOutputInfo
#define XRRFreeScreenConfigInfo p_XRRFreeScreenConfigInfo
#define XRRFreeScreenResources p_XRRFreeScreenResources
#define XRRGetCrtcInfo p_XRRGetCrtcInfo
#define XRRGetOutputInfo p_XRRGetOutputInfo
#define XRRGetScreenInfo p_XRRGetScreenInfo
#define XRRGetScreenResourcesCurrent p_XRRGetScreenResourcesCurrent
#define XRRQueryVersion p_XRRQueryVersion
#define XRRSetCrtcConfig p_XRRSetCrtcConfig
#define XRRSetScreenSize p_XRRSetScreenSize
#define XRRGetScreenSizeRange p_XRRGetScreenSizeRange
#define XCloseDisplay p_XCloseDisplay
#define XGrabServer p_XGrabServer
#define XOpenDisplay p_XOpenDisplay
#define XSync p_XSync
#define XUngrabServer p_XUngrabServer
#define XSetErrorHandler p_XSetErrorHandler
#define XClearWindow p_XClearWindow
#define XFillRectangle p_XFillRectangle
#define XCreateGC p_XCreateGC
//============================================================
// error_handler
// xorg error handler (static)
//============================================================
int xrandr_timing::ms_xerrors = 0;
int xrandr_timing::ms_xerrors_flag = 0;
static int (*old_error_handler)(Display *, XErrorEvent *);
static __typeof__(XGetErrorText) *p_XGetErrorText;
#define XGetErrorText p_XGetErrorText
static int error_handler(Display *dpy, XErrorEvent *err)
{
char buf[64];
XGetErrorText(dpy, err->error_code, buf, 64);
buf[0] = '\0';
xrandr_timing::ms_xerrors |= xrandr_timing::ms_xerrors_flag;
old_error_handler(dpy, err);
log_error("XRANDR: <-> (error_handler) [ERROR] %s error code %d flags %02x\n", buf, err->error_code, xrandr_timing::ms_xerrors);
return 0;
}
//============================================================
// id for class object (static)
//============================================================
static int s_id = 0;
//============================================================
// screen management exclusivity array (static)
//============================================================
static int s_total_managed_screen = 0;
static int *sp_shared_screen_manager = NULL;
//============================================================
// desktop screen positions (static)
//============================================================
static XRRCrtcInfo *sp_desktop_crtc = NULL;
//============================================================
// xrandr_timing::xrandr_timing
//============================================================
xrandr_timing::xrandr_timing(char *device_name, custom_video_settings *vs)
{
m_vs = *vs;
// Increment id for each new screen
m_id = ++s_id;
log_verbose("XRANDR: <%d> (xrandr_timing) creation (%s)\n", m_id, device_name);
// Copy screen device name and limit size
if ((strlen(device_name) + 1) > 32)
{
strncpy(m_device_name, device_name, 31);
log_error("XRANDR: <%d> (xrandr_timing) [ERROR] the device name is too long it has been trucated to %s\n", m_id, m_device_name);
}
else
strcpy(m_device_name, device_name);
if (m_vs.screen_reordering)
{
if (m_id == 1)
m_enable_screen_reordering = 1;
}
else if (m_vs.screen_compositing)
m_enable_screen_compositing = 1;
log_verbose("XRANDR: <%d> (xrandr_timing) checking X availability (early stub)\n", m_id);
m_x11_handle = dlopen("libX11.so", RTLD_NOW);
if (m_x11_handle)
{
p_XOpenDisplay = (__typeof__(XOpenDisplay)) dlsym(m_x11_handle, "XOpenDisplay");
if (p_XOpenDisplay == NULL)
{
log_error("XRANDR: <%d> (xrandr_timing) [ERROR] missing func %s in %s\n", m_id, "XOpenDisplay", "X11_LIBRARY");
throw std::exception();
}
else
{
if (!XOpenDisplay(NULL))
{
log_verbose("XRANDR: <%d> (xrandr_timing) X server not found\n", m_id);
throw std::exception();
}
}
}
else
{
log_error("XRANDR: <%d> (xrandr_timing) [ERROR] missing %s library\n", m_id, "X11_LIBRARY");
throw std::exception();
}
s_total_managed_screen++;
}
//============================================================
// xrandr_timing::~xrandr_timing
//============================================================
xrandr_timing::~xrandr_timing()
{
s_total_managed_screen--;
if (s_total_managed_screen == 0)
{
s_id = 0;
if (sp_desktop_crtc)
delete[]sp_desktop_crtc;
if (sp_shared_screen_manager)
delete[]sp_shared_screen_manager;
// Restore default desktop background
XClearWindow(m_pdisplay, m_root);
}
// Free the display
if (m_pdisplay != NULL)
XCloseDisplay(m_pdisplay);
// close Xrandr library
if (m_xrandr_handle)
dlclose(m_xrandr_handle);
// close X11 library
if (m_x11_handle)
dlclose(m_x11_handle);
}
//============================================================
// xrandr_timing::init
//============================================================
bool xrandr_timing::init()
{
log_verbose("XRANDR: <%d> (init) loading Xrandr library\n", m_id);
if (!m_xrandr_handle)
m_xrandr_handle = dlopen("libXrandr.so", RTLD_NOW);
if (m_xrandr_handle)
{
p_XRRAddOutputMode = (__typeof__(XRRAddOutputMode)) dlsym(m_xrandr_handle, "XRRAddOutputMode");
if (p_XRRAddOutputMode == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRAddOutputMode", "XRANDR_LIBRARY");
return false;
}
p_XRRConfigCurrentConfiguration = (__typeof__(XRRConfigCurrentConfiguration)) dlsym(m_xrandr_handle, "XRRConfigCurrentConfiguration");
if (p_XRRConfigCurrentConfiguration == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRConfigCurrentConfiguration", "XRANDR_LIBRARY");
return false;
}
p_XRRCreateMode = (__typeof__(XRRCreateMode)) dlsym(m_xrandr_handle, "XRRCreateMode");
if (p_XRRCreateMode == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRCreateMode", "XRANDR_LIBRARY");
return false;
}
p_XRRDeleteOutputMode = (__typeof__(XRRDeleteOutputMode)) dlsym(m_xrandr_handle, "XRRDeleteOutputMode");
if (p_XRRDeleteOutputMode == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRDeleteOutputMode", "XRANDR_LIBRARY");
return false;
}
p_XRRDestroyMode = (__typeof__(XRRDestroyMode)) dlsym(m_xrandr_handle, "XRRDestroyMode");
if (p_XRRDestroyMode == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRDestroyMode", "XRANDR_LIBRARY");
return false;
}
p_XRRFreeCrtcInfo = (__typeof__(XRRFreeCrtcInfo)) dlsym(m_xrandr_handle, "XRRFreeCrtcInfo");
if (p_XRRFreeCrtcInfo == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRFreeCrtcInfo", "XRANDR_LIBRARY");
return false;
}
p_XRRFreeOutputInfo = (__typeof__(XRRFreeOutputInfo)) dlsym(m_xrandr_handle, "XRRFreeOutputInfo");
if (p_XRRFreeOutputInfo == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRFreeOutputInfo", "XRANDR_LIBRARY");
return false;
}
p_XRRFreeScreenConfigInfo = (__typeof__(XRRFreeScreenConfigInfo)) dlsym(m_xrandr_handle, "XRRFreeScreenConfigInfo");
if (p_XRRFreeScreenConfigInfo == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRFreeScreenConfigInfo", "XRANDR_LIBRARY");
return false;
}
p_XRRFreeScreenResources = (__typeof__(XRRFreeScreenResources)) dlsym(m_xrandr_handle, "XRRFreeScreenResources");
if (p_XRRFreeScreenResources == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRFreeScreenResources", "XRANDR_LIBRARY");
return false;
}
p_XRRGetCrtcInfo = (__typeof__(XRRGetCrtcInfo)) dlsym(m_xrandr_handle, "XRRGetCrtcInfo");
if (p_XRRGetCrtcInfo == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRGetCrtcInfo", "XRANDR_LIBRARY");
return false;
}
p_XRRGetOutputInfo = (__typeof__(XRRGetOutputInfo)) dlsym(m_xrandr_handle, "XRRGetOutputInfo");
if (p_XRRGetOutputInfo == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRGetOutputInfo", "XRANDR_LIBRARY");
return false;
}
p_XRRGetScreenInfo = (__typeof__(XRRGetScreenInfo)) dlsym(m_xrandr_handle, "XRRGetScreenInfo");
if (p_XRRGetScreenInfo == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRGetScreenInfo", "XRANDR_LIBRARY");
return false;
}
p_XRRGetScreenResourcesCurrent = (__typeof__(XRRGetScreenResourcesCurrent)) dlsym(m_xrandr_handle, "XRRGetScreenResourcesCurrent");
if (p_XRRGetScreenResourcesCurrent == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRGetScreenResourcesCurrent", "XRANDR_LIBRARY");
return false;
}
p_XRRQueryVersion = (__typeof__(XRRQueryVersion)) dlsym(m_xrandr_handle, "XRRQueryVersion");
if (p_XRRQueryVersion == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRQueryVersion", "XRANDR_LIBRARY");
return false;
}
p_XRRSetCrtcConfig = (__typeof__(XRRSetCrtcConfig)) dlsym(m_xrandr_handle, "XRRSetCrtcConfig");
if (p_XRRSetCrtcConfig == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRSetCrtcConfig", "XRANDR_LIBRARY");
return false;
}
p_XRRSetScreenSize = (__typeof__(XRRSetScreenSize)) dlsym(m_xrandr_handle, "XRRSetScreenSize");
if (p_XRRSetScreenSize == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRSetScreenSize", "XRANDR_LIBRARY");
return false;
}
p_XRRGetScreenSizeRange = (__typeof__(XRRGetScreenSizeRange)) dlsym(m_xrandr_handle, "XRRGetScreenSizeRange");
if (p_XRRGetScreenSizeRange == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XRRSetScreenSize", "XRANDR_LIBRARY");
return false;
}
}
else
{
log_error("XRANDR: <%d> (init) [ERROR] missing %s library\n", m_id, "XRANDR_LIBRARY");
return false;
}
log_verbose("XRANDR: <%d> (init) loading X11 library\n", m_id);
if (!m_x11_handle)
m_x11_handle = dlopen("libX11.so", RTLD_NOW);
if (m_x11_handle)
{
p_XCloseDisplay = (__typeof__(XCloseDisplay)) dlsym(m_x11_handle, "XCloseDisplay");
if (p_XCloseDisplay == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s\n", m_id, "XCloseDisplay", "X11_LIBRARY");
return false;
}
p_XGrabServer = (__typeof__(XGrabServer)) dlsym(m_x11_handle, "XGrabServer");
if (p_XGrabServer == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s\n", m_id, "XGrabServer", "X11_LIBRARY");
return false;
}
p_XOpenDisplay = (__typeof__(XOpenDisplay)) dlsym(m_x11_handle, "XOpenDisplay");
if (p_XOpenDisplay == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s\n", m_id, "XOpenDisplay", "X11_LIBRARY");
return false;
}
p_XSync = (__typeof__(XSync)) dlsym(m_x11_handle, "XSync");
if (p_XSync == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s\n", m_id, "XSync", "X11_LIBRARY");
return false;
}
p_XUngrabServer = (__typeof__(XUngrabServer)) dlsym(m_x11_handle, "XUngrabServer");
if (p_XUngrabServer == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s\n", m_id, "XUngrabServer", "X11_LIBRARY");
return false;
}
p_XSetErrorHandler = (__typeof__(XSetErrorHandler)) dlsym(m_x11_handle, "XSetErrorHandler");
if (p_XSetErrorHandler == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s\n", m_id, "XSetErrorHandler", "X11_LIBRARY");
return false;
}
p_XGetErrorText = (__typeof__(XGetErrorText)) dlsym(m_x11_handle, "XGetErrorText");
if (p_XGetErrorText == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s\n", m_id, "XGetErrorText", "X11_LIBRARY");
return false;
}
p_XClearWindow = (__typeof__(XClearWindow)) dlsym(m_x11_handle, "XClearWindow");
if (p_XClearWindow == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XClearWindow", "X11_LIBRARY");
return false;
}
p_XFillRectangle = (__typeof__(XFillRectangle)) dlsym(m_x11_handle, "XFillRectangle");
if (p_XFillRectangle == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XFillRectangle", "X11_LIBRARY");
return false;
}
p_XCreateGC = (__typeof__(XCreateGC)) dlsym(m_x11_handle, "XCreateGC");
if (p_XCreateGC == NULL)
{
log_error("XRANDR: <%d> (init) [ERROR] missing func %s in %s", m_id, "XCreateGC", "X11_LIBRARY");
return false;
}
}
else
{
log_error("XRANDR: <%d> (init) [ERROR] missing %s library\n", m_id, "X11_LIBRARY");
return false;
}
// Select current display and root window
// m_pdisplay is global to reduce open/close calls, resource is freed when class is destroyed
if (!m_pdisplay)
m_pdisplay = XOpenDisplay(NULL);
if (!m_pdisplay)
{
log_error("XRANDR: <%d> (init) [ERROR] failed to connect to the X server\n", m_id);
return false;
}
// Display XRANDR version
int major_version, minor_version;
XRRQueryVersion(m_pdisplay, &major_version, &minor_version);
log_verbose("XRANDR: <%d> (init) version %d.%d\n", m_id, major_version, minor_version);
if (major_version < 1 || (major_version == 1 && minor_version < 2))
{
log_error("XRANDR: <%d> (init) [ERROR] Xrandr version 1.2 or above is required\n", m_id);
return false;
}
// screen_pos defines screen position, 0 is default first screen position and equivalent to 'auto'
int screen_pos = -1;
bool detected = false;
// Handle the screen name, "auto", "screen[0-9]" and XRANDR device name
if (strlen(m_device_name) == 7 && !strncmp(m_device_name, "screen", 6) && m_device_name[6] >= '0' && m_device_name[6] <= '9')
screen_pos = m_device_name[6] - '0';
else if (strlen(m_device_name) == 1 && m_device_name[0] >= '0' && m_device_name[0] <= '9')
screen_pos = m_device_name[0] - '0';
if (ScreenCount(m_pdisplay) > 1)
log_verbose("XRANDR: <%d> (init) [WARNING] screen count is %d, unpredictable behavior to be expected\n", m_id, ScreenCount(m_pdisplay));
for (int screen = 0; !detected && screen < ScreenCount(m_pdisplay); screen++)
{
log_verbose("XRANDR: <%d> (init) check screen number %d\n", m_id, screen);
m_screen = screen;
m_root = RootWindow(m_pdisplay, screen);
XRRScreenResources *resources = XRRGetScreenResourcesCurrent(m_pdisplay, m_root);
if (m_id == 1)
{
// Prepare the shared screen array
sp_shared_screen_manager = new int[resources->noutput];
for (int o = 0; o < resources->noutput; o++)
sp_shared_screen_manager[o] = 0;
// Save all active crtc positions
sp_desktop_crtc = new XRRCrtcInfo[resources->ncrtc];
for (int c = 0; c < resources->ncrtc; c++)
memcpy(&sp_desktop_crtc[c], XRRGetCrtcInfo(m_pdisplay, resources, resources->crtcs[c]), sizeof(XRRCrtcInfo));
}
// Get default screen rotation from screen configuration
XRRScreenConfiguration *sc = XRRGetScreenInfo(m_pdisplay, m_root);
XRRConfigCurrentConfiguration(sc, &m_desktop_rotation);
XRRFreeScreenConfigInfo(sc);
Rotation current_rotation = 0;
int output_position = 0;
for (int o = 0; o < resources->noutput; o++)
{
XRROutputInfo *output_info = XRRGetOutputInfo(m_pdisplay, resources, resources->outputs[o]);
if (!output_info)
{
log_error("XRANDR: <%d> (init) [ERROR] could not get output 0x%x information\n", m_id, (unsigned int)resources->outputs[o]);
continue;
}
// Check all connected output
if (m_desktop_output == -1 && output_info->connection == RR_Connected && output_info->crtc)
{
if (!strcmp(m_device_name, "auto") || !strcmp(m_device_name, output_info->name) || output_position == screen_pos)
{
// store the output connector
m_desktop_output = o;
// store screen minium and maximum resolutions
int min_width;
int max_width;
int min_height;
int max_height;
XRRGetScreenSizeRange (m_pdisplay, m_root, &min_width, &min_height, &max_width, &max_height);
m_min_width = min_width;
m_max_width = max_width;
m_min_height = min_height;
m_max_height = max_height;
if (sp_shared_screen_manager[m_desktop_output] == 0)
{
sp_shared_screen_manager[m_desktop_output] = m_id;
m_managed = 1;
}
// identify the current modeline and rotation
XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(m_pdisplay, resources, output_info->crtc);
current_rotation = crtc_info->rotation;
for (int m = 0; m < resources->nmode && m_desktop_mode.id == 0; m++)
{
// Get screen mode
if (crtc_info->mode == resources->modes[m].id)
{
m_desktop_mode = resources->modes[m];
m_last_crtc = *crtc_info;
}
}
XRRFreeCrtcInfo(crtc_info);
// check screen rotation (left or right)
if (current_rotation & 0xe)
{
m_crtc_flags = MODE_ROTATED;
log_verbose("XRANDR: <%d> (init) desktop rotation is %s\n", m_id, (current_rotation & 0x2) ? "left" : ((current_rotation & 0x8) ? "right" : "inverted"));
}
}
output_position++;
}
log_verbose("XRANDR: <%d> (init) check output connector '%s' active %d crtc %d %s\n", m_id, output_info->name, output_info->connection == RR_Connected ? 1 : 0, output_info->crtc ? 1 : 0, m_desktop_output == o ? (m_managed ? "[SELECTED]" : "[UNMANAGED]") : "");
XRRFreeOutputInfo(output_info);
}
XRRFreeScreenResources(resources);
// Check if screen has been detected
detected = m_desktop_output != -1;
}
if (!detected)
log_error("XRANDR: <%d> (init) [ERROR] no screen detected\n", m_id);
else if (m_enable_screen_reordering)
{
// Global screen placement
modeline mode = {};
mode.type = MODE_DESKTOP;
set_timing(&mode, XRANDR_ENABLE_SCREEN_REORDERING);
}
return detected;
}
//============================================================
// xrandr_timing::update_mode
//============================================================
bool xrandr_timing::update_mode(modeline *mode)
{
if (!mode)
return false;
// Handle no screen detected case
if (m_desktop_output == -1)
{
log_error("XRANDR: <%d> (update_mode) [ERROR] no screen detected\n", m_id);
return false;
}
if (!delete_mode(mode))
{
log_error("XRANDR: <%d> (update_mode) [ERROR] delete operation not successful", m_id);
return false;
}
if (!add_mode(mode))
{
log_error("XRANDR: <%d> (update_mode) [ERROR] add operation not successful", m_id);
return false;
}
return true;
}
//============================================================
// xrandr_timing::add_mode
//============================================================
bool xrandr_timing::add_mode(modeline *mode)
{
if (!mode)
return false;
// Handle no screen detected case
if (m_desktop_output == -1)
{
log_error("XRANDR: <%d> (add_mode) [ERROR] no screen detected\n", m_id);
return false;
}
if (!m_managed)
{
log_error("XRANDR: <%d> (add_mode) [WARNING] this screen is managed by <%d>\n", m_id, sp_shared_screen_manager[m_desktop_output]);
return false;
}
// Check if mode is available from the plaftform_data mode id
XRRModeInfo *pxmode = find_mode(mode);
if (pxmode != NULL)
{
log_error("XRANDR: <%d> (add_mode) [WARNING] mode already exist\n", m_id);
return true;
}
// Create specific mode name
char name[48];
sprintf(name, "SR-%d_%dx%d@%.02f%s", m_id, mode->hactive, mode->vactive, mode->vfreq, mode->interlace ? "i" : "");
// Check if mode is available from the SR name (should not be the case, otherwise it means that we recevied twice the same mode request)
pxmode = find_mode_by_name(name);
if (pxmode != NULL)
{
log_error("XRANDR: <%d> (add_mode) [WARNING] mode already exist (duplicate request)\n", m_id);
mode->platform_data = pxmode->id;
return true;
}
log_verbose("XRANDR: <%d> (add_mode) create mode %s\n", m_id, name);
// Setup the xrandr mode structure
XRRModeInfo xmode = {};
xmode.name = name;
xmode.nameLength = strlen(name);
xmode.dotClock = mode->pclock;
xmode.width = mode->hactive;
xmode.hSyncStart = mode->hbegin;
xmode.hSyncEnd = mode->hend;
xmode.hTotal = mode->htotal;
xmode.height = mode->vactive;
xmode.vSyncStart = mode->vbegin;
xmode.vSyncEnd = mode->vend;
xmode.vTotal = mode->vtotal;
xmode.modeFlags = (mode->interlace ? RR_Interlace : 0) | (mode->doublescan ? RR_DoubleScan : 0) | (mode->hsync ? RR_HSyncPositive : RR_HSyncNegative) | (mode->vsync ? RR_VSyncPositive : RR_VSyncNegative);
xmode.hSkew = 0;
mode->type |= CUSTOM_VIDEO_TIMING_XRANDR;
// Create the modeline
XSync(m_pdisplay, False);
ms_xerrors = 0;
ms_xerrors_flag = 0x01;
old_error_handler = XSetErrorHandler(error_handler);
RRMode gmid = XRRCreateMode(m_pdisplay, m_root, &xmode);
XSync(m_pdisplay, False);
XSetErrorHandler(old_error_handler);
if (ms_xerrors & ms_xerrors_flag)
{
log_error("XRANDR: <%d> (add_mode) [ERROR] in %s\n", m_id, "XRRCreateMode");
return false;
}
mode->platform_data = gmid;
// Add new modeline to primary output
XRRScreenResources *resources = XRRGetScreenResourcesCurrent(m_pdisplay, m_root);
XSync(m_pdisplay, False);
ms_xerrors_flag = 0x02;
old_error_handler = XSetErrorHandler(error_handler);
XRRAddOutputMode(m_pdisplay, resources->outputs[m_desktop_output], mode->platform_data);
XSync(m_pdisplay, False);
XSetErrorHandler(old_error_handler);
XRRFreeScreenResources(resources);
if (ms_xerrors & ms_xerrors_flag)
{
log_error("XRANDR: <%d> (add_mode) [ERROR] in %s\n", m_id, "XRRAddOutputMode");
// remove unlinked modeline
if (mode->platform_data)
{
log_error("XRANDR: <%d> (add_mode) [ERROR] remove mode [%04lx]\n", m_id, mode->platform_data);
XRRDestroyMode(m_pdisplay, mode->platform_data);
mode->platform_data = 0;
}
}
else
log_verbose("XRANDR: <%d> (add_mode) mode %04lx %dx%d refresh %.6f added\n", m_id, mode->platform_data, mode->hactive, mode->vactive, mode->vfreq);
return ms_xerrors == 0;
}
//============================================================
// xrandr_timing::find_mode_by_name
//============================================================
XRRModeInfo *xrandr_timing::find_mode_by_name(char *name)
{
XRRModeInfo *pxmode = NULL;
XRRScreenResources *resources = XRRGetScreenResourcesCurrent(m_pdisplay, m_root);
// use SR name to return the mode
for (int m = 0; m < resources->nmode; m++)
{
if (strcmp(resources->modes[m].name, name) == 0)
{
pxmode = &resources->modes[m];
break;
}
}
XRRFreeScreenResources(resources);
return pxmode;
}
//============================================================
// xrandr_timing::find_mode
//============================================================
XRRModeInfo *xrandr_timing::find_mode(modeline *mode)
{
XRRModeInfo *pxmode = NULL;
XRRScreenResources *resources = XRRGetScreenResourcesCurrent(m_pdisplay, m_root);
// use platform_data (mode id) to return the mode
for (int m = 0; m < resources->nmode; m++)
{
if (mode->platform_data == resources->modes[m].id)
{
pxmode = &resources->modes[m];
break;
}
}
XRRFreeScreenResources(resources);
return pxmode;
}
//============================================================
// xrandr_timing::set_timing
//============================================================
bool xrandr_timing::set_timing(modeline *mode)
{
if (m_enable_screen_compositing)
return set_timing(mode, 0);
return set_timing(mode, XRANDR_DISABLE_CRTC_RELOCATION);
}
//============================================================
// xrandr_timing::set_timing
//============================================================
bool xrandr_timing::set_timing(modeline *mode, int flags)
{
// Handle no screen detected case
if (m_desktop_output == -1)
{
log_error("XRANDR: <%d> (set_timing) [ERROR] no screen detected\n", m_id);
return false;
}
if (!m_managed)
{
log_error("XRANDR: <%d> (set_timing) [WARNING] this screen is managed by <%d>\n", m_id, sp_shared_screen_manager[m_desktop_output]);
return false;
}
if (m_id != 1 && (flags & XRANDR_ENABLE_SCREEN_REORDERING))
flags = XRANDR_DISABLE_CRTC_RELOCATION; // only master can do global screen preparation
XRRModeInfo *pxmode = NULL;
if (mode->type & MODE_DESKTOP)
pxmode = &m_desktop_mode;
else
pxmode = find_mode(mode);
if (pxmode == NULL)
{
log_error("XRANDR: <%d> (set_timing) [ERROR] mode not found\n", m_id);
return false;
}
// Use xrandr to switch to new mode.
XRRScreenResources *resources = XRRGetScreenResourcesCurrent(m_pdisplay, m_root);
XRROutputInfo *output_info = XRRGetOutputInfo(m_pdisplay, resources, resources->outputs[m_desktop_output]);
XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(m_pdisplay, resources, output_info->crtc);
if (flags & XRANDR_DISABLE_CRTC_RELOCATION)
log_verbose("XRANDR: <%d> (set_timing) DISABLE crtc relocation\n", m_id);
if (flags & XRANDR_ENABLE_SCREEN_REORDERING)
log_verbose("XRANDR: <%d> (set_timing) GLOBAL desktop screen preparation\n", m_id);
else if (m_last_crtc.mode == crtc_info->mode && m_last_crtc.x == crtc_info->x && m_last_crtc.y == crtc_info->y && pxmode->id == crtc_info->mode)
log_verbose("XRANDR: <%d> (set_timing) requested mode is already active [%04lx] %ux%u+%d+%d\n", m_id, crtc_info->mode, crtc_info->width, crtc_info->height, crtc_info->x, crtc_info->y);
else if (m_last_crtc.mode != crtc_info->mode)
{
log_verbose("XRANDR: <%d> (set_timing) [WARNING] unexpected active modeline detected (last:[%04lx] now:[%04lx] %ux%u+%d+%d want:[%04lx])\n", m_id, m_last_crtc.mode, crtc_info->mode, crtc_info->width, crtc_info->height, crtc_info->x, crtc_info->y, pxmode->id);
*crtc_info = m_last_crtc;
}
// Grab X server to prevent unwanted interaction from the window manager
XGrabServer(m_pdisplay);
unsigned int width = m_min_width;
unsigned int height = m_min_height;
unsigned int active_crtc = 0;
unsigned int reordering_last_y = 0;
ms_xerrors = 0;
XRRCrtcInfo *global_crtc = new XRRCrtcInfo[resources->ncrtc];
XRRCrtcInfo *original_crtc = new XRRCrtcInfo[resources->ncrtc];
// caculate necessary screen size and of crtc neighborhood if they have at least one side aligned with the mode changed crtc
for (int c = 0; c < resources->ncrtc; c++)
{
// Prepare crtc references
memcpy(&original_crtc[c], XRRGetCrtcInfo(m_pdisplay, resources, resources->crtcs[c]), sizeof(XRRCrtcInfo));
memcpy(&global_crtc[c], XRRGetCrtcInfo(m_pdisplay, resources, resources->crtcs[c]), sizeof(XRRCrtcInfo));
// Original state
XRRCrtcInfo *crtc_info0 = &original_crtc[c];
// Modified state
XRRCrtcInfo *crtc_info1 = &global_crtc[c];
// clear timestamp
crtc_info1->timestamp = 0;
// Skip unused crtc
if (output_info->crtc != 0 && crtc_info0->mode != 0)
{
if (flags & XRANDR_ENABLE_SCREEN_REORDERING)
{
// Relocate all crtcs
// Super resolution placement, vertical stacking, reserved XRANDR_REORDERING_MAXIMUM_HEIGHT pixels
crtc_info1->x = 0;
crtc_info1->y = reordering_last_y;
if (crtc_info1->height > XRANDR_REORDERING_MAXIMUM_HEIGHT)
reordering_last_y += crtc_info1->height;
else
reordering_last_y += XRANDR_REORDERING_MAXIMUM_HEIGHT;
crtc_info1->timestamp |= XRANDR_SETMODE_UPDATE_REORDERING;
active_crtc++;
}
// Switchres selected desktop output
else if (resources->crtcs[c] == output_info->crtc)
{
crtc_info1->timestamp |= XRANDR_SETMODE_IS_DESKTOP;
crtc_info1->mode = pxmode->id;
crtc_info1->width = pxmode->width;
crtc_info1->height = pxmode->height;
if (mode->type & MODE_DESKTOP)
{
if (!m_enable_screen_compositing && (crtc_info1->x != sp_desktop_crtc[c].x || crtc_info1->y != sp_desktop_crtc[c].y))
{
// Restore original desktop position
crtc_info1->x = sp_desktop_crtc[c].x;
crtc_info1->y = sp_desktop_crtc[c].y;
crtc_info1->timestamp |= XRANDR_SETMODE_RESTORE_DESKTOP;
}
}
else
{
// Use curent position
crtc_info1->x = crtc_info->x;
crtc_info1->y = crtc_info->y;
}
if (crtc_info0->mode != crtc_info1->mode || crtc_info0->width != crtc_info1->width || crtc_info0->height != crtc_info1->height || crtc_info0->x != crtc_info1->x || crtc_info0->y != crtc_info1->y)
crtc_info1->timestamp |= XRANDR_SETMODE_UPDATE_DESKTOP_CRTC;
}
else if (mode->type & MODE_DESKTOP && m_enable_screen_reordering && (crtc_info1->x != sp_desktop_crtc[c].x || crtc_info1->y != sp_desktop_crtc[c].y))
{
crtc_info1->x = sp_desktop_crtc[c].x;
crtc_info1->y = sp_desktop_crtc[c].y;
crtc_info1->timestamp |= (XRANDR_SETMODE_RESTORE_DESKTOP | XRANDR_SETMODE_UPDATE_REORDERING);
}
}
}
for (int c = 0; c < resources->ncrtc; c++)
{
// Original state
XRRCrtcInfo *crtc_info0 = &original_crtc[c];
// Modified state
XRRCrtcInfo *crtc_info1 = &global_crtc[c];
// Skip unused crtc
if (output_info->crtc != 0 && crtc_info0->mode != 0)
{
if ((flags & XRANDR_DISABLE_CRTC_RELOCATION) == 0 && (crtc_info1->timestamp & XRANDR_SETMODE_IS_DESKTOP) == 0)
{
// relocate crtc impacted by new width
if (crtc_info1->x >= crtc_info->x + (int)crtc_info->width)
{
crtc_info1->x += pxmode->width - crtc_info->width;
crtc_info1->timestamp |= XRANDR_SETMODE_UPDATE_OTHER_CRTC;
}
// relocate crtc impacted by new height
if (crtc_info1->y >= crtc_info->y + (int)crtc_info->height)
{
crtc_info1->y += pxmode->height - crtc_info->height;
crtc_info1->timestamp |= XRANDR_SETMODE_UPDATE_OTHER_CRTC;
}
}
// Calculate overall screen size based on crtcs placement
if (crtc_info1->x + crtc_info1->width > width)
width = crtc_info1->x + crtc_info1->width;
if (crtc_info1->y + crtc_info1->height > height)
height = crtc_info1->y + crtc_info1->height;
if (width > m_max_width)
{
log_error("XRANDR: <%d> (set_timing) [ERROR] width is above allowed maximum (%d > %d)\n", m_id, width, m_max_width);
width = m_max_width;
}
if (height > m_max_height)
{
log_error("XRANDR: <%d> (set_timing) [ERROR] height is above allowed maximum (%d > %d)\n", m_id, height, m_max_height);
height = m_max_height;
}
if (crtc_info1->timestamp & XRANDR_SETMODE_UPDATE_MASK)
log_verbose("XRANDR: <%d> (set_timing) crtc %d%s [%04lx] %ux%u+%d+%d --> [%04lx] %ux%u+%d+%d flags [%02lx]\n", m_id, c, crtc_info1->timestamp & 1 ? "*" : " ", crtc_info0->mode, crtc_info0->width, crtc_info0->height, crtc_info0->x, crtc_info0->y, crtc_info1->mode, crtc_info1->width, crtc_info1->height, crtc_info1->x, crtc_info1->y, crtc_info1->timestamp);
else if (crtc_info1->timestamp & XRANDR_SETMODE_INFO_MASK)
log_verbose("XRANDR: <%d> (set_timing) crtc %d%s [%04lx] %ux%u+%d+%d flags [%02lx]\n", m_id, c, crtc_info1->timestamp & 1 ? "*" : " ", crtc_info1->mode, crtc_info1->width, crtc_info1->height, crtc_info1->x, crtc_info1->y, crtc_info1->timestamp);
else
log_verbose("XRANDR: <%d> (set_timing) crtc %d [%04lx] %ux%u+%d+%d\n", m_id, c, crtc_info1->mode, crtc_info1->width, crtc_info1->height, crtc_info1->x, crtc_info1->y);
}
}
// Disable crtc with pending modification
for (int c = 0; c < resources->ncrtc; c++)
{
// Modified state
if (global_crtc[c].timestamp & XRANDR_SETMODE_UPDATE_MASK)
{
if (XRRSetCrtcConfig(m_pdisplay, resources, resources->crtcs[c], CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0) != RRSetConfigSuccess)
{
log_error("XRANDR: <%d> (set_timing) [ERROR] when disabling crtc %d\n", m_id, c);
ms_xerrors_flag = 0x01;
ms_xerrors |= ms_xerrors_flag;
}
}
}
// Set the framebuffer screen size to enable all crtc
if (ms_xerrors == 0)
{
log_verbose("XRANDR: <%d> (set_timing) setting screen size to %d x %d\n", m_id, width, height);
XSync(m_pdisplay, False);
ms_xerrors_flag = 0x02;
old_error_handler = XSetErrorHandler(error_handler);
XRRSetScreenSize(m_pdisplay, m_root, width, height, (int) ((25.4 * width) / 96.0), (int) ((25.4 * height) / 96.0));
XSync(m_pdisplay, False);
XSetErrorHandler(old_error_handler);
if (ms_xerrors & ms_xerrors_flag)
log_error("XRANDR: <%d> (set_timing) [ERROR] in %s\n", m_id, "XRRSetScreenSize");
}
// Refresh all crtc, switch modeline and set new placement
for (int c = 0; c < resources->ncrtc; c++)
{
// Modified state
XRRCrtcInfo *crtc_info1 = &global_crtc[c];
if (crtc_info1->timestamp & XRANDR_SETMODE_UPDATE_MASK)
{
if (crtc_info1->timestamp & XRANDR_SETMODE_IS_DESKTOP)
XFillRectangle(m_pdisplay, m_root, XCreateGC(m_pdisplay, m_root, 0, 0), crtc_info1->x, crtc_info1->y, crtc_info1->width, crtc_info1->height);
// enable crtc with updated parameters
XSync(m_pdisplay, False);
ms_xerrors_flag = 0x14;
old_error_handler = XSetErrorHandler(error_handler);
XRRSetCrtcConfig(m_pdisplay, resources, resources->crtcs[c], CurrentTime, crtc_info1->x, crtc_info1->y, crtc_info1->mode, crtc_info1->rotation, crtc_info1->outputs, crtc_info1->noutput);
XSync(m_pdisplay, False);
XSetErrorHandler(old_error_handler);
if (ms_xerrors & 0x10)
{
log_error("XRANDR: <%d> (set_timing) [ERROR] in %s crtc %d set modeline %04lx\n", m_id, "XRRSetCrtcConfig", c, crtc_info1->mode);
ms_xerrors &= 0xEF;
}
}
}
delete[]original_crtc;
delete[]global_crtc;
// Release X server, events can be processed now
XUngrabServer(m_pdisplay);
if (ms_xerrors & ms_xerrors_flag)
log_error("XRANDR: <%d> (set_timing) [ERROR] in %s\n", m_id, "XRRSetCrtcConfig");
// Recall the impacted crtc to settle parameters
XRRFreeCrtcInfo(crtc_info);
crtc_info = XRRGetCrtcInfo(m_pdisplay, resources, output_info->crtc);
// crtc config modeline change fail
if (crtc_info->mode == 0)
log_error("XRANDR: <%d> (set_timing) [ERROR] switching resolution failed, no modeline is set\n", m_id);
else
// save last crtc
m_last_crtc = *crtc_info;
XRRFreeCrtcInfo(crtc_info);
XRRFreeOutputInfo(output_info);
XRRFreeScreenResources(resources);
return (ms_xerrors == 0 && crtc_info->mode != 0);
}
//============================================================
// xrandr_timing::delete_mode
//============================================================
bool xrandr_timing::delete_mode(modeline *mode)
{
// Handle no screen detected case
if (m_desktop_output == -1)
{
log_error("XRANDR: <%d> (delete_mode) [ERROR] no screen detected\n", m_id);
return false;
}
if (!m_managed)
{
log_error("XRANDR: <%d> (delete_mode) [WARNING] this screen is managed by <%d>\n", m_id, sp_shared_screen_manager[m_desktop_output]);
return false;
}
if (!mode)
return false;
XRRScreenResources *resources = XRRGetScreenResourcesCurrent(m_pdisplay, m_root);
int total_xerrors = 0;
// Delete modeline
for (int m = 0; m < resources->nmode && mode->platform_data != 0; m++)
{
if (mode->platform_data == resources->modes[m].id)
{
XRROutputInfo *output_info = XRRGetOutputInfo(m_pdisplay, resources, resources->outputs[m_desktop_output]);
XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(m_pdisplay, resources, output_info->crtc);
if (resources->modes[m].id == crtc_info->mode)
{
log_verbose("XRANDR: <%d> (delete_mode) [WARNING] modeline [%04lx] is currently active, restoring desktop mode first\n", m_id, resources->modes[m].id);
modeline desktop_mode = {};
desktop_mode.type |= MODE_DESKTOP;
if (!set_timing(&desktop_mode, 0))
{
log_error("XRANDR: <%d> (delete_mode) [ERROR] Could not restore desktop mode\n", m_id);
return false;
}
}
XRRFreeCrtcInfo(crtc_info);
XRRFreeOutputInfo(output_info);
log_verbose("XRANDR: <%d> (delete_mode) remove mode %s\n", m_id, resources->modes[m].name);
XSync(m_pdisplay, False);
ms_xerrors = 0;
ms_xerrors_flag = 0x01;
old_error_handler = XSetErrorHandler(error_handler);
XRRDeleteOutputMode(m_pdisplay, resources->outputs[m_desktop_output], resources->modes[m].id);
if (ms_xerrors & ms_xerrors_flag)
{
log_error("XRANDR: <%d> (delete_mode) [ERROR] in %s\n", m_id, "XRRDeleteOutputMode");
total_xerrors++;
}
ms_xerrors_flag = 0x02;
XRRDestroyMode(m_pdisplay, resources->modes[m].id);
XSync(m_pdisplay, False);
XSetErrorHandler(old_error_handler);
if (ms_xerrors & ms_xerrors_flag)
{
log_error("XRANDR: <%d> (delete_mode) [ERROR] in %s\n", m_id, "XRRDestroyMode");
total_xerrors++;
}
mode->platform_data = 0;
}
}
XRRFreeScreenResources(resources);
return total_xerrors == 0;
}
//============================================================
// xrandr_timing::get_timing
//============================================================
bool xrandr_timing::get_timing(modeline *mode)
{
// Handle no screen detected case
if (m_desktop_output == -1)
{
log_error("XRANDR: <%d> (get_timing) [ERROR] no screen detected\n", m_id);
return false;
}
XRRScreenResources *resources = XRRGetScreenResourcesCurrent(m_pdisplay, m_root);
XRROutputInfo *output_info = XRRGetOutputInfo(m_pdisplay, resources, resources->outputs[m_desktop_output]);
// Cycle through the modelines and report them back to the display manager
if (m_video_modes_position < output_info->nmode)
{
for (int m = 0; m < resources->nmode; m++)
{
XRRModeInfo *pxmode = &resources->modes[m];
if (pxmode->id == output_info->modes[m_video_modes_position])
{
mode->platform_data = pxmode->id;
mode->pclock = pxmode->dotClock;
mode->hactive = pxmode->width;
mode->hbegin = pxmode->hSyncStart;
mode->hend = pxmode->hSyncEnd;
mode->htotal = pxmode->hTotal;
mode->vactive = pxmode->height;
mode->vbegin = pxmode->vSyncStart;
mode->vend = pxmode->vSyncEnd;
mode->vtotal = pxmode->vTotal;
mode->interlace = (pxmode->modeFlags & RR_Interlace) ? 1 : 0;
mode->doublescan = (pxmode->modeFlags & RR_DoubleScan) ? 1 : 0;
mode->hsync = (pxmode->modeFlags & RR_HSyncPositive) ? 1 : 0;
mode->vsync = (pxmode->modeFlags & RR_VSyncPositive) ? 1 : 0;
mode->hfreq = mode->pclock / mode->htotal;
mode->vfreq = mode->hfreq / mode->vtotal * (mode->interlace ? 2 : 1);
mode->refresh = mode->vfreq;
mode->width = pxmode->width;
mode->height = pxmode->height;
// Add the rotation flag from the crtc
mode->type |= m_crtc_flags;
mode->type |= CUSTOM_VIDEO_TIMING_XRANDR;
if (strncmp(pxmode->name, "SR-", 3) == 0)
log_verbose("XRANDR: <%d> (get_timing) [WARNING] modeline %s detected\n", m_id, pxmode->name);
// Add the desktop flag to desktop modeline
if (m_desktop_mode.id == pxmode->id)
mode->type |= MODE_DESKTOP;
log_verbose("XRANDR: <%d> (get_timing) mode %04lx %dx%d refresh %.6f added\n", m_id, pxmode->id, pxmode->width, pxmode->height, mode->vfreq);
}
}
m_video_modes_position++;
}
else
{
// Inititalise the position for the modeline list
m_video_modes_position = 0;
}
XRRFreeOutputInfo(output_info);
XRRFreeScreenResources(resources);
return true;
}
//============================================================
// xrandr_timing::process_modelist
//============================================================
bool xrandr_timing::process_modelist(std::vector<modeline *> modelist)
{
bool error = false;
bool result = false;
for (auto &mode : modelist)
{
if (mode->type & MODE_DELETE)
result = delete_mode(mode);
else if (mode->type & MODE_ADD)
result = add_mode(mode);
else if (mode->type & MODE_UPDATE)
result = update_mode(mode);
if (!result)
{
mode->type |= MODE_ERROR;
error = true;
}
else
// succeed
mode->type &= ~MODE_ERROR;
}
return !error;
}