From c67213f331c018f41475b2eb552e23c0b2aa6ac0 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Fri, 7 Aug 2015 01:02:35 -0400 Subject: [PATCH] X11: Fixed XRandR display detection. Previously this only worked on X11 when Xinerama was carrying the weight. Fixes Bugzilla #3062. --- src/video/x11/SDL_x11modes.c | 339 +++++++++++++++++++++++------------ src/video/x11/SDL_x11sym.h | 1 + 2 files changed, 221 insertions(+), 119 deletions(-) diff --git a/src/video/x11/SDL_x11modes.c b/src/video/x11/SDL_x11modes.c index b184dd882..eb4bf7e27 100644 --- a/src/video/x11/SDL_x11modes.c +++ b/src/video/x11/SDL_x11modes.c @@ -244,10 +244,12 @@ CheckXRandR(Display * display, int *major, int *minor) } /* Query the extension version */ + *major = 1; *minor = 3; /* we want 1.3 */ if (!X11_XRRQueryVersion(display, major, minor)) { #ifdef X11MODES_DEBUG printf("XRandR not active on the display\n"); #endif + *major = *minor = 0; return SDL_FALSE; } #ifdef X11MODES_DEBUG @@ -267,20 +269,20 @@ CalculateXRandRRefreshRate(const XRRModeInfo *info) } static SDL_bool -SetXRandRModeInfo(Display *display, XRRScreenResources *res, XRROutputInfo *output_info, +SetXRandRModeInfo(Display *display, XRRScreenResources *res, RRCrtc crtc, RRMode modeID, SDL_DisplayMode *mode) { int i; for (i = 0; i < res->nmode; ++i) { - if (res->modes[i].id == modeID) { - XRRCrtcInfo *crtc; + const XRRModeInfo *info = &res->modes[i]; + if (info->id == modeID) { + XRRCrtcInfo *crtcinfo; Rotation rotation = 0; - const XRRModeInfo *info = &res->modes[i]; - crtc = X11_XRRGetCrtcInfo(display, res, output_info->crtc); - if (crtc) { - rotation = crtc->rotation; - X11_XRRFreeCrtcInfo(crtc); + crtcinfo = X11_XRRGetCrtcInfo(display, res, crtc); + if (crtcinfo) { + rotation = crtcinfo->rotation; + X11_XRRFreeCrtcInfo(crtcinfo); } if (rotation & (XRANDR_ROTATION_LEFT|XRANDR_ROTATION_RIGHT)) { @@ -300,6 +302,203 @@ SetXRandRModeInfo(Display *display, XRRScreenResources *res, XRROutputInfo *outp } return SDL_FALSE; } + +static void +SetXRandRDisplayName(Display *dpy, Atom EDID, char *name, const size_t namelen, RROutput output, const unsigned long widthmm, const unsigned long heightmm) +{ + /* See if we can get the EDID data for the real monitor name */ + 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); + free(info); + } + X11_XFree(prop); + } + break; + } + } + + if (props) { + X11_XFree(props); + } + + inches = (int)((SDL_sqrt(widthmm * widthmm + heightmm * heightmm) / 25.4f) + 0.5f); + if (*name && inches) { + const size_t len = SDL_strlen(name); + SDL_snprintf(&name[len], namelen-len, " %d\"", inches); + } + +#ifdef X11MODES_DEBUG + printf("Display name: %s\n", name); +#endif +} + + +int +X11_InitModes_XRandR(_THIS) +{ + /* In theory, you _could_ have multiple screens (like DISPLAY=:0.0 + and DISPLAY=:0.1) but no XRandR system we care about is like this, + as all the physical displays would be separate XRandR "outputs" on + the one X11 virtual "screen". So we don't use ScreenCount() here. */ + + SDL_VideoData *data = (SDL_VideoData *) _this->driverdata; + Display *dpy = data->display; + Atom EDID = X11_XInternAtom(dpy, "EDID", False); + const int screen = DefaultScreen(dpy); + RROutput primary; + XRRScreenResources *res = NULL; + Uint32 pixelformat; + XVisualInfo vinfo; + XPixmapFormatValues *pixmapformats; + int looking_for_primary; + int scanline_pad; + int output; + int i, n; + + if (get_visualinfo(dpy, screen, &vinfo) < 0) { + return -1; + } + + pixelformat = X11_GetPixelFormatFromVisualInfo(dpy, &vinfo); + if (SDL_ISPIXELFORMAT_INDEXED(pixelformat)) { + return SDL_SetError("Palettized video modes are no longer supported"); + } + + 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); + } + + res = X11_XRRGetScreenResources(dpy, RootWindow(dpy, screen)); + if (!res) { + return -1; + } + + primary = X11_XRRGetOutputPrimary(dpy, RootWindow(dpy, screen)); + + for (looking_for_primary = 1; looking_for_primary >= 0; looking_for_primary--) { + for (output = 0; output < res->noutput; output++) { + XRROutputInfo *output_info; + int display_x, display_y; + unsigned long display_mm_width, display_mm_height; + SDL_DisplayData *displaydata; + char display_name[128]; + SDL_DisplayMode mode; + SDL_DisplayModeData *modedata; + SDL_VideoDisplay display; + RRMode modeID; + RRCrtc output_crtc; + XRRCrtcInfo *crtc; + + /* The primary output _should_ always be sorted first, but just in case... */ + if ((looking_for_primary && (res->outputs[output] != primary)) || + (!looking_for_primary && (res->outputs[output] == primary))) { + continue; + } + + output_info = X11_XRRGetOutputInfo(dpy, res, res->outputs[output]); + if (!output_info || !output_info->crtc || output_info->connection == RR_Disconnected) { + X11_XRRFreeOutputInfo(output_info); + continue; + } + + SDL_strlcpy(display_name, output_info->name, sizeof(display_name)); + 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) { + continue; + } + + 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 SDL_OutOfMemory(); + } + + modedata = (SDL_DisplayModeData *) SDL_calloc(1, sizeof(SDL_DisplayModeData)); + if (!modedata) { + SDL_free(displaydata); + return SDL_OutOfMemory(); + } + modedata->xrandr_mode = modeID; + mode.driverdata = modedata; + + displaydata->screen = screen; + displaydata->visual = vinfo.visual; + displaydata->depth = vinfo.depth; + displaydata->hdpi = ((float) mode.w) * 25.4f / display_mm_width; + displaydata->vdpi = ((float) mode.h) * 25.4f / display_mm_height; + displaydata->ddpi = SDL_ComputeDiagonalDPI(mode.w, mode.h, ((float) display_mm_width) / 25.4f,((float) display_mm_height) / 25.4f); + displaydata->scanline_pad = scanline_pad; + displaydata->x = display_x; + displaydata->y = display_y; + displaydata->use_xrandr = 1; + displaydata->xrandr_output = res->outputs[output]; + + SetXRandRModeInfo(dpy, res, output_crtc, modeID, &mode); + SetXRandRDisplayName(dpy, EDID, display_name, sizeof (display_name), res->outputs[output], display_mm_width, display_mm_height); + + SDL_zero(display); + if (*display_name) { + display.name = display_name; + } + display.desktop_mode = mode; + display.current_mode = mode; + display.driverdata = displaydata; + SDL_AddVideoDisplay(&display); + } + } + + X11_XRRFreeScreenResources(res); + + if (_this->num_displays == 0) { + return SDL_SetError("No available displays"); + } + + return 0; +} #endif /* SDL_VIDEO_DRIVER_X11_XRANDR */ #if SDL_VIDEO_DRIVER_X11_XVIDMODE @@ -399,14 +598,24 @@ X11_InitModes(_THIS) #endif #if SDL_VIDEO_DRIVER_X11_XRANDR int xrandr_major, xrandr_minor; - int use_xrandr = 0; - XRRScreenResources *res = NULL; #endif #if SDL_VIDEO_DRIVER_X11_XVIDMODE int vm_major, vm_minor; int use_vidmode = 0; #endif +/* XRandR is the One True Modern Way to do this on X11. If it's enabled and + available, don't even look at other ways of doing things. */ +#if SDL_VIDEO_DRIVER_X11_XRANDR + /* require at least XRandR v1.3 */ + if (CheckXRandR(data->display, &xrandr_major, &xrandr_minor) && + (xrandr_major >= 2 || (xrandr_major == 1 && xrandr_minor >= 3))) { + return X11_InitModes_XRandR(_this); + } +#endif /* SDL_VIDEO_DRIVER_X11_XRANDR */ + +/* !!! FIXME: eventually remove support for Xinerama and XVidMode (everything below here). */ + #if SDL_VIDEO_DRIVER_X11_XINERAMA /* Query Xinerama extention * NOTE: This works with Nvidia Twinview correctly, but you need version 302.17 (released on June 2012) @@ -433,14 +642,6 @@ X11_InitModes(_THIS) screencount = ScreenCount(data->display); #endif /* SDL_VIDEO_DRIVER_X11_XINERAMA */ -#if SDL_VIDEO_DRIVER_X11_XRANDR - /* require at least XRandR v1.2 */ - if (CheckXRandR(data->display, &xrandr_major, &xrandr_minor) && - (xrandr_major >= 2 || (xrandr_major == 1 && xrandr_minor >= 2))) { - use_xrandr = xrandr_major * 100 + xrandr_minor; - } -#endif /* SDL_VIDEO_DRIVER_X11_XRANDR */ - #if SDL_VIDEO_DRIVER_X11_XVIDMODE if (CheckVidMode(data->display, &vm_major, &vm_minor)) { use_vidmode = vm_major * 100 + vm_minor; @@ -569,106 +770,6 @@ X11_InitModes(_THIS) displaydata->y = 0; } -#if SDL_VIDEO_DRIVER_X11_XRANDR - if (use_xrandr) { - res = X11_XRRGetScreenResources(data->display, RootWindow(data->display, displaydata->screen)); - } - if (res) { - XRROutputInfo *output_info; - XRRCrtcInfo *crtc; - int output; - Atom EDID = X11_XInternAtom(data->display, "EDID", False); - Atom *props; - int nprop; - unsigned long width_mm; - unsigned long height_mm; - int inches = 0; - - for (output = 0; output < res->noutput; output++) { - output_info = X11_XRRGetOutputInfo(data->display, res, res->outputs[output]); - if (!output_info || !output_info->crtc || - output_info->connection == RR_Disconnected) { - X11_XRRFreeOutputInfo(output_info); - continue; - } - - /* Is this the output that corresponds to the current screen? - We're checking the crtc position, but that may not be a valid test - in all cases. Anybody want to give this some love? - */ - crtc = X11_XRRGetCrtcInfo(data->display, res, output_info->crtc); - if (!crtc || crtc->x != displaydata->x || crtc->y != displaydata->y || - crtc->width != mode.w || crtc->height != mode.h) { - X11_XRRFreeOutputInfo(output_info); - X11_XRRFreeCrtcInfo(crtc); - continue; - } - - displaydata->use_xrandr = use_xrandr; - displaydata->xrandr_output = res->outputs[output]; - SetXRandRModeInfo(data->display, res, output_info, crtc->mode, &mode); - - /* Get the name of this display */ - width_mm = output_info->mm_width; - height_mm = output_info->mm_height; - inches = (int)((sqrt(width_mm * width_mm + - height_mm * height_mm) / 25.4f) + 0.5f); - SDL_strlcpy(display_name, output_info->name, sizeof(display_name)); - - /* See if we can get the EDID data for the real monitor name */ - props = X11_XRRListOutputProperties(data->display, res->outputs[output], &nprop); - 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(data->display, - res->outputs[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", output_info->name); - dump_monitor_info(info); - #endif - SDL_strlcpy(display_name, info->dsc_product_name, sizeof(display_name)); - free(info); - } - X11_XFree(prop); - } - break; - } - } - if (props) { - X11_XFree(props); - } - - if (*display_name && inches) { - size_t len = SDL_strlen(display_name); - SDL_snprintf(&display_name[len], sizeof(display_name)-len, " %d\"", inches); - } -#ifdef X11MODES_DEBUG - printf("Display name: %s\n", display_name); -#endif - - X11_XRRFreeOutputInfo(output_info); - X11_XRRFreeCrtcInfo(crtc); - break; - } -#ifdef X11MODES_DEBUG - if (output == res->noutput) { - printf("Couldn't find XRandR CRTC at %d,%d\n", displaydata->x, displaydata->y); - } -#endif - X11_XRRFreeScreenResources(res); - } -#endif /* SDL_VIDEO_DRIVER_X11_XRANDR */ - #if SDL_VIDEO_DRIVER_X11_XVIDMODE if (!displaydata->use_xrandr && #if SDL_VIDEO_DRIVER_X11_XINERAMA @@ -791,7 +892,7 @@ X11_GetDisplayModes(_THIS, SDL_VideoDisplay * sdl_display) } mode.driverdata = modedata; - if (!SetXRandRModeInfo(display, res, output_info, output_info->modes[i], &mode) || + if (!SetXRandRModeInfo(display, res, output_info->crtc, output_info->modes[i], &mode) || !SDL_AddDisplayMode(sdl_display, &mode)) { SDL_free(modedata); } diff --git a/src/video/x11/SDL_x11sym.h b/src/video/x11/SDL_x11sym.h index 9245f198b..d9843f090 100644 --- a/src/video/x11/SDL_x11sym.h +++ b/src/video/x11/SDL_x11sym.h @@ -283,6 +283,7 @@ SDL_X11_SYM(Status,XRRSetCrtcConfig,(Display *dpy, XRRScreenResources *resources SDL_X11_SYM(Atom*,XRRListOutputProperties,(Display *dpy, RROutput output, int *nprop),(dpy,output,nprop),return) SDL_X11_SYM(XRRPropertyInfo*,XRRQueryOutputProperty,(Display *dpy,RROutput output, Atom property),(dpy,output,property),return) SDL_X11_SYM(int,XRRGetOutputProperty,(Display *dpy,RROutput output, Atom property, long offset, long length, Bool _delete, Bool pending, Atom req_type, Atom *actual_type, int *actual_format, unsigned long *nitems, unsigned long *bytes_after, unsigned char **prop),(dpy,output,property,offset,length, _delete, pending, req_type, actual_type, actual_format, nitems, bytes_after, prop),return) +SDL_X11_SYM(RROutput,XRRGetOutputPrimary,(Display *dpy,Window window),(dpy,window),return) #endif /* MIT-SCREEN-SAVER support */