Zebediah Figura 70d842b106 winex11: Resize the screen when changing CRTC modes.
Based on a patch by Gabriel Corona.

According to the RandR spec for RRSetCrtcConfig:

"The entire area of the CRTC must fit within the screen size, else a Match
error results. As an example, rotating the screen so that a single CRTC fills
the entire screen before and after may necessitate disabling the CRTC,
resizing the screen, then re-enabling the CRTC at the new configuration to
avoid an invalid intermediate configuration."

This patch involves resizing the screen also when shrinking a CRTC, not just
when expanding it past the current screen size. This is partially because we
have no way to reliably determine the current display width (DisplayWidth() is
never updated past opening the connection, and RandR exposes no way to
retrieve the screen dimensions), and partially because it's probably what the
user wants anyway (e.g. it's what the `xrandr` configuration app does when the
screen size is not expliticly specified).

This patch fixes TestBot failures on the Debian machines for ddraw, d3d8, and
d3d9 device tests.

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=33290
Signed-off-by: Zebediah Figura <z.figura12@gmail.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
2019-04-11 09:15:32 +02:00

603 lines
18 KiB
C

/*
* Wine X11drv Xrandr interface
*
* Copyright 2003 Alexander James Pasadyn
* Copyright 2012 Henri Verbeet for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "config.h"
#include "wine/port.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(xrandr);
#ifdef HAVE_XRRGETSCREENRESOURCES
WINE_DECLARE_DEBUG_CHANNEL(winediag);
#endif
#ifdef SONAME_LIBXRANDR
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
#include "x11drv.h"
#include "wine/library.h"
static void *xrandr_handle;
#define MAKE_FUNCPTR(f) static typeof(f) * p##f;
MAKE_FUNCPTR(XRRConfigCurrentConfiguration)
MAKE_FUNCPTR(XRRConfigCurrentRate)
MAKE_FUNCPTR(XRRFreeScreenConfigInfo)
MAKE_FUNCPTR(XRRGetScreenInfo)
MAKE_FUNCPTR(XRRQueryExtension)
MAKE_FUNCPTR(XRRQueryVersion)
MAKE_FUNCPTR(XRRRates)
MAKE_FUNCPTR(XRRSetScreenConfig)
MAKE_FUNCPTR(XRRSetScreenConfigAndRate)
MAKE_FUNCPTR(XRRSizes)
#ifdef HAVE_XRRGETSCREENRESOURCES
MAKE_FUNCPTR(XRRFreeCrtcInfo)
MAKE_FUNCPTR(XRRFreeOutputInfo)
MAKE_FUNCPTR(XRRFreeScreenResources)
MAKE_FUNCPTR(XRRGetCrtcInfo)
MAKE_FUNCPTR(XRRGetOutputInfo)
MAKE_FUNCPTR(XRRGetScreenResources)
MAKE_FUNCPTR(XRRSetCrtcConfig)
MAKE_FUNCPTR(XRRSetScreenSize)
static typeof(XRRGetScreenResources) *pXRRGetScreenResourcesCurrent;
static RRMode *xrandr12_modes;
static int primary_crtc;
#endif
#undef MAKE_FUNCPTR
static struct x11drv_mode_info *dd_modes;
static SizeID *xrandr10_modes;
static unsigned int xrandr_mode_count;
static int xrandr_current_mode = -1;
static int load_xrandr(void)
{
int r = 0;
if (wine_dlopen(SONAME_LIBXRENDER, RTLD_NOW|RTLD_GLOBAL, NULL, 0) &&
(xrandr_handle = wine_dlopen(SONAME_LIBXRANDR, RTLD_NOW, NULL, 0)))
{
#define LOAD_FUNCPTR(f) \
if((p##f = wine_dlsym(xrandr_handle, #f, NULL, 0)) == NULL) \
goto sym_not_found;
LOAD_FUNCPTR(XRRConfigCurrentConfiguration)
LOAD_FUNCPTR(XRRConfigCurrentRate)
LOAD_FUNCPTR(XRRFreeScreenConfigInfo)
LOAD_FUNCPTR(XRRGetScreenInfo)
LOAD_FUNCPTR(XRRQueryExtension)
LOAD_FUNCPTR(XRRQueryVersion)
LOAD_FUNCPTR(XRRRates)
LOAD_FUNCPTR(XRRSetScreenConfig)
LOAD_FUNCPTR(XRRSetScreenConfigAndRate)
LOAD_FUNCPTR(XRRSizes)
r = 1;
#ifdef HAVE_XRRGETSCREENRESOURCES
LOAD_FUNCPTR(XRRFreeCrtcInfo)
LOAD_FUNCPTR(XRRFreeOutputInfo)
LOAD_FUNCPTR(XRRFreeScreenResources)
LOAD_FUNCPTR(XRRGetCrtcInfo)
LOAD_FUNCPTR(XRRGetOutputInfo)
LOAD_FUNCPTR(XRRGetScreenResources)
LOAD_FUNCPTR(XRRSetCrtcConfig)
LOAD_FUNCPTR(XRRSetScreenSize)
r = 2;
#endif
#undef LOAD_FUNCPTR
sym_not_found:
if (!r) TRACE("Unable to load function ptrs from XRandR library\n");
}
return r;
}
static int XRandRErrorHandler(Display *dpy, XErrorEvent *event, void *arg)
{
return 1;
}
static int xrandr10_get_current_mode(void)
{
SizeID size;
Rotation rot;
XRRScreenConfiguration *sc;
short rate;
unsigned int i;
int res = -1;
if (xrandr_current_mode != -1)
return xrandr_current_mode;
sc = pXRRGetScreenInfo (gdi_display, DefaultRootWindow( gdi_display ));
size = pXRRConfigCurrentConfiguration (sc, &rot);
rate = pXRRConfigCurrentRate (sc);
pXRRFreeScreenConfigInfo(sc);
for (i = 0; i < xrandr_mode_count; ++i)
{
if (xrandr10_modes[i] == size && dd_modes[i].refresh_rate == rate)
{
res = i;
break;
}
}
if (res == -1)
{
ERR("In unknown mode, returning default\n");
return 0;
}
xrandr_current_mode = res;
return res;
}
static LONG xrandr10_set_current_mode( int mode )
{
SizeID size;
Rotation rot;
Window root;
XRRScreenConfiguration *sc;
Status stat;
short rate;
root = DefaultRootWindow( gdi_display );
sc = pXRRGetScreenInfo (gdi_display, root);
pXRRConfigCurrentConfiguration (sc, &rot);
mode = mode % xrandr_mode_count;
TRACE("Changing Resolution to %dx%d @%d Hz\n",
dd_modes[mode].width,
dd_modes[mode].height,
dd_modes[mode].refresh_rate);
size = xrandr10_modes[mode];
rate = dd_modes[mode].refresh_rate;
if (rate)
stat = pXRRSetScreenConfigAndRate( gdi_display, sc, root, size, rot, rate, CurrentTime );
else
stat = pXRRSetScreenConfig( gdi_display, sc, root, size, rot, CurrentTime );
pXRRFreeScreenConfigInfo(sc);
if (stat == RRSetConfigSuccess)
{
xrandr_current_mode = mode;
X11DRV_resize_desktop( dd_modes[mode].width, dd_modes[mode].height );
return DISP_CHANGE_SUCCESSFUL;
}
ERR("Resolution change not successful -- perhaps display has changed?\n");
return DISP_CHANGE_FAILED;
}
static void xrandr10_init_modes(void)
{
XRRScreenSize *sizes;
int sizes_count;
int i, j, nmodes = 0;
sizes = pXRRSizes( gdi_display, DefaultScreen(gdi_display), &sizes_count );
if (sizes_count <= 0) return;
TRACE("XRandR: found %d sizes.\n", sizes_count);
for (i = 0; i < sizes_count; ++i)
{
int rates_count;
short *rates;
rates = pXRRRates( gdi_display, DefaultScreen(gdi_display), i, &rates_count );
TRACE("- at %d: %dx%d (%d rates):", i, sizes[i].width, sizes[i].height, rates_count);
if (rates_count)
{
nmodes += rates_count;
for (j = 0; j < rates_count; ++j)
{
if (j > 0)
TRACE(",");
TRACE(" %d", rates[j]);
}
}
else
{
++nmodes;
TRACE(" <default>");
}
TRACE(" Hz\n");
}
TRACE("XRandR modes: count=%d\n", nmodes);
if (!(xrandr10_modes = HeapAlloc( GetProcessHeap(), 0, sizeof(*xrandr10_modes) * nmodes )))
{
ERR("Failed to allocate xrandr mode info array.\n");
return;
}
dd_modes = X11DRV_Settings_SetHandlers( "XRandR 1.0",
xrandr10_get_current_mode,
xrandr10_set_current_mode,
nmodes, 1 );
xrandr_mode_count = 0;
for (i = 0; i < sizes_count; ++i)
{
int rates_count;
short *rates;
rates = pXRRRates( gdi_display, DefaultScreen(gdi_display), i, &rates_count );
if (rates_count)
{
for (j = 0; j < rates_count; ++j)
{
X11DRV_Settings_AddOneMode( sizes[i].width, sizes[i].height, 0, rates[j] );
xrandr10_modes[xrandr_mode_count++] = i;
}
}
else
{
X11DRV_Settings_AddOneMode( sizes[i].width, sizes[i].height, 0, 0 );
xrandr10_modes[xrandr_mode_count++] = i;
}
}
X11DRV_Settings_AddDepthModes();
nmodes = X11DRV_Settings_GetModeCount();
TRACE("Available DD modes: count=%d\n", nmodes);
TRACE("Enabling XRandR\n");
}
#ifdef HAVE_XRRGETSCREENRESOURCES
static int xrandr12_get_current_mode(void)
{
XRRScreenResources *resources;
XRRCrtcInfo *crtc_info;
int i, ret = -1;
if (xrandr_current_mode != -1)
return xrandr_current_mode;
if (!(resources = pXRRGetScreenResourcesCurrent( gdi_display, root_window )))
{
ERR("Failed to get screen resources.\n");
return 0;
}
if (resources->ncrtc <= primary_crtc ||
!(crtc_info = pXRRGetCrtcInfo( gdi_display, resources, resources->crtcs[primary_crtc] )))
{
pXRRFreeScreenResources( resources );
ERR("Failed to get CRTC info.\n");
return 0;
}
TRACE("CRTC %d: mode %#lx, %ux%u+%d+%d.\n", primary_crtc, crtc_info->mode,
crtc_info->width, crtc_info->height, crtc_info->x, crtc_info->y);
for (i = 0; i < xrandr_mode_count; ++i)
{
if (xrandr12_modes[i] == crtc_info->mode)
{
ret = i;
break;
}
}
pXRRFreeCrtcInfo( crtc_info );
pXRRFreeScreenResources( resources );
if (ret == -1)
{
ERR("Unknown mode, returning default.\n");
return 0;
}
xrandr_current_mode = ret;
return ret;
}
static void get_screen_size( XRRScreenResources *resources, unsigned int *width, unsigned int *height )
{
XRRCrtcInfo *crtc_info;
int i;
*width = *height = 0;
for (i = 0; i < resources->ncrtc; ++i)
{
if (!(crtc_info = pXRRGetCrtcInfo( gdi_display, resources, resources->crtcs[i] )))
continue;
if (crtc_info->mode != None)
{
*width = max(*width, crtc_info->x + crtc_info->width);
*height = max(*height, crtc_info->y + crtc_info->height);
}
pXRRFreeCrtcInfo( crtc_info );
}
}
static LONG xrandr12_set_current_mode( int mode )
{
unsigned int screen_width, screen_height;
Status status = RRSetConfigFailed;
XRRScreenResources *resources;
XRRCrtcInfo *crtc_info;
mode = mode % xrandr_mode_count;
if (!(resources = pXRRGetScreenResourcesCurrent( gdi_display, root_window )))
{
ERR("Failed to get screen resources.\n");
return DISP_CHANGE_FAILED;
}
if (resources->ncrtc <= primary_crtc ||
!(crtc_info = pXRRGetCrtcInfo( gdi_display, resources, resources->crtcs[primary_crtc] )))
{
pXRRFreeScreenResources( resources );
ERR("Failed to get CRTC info.\n");
return DISP_CHANGE_FAILED;
}
TRACE("CRTC %d: mode %#lx, %ux%u+%d+%d.\n", primary_crtc, crtc_info->mode,
crtc_info->width, crtc_info->height, crtc_info->x, crtc_info->y);
/* According to the RandR spec, the entire CRTC must fit inside the screen.
* Since we use the union of all enabled CRTCs to determine the necessary
* screen size, this might involve shrinking the screen, so we must disable
* the CRTC in question first. */
XGrabServer( gdi_display );
status = pXRRSetCrtcConfig( gdi_display, resources, resources->crtcs[primary_crtc],
CurrentTime, crtc_info->x, crtc_info->y, None,
crtc_info->rotation, NULL, 0 );
if (status != RRSetConfigSuccess)
{
XUngrabServer( gdi_display );
ERR("Failed to disable CRTC.\n");
pXRRFreeCrtcInfo( crtc_info );
pXRRFreeScreenResources( resources );
return DISP_CHANGE_FAILED;
}
get_screen_size( resources, &screen_width, &screen_height );
screen_width = max( screen_width, crtc_info->x + dd_modes[mode].width );
screen_height = max( screen_height, crtc_info->y + dd_modes[mode].height );
pXRRSetScreenSize( gdi_display, root_window, screen_width, screen_height,
screen_width * DisplayWidthMM( gdi_display, default_visual.screen )
/ DisplayWidth( gdi_display, default_visual.screen ),
screen_height * DisplayHeightMM( gdi_display, default_visual.screen )
/ DisplayHeight( gdi_display, default_visual.screen ));
status = pXRRSetCrtcConfig( gdi_display, resources, resources->crtcs[primary_crtc],
CurrentTime, crtc_info->x, crtc_info->y, xrandr12_modes[mode],
crtc_info->rotation, crtc_info->outputs, crtc_info->noutput );
XUngrabServer( gdi_display );
pXRRFreeCrtcInfo( crtc_info );
pXRRFreeScreenResources( resources );
if (status != RRSetConfigSuccess)
{
ERR("Resolution change not successful -- perhaps display has changed?\n");
return DISP_CHANGE_FAILED;
}
xrandr_current_mode = mode;
X11DRV_resize_desktop( dd_modes[mode].width, dd_modes[mode].height );
return DISP_CHANGE_SUCCESSFUL;
}
static XRRCrtcInfo *xrandr12_get_primary_crtc_info( XRRScreenResources *resources, int *crtc_idx )
{
XRRCrtcInfo *crtc_info;
int i;
for (i = 0; i < resources->ncrtc; ++i)
{
crtc_info = pXRRGetCrtcInfo( gdi_display, resources, resources->crtcs[i] );
if (!crtc_info || crtc_info->mode == None)
{
pXRRFreeCrtcInfo( crtc_info );
continue;
}
*crtc_idx = i;
return crtc_info;
}
return NULL;
}
static int xrandr12_init_modes(void)
{
unsigned int only_one_resolution = 1, mode_count;
XRRScreenResources *resources;
XRROutputInfo *output_info;
XRRCrtcInfo *crtc_info;
int ret = -1;
int i, j;
if (!(resources = pXRRGetScreenResourcesCurrent( gdi_display, root_window )))
{
ERR("Failed to get screen resources.\n");
return ret;
}
if (!resources->ncrtc)
{
pXRRFreeScreenResources( resources );
if (!(resources = pXRRGetScreenResources( gdi_display, root_window )))
{
ERR("Failed to get screen resources.\n");
return ret;
}
}
if (!(crtc_info = xrandr12_get_primary_crtc_info( resources, &primary_crtc )))
{
pXRRFreeScreenResources( resources );
ERR("Failed to get primary CRTC info.\n");
return ret;
}
TRACE("CRTC %d: mode %#lx, %ux%u+%d+%d.\n", primary_crtc, crtc_info->mode,
crtc_info->width, crtc_info->height, crtc_info->x, crtc_info->y);
if (!crtc_info->noutput || !(output_info = pXRRGetOutputInfo( gdi_display, resources, crtc_info->outputs[0] )))
{
pXRRFreeCrtcInfo( crtc_info );
pXRRFreeScreenResources( resources );
ERR("Failed to get output info.\n");
return ret;
}
TRACE("OUTPUT 0: name %s.\n", debugstr_a(output_info->name));
if (!output_info->nmode)
{
WARN("Output has no modes.\n");
goto done;
}
if (!(xrandr12_modes = HeapAlloc( GetProcessHeap(), 0, sizeof(*xrandr12_modes) * output_info->nmode )))
{
ERR("Failed to allocate xrandr mode info array.\n");
goto done;
}
dd_modes = X11DRV_Settings_SetHandlers( "XRandR 1.2",
xrandr12_get_current_mode,
xrandr12_set_current_mode,
output_info->nmode, 1 );
xrandr_mode_count = 0;
for (i = 0; i < output_info->nmode; ++i)
{
for (j = 0; j < resources->nmode; ++j)
{
XRRModeInfo *mode = &resources->modes[j];
if (mode->id == output_info->modes[i])
{
unsigned int dots = mode->hTotal * mode->vTotal;
unsigned int refresh = dots ? (mode->dotClock + dots / 2) / dots : 0;
TRACE("Adding mode %#lx: %ux%u@%u.\n", mode->id, mode->width, mode->height, refresh);
X11DRV_Settings_AddOneMode( mode->width, mode->height, 0, refresh );
xrandr12_modes[xrandr_mode_count++] = mode->id;
break;
}
}
}
mode_count = X11DRV_Settings_GetModeCount();
for (i = 1; i < mode_count; ++i)
{
if (dd_modes[i].width != dd_modes[0].width || dd_modes[i].height != dd_modes[0].height)
{
only_one_resolution = 0;
break;
}
}
/* Recent (304.64, possibly earlier) versions of the nvidia driver only
* report a DFP's native mode through RandR 1.2 / 1.3. Standard DMT modes
* are only listed through RandR 1.0 / 1.1. This is completely useless,
* but NVIDIA considers this a feature, so it's unlikely to change. The
* best we can do is to fall back to RandR 1.0 and encourage users to
* consider more cooperative driver vendors when we detect such a
* configuration. */
if (only_one_resolution && XQueryExtension( gdi_display, "NV-CONTROL", &i, &j, &ret ))
{
ERR_(winediag)("Broken NVIDIA RandR detected, falling back to RandR 1.0. "
"Please consider using the Nouveau driver instead.\n");
ret = -1;
HeapFree( GetProcessHeap(), 0, xrandr12_modes );
goto done;
}
X11DRV_Settings_AddDepthModes();
ret = 0;
done:
pXRRFreeOutputInfo( output_info );
pXRRFreeCrtcInfo( crtc_info );
pXRRFreeScreenResources( resources );
return ret;
}
#endif /* HAVE_XRRGETSCREENRESOURCES */
void X11DRV_XRandR_Init(void)
{
int event_base, error_base, minor, ret;
static int major;
Bool ok;
if (major) return; /* already initialized? */
if (!usexrandr) return; /* disabled in config */
if (root_window != DefaultRootWindow( gdi_display )) return;
if (!(ret = load_xrandr())) return; /* can't load the Xrandr library */
/* see if Xrandr is available */
if (!pXRRQueryExtension( gdi_display, &event_base, &error_base )) return;
X11DRV_expect_error( gdi_display, XRandRErrorHandler, NULL );
ok = pXRRQueryVersion( gdi_display, &major, &minor );
if (X11DRV_check_error() || !ok) return;
TRACE("Found XRandR %d.%d.\n", major, minor);
#ifdef HAVE_XRRGETSCREENRESOURCES
if (ret >= 2 && (major > 1 || (major == 1 && minor >= 2)))
{
if (major > 1 || (major == 1 && minor >= 3))
pXRRGetScreenResourcesCurrent = wine_dlsym( xrandr_handle, "XRRGetScreenResourcesCurrent", NULL, 0 );
if (!pXRRGetScreenResourcesCurrent)
pXRRGetScreenResourcesCurrent = pXRRGetScreenResources;
}
if (!pXRRGetScreenResourcesCurrent || xrandr12_init_modes() < 0)
#endif
xrandr10_init_modes();
}
#else /* SONAME_LIBXRANDR */
void X11DRV_XRandR_Init(void)
{
TRACE("XRandR support not compiled in.\n");
}
#endif /* SONAME_LIBXRANDR */