#include "SDL_internal.h"
#ifdef SDL_VIDEO_DRIVER_X11
#include "SDL_x11video.h"
#include "SDL_x11settings.h"
#include "edid.h"
#include "../../events/SDL_displayevents_c.h"
#include "../../core/unix/SDL_gtk.h"
#define MODE_SWITCH_TIMEOUT_NS SDL_NS_PER_SECOND * 5
float X11_GetGlobalContentScale(Display *display, XSettingsClient *client)
{
double scale_factor = 0.0;
const char *hint = SDL_GetHint(SDL_HINT_VIDEO_X11_SCALING_FACTOR);
if (hint && *hint) {
double value = SDL_atof(hint);
if (value >= 1.0f && value <= 10.0f) {
scale_factor = value;
}
}
if (scale_factor <= 0.0) {
int status, real_format;
Atom real_type;
Atom res_mgr;
unsigned long items_read, items_left;
char *resource_manager;
bool owns_resource_manager = false;
X11_XrmInitialize();
res_mgr = X11_XInternAtom(display, "RESOURCE_MANAGER", False);
status = X11_XGetWindowProperty(display, RootWindow(display, DefaultScreen(display)),
res_mgr, 0L, 8192L, False, XA_STRING,
&real_type, &real_format, &items_read, &items_left,
(unsigned char **)&resource_manager);
if (status == Success && resource_manager) {
owns_resource_manager = true;
} else {
resource_manager = X11_XResourceManagerString(display);
}
if (resource_manager) {
XrmDatabase db;
XrmValue value;
char *type;
db = X11_XrmGetStringDatabase(resource_manager);
if (X11_XrmGetResource(db, "Xft.dpi", "String", &type, &value)) {
if (value.addr && type && SDL_strcmp(type, "String") == 0) {
int dpi = SDL_atoi(value.addr);
scale_factor = dpi / 96.0;
}
}
X11_XrmDestroyDatabase(db);
if (owns_resource_manager) {
X11_XFree(resource_manager);
}
}
}
if (scale_factor <= 0.0) {
scale_factor = X11_GetXsettingsClientIntKey(client, "Gdk/WindowScalingFactor", -1);
if (scale_factor <= 0.0) {
int dpi = X11_GetXsettingsClientIntKey(client, "Xft/DPI", -1);
if (dpi > 0) {
scale_factor = (double) dpi / 1024.0;
scale_factor /= 96.0;
}
}
}
if (scale_factor <= 0.0) {
const char *scale_str = SDL_getenv("GDK_SCALE");
if (scale_str) {
scale_factor = SDL_atoi(scale_str);
}
}
if (scale_factor <= 0.0) {
scale_factor = 1.0;
}
return (float)scale_factor;
}
float X11_GetGlobalContentScaleForDevice(SDL_VideoDevice *_this)
{
return X11_GetGlobalContentScale(_this->internal->display, _this->internal->xsettings_data.xsettings);
}
static bool get_visualinfo(Display *display, int screen, XVisualInfo *vinfo)
{
const char *visual_id = SDL_GetHint(SDL_HINT_VIDEO_X11_VISUALID);
int depth;
if (visual_id && *visual_id) {
XVisualInfo *vi, template;
int nvis;
SDL_zero(template);
template.visualid = SDL_strtol(visual_id, NULL, 0);
vi = X11_XGetVisualInfo(display, VisualIDMask, &template, &nvis);
if (vi) {
*vinfo = *vi;
X11_XFree(vi);
return true;
}
}
depth = DefaultDepth(display, screen);
if ((X11_UseDirectColorVisuals() &&
X11_XMatchVisualInfo(display, screen, depth, DirectColor, vinfo)) ||
X11_XMatchVisualInfo(display, screen, depth, TrueColor, vinfo) ||
X11_XMatchVisualInfo(display, screen, depth, PseudoColor, vinfo) ||
X11_XMatchVisualInfo(display, screen, depth, StaticColor, vinfo)) {
return true;
}
return false;
}
bool X11_GetVisualInfoFromVisual(Display *display, Visual *visual, XVisualInfo *vinfo)
{
XVisualInfo *vi;
int nvis;
vinfo->visualid = X11_XVisualIDFromVisual(visual);
vi = X11_XGetVisualInfo(display, VisualIDMask, vinfo, &nvis);
if (vi) {
*vinfo = *vi;
X11_XFree(vi);
return true;
}
return false;
}
SDL_PixelFormat X11_GetPixelFormatFromVisualInfo(Display *display, XVisualInfo *vinfo)
{
if (vinfo->class == DirectColor || vinfo->class == TrueColor) {
int bpp;
Uint32 Rmask, Gmask, Bmask, Amask;
Rmask = vinfo->visual->red_mask;
Gmask = vinfo->visual->green_mask;
Bmask = vinfo->visual->blue_mask;
if (vinfo->depth == 32) {
Amask = (0xFFFFFFFF & ~(Rmask | Gmask | Bmask));
} else {
Amask = 0;
}
bpp = vinfo->depth;
if (bpp == 24) {
int i, n;
XPixmapFormatValues *p = X11_XListPixmapFormats(display, &n);
if (p) {
for (i = 0; i < n; ++i) {
if (p[i].depth == 24) {
bpp = p[i].bits_per_pixel;
break;
}
}
X11_XFree(p);
}
}
return SDL_GetPixelFormatForMasks(bpp, Rmask, Gmask, Bmask, Amask);
}
if (vinfo->class == PseudoColor || vinfo->class == StaticColor) {
switch (vinfo->depth) {
case 8:
return SDL_PIXELFORMAT_INDEX8;
case 4:
if (BitmapBitOrder(display) == LSBFirst) {
return SDL_PIXELFORMAT_INDEX4LSB;
} else {
return SDL_PIXELFORMAT_INDEX4MSB;
}
case 1:
if (BitmapBitOrder(display) == LSBFirst) {
return SDL_PIXELFORMAT_INDEX1LSB;
} else {
return SDL_PIXELFORMAT_INDEX1MSB;
}
}
}
return SDL_PIXELFORMAT_UNKNOWN;
}
static SDL_DisplayID X11_AddGenericDisplay(SDL_VideoDevice *_this, bool send_event)
{
SDL_VideoData *data = _this->internal;
Display *dpy = data->display;
const int default_screen = DefaultScreen(dpy);
Screen *screen = ScreenOfDisplay(dpy, default_screen);
int scanline_pad, n, i;
SDL_DisplayModeData *modedata;
SDL_DisplayData *displaydata;
SDL_DisplayMode mode;
XPixmapFormatValues *pixmapformats;
Uint32 pixelformat;
XVisualInfo vinfo;
SDL_VideoDisplay display;
if (!get_visualinfo(dpy, default_screen, &vinfo)) {
return SDL_SetError("Failed to find an X11 visual for the primary display");
}
pixelformat = X11_GetPixelFormatFromVisualInfo(dpy, &vinfo);
if (SDL_ISPIXELFORMAT_INDEXED(pixelformat)) {
return SDL_SetError("Palettized video modes are no longer supported");
}
SDL_zero(mode);
mode.w = WidthOfScreen(screen);
mode.h = HeightOfScreen(screen);
mode.format = pixelformat;
displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata));
if (!displaydata) {
return false;
}
modedata = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData));
if (!modedata) {
SDL_free(displaydata);
return false;
}
mode.internal = modedata;
displaydata->screen = default_screen;
displaydata->visual = vinfo.visual;
displaydata->depth = vinfo.depth;
scanline_pad = SDL_BYTESPERPIXEL(pixelformat) * 8;
pixmapformats = X11_XListPixmapFormats(dpy, &n);
if (pixmapformats) {
for (i = 0; i < n; ++i) {
if (pixmapformats[i].depth == vinfo.depth) {
scanline_pad = pixmapformats[i].scanline_pad;
break;
}
}
X11_XFree(pixmapformats);
}
displaydata->scanline_pad = scanline_pad;
displaydata->x = 0;
displaydata->y = 0;
displaydata->use_xrandr = false;
SDL_zero(display);
display.name = (char *)"Generic X11 Display";
display.desktop_mode = mode;
display.internal = displaydata;
display.content_scale = X11_GetGlobalContentScaleForDevice(_this);
return SDL_AddVideoDisplay(&display, send_event);
}
#ifdef SDL_VIDEO_DRIVER_X11_XRANDR
static void X11_RemoveGenericDisplay(SDL_VideoDevice *_this)
{
SDL_DisplayID *displays = SDL_GetDisplays(NULL);
if (displays) {
for (int i = 0; displays[i]; ++i) {
SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]);
const SDL_DisplayData *displaydata = display->internal;
if (!displaydata->xrandr_output) {
SDL_DelVideoDisplay(displays[i], true);
}
}
SDL_free(displays);
}
}
static bool CheckXRandR(Display *display, int *major, int *minor)
{
*major = *minor = 0;
#ifdef XRANDR_DISABLED_BY_DEFAULT
if (!SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XRANDR, false)) {
#ifdef X11MODES_DEBUG
printf("XRandR disabled by default due to window manager issues\n");
#endif
return false;
}
#else
if (!SDL_GetHintBoolean(SDL_HINT_VIDEO_X11_XRANDR, true)) {
#ifdef X11MODES_DEBUG
printf("XRandR disabled due to hint\n");
#endif
return false;
}
#endif
if (!SDL_X11_HAVE_XRANDR) {
#ifdef X11MODES_DEBUG
printf("XRandR support not available\n");
#endif
return false;
}
*major = 1;
*minor = 3; if (!X11_XRRQueryVersion(display, major, minor)) {
#ifdef X11MODES_DEBUG
printf("XRandR not active on the display\n");
#endif
*major = *minor = 0;
return false;
}
#ifdef X11MODES_DEBUG
printf("XRandR available at version %d.%d!\n", *major, *minor);
#endif
return true;
}
#define XRANDR_ROTATION_LEFT (1 << 1)
#define XRANDR_ROTATION_RIGHT (1 << 3)
static void CalculateXRandRRefreshRate(const XRRModeInfo *info, int *numerator, int *denominator)
{
unsigned int vTotal = info->vTotal;
if (info->modeFlags & RR_DoubleScan) {
vTotal *= 2;
}
if (info->modeFlags & RR_Interlace) {
vTotal /= 2;
}
if (info->hTotal && vTotal) {
*numerator = info->dotClock;
*denominator = (info->hTotal * vTotal);
} else {
*numerator = 0;
*denominator = 0;
}
}
static bool SetXRandRModeInfo(Display *display, XRRScreenResources *res, RRCrtc crtc,
RRMode modeID, SDL_DisplayMode *mode)
{
int i;
for (i = 0; i < res->nmode; ++i) {
const XRRModeInfo *info = &res->modes[i];
if (info->id == modeID) {
XRRCrtcInfo *crtcinfo;
Rotation rotation = 0;
XFixed scale_w = 0x10000, scale_h = 0x10000;
XRRCrtcTransformAttributes *attr;
crtcinfo = X11_XRRGetCrtcInfo(display, res, crtc);
if (crtcinfo) {
rotation = crtcinfo->rotation;
X11_XRRFreeCrtcInfo(crtcinfo);
}
if (X11_XRRGetCrtcTransform(display, crtc, &attr) && attr) {
scale_w = attr->currentTransform.matrix[0][0];
scale_h = attr->currentTransform.matrix[1][1];
X11_XFree(attr);
}
if (rotation & (XRANDR_ROTATION_LEFT | XRANDR_ROTATION_RIGHT)) {
mode->w = (info->height * scale_w + 0xffff) >> 16;
mode->h = (info->width * scale_h + 0xffff) >> 16;
} else {
mode->w = (info->width * scale_w + 0xffff) >> 16;
mode->h = (info->height * scale_h + 0xffff) >> 16;
}
CalculateXRandRRefreshRate(info, &mode->refresh_rate_numerator, &mode->refresh_rate_denominator);
mode->internal->xrandr_mode = modeID;
#ifdef X11MODES_DEBUG
printf("XRandR mode %d: %dx%d@%d/%dHz\n", (int)modeID,
mode->screen_w, mode->screen_h, mode->refresh_rate_numerator, mode->refresh_rate_denominator);
#endif
return true;
}
}
return false;
}
static void SetXRandRDisplayName(Display *dpy, Atom EDID, char *name, const size_t namelen, RROutput output, const unsigned long widthmm, const unsigned long heightmm)
{
int inches;
int nprop;
Atom *props = X11_XRRListOutputProperties(dpy, output, &nprop);
int i;
for (i = 0; i < nprop; ++i) {
unsigned char *prop;
int actual_format;
unsigned long nitems, bytes_after;
Atom actual_type;
if (props[i] == EDID) {
if (X11_XRRGetOutputProperty(dpy, output, props[i], 0, 100, False,
False, AnyPropertyType, &actual_type,
&actual_format, &nitems, &bytes_after,
&prop) == Success) {
MonitorInfo *info = decode_edid(prop);
if (info) {
#ifdef X11MODES_DEBUG
printf("Found EDID data for %s\n", name);
dump_monitor_info(info);
#endif
SDL_strlcpy(name, info->dsc_product_name, namelen);
SDL_free(info);
}
X11_XFree(prop);
}
break;
}
}
if (props) {
X11_XFree(props);
}
inches = (int)((SDL_sqrtf(widthmm * widthmm + heightmm * heightmm) / 25.4f) + 0.5f);
if (*name && inches) {
const size_t len = SDL_strlen(name);
(void)SDL_snprintf(&name[len], namelen - len, " %d\"", inches);
}
#ifdef X11MODES_DEBUG
printf("Display name: %s\n", name);
#endif
}
static bool X11_FillXRandRDisplayInfo(SDL_VideoDevice *_this, Display *dpy, int screen, RROutput outputid, XRRScreenResources *res, SDL_VideoDisplay *display, char *display_name, size_t display_name_size)
{
Atom EDID = X11_XInternAtom(dpy, "EDID", False);
XRROutputInfo *output_info;
int display_x, display_y;
unsigned long display_mm_width, display_mm_height;
SDL_DisplayData *displaydata;
SDL_DisplayMode mode;
SDL_DisplayModeData *modedata;
RRMode modeID;
RRCrtc output_crtc;
XRRCrtcInfo *crtc;
XVisualInfo vinfo;
Uint32 pixelformat;
XPixmapFormatValues *pixmapformats;
int scanline_pad;
int i, n;
if (!display || !display_name) {
return false; }
if (!get_visualinfo(dpy, screen, &vinfo)) {
return false; }
pixelformat = X11_GetPixelFormatFromVisualInfo(dpy, &vinfo);
if (SDL_ISPIXELFORMAT_INDEXED(pixelformat)) {
return false; }
scanline_pad = SDL_BYTESPERPIXEL(pixelformat) * 8;
pixmapformats = X11_XListPixmapFormats(dpy, &n);
if (pixmapformats) {
for (i = 0; i < n; i++) {
if (pixmapformats[i].depth == vinfo.depth) {
scanline_pad = pixmapformats[i].scanline_pad;
break;
}
}
X11_XFree(pixmapformats);
}
output_info = X11_XRRGetOutputInfo(dpy, res, outputid);
if (!output_info || !output_info->crtc || output_info->connection == RR_Disconnected) {
X11_XRRFreeOutputInfo(output_info);
return false; }
SDL_strlcpy(display_name, output_info->name, display_name_size);
display_mm_width = output_info->mm_width;
display_mm_height = output_info->mm_height;
output_crtc = output_info->crtc;
X11_XRRFreeOutputInfo(output_info);
crtc = X11_XRRGetCrtcInfo(dpy, res, output_crtc);
if (!crtc) {
return false; }
SDL_zero(mode);
modeID = crtc->mode;
mode.w = crtc->width;
mode.h = crtc->height;
mode.format = pixelformat;
display_x = crtc->x;
display_y = crtc->y;
X11_XRRFreeCrtcInfo(crtc);
displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata));
if (!displaydata) {
return false;
}
modedata = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData));
if (!modedata) {
SDL_free(displaydata);
return false;
}
modedata->xrandr_mode = modeID;
mode.internal = modedata;
displaydata->screen = screen;
displaydata->visual = vinfo.visual;
displaydata->depth = vinfo.depth;
displaydata->scanline_pad = scanline_pad;
displaydata->x = display_x;
displaydata->y = display_y;
displaydata->use_xrandr = true;
displaydata->xrandr_output = outputid;
SDL_strlcpy(displaydata->connector_name, display_name, sizeof(displaydata->connector_name));
SetXRandRModeInfo(dpy, res, output_crtc, modeID, &mode);
SetXRandRDisplayName(dpy, EDID, display_name, display_name_size, outputid, display_mm_width, display_mm_height);
SDL_zero(*display);
if (*display_name) {
display->name = display_name;
}
display->desktop_mode = mode;
display->content_scale = X11_GetGlobalContentScaleForDevice(_this);
display->internal = displaydata;
return true;
}
static bool X11_AddXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen, RROutput outputid, XRRScreenResources *res, bool send_event)
{
SDL_VideoDisplay display;
char display_name[128];
if (!X11_FillXRandRDisplayInfo(_this, dpy, screen, outputid, res, &display, display_name, sizeof(display_name))) {
return true; }
SDL_DisplayID displayID = SDL_AddVideoDisplay(&display, false);
if (displayID == 0) {
return false;
}
X11_RemoveGenericDisplay(_this);
if (send_event) {
SDL_SendDisplayEvent(SDL_GetVideoDisplay(displayID), SDL_EVENT_DISPLAY_ADDED, 0, 0);
}
return true;
}
static bool X11_UpdateXRandRDisplay(SDL_VideoDevice *_this, Display *dpy, int screen, RROutput outputid, XRRScreenResources *res, SDL_VideoDisplay *existing_display)
{
SDL_VideoDisplay display;
char display_name[128];
if (!X11_FillXRandRDisplayInfo(_this, dpy, screen, outputid, res, &display, display_name, sizeof(display_name))) {
return false; }
SDL_SetDesktopDisplayMode(existing_display, &display.desktop_mode);
if (existing_display->internal->x != display.internal->x ||
existing_display->internal->y != display.internal->y) {
existing_display->internal->x = display.internal->x;
existing_display->internal->y = display.internal->y;
SDL_SendDisplayEvent(existing_display, SDL_EVENT_DISPLAY_MOVED, 0, 0);
}
SDL_SetDisplayContentScale(existing_display, display.content_scale);
SDL_free( display.internal );
return true;
}
static XRRScreenResources *X11_GetScreenResources(Display *dpy, int screen)
{
XRRScreenResources *res = X11_XRRGetScreenResourcesCurrent(dpy, RootWindow(dpy, screen));
if (!res || res->noutput == 0) {
if (res) {
X11_XRRFreeScreenResources(res);
}
res = X11_XRRGetScreenResources(dpy, RootWindow(dpy, screen));
}
return res;
}
static void X11_CheckDisplaysMoved(SDL_VideoDevice *_this, Display *dpy)
{
const int screencount = ScreenCount(dpy);
SDL_DisplayID *displays = SDL_GetDisplays(NULL);
if (!displays) {
return;
}
for (int screen = 0; screen < screencount; ++screen) {
XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
if (!res) {
continue;
}
for (int i = 0; displays[i]; ++i) {
SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]);
const SDL_DisplayData *displaydata = display->internal;
if (displaydata->xrandr_output && displaydata->screen == screen) {
X11_UpdateXRandRDisplay(_this, dpy, screen, displaydata->xrandr_output, res, display);
}
}
X11_XRRFreeScreenResources(res);
}
SDL_free(displays);
}
static void X11_CheckDisplaysRemoved(SDL_VideoDevice *_this, Display *dpy)
{
const int screencount = ScreenCount(dpy);
int num_displays = 0;
SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
if (!displays) {
return;
}
for (int screen = 0; screen < screencount; ++screen) {
XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
if (!res) {
continue;
}
for (int output = 0; output < res->noutput; output++) {
for (int i = 0; i < num_displays; ++i) {
if (!displays[i]) {
continue;
}
SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]);
const SDL_DisplayData *displaydata = display->internal;
if (displaydata->xrandr_output == res->outputs[output]) {
displays[i] = 0;
break;
}
}
}
X11_XRRFreeScreenResources(res);
}
for (int i = 0; i < num_displays; ++i) {
if (displays[i]) {
SDL_VideoDisplay *display = SDL_GetVideoDisplay(displays[i]);
const SDL_DisplayData *displaydata = display->internal;
if (displaydata->xrandr_output) {
SDL_DelVideoDisplay(displays[i], true);
}
}
}
SDL_free(displays);
}
static void X11_HandleXRandROutputChange(SDL_VideoDevice *_this, const XRROutputChangeNotifyEvent *ev)
{
SDL_DisplayID *displays;
SDL_VideoDisplay *display = NULL;
int i;
#if 0#endif
X11_CheckDisplaysRemoved(_this, ev->display);
displays = SDL_GetDisplays(NULL);
if (displays) {
for (i = 0; displays[i]; ++i) {
SDL_VideoDisplay *thisdisplay = SDL_GetVideoDisplay(displays[i]);
const SDL_DisplayData *displaydata = thisdisplay->internal;
if (displaydata->xrandr_output == ev->output) {
display = thisdisplay;
break;
}
}
SDL_free(displays);
}
if (ev->connection == RR_Disconnected) { if (display) {
SDL_DisplayID generic_display = 0;
if (_this->num_displays == 1) {
generic_display = X11_AddGenericDisplay(_this, false);
}
SDL_DelVideoDisplay(display->id, true);
if (generic_display) {
SDL_SendDisplayEvent(SDL_GetVideoDisplay(generic_display), SDL_EVENT_DISPLAY_ADDED, 0, 0);
}
}
X11_CheckDisplaysMoved(_this, ev->display);
} else if (ev->connection == RR_Connected) { if (!display) {
Display *dpy = ev->display;
const int screen = DefaultScreen(dpy);
XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
if (res) {
X11_AddXRandRDisplay(_this, dpy, screen, ev->output, res, true);
X11_XRRFreeScreenResources(res);
}
}
X11_CheckDisplaysMoved(_this, ev->display);
}
}
void X11_HandleXRandREvent(SDL_VideoDevice *_this, const XEvent *xevent)
{
SDL_VideoData *videodata = _this->internal;
SDL_assert(xevent->type == (videodata->xrandr_event_base + RRNotify));
switch (((const XRRNotifyEvent *)xevent)->subtype) {
case RRNotify_OutputChange:
X11_HandleXRandROutputChange(_this, (const XRROutputChangeNotifyEvent *)xevent);
break;
default:
break;
}
}
static void X11_SortOutputsByPriorityHint(SDL_VideoDevice *_this)
{
const char *name_hint = SDL_GetHint(SDL_HINT_VIDEO_DISPLAY_PRIORITY);
if (name_hint) {
char *saveptr;
char *str = SDL_strdup(name_hint);
SDL_VideoDisplay **sorted_list = SDL_malloc(sizeof(SDL_VideoDisplay *) * _this->num_displays);
if (str && sorted_list) {
int sorted_index = 0;
const char *token = SDL_strtok_r(str, ",", &saveptr);
while (token) {
for (int i = 0; i < _this->num_displays; ++i) {
SDL_VideoDisplay *d = _this->displays[i];
if (d) {
SDL_DisplayData *data = d->internal;
if (SDL_strcmp(token, data->connector_name) == 0) {
sorted_list[sorted_index++] = d;
_this->displays[i] = NULL;
break;
}
}
}
token = SDL_strtok_r(NULL, ",", &saveptr);
}
for (int i = 0; i < _this->num_displays; ++i) {
if (_this->displays[i]) {
sorted_list[sorted_index++] = _this->displays[i];
}
}
SDL_memcpy(_this->displays, sorted_list, sizeof(SDL_VideoDisplay *) * _this->num_displays);
}
SDL_free(str);
SDL_free(sorted_list);
}
}
static bool X11_InitModes_XRandR(SDL_VideoDevice *_this)
{
SDL_VideoData *data = _this->internal;
Display *dpy = data->display;
const int screencount = ScreenCount(dpy);
const int default_screen = DefaultScreen(dpy);
RROutput primary = X11_XRRGetOutputPrimary(dpy, RootWindow(dpy, default_screen));
int xrandr_error_base = 0;
int looking_for_primary;
int output;
int screen;
if (!X11_XRRQueryExtension(dpy, &data->xrandr_event_base, &xrandr_error_base)) {
return SDL_SetError("XRRQueryExtension failed");
}
for (looking_for_primary = 1; looking_for_primary >= 0; looking_for_primary--) {
for (screen = 0; screen < screencount; screen++) {
if (looking_for_primary && (screen != default_screen)) {
continue;
}
XRRScreenResources *res = X11_GetScreenResources(dpy, screen);
if (!res) {
continue;
}
for (output = 0; output < res->noutput; output++) {
if ((looking_for_primary && (res->outputs[output] != primary)) ||
(!looking_for_primary && (screen == default_screen) && (res->outputs[output] == primary))) {
continue;
}
if (!X11_AddXRandRDisplay(_this, dpy, screen, res->outputs[output], res, false)) {
break;
}
}
X11_XRRFreeScreenResources(res);
X11_XRRSelectInput(dpy, RootWindow(dpy, screen), RROutputChangeNotifyMask);
}
}
if (_this->num_displays == 0) {
return SDL_SetError("No available displays");
}
X11_SortOutputsByPriorityHint(_this);
return true;
}
#endif
static bool X11_InitModes_StdXlib(SDL_VideoDevice *_this)
{
if (X11_AddGenericDisplay(_this, true) == 0) {
return false;
}
return true;
}
bool X11_InitModes(SDL_VideoDevice *_this)
{
#ifdef SDL_VIDEO_DRIVER_X11_XRANDR
{
SDL_VideoData *data = _this->internal;
int xrandr_major, xrandr_minor;
if (CheckXRandR(data->display, &xrandr_major, &xrandr_minor) &&
(xrandr_major >= 2 || (xrandr_major == 1 && xrandr_minor >= 3)) &&
X11_InitModes_XRandR(_this)) {
return true;
}
}
#endif
return X11_InitModes_StdXlib(_this);
}
bool X11_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display)
{
#ifdef SDL_VIDEO_DRIVER_X11_XRANDR
SDL_DisplayData *data = sdl_display->internal;
SDL_DisplayMode mode;
SDL_zero(mode);
mode.format = sdl_display->desktop_mode.format;
if (data->use_xrandr) {
Display *display = _this->internal->display;
XRRScreenResources *res;
res = X11_XRRGetScreenResources(display, RootWindow(display, data->screen));
if (res) {
SDL_DisplayModeData *modedata;
XRROutputInfo *output_info;
int i;
output_info = X11_XRRGetOutputInfo(display, res, data->xrandr_output);
if (output_info && output_info->connection != RR_Disconnected) {
for (i = 0; i < output_info->nmode; ++i) {
modedata = (SDL_DisplayModeData *)SDL_calloc(1, sizeof(SDL_DisplayModeData));
if (!modedata) {
continue;
}
mode.internal = modedata;
if (!SetXRandRModeInfo(display, res, output_info->crtc, output_info->modes[i], &mode) ||
!SDL_AddFullscreenDisplayMode(sdl_display, &mode)) {
SDL_free(modedata);
}
}
}
X11_XRRFreeOutputInfo(output_info);
X11_XRRFreeScreenResources(res);
}
}
#endif return true;
}
#ifdef SDL_VIDEO_DRIVER_X11_XRANDR
static int (*PreXRRSetScreenSizeErrorHandler)(Display *, XErrorEvent *) = NULL;
static int SDL_XRRSetScreenSizeErrHandler(Display *d, XErrorEvent *e)
{
if ((e->error_code == BadMatch) || (e->error_code == BadValue)) {
return 0;
}
return PreXRRSetScreenSizeErrorHandler(d, e);
}
#endif
bool X11_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SDL_DisplayMode *mode)
{
SDL_VideoData *viddata = _this->internal;
SDL_DisplayData *data = sdl_display->internal;
viddata->last_mode_change_deadline = SDL_GetTicks() + (PENDING_FOCUS_TIME * 2);
if (!viddata->is_xwayland) {
if (sdl_display->current_mode != mode) {
data->mode_switch_deadline_ns = SDL_GetTicksNS() + MODE_SWITCH_TIMEOUT_NS;
} else {
data->mode_switch_deadline_ns = 0;
}
}
#ifdef SDL_VIDEO_DRIVER_X11_XRANDR
if (data->use_xrandr) {
Display *display = viddata->display;
SDL_DisplayModeData *modedata = mode->internal;
int mm_width, mm_height;
XRRScreenResources *res;
XRROutputInfo *output_info;
XRRCrtcInfo *crtc;
Status status;
res = X11_XRRGetScreenResources(display, RootWindow(display, data->screen));
if (!res) {
return SDL_SetError("Couldn't get XRandR screen resources");
}
output_info = X11_XRRGetOutputInfo(display, res, data->xrandr_output);
if (!output_info || output_info->connection == RR_Disconnected) {
X11_XRRFreeScreenResources(res);
return SDL_SetError("Couldn't get XRandR output info");
}
crtc = X11_XRRGetCrtcInfo(display, res, output_info->crtc);
if (!crtc) {
X11_XRRFreeOutputInfo(output_info);
X11_XRRFreeScreenResources(res);
return SDL_SetError("Couldn't get XRandR crtc info");
}
if (crtc->mode == modedata->xrandr_mode) {
#ifdef X11MODES_DEBUG
printf("already in desired mode 0x%lx (%ux%u), nothing to do\n",
crtc->mode, crtc->width, crtc->height);
#endif
status = Success;
goto freeInfo;
}
X11_XGrabServer(display);
status = X11_XRRSetCrtcConfig(display, res, output_info->crtc, CurrentTime,
0, 0, None, crtc->rotation, NULL, 0);
if (status != Success) {
goto ungrabServer;
}
mm_width = mode->w * DisplayWidthMM(display, data->screen) / DisplayWidth(display, data->screen);
mm_height = mode->h * DisplayHeightMM(display, data->screen) / DisplayHeight(display, data->screen);
X11_XSync(display, False);
PreXRRSetScreenSizeErrorHandler = X11_XSetErrorHandler(SDL_XRRSetScreenSizeErrHandler);
X11_XRRSetScreenSize(display, RootWindow(display, data->screen),
mode->w, mode->h, mm_width, mm_height);
X11_XSync(display, False);
X11_XSetErrorHandler(PreXRRSetScreenSizeErrorHandler);
status = X11_XRRSetCrtcConfig(display, res, output_info->crtc, CurrentTime,
crtc->x, crtc->y, modedata->xrandr_mode, crtc->rotation,
&data->xrandr_output, 1);
ungrabServer:
X11_XUngrabServer(display);
freeInfo:
X11_XRRFreeCrtcInfo(crtc);
X11_XRRFreeOutputInfo(output_info);
X11_XRRFreeScreenResources(res);
if (status != Success) {
return SDL_SetError("X11_XRRSetCrtcConfig failed");
}
}
#else
(void)data;
#endif
return true;
}
void X11_QuitModes(SDL_VideoDevice *_this)
{
}
bool X11_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SDL_Rect *rect)
{
SDL_DisplayData *data = sdl_display->internal;
rect->x = data->x;
rect->y = data->y;
rect->w = sdl_display->current_mode->w;
rect->h = sdl_display->current_mode->h;
return true;
}
bool X11_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *sdl_display, SDL_Rect *rect)
{
SDL_VideoData *data = _this->internal;
Display *display = data->display;
Atom _NET_WORKAREA;
int real_format;
Atom real_type;
unsigned long items_read = 0, items_left = 0;
unsigned char *propdata = NULL;
bool result = false;
if (!X11_GetDisplayBounds(_this, sdl_display, rect)) {
return false;
}
_NET_WORKAREA = X11_XInternAtom(display, "_NET_WORKAREA", False);
int status = X11_XGetWindowProperty(display, DefaultRootWindow(display),
_NET_WORKAREA, 0L, 4L, False, XA_CARDINAL,
&real_type, &real_format, &items_read,
&items_left, &propdata);
if ((status == Success) && (items_read >= 4)) {
const long *p = (long *)propdata;
const SDL_Rect usable = { (int)p[0], (int)p[1], (int)p[2], (int)p[3] };
result = true;
if (!SDL_GetRectIntersection(rect, &usable, rect)) {
SDL_zerop(rect);
}
}
if (propdata) {
X11_XFree(propdata);
}
return result;
}
#endif