-#include <iostream>
+#include "display.h"
+#include "display_private.h"
#include <X11/Xlib.h>
-#ifdef WITH_XF86VIDMODE
-#include <X11/extensions/xf86vmode.h>
+#include <X11/Xatom.h>
+#ifdef WITH_XRANDR
+#include <X11/extensions/Xrandr.h>
#endif
+#include <msp/core/algorithm.h>
#include <msp/io/print.h>
#include <msp/strings/format.h>
#include <msp/strings/lexicalcast.h>
-#include "display.h"
-#include "display_private.h"
using namespace std;
namespace {
bool error_flag = false;
-std::string error_msg;
+string error_msg;
int x_error_handler(Display *display, XErrorEvent *event)
{
return 0;
}
+#ifdef WITH_XRANDR
+inline Msp::Graphics::VideoRotation rotation_from_sys(Rotation r)
+{
+ switch(r)
+ {
+ case RR_Rotate_90: return Msp::Graphics::ROTATE_RIGHT;
+ case RR_Rotate_180: return Msp::Graphics::ROTATE_INVERTED;
+ case RR_Rotate_270: return Msp::Graphics::ROTATE_LEFT;
+ default: return Msp::Graphics::ROTATE_NORMAL;
+ }
+}
+
+inline Rotation rotation_to_sys(Msp::Graphics::VideoRotation r)
+{
+ switch(r)
+ {
+ case Msp::Graphics::ROTATE_RIGHT: return RR_Rotate_90;
+ case Msp::Graphics::ROTATE_INVERTED: return RR_Rotate_180;
+ case Msp::Graphics::ROTATE_LEFT: return RR_Rotate_270;
+ default: return RR_Rotate_0;
+ }
+}
+#endif
+
+inline unsigned mode_width(const Msp::Graphics::VideoMode &m, Msp::Graphics::VideoRotation r)
+{
+ if(r==Msp::Graphics::ROTATE_RIGHT || r==Msp::Graphics::ROTATE_LEFT)
+ return m.height;
+ else
+ return m.width;
+}
+
}
namespace Graphics {
Display::Display(const string &disp_name):
- primary_monitor(0),
priv(new Private)
{
if(disp_name.empty())
- priv->display = XOpenDisplay(0);
+ priv->display = XOpenDisplay(nullptr);
else
priv->display = XOpenDisplay(disp_name.c_str());
if(!priv->display)
XSetErrorHandler(x_error_handler);
-#ifdef WITH_XF86VIDMODE
- int screen = DefaultScreen(priv->display);
+ priv->root_window = DefaultRootWindow(priv->display);
- monitors.push_back(Monitor());
- Monitor &monitor = monitors.back();
- primary_monitor = &monitor;
+ err_dialog = new ErrorDialog(this);
- int nmodes;
- XF86VidModeModeInfo **infos;
- XF86VidModeGetAllModeLines(priv->display, screen, &nmodes, &infos);
+#ifdef WITH_XRANDR
+ int event_base;
+ int error_base;
+ if(XRRQueryExtension(priv->display, &event_base, &error_base))
+ {
+ int major, minor;
+ XRRQueryVersion(priv->display, &major, &minor);
+ if(major>1 || (major==1 && minor>=2))
+ {
+ XRRScreenResources *res = XRRGetScreenResources(priv->display, priv->root_window);
+ RROutput primary = XRRGetOutputPrimary(priv->display, priv->root_window);
+ Atom edid_prop = XInternAtom(priv->display, RR_PROPERTY_RANDR_EDID, true);
+
+ map<RRMode, XRRModeInfo *> modes_by_id;
+ for(int i=0; i<res->nmode; ++i)
+ modes_by_id[res->modes[i].id] = &res->modes[i];
+
+ int primary_index = -1;
+ vector<unsigned> mode_monitor_indices;
+ vector<int> desktop_mode_indices;
+ for(int i=0; i<res->noutput; ++i)
+ {
+ XRROutputInfo *output = XRRGetOutputInfo(priv->display, res, res->outputs[i]);
+ XRRCrtcInfo *crtc = (output->crtc ? XRRGetCrtcInfo(priv->display, res, output->crtc) : nullptr);
- XF86VidModeModeLine modeline;
- int dotclock;
- XF86VidModeGetModeLine(priv->display, screen, &dotclock, &modeline);
+ monitors.push_back(Monitor());
+ Monitor &monitor = monitors.back();
+ monitor.index = monitors.size()-1;
+ monitor.name.assign(output->name, output->nameLen);
+ priv->monitors.push_back(res->outputs[i]);
- for(int i=0; i<nmodes; ++i)
- {
- XF86VidModeModeInfo &info = *infos[i];
-
- VideoMode mode(info.hdisplay, info.vdisplay);
- mode.monitor = &monitor;
- if(info.htotal && info.vtotal)
- mode.rate = info.dotclock/(info.htotal*info.vtotal);
- modes.push_back(mode);
- monitor.video_modes.push_back(&modes.back());
-
- if(info.htotal==modeline.htotal && info.vtotal==modeline.vtotal && info.dotclock==static_cast<unsigned>(dotclock))
- monitor.desktop_mode = &modes.back();
- }
+ if(edid_prop)
+ {
+ Atom prop_type;
+ int prop_format;
+ unsigned long length;
+ unsigned long overflow;
+ unsigned char *edid = nullptr;
+ XRRGetOutputProperty(priv->display, res->outputs[i], edid_prop, 0, 32, false, false, XA_INTEGER, &prop_type, &prop_format, &length, &overflow, &edid);
+ if(prop_type==XA_INTEGER && prop_format==8)
+ {
+ for(unsigned j=0; j<4; ++j)
+ {
+ unsigned offset = 54+j*18;
+ if(edid[offset]==0 && edid[offset+1]==0 && edid[offset+3]==0xFC)
+ {
+ unsigned k;
+ for(k=0; (k<13 && edid[offset+5+k]!=0x0A); ++k) ;
+ monitor.name.assign(reinterpret_cast<char *>(edid+offset+5), k);
+ }
+ }
+ }
+ XFree(edid);
+ }
+
+ if(crtc)
+ {
+ monitor.desktop_settings.rotation = rotation_from_sys(crtc->rotation);
+ monitor.desktop_settings.x = crtc->x;
+ monitor.desktop_settings.y = crtc->y;
+ }
+
+ if(res->outputs[i]==primary)
+ primary_index = monitor.index;
+
+ unsigned first_mode = modes.size();
+ int desktop_mode_index = -1;
+ for(int j=0; j<output->nmode; ++j)
+ {
+ auto k = modes_by_id.find(output->modes[j]);
+ if(k==modes_by_id.end())
+ continue;
+
+ XRRModeInfo *info = k->second;
+ float rate = static_cast<float>(info->dotClock)/(info->hTotal*info->vTotal);
+
+ if(any_of(modes.begin()+first_mode, modes.end(), [&info, rate](const VideoMode &m){
+ return (m.width==info->width && m.height==info->height && abs(m.rate-rate)<0.01f);
+ }))
+ continue;
+
+ VideoMode mode(info->width, info->height);
+ mode.index = modes.size();
+ mode.rate = rate;
- XFree(infos);
+ modes.push_back(mode);
+ priv->modes.push_back(info->id);
+ mode_monitor_indices.push_back(monitor.index);
+
+ if(crtc && info->id==crtc->mode)
+ desktop_mode_index = mode.index;
+ }
+ desktop_mode_indices.push_back(desktop_mode_index);
+
+ XRRFreeOutputInfo(output);
+ if(crtc)
+ XRRFreeCrtcInfo(crtc);
+ }
+
+ XRRFreeScreenResources(res);
+
+ for(unsigned i=0; i<monitors.size(); ++i)
+ {
+ int j = desktop_mode_indices[i];
+ if(j>=0)
+ monitors[i].desktop_settings.mode = &modes[j];
+ monitors[i].current_settings = monitors[i].desktop_settings;
+ }
+
+ sort(monitors, [](const Msp::Graphics::Monitor &m1, const Msp::Graphics::Monitor &m2){
+ if(m1.desktop_settings.mode && !m2.desktop_settings.mode)
+ return true;
+ if(!m1.desktop_settings.mode && m2.desktop_settings.mode)
+ return false;
+ return m1.desktop_settings.x<m2.desktop_settings.x;
+ });
+
+ for(unsigned i=0; i<modes.size(); ++i)
+ {
+ auto j = find_member(monitors, mode_monitor_indices[i], &Monitor::index);
+ modes[i].monitor = &*j;
+ j->video_modes.push_back(&modes[i]);
+ }
+
+ Monitor *prev_enabled = nullptr;
+ for(Monitor &m: monitors)
+ if(m.desktop_settings.mode)
+ {
+ m.next_left = prev_enabled;
+ if(prev_enabled)
+ prev_enabled->next_right = &m;
+ prev_enabled = &m;
+ }
+
+ if(primary_index>=0)
+ primary_monitor = &*find_member(monitors, static_cast<unsigned>(primary_index), &Monitor::index);
+
+ if(!primary_monitor || !primary_monitor->desktop_settings.mode)
+ {
+ // XRandR didn't give a sensible primary monitor. Try to guess one.
+ unsigned largest = 0;
+ for(Monitor &m: monitors)
+ if(const VideoMode *desktop_mode = m.desktop_settings.mode)
+ {
+ unsigned size = desktop_mode->width*desktop_mode->height;
+ if(size>largest)
+ {
+ largest = size;
+ primary_monitor = &m;
+ }
+ }
+ }
+ }
+ }
#endif
}
{
XCloseDisplay(priv->display);
delete priv;
+ delete err_dialog;
}
-void Display::set_mode(const VideoMode &mode, bool)
+void Display::set_mode(const VideoMode &requested_mode, bool exclusive)
{
-#ifdef WITH_XF86VIDMODE
- int screen = DefaultScreen(priv->display);
-
- int nmodes;
- XF86VidModeModeInfo **infos;
- XF86VidModeGetAllModeLines(priv->display, screen, &nmodes, &infos);
- for(int i=0; i<nmodes; ++i)
+#ifdef WITH_XRANDR
+ const VideoMode *mode = find_mode(requested_mode);
+ if(!mode)
+ throw unsupported_video_mode(requested_mode);
+
+ VideoRotation requested_rotation = requested_mode.rotation;
+ if(requested_rotation==ROTATE_ANY)
+ requested_rotation = mode->monitor->desktop_settings.rotation;
+
+ XRRScreenResources *res = XRRGetScreenResources(priv->display, priv->root_window);
+ RROutput output = priv->monitors[mode->monitor->index];
+ XRROutputInfo *output_info = XRRGetOutputInfo(priv->display, res, output);
+
+ // Check if the output already has a CRTC and find a free one if it doesn't
+ RRCrtc crtc = output_info->crtc;
+ XRRCrtcInfo *crtc_info = nullptr;
+ if(crtc)
+ crtc_info = XRRGetCrtcInfo(priv->display, res, crtc);
+ else
{
- XF86VidModeModeInfo &info = *infos[i];
+ for(int i=0; i<res->ncrtc; ++i)
+ {
+ crtc_info = XRRGetCrtcInfo(priv->display, res, res->crtcs[i]);
+ if(!crtc_info->noutput)
+ {
+ crtc = res->crtcs[i];
+ break;
+ }
+ XRRFreeCrtcInfo(crtc_info);
+ }
- unsigned rate = 0;
- if(info.htotal && info.vtotal)
- rate = info.dotclock/(info.htotal*info.vtotal);
- if(info.hdisplay==mode.width && info.vdisplay==mode.height && (mode.rate==0 || rate==mode.rate))
+ if(!crtc)
{
- XF86VidModeSwitchToMode(priv->display, screen, &info);
- XF86VidModeSetViewPort(priv->display, screen, 0, 0);
- return;
+ XRRFreeOutputInfo(output_info);
+ throw unsupported_video_mode(requested_mode);
}
}
- throw unsupported_video_mode(mode);
+ /* Due to the semantics of find_mode, the mode's monitor pointer must point
+ to one of the elements of the monitors list, which is non-const here. */
+ Monitor *monitor = const_cast<Monitor *>(mode->monitor);
+
+ if(exclusive)
+ {
+ monitor->current_settings.mode = mode;
+ monitor->current_settings.rotation = requested_rotation;
+ monitor->current_settings.x = 0;
+ monitor->current_settings.y = 0;
+
+ // Disable other outputs for exclusive mode
+ for(Monitor &m: monitors)
+ if(&m!=mode->monitor)
+ {
+ XRROutputInfo *o = XRRGetOutputInfo(priv->display, res, priv->monitors[m.index]);
+ if(o->crtc)
+ XRRSetCrtcConfig(priv->display, res, o->crtc, CurrentTime, 0, 0, 0, RR_Rotate_0, nullptr, 0);
+ XRRFreeOutputInfo(o);
+
+ m.current_settings.mode = nullptr;
+ m.current_settings.rotation = ROTATE_NORMAL;
+ m.current_settings.x = 0;
+ m.current_settings.y = 0;
+ }
+ }
+ else
+ {
+ monitor->current_settings.x = monitor->desktop_settings.x;
+ monitor->current_settings.y = monitor->desktop_settings.y;
+ }
+
+ RRMode mode_id = priv->modes[mode->index];
+ int x = monitor->current_settings.x;
+ int y = monitor->current_settings.y;
+ Rotation sys_rot = rotation_to_sys(requested_rotation);
+ XRRSetCrtcConfig(priv->display, res, crtc, CurrentTime, x, y, mode_id, sys_rot, &output, 1);
+
+ XRRFreeOutputInfo(output_info);
+ XRRFreeCrtcInfo(crtc_info);
+ XRRFreeScreenResources(res);
#else
- (void)mode;
- throw runtime_error("no xf86vidmode support");
+ (void)requested_mode;
+ (void)exclusive;
+ throw runtime_error("no xrandr support");
#endif
}
check_error();
- map<WindowHandle, Window *>::iterator j = priv->windows.find(event.xevent.xany.window);
+ auto j = priv->windows.find(event.xevent.xany.window);
if(j!=priv->windows.end())
{
/* Filter keyboard autorepeat. If this packet is a KeyRelease and