1 /** @file displaymode_x11.cpp X11 implementation of the DisplayMode native functionality.
2  * @ingroup gl
3  *
4  * Uses the XRandR extension to manipulate the display.
5  *
6  * @authors Copyright © 2012-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
7  *
8  * @par License
9  * LGPL: http://www.gnu.org/licenses/lgpl.html
10  *
11  * <small>This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation; either version 3 of the License, or (at your
14  * option) any later version. This program is distributed in the hope that it
15  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
16  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
17  * General Public License for more details. You should have received a copy of
18  * the GNU Lesser General Public License along with this program; if not, see:
19  * http://www.gnu.org/licenses</small>
20  */
21 
22 #include <QDebug>
23 #include <QX11Info>
24 #include <QApplication>
25 #include <QScreen>
26 #include <de/LogBuffer>
27 
28 #include <X11/Xlib.h>
29 #include <X11/extensions/Xrandr.h>
30 #include <X11/extensions/xf86vmode.h>
31 #undef Always
32 #undef None
33 
34 #include "de/gui/displaymode_native.h"
35 
36 #include <assert.h>
37 #include <vector>
38 
39 namespace de {
40 
41 typedef std::vector<DisplayMode> DisplayModes;
42 
43 static int displayDepth;
44 static Rotation displayRotation;
45 static DisplayModes availableModes;
46 static DisplayMode currentMode;
47 
48 namespace internal {
49 
50 /**
51  * Wrapper for the Xrandr configuration info. The config is kept in memory only
52  * for the lifetime of an RRInfo instance.
53  */
54 class RRInfo
55 {
56 public:
57     /**
58      * Queries all the available modes in the display configuration.
59      */
RRInfo()60     RRInfo() : _conf(NULL), _numSizes(0)
61     {
62         int dummy;
63         if (!XRRQueryExtension(QX11Info::display(), &dummy, &dummy)) return; // Not available.
64 
65         _conf = XRRGetScreenInfo(QX11Info::display(), QX11Info::appRootWindow());
66         if (!_conf) return; // Not available.
67 
68         // Let's see which modes are available.
69         _sizes = XRRConfigSizes(_conf, &_numSizes);
70         for (int i = 0; i < _numSizes; ++i)
71         {
72             int numRates = 0;
73             short* rates = XRRConfigRates(_conf, i, &numRates);
74             for (int k = 0; k < numRates; k++)
75             {
76                 DisplayMode mode;
77                 de::zap(mode);
78                 mode.width = _sizes[i].width;
79                 mode.height = _sizes[i].height;
80                 mode.depth = displayDepth;
81                 mode.refreshRate = rates[k];
82                 _modes.push_back(mode);
83             }
84         }
85 
86         ::Time prevConfTime;
87         _confTime = XRRConfigTimes(_conf, &prevConfTime);
88     }
89 
~RRInfo()90     ~RRInfo()
91     {
92         if (_conf) XRRFreeScreenConfigInfo(_conf);
93     }
94 
95     /**
96      * Returns the currently active mode as specified in the Xrandr config.
97      * Also determines the display's current rotation angle.
98      */
currentMode() const99     DisplayMode currentMode() const
100     {
101         DisplayMode mode;
102         de::zap(mode);
103 
104         if (!_conf) return mode;
105 
106         SizeID currentSize = XRRConfigCurrentConfiguration(_conf, &displayRotation);
107 
108         // Update the current mode.
109         mode.width = _sizes[currentSize].width;
110         mode.height = _sizes[currentSize].height;
111         mode.depth = displayDepth;
112         mode.refreshRate = XRRConfigCurrentRate(_conf);
113         return mode;
114     }
115 
modes()116     std::vector<DisplayMode>& modes() { return _modes; }
117 
rateFromMode(const DisplayMode * mode)118     static short rateFromMode(const DisplayMode* mode)
119     {
120         return short(qRound(mode->refreshRate));
121     }
122 
find(const DisplayMode * mode) const123     int find(const DisplayMode* mode) const
124     {
125         for (int i = 0; i < _numSizes; ++i)
126         {
127             int numRates = 0;
128             short* rates = XRRConfigRates(_conf, i, &numRates);
129             for (int k = 0; k < numRates; k++)
130             {
131                 if (rateFromMode(mode) == rates[k] &&
132                    mode->width == _sizes[i].width &&
133                    mode->height == _sizes[i].height)
134                 {
135                     // This is the one.
136                     return i;
137                 }
138             }
139         }
140         return -1;
141     }
142 
apply(const DisplayMode * mode)143     bool apply(const DisplayMode* mode)
144     {
145         if (!_conf) return false;
146 
147         int sizeIdx = find(mode);
148         assert(sizeIdx >= 0);
149 
150         //qDebug() << "calling XRRSetScreenConfig" << _confTime;
151         Status result = XRRSetScreenConfigAndRate(QX11Info::display(), _conf, QX11Info::appRootWindow(),
152                                                   sizeIdx, displayRotation, rateFromMode(mode), _confTime);
153         //qDebug() << "result" << result;
154         if (result == BadValue)
155         {
156             qDebug() << "Failed to apply screen config and rate with Xrandr";
157             return false;
158         }
159 
160         // Update the current mode.
161         de::currentMode = *mode;
162         return true;
163     }
164 
165 private:
166     XRRScreenConfiguration* _conf;
167     XRRScreenSize* _sizes;
168     ::Time _confTime;
169     int _numSizes;
170     DisplayModes _modes;
171 };
172 
173 } // namespace internal
174 } // namespace de
175 
176 using namespace de;
177 using namespace de::internal;
178 
DisplayMode_Native_Init(void)179 void DisplayMode_Native_Init(void)
180 {
181     // We will not be changing the depth at runtime.
182 #ifdef DENG2_QT_5_0_OR_NEWER
183     displayDepth = qApp->screens().at(0)->depth();
184 #else
185     displayDepth = QX11Info::appDepth();
186 #endif
187 
188     RRInfo info;
189     availableModes = info.modes();
190     currentMode = info.currentMode();
191 }
192 
DisplayMode_Native_Shutdown(void)193 void DisplayMode_Native_Shutdown(void)
194 {
195     availableModes.clear();
196 }
197 
DisplayMode_Native_Count(void)198 int DisplayMode_Native_Count(void)
199 {
200     return availableModes.size();
201 }
202 
DisplayMode_Native_GetMode(int index,DisplayMode * mode)203 void DisplayMode_Native_GetMode(int index, DisplayMode* mode)
204 {
205     assert(index >= 0 && index < DisplayMode_Native_Count());
206     *mode = availableModes[index];
207 }
208 
DisplayMode_Native_GetCurrentMode(DisplayMode * mode)209 void DisplayMode_Native_GetCurrentMode(DisplayMode* mode)
210 {
211     *mode = currentMode;
212 }
213 
214 #if 0
215 static int findMode(const DisplayMode* mode)
216 {
217     for (int i = 0; i < DisplayMode_Native_Count(); ++i)
218     {
219         DisplayMode d = devToDisplayMode(devModes[i]);
220         if (DisplayMode_IsEqual(&d, mode))
221         {
222             return i;
223         }
224     }
225     return -1;
226 }
227 #endif
228 
DisplayMode_Native_Change(DisplayMode const * mode,int shouldCapture)229 int DisplayMode_Native_Change(DisplayMode const *mode, int shouldCapture)
230 {
231     DENG2_UNUSED(shouldCapture);
232     return RRInfo().apply(mode);
233 }
234 
DisplayMode_Native_GetColorTransfer(DisplayColorTransfer * colors)235 void DisplayMode_Native_GetColorTransfer(DisplayColorTransfer *colors)
236 {
237     Display *dpy = QX11Info::display();
238     int screen = QX11Info::appScreen();
239     int event = 0, error = 0;
240 
241     LOG_AS("GetColorTransfer");
242 
243     if (!dpy || !XF86VidModeQueryExtension(dpy, &event, &error))
244     {
245         LOG_GL_WARNING("XFree86-VidModeExtension not available.");
246         return;
247     }
248     LOGDEV_GL_XVERBOSE("event# %i error# %i", event << error);
249 
250     // Ramp size.
251     int rampSize = 0;
252     XF86VidModeGetGammaRampSize(dpy, screen, &rampSize);
253     LOGDEV_GL_VERBOSE("Gamma ramp size: %i") << rampSize;
254     if (!rampSize) return;
255 
256     ushort* xRamp = new ushort[3 * rampSize];
257 
258     // Get the current ramps.
259     XF86VidModeGetGammaRamp(dpy, screen, rampSize, xRamp,
260                             xRamp + rampSize, xRamp + 2*rampSize);
261 
262     for (uint i = 0; i < 256; ++i)
263     {
264         const uint tx = qMin(uint(rampSize - 1), i * rampSize / 255);
265         colors->table[i]       = xRamp[tx];
266         colors->table[i + 256] = xRamp[tx + rampSize];
267         colors->table[i + 512] = xRamp[tx + 2*rampSize];
268     }
269 
270     delete [] xRamp;
271 }
272 
DisplayMode_Native_SetColorTransfer(DisplayColorTransfer const * colors)273 void DisplayMode_Native_SetColorTransfer(DisplayColorTransfer const *colors)
274 {
275     Display* dpy = QX11Info::display();
276     if (!dpy) return;
277 
278     // Ramp size.
279     int rampSize = 0;
280     XF86VidModeGetGammaRampSize(dpy, QX11Info::appScreen(), &rampSize);
281     if (!rampSize) return;
282 
283     ushort* xRamp = new ushort[3 * rampSize];
284 
285     for (int i = 0; i < rampSize; ++i)
286     {
287         const uint tx = qMin(255, i * 256 / (rampSize - 1));
288         xRamp[i]              = colors->table[tx];
289         xRamp[i + rampSize]   = colors->table[tx + 256];
290         xRamp[i + 2*rampSize] = colors->table[tx + 512];
291     }
292 
293     XF86VidModeSetGammaRamp(dpy, QX11Info::appScreen(), rampSize,
294                             xRamp, xRamp + rampSize, xRamp + 2*rampSize);
295 
296     delete [] xRamp;
297 }
298