x11display: fixes for multi-monitor and fullscreen support

- DisplayInformation now gives the list of display modes returned by XRRGetScreenResources if RandR 1.2 is supported, which is apparently more reliable than the previous XRRSizes/XRRRates approach.
- Switching fullscreen on X11 to the current resolution on the CRTC that the monitor is on will make the window cover the CRTC properly, rather than forcing the window to have an origin of (0, 0).
- Passing in the pipe's display width/height is commonly done to go fullscreen at native res, but this may not correspond to a real display resolution.  It now detects when this is done, and interprets this as "go fullscreen at the native resolution on the current CRTC".

Fixes #575
This commit is contained in:
rdb 2019-03-10 19:30:13 +01:00
parent a634e729c8
commit 0e70997fbf
3 changed files with 283 additions and 47 deletions

View File

@ -156,21 +156,55 @@ x11GraphicsPipe(const std::string &display) :
void *xrandr = dlopen("libXrandr.so.2", RTLD_NOW | RTLD_LOCAL);
if (xrandr != nullptr) {
pfn_XRRQueryExtension _XRRQueryExtension = (pfn_XRRQueryExtension)dlsym(xrandr, "XRRQueryExtension");
pfn_XRRQueryVersion _XRRQueryVersion = (pfn_XRRQueryVersion)dlsym(xrandr, "XRRQueryVersion");
_XRRSizes = (pfn_XRRSizes)dlsym(xrandr, "XRRSizes");
_XRRRates = (pfn_XRRRates)dlsym(xrandr, "XRRRates");
_XRRGetScreenInfo = (pfn_XRRGetScreenInfo)dlsym(xrandr, "XRRGetScreenInfo");
_XRRConfigCurrentConfiguration = (pfn_XRRConfigCurrentConfiguration)dlsym(xrandr, "XRRConfigCurrentConfiguration");
_XRRSetScreenConfig = (pfn_XRRSetScreenConfig)dlsym(xrandr, "XRRSetScreenConfig");
int event, error, major, minor;
if (_XRRQueryExtension == nullptr || _XRRSizes == nullptr || _XRRRates == nullptr ||
_XRRGetScreenInfo == nullptr || _XRRConfigCurrentConfiguration == nullptr ||
_XRRSetScreenConfig == nullptr) {
_XRRSetScreenConfig == nullptr || _XRRQueryVersion == nullptr) {
_have_xrandr = false;
x11display_cat.warning()
<< "libXrandr.so.2 does not provide required functions; resolution setting will not work.\n";
}
else if (_XRRQueryExtension(_display, &event, &error) &&
_XRRQueryVersion(_display, &major, &minor)) {
_have_xrandr = true;
if (x11display_cat.is_debug()) {
x11display_cat.debug()
<< "Found RandR extension " << major << "." << minor << "\n";
}
if (major > 1 || (major == 1 && minor >= 2)) {
if (major > 1 || (major == 1 && minor >= 3)) {
_XRRGetScreenResourcesCurrent = (pfn_XRRGetScreenResources)
dlsym(xrandr, "XRRGetScreenResourcesCurrent");
} else {
// Fall back to this slower version.
_XRRGetScreenResourcesCurrent = (pfn_XRRGetScreenResources)
dlsym(xrandr, "XRRGetScreenResources");
}
_XRRFreeScreenResources = (pfn_XRRFreeScreenResources)dlsym(xrandr, "XRRFreeScreenResources");
_XRRGetCrtcInfo = (pfn_XRRGetCrtcInfo)dlsym(xrandr, "XRRGetCrtcInfo");
_XRRFreeCrtcInfo = (pfn_XRRFreeCrtcInfo)dlsym(xrandr, "XRRFreeCrtcInfo");
} else {
_XRRGetScreenResourcesCurrent = nullptr;
_XRRFreeScreenResources = nullptr;
_XRRGetCrtcInfo = nullptr;
_XRRFreeCrtcInfo = nullptr;
}
} else {
int event, error;
_have_xrandr = _XRRQueryExtension(_display, &event, &error);
_have_xrandr = false;
if (x11display_cat.is_debug()) {
x11display_cat.debug()
<< "RandR extension not supported; resolution setting will not work.\n";
}
}
} else {
_have_xrandr = false;
@ -182,29 +216,61 @@ x11GraphicsPipe(const std::string &display) :
// Use Xrandr to fill in the supported resolution list.
if (_have_xrandr) {
int num_sizes, num_rates;
XRRScreenSize *xrrs;
xrrs = _XRRSizes(_display, 0, &num_sizes);
_display_information->_total_display_modes = 0;
for (int i = 0; i < num_sizes; ++i) {
_XRRRates(_display, 0, i, &num_rates);
_display_information->_total_display_modes += num_rates;
}
// If we have XRRGetScreenResources, we prefer that. It seems to be more
// reliable than XRRSizes in multi-monitor set-ups.
if (auto res = get_screen_resources()) {
if (x11display_cat.is_debug()) {
x11display_cat.debug()
<< "Using XRRScreenResources to obtain display modes\n";
}
_display_information->_total_display_modes = res->nmode;
_display_information->_display_mode_array = new DisplayMode[res->nmode];
for (int i = 0; i < res->nmode; ++i) {
XRRModeInfo &mode = res->modes[i];
short *rates;
short counter = 0;
_display_information->_display_mode_array = new DisplayMode[_display_information->_total_display_modes];
for (int i = 0; i < num_sizes; ++i) {
int num_rates;
rates = _XRRRates(_display, 0, i, &num_rates);
for (int j = 0; j < num_rates; ++j) {
DisplayMode* dm = _display_information->_display_mode_array + counter;
dm->width = xrrs[i].width;
dm->height = xrrs[i].height;
dm->refresh_rate = rates[j];
DisplayMode *dm = _display_information->_display_mode_array + i;
dm->width = mode.width;
dm->height = mode.height;
dm->bits_per_pixel = -1;
dm->fullscreen_only = false;
++counter;
if (mode.hTotal && mode.vTotal) {
dm->refresh_rate = (double)mode.dotClock /
((double)mode.hTotal * (double)mode.vTotal);
} else {
dm->refresh_rate = 0;
}
}
} else {
if (x11display_cat.is_debug()) {
x11display_cat.debug()
<< "Using XRRSizes and XRRRates to obtain display modes\n";
}
int num_sizes, num_rates;
XRRScreenSize *xrrs;
xrrs = _XRRSizes(_display, 0, &num_sizes);
_display_information->_total_display_modes = 0;
for (int i = 0; i < num_sizes; ++i) {
_XRRRates(_display, 0, i, &num_rates);
_display_information->_total_display_modes += num_rates;
}
short *rates;
short counter = 0;
_display_information->_display_mode_array = new DisplayMode[_display_information->_total_display_modes];
for (int i = 0; i < num_sizes; ++i) {
int num_rates;
rates = _XRRRates(_display, 0, i, &num_rates);
for (int j = 0; j < num_rates; ++j) {
DisplayMode* dm = _display_information->_display_mode_array + counter;
dm->width = xrrs[i].width;
dm->height = xrrs[i].height;
dm->refresh_rate = rates[j];
dm->bits_per_pixel = -1;
dm->fullscreen_only = false;
++counter;
}
}
}
}
@ -258,6 +324,69 @@ x11GraphicsPipe::
}
}
/**
* Returns an XRRScreenResources object, or null if RandR 1.2 is not supported.
*/
std::unique_ptr<XRRScreenResources, pfn_XRRFreeScreenResources> x11GraphicsPipe::
get_screen_resources() const {
XRRScreenResources *res = nullptr;
if (_have_xrandr && _XRRGetScreenResourcesCurrent != nullptr) {
res = _XRRGetScreenResourcesCurrent(_display, _root);
}
return std::unique_ptr<XRRScreenResources, pfn_XRRFreeScreenResources>(res, _XRRFreeScreenResources);
}
/**
* Returns an XRRCrtcInfo object, or null if RandR 1.2 is not supported.
*/
std::unique_ptr<XRRCrtcInfo, pfn_XRRFreeCrtcInfo> x11GraphicsPipe::
get_crtc_info(XRRScreenResources *res, RRCrtc crtc) const {
XRRCrtcInfo *info = nullptr;
if (_have_xrandr && _XRRGetCrtcInfo != nullptr) {
info = _XRRGetCrtcInfo(_display, res, crtc);
}
return std::unique_ptr<XRRCrtcInfo, pfn_XRRFreeCrtcInfo>(info, _XRRFreeCrtcInfo);
}
/**
* Finds a CRTC for going fullscreen to, at the given origin. The new CRTC
* is returned, along with its x, y, width and height.
*
* If the required RandR extension is not supported, a value of None will be
* returned, but x, y, width and height will still be populated.
*/
RRCrtc x11GraphicsPipe::
find_fullscreen_crtc(const LPoint2i &point,
int &x, int &y, int &width, int &height) {
x = 0;
y = 0;
width = DisplayWidth(_display, _screen);
height = DisplayHeight(_display, _screen);
if (auto res = get_screen_resources()) {
for (int i = 0; i < res->ncrtc; ++i) {
RRCrtc crtc = res->crtcs[i];
if (auto info = get_crtc_info(res.get(), crtc)) {
if (point[0] >= info->x && point[0] < info->x + info->width &&
point[1] >= info->y && point[1] < info->y + info->height) {
x = info->x;
y = info->y;
width = info->width;
height = info->height;
return crtc;
}
}
}
}
return None;
}
/**
* Returns an indication of the thread in which this GraphicsPipe requires its
* window processing to be performed: typically either the app thread (e.g.

View File

@ -32,12 +32,61 @@ typedef struct _XcursorImages XcursorImages;
typedef unsigned short Rotation;
typedef unsigned short SizeID;
typedef unsigned long XRRModeFlags;
typedef XID RROutput;
typedef XID RRCrtc;
typedef XID RRMode;
typedef struct _XRRScreenConfiguration XRRScreenConfiguration;
typedef struct {
int width, height;
int mwidth, mheight;
} XRRScreenSize;
typedef struct _XRRModeInfo {
RRMode id;
unsigned int width;
unsigned int height;
unsigned long dotClock;
unsigned int hSyncStart;
unsigned int hSyncEnd;
unsigned int hTotal;
unsigned int hSkew;
unsigned int vSyncStart;
unsigned int vSyncEnd;
unsigned int vTotal;
char *name;
unsigned int nameLength;
XRRModeFlags modeFlags;
} XRRModeInfo;
typedef struct _XRRScreenResources {
Time timestamp;
Time configTimestamp;
int ncrtc;
RRCrtc *crtcs;
int noutput;
RROutput *outputs;
int nmode;
XRRModeInfo *modes;
} XRRScreenResources;
typedef struct _XRRCrtcInfo {
Time timestamp;
int x, y;
unsigned int width, height;
RRMode mode;
Rotation rotation;
int noutput;
RROutput *outputs;
Rotation rotations;
int npossible;
RROutput *possible;
} XRRCrtcInfo;
typedef void (*pfn_XRRFreeScreenResources)(XRRScreenResources *resources);
typedef void (*pfn_XRRFreeCrtcInfo)(XRRCrtcInfo *crtcInfo);
class FrameBufferProperties;
/**
@ -64,6 +113,12 @@ public:
static INLINE int enable_x_error_messages();
static INLINE int get_x_error_count();
std::unique_ptr<XRRScreenResources, pfn_XRRFreeScreenResources> get_screen_resources() const;
std::unique_ptr<XRRCrtcInfo, pfn_XRRFreeCrtcInfo> get_crtc_info(XRRScreenResources *res, RRCrtc crtc) const;
RRCrtc find_fullscreen_crtc(const LPoint2i &point,
int &x, int &y, int &width, int &height);
public:
virtual PreferredWindowThread get_preferred_window_thread() const;
@ -100,6 +155,7 @@ public:
pfn_XcursorImageDestroy _XcursorImageDestroy;
typedef Bool (*pfn_XRRQueryExtension)(X11_Display *, int*, int*);
typedef Status (*pfn_XRRQueryVersion)(X11_Display *, int*, int*);
typedef XRRScreenSize *(*pfn_XRRSizes)(X11_Display*, int, int*);
typedef short *(*pfn_XRRRates)(X11_Display*, int, int, int*);
typedef XRRScreenConfiguration *(*pfn_XRRGetScreenInfo)(X11_Display*, X11_Window);
@ -126,6 +182,14 @@ protected:
typedef Status (*pfn_XF86DGADirectVideo)(X11_Display *, int, int);
pfn_XF86DGADirectVideo _XF86DGADirectVideo;
typedef XRRScreenResources *(*pfn_XRRGetScreenResources)(X11_Display*, X11_Window);
typedef XRRCrtcInfo *(*pfn_XRRGetCrtcInfo)(X11_Display *dpy, XRRScreenResources *resources, RRCrtc crtc);
pfn_XRRGetScreenResources _XRRGetScreenResourcesCurrent;
pfn_XRRFreeScreenResources _XRRFreeScreenResources;
pfn_XRRGetCrtcInfo _XRRGetCrtcInfo;
pfn_XRRFreeCrtcInfo _XRRFreeCrtcInfo;
private:
void make_hidden_cursor();
void release_hidden_cursor();

View File

@ -579,20 +579,60 @@ set_properties_now(WindowProperties &properties) {
bool is_fullscreen = _properties.has_fullscreen() && _properties.get_fullscreen();
bool want_fullscreen = properties.has_fullscreen() ? properties.get_fullscreen() : is_fullscreen;
if (want_fullscreen && properties.has_origin()) {
// If we're fullscreen, reject changes to the origin.
properties.clear_origin();
}
if (is_fullscreen != want_fullscreen || (is_fullscreen && properties.has_size())) {
if (want_fullscreen) {
if (x11_pipe->_have_xrandr) {
XRRScreenConfiguration* conf = _XRRGetScreenInfo(_display, x11_pipe->get_root());
// OK, first figure out which CRTC the window is on. It may be on more
// than one, actually, so grab a point in the center in order to figure
// out which one it's more-or-less mostly on.
LPoint2i center = _properties.get_origin() + _properties.get_size() / 2;
int x, y, width, height;
x11_pipe->find_fullscreen_crtc(center, x, y, width, height);
// Which size should we go fullscreen in?
int reqsizex, reqsizey;
if (properties.has_size()) {
reqsizex = properties.get_x_size();
reqsizey = properties.get_y_size();
} else if (_properties.has_size()) {
reqsizex = _properties.get_x_size();
reqsizey = _properties.get_y_size();
} else {
reqsizex = width;
reqsizey = height;
}
// Are we passing in pipe.display_width/height? This is actually the
// size of the virtual desktop, which may not be a real resolution, so
// if that is passed in, we have to assume that the user means to just
// fullscreen without changing the screen resolution.
if ((reqsizex == x11_pipe->get_display_width() &&
reqsizey == x11_pipe->get_display_height())
|| (width == reqsizex && height == reqsizey)
|| !x11_pipe->_have_xrandr) {
// Cover the current CRTC.
properties.set_origin(x, y);
properties.set_size(width, height);
if (x11display_cat.is_debug()) {
x11display_cat.debug()
<< "Setting window to fullscreen on CRTC "
<< width << "x" << height << "+" << x << "+" << y << "\n";
}
} else {
// We may need to change the screen resolution. The code below is
// suboptimal; in the future, we probably want to only touch the CRTC
// that the window is on.
XRRScreenConfiguration *conf = _XRRGetScreenInfo(_display, _xwindow);
SizeID old_size_id = x11_pipe->_XRRConfigCurrentConfiguration(conf, &_orig_rotation);
SizeID new_size_id = (SizeID) -1;
int num_sizes = 0, reqsizex, reqsizey;
if (properties.has_size()) {
reqsizex = properties.get_x_size();
reqsizey = properties.get_y_size();
} else {
reqsizex = _properties.get_x_size();
reqsizey = _properties.get_y_size();
}
int num_sizes = 0;
XRRScreenSize *xrrs;
xrrs = x11_pipe->_XRRSizes(_display, 0, &num_sizes);
for (int i = 0; i < num_sizes; ++i) {
@ -605,21 +645,29 @@ set_properties_now(WindowProperties &properties) {
x11display_cat.error()
<< "Videocard has no supported display resolutions at specified res ("
<< reqsizex << " x " << reqsizey << ")\n";
} else {
if (new_size_id != old_size_id) {
// Just go fullscreen at native resolution, then.
properties.set_origin(x, y);
properties.set_size(width, height);
} else {
if (x11display_cat.is_debug()) {
x11display_cat.debug()
<< "Switching to fullscreen with resolution "
<< reqsizex << "x" << reqsizey << "\n";
}
if (new_size_id != old_size_id) {
_XRRSetScreenConfig(_display, conf, x11_pipe->get_root(), new_size_id, _orig_rotation, CurrentTime);
if (_orig_size_id == (SizeID) -1) {
// Remember the original resolution so we can switch back to it.
_orig_size_id = old_size_id;
}
// Since the above changes the entire screen configuration, we
// have to set the origin to 0, 0.
properties.set_origin(0, 0);
}
}
} else {
// If we don't have Xrandr support, we fake the fullscreen support by
// setting the window size to the desktop size.
properties.set_size(x11_pipe->get_display_width(),
x11_pipe->get_display_height());
}
} else {
// Change the resolution back to what it was. Don't remove the SizeID
@ -1130,13 +1178,8 @@ set_wm_properties(const WindowProperties &properties, bool already_mapped) {
size_hints_p = XAllocSizeHints();
if (size_hints_p != nullptr) {
if (properties.has_origin()) {
if (_properties.get_fullscreen()) {
size_hints_p->x = 0;
size_hints_p->y = 0;
} else {
size_hints_p->x = properties.get_x_origin();
size_hints_p->y = properties.get_y_origin();
}
size_hints_p->x = properties.get_x_origin();
size_hints_p->y = properties.get_y_origin();
size_hints_p->flags |= USPosition;
}
LVecBase2i size = _properties.get_size();