]> git.tdb.fi Git - libs/gui.git/blobdiff - source/graphics/x11/display.cpp
Retain monitor positions when setting non-exclusive modes
[libs/gui.git] / source / graphics / x11 / display.cpp
index 6fee30b26ff6873d88b1f69e1b6ec789358e1c06..972cc8236f25024eec7fee0167811a4aeb76814b 100644 (file)
@@ -1,7 +1,7 @@
-#include <iostream>
 #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/io/print.h>
 #include <msp/strings/format.h>
@@ -38,6 +38,45 @@ 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;
+       }
+}
+
+inline bool monitor_x_compare(const Msp::Graphics::Monitor &m1, const Msp::Graphics::Monitor &m2)
+{
+       if(m1.desktop_settings.mode && !m2.desktop_settings.mode)
+               return true;
+       return m1.desktop_settings.x<m2.desktop_settings.x;
+}
+#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;
+}
+
 }
 
 
@@ -46,7 +85,8 @@ namespace Graphics {
 
 Display::Display(const string &disp_name):
        primary_monitor(0),
-       priv(new Private)
+       priv(new Private),
+       focus_window(0)
 {
        if(disp_name.empty())
                priv->display = XOpenDisplay(0);
@@ -57,37 +97,132 @@ Display::Display(const string &disp_name):
 
        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);
 
-       XF86VidModeModeLine modeline;
-       int dotclock;
-       XF86VidModeGetModeLine(priv->display, screen, &dotclock, &modeline);
+                       map<RRMode, XRRModeInfo *> modes_by_id;
+                       for(int i=0; i<res->nmode; ++i)
+                               modes_by_id[res->modes[i].id] = &res->modes[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();
-       }
+                       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) : 0);
+
+                               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]);
+
+                               if(edid_prop)
+                               {
+                                       Atom prop_type;
+                                       int prop_format;
+                                       unsigned long length;
+                                       unsigned long overflow;
+                                       unsigned char *edid = 0;
+                                       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_monitor = &monitor;
+
+                               for(int j=0; j<output->nmode; ++j)
+                               {
+                                       map<RRMode, XRRModeInfo *>::iterator k = modes_by_id.find(output->modes[j]);
+                                       if(k==modes_by_id.end())
+                                               continue;
 
-       XFree(infos);
+                                       XRRModeInfo *info = k->second;
+
+                                       VideoMode mode(info->width, info->height);
+                                       mode.index = modes.size();
+                                       mode.monitor = &monitor;
+                                       mode.rate = static_cast<float>(info->dotClock)/(info->hTotal*info->vTotal);
+                                       if(find_mode(mode, 0.01f))
+                                               continue;
+
+                                       modes.push_back(mode);
+                                       priv->modes.push_back(info->id);
+                                       monitor.video_modes.push_back(&modes.back());
+
+                                       if(crtc && info->id==crtc->mode)
+                                               monitor.desktop_settings.mode = &modes.back();
+                               }
+
+                               monitor.current_settings = monitor.desktop_settings;
+
+                               XRRFreeOutputInfo(output);
+                               if(crtc)
+                                       XRRFreeCrtcInfo(crtc);
+                       }
+
+                       XRRFreeScreenResources(res);
+
+                       monitors.sort(monitor_x_compare);
+                       Monitor *prev_enabled = 0;
+                       for(list<Monitor>::iterator i=monitors.begin(); i!=monitors.end(); ++i)
+                               if(i->desktop_settings.mode)
+                               {
+                                       i->next_left = prev_enabled;
+                                       if(prev_enabled)
+                                               prev_enabled->next_right = &*i;
+                                       prev_enabled = &*i;
+                               }
+
+                       if(!primary_monitor || !primary_monitor->desktop_settings.mode)
+                       {
+                               // XRandR didn't give a sensible primary monitor.  Try to guess one.
+                               unsigned largest = 0;
+                               for(list<Monitor>::iterator i=monitors.begin(); i!=monitors.end(); ++i)
+                                       if(const VideoMode *desktop_mode = i->desktop_settings.mode)
+                                       {
+                                               unsigned size = desktop_mode->width*desktop_mode->height;
+                                               if(size>largest)
+                                               {
+                                                       largest = size;
+                                                       primary_monitor = &*i;
+                                               }
+                                       }
+                       }
+               }
+       }
 #endif
 }
 
@@ -95,35 +230,94 @@ Display::~Display()
 {
        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 = 0;
+       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(list<Monitor>::iterator i=monitors.begin(); i!=monitors.end(); ++i)
+                       if(&*i!=mode->monitor)
+                       {
+                               XRROutputInfo *o = XRRGetOutputInfo(priv->display, res, priv->monitors[i->index]);
+                               if(o->crtc)
+                                       XRRSetCrtcConfig(priv->display, res, o->crtc, CurrentTime, 0, 0, 0, RR_Rotate_0, 0, 0);
+                               XRRFreeOutputInfo(o);
+
+                               i->current_settings.mode = 0;
+                               i->current_settings.rotation = ROTATE_NORMAL;
+                               i->current_settings.x = 0;
+                               i->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
 }