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