diff --git a/panda/src/x11display/x11GraphicsPipe.cxx b/panda/src/x11display/x11GraphicsPipe.cxx index e5e8da18c8..6050911fb4 100644 --- a/panda/src/x11display/x11GraphicsPipe.cxx +++ b/panda/src/x11display/x11GraphicsPipe.cxx @@ -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 x11GraphicsPipe:: +get_screen_resources() const { + XRRScreenResources *res = nullptr; + + if (_have_xrandr && _XRRGetScreenResourcesCurrent != nullptr) { + res = _XRRGetScreenResourcesCurrent(_display, _root); + } + + return std::unique_ptr(res, _XRRFreeScreenResources); +} + +/** + * Returns an XRRCrtcInfo object, or null if RandR 1.2 is not supported. + */ +std::unique_ptr 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(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. diff --git a/panda/src/x11display/x11GraphicsPipe.h b/panda/src/x11display/x11GraphicsPipe.h index 3b8802e6e2..e1abf80b00 100644 --- a/panda/src/x11display/x11GraphicsPipe.h +++ b/panda/src/x11display/x11GraphicsPipe.h @@ -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 get_screen_resources() const; + std::unique_ptr 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(); diff --git a/panda/src/x11display/x11GraphicsWindow.cxx b/panda/src/x11display/x11GraphicsWindow.cxx index 0aa4e0bf64..5599959cdb 100644 --- a/panda/src/x11display/x11GraphicsWindow.cxx +++ b/panda/src/x11display/x11GraphicsWindow.cxx @@ -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();