1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "Linux/BsLinuxVideoModeInfo.h"
4#include "Private/Linux/BsLinuxPlatform.h"
5#include <X11/extensions/Xrandr.h>
6
7#define XRANDR_ROTATION_LEFT (1 << 1)
8#define XRANDR_ROTATION_RIGHT (1 << 3)
9
10namespace bs { namespace ct
11{
12 LinuxVideoModeInfo::LinuxVideoModeInfo()
13 {
14 LinuxPlatform::lockX();
15
16 ::Display* display = LinuxPlatform::getXDisplay();
17
18 INT32 minor, major;
19 XRRQueryVersion(display, &minor, &major);
20
21 INT32 defaultScreen = XDefaultScreen(display);
22 RROutput primaryOutput = XRRGetOutputPrimary(display, RootWindow(display, defaultScreen));
23
24 INT32 screenCount = XScreenCount(display);
25 for(INT32 i = 0; i < screenCount; i++)
26 {
27 XRRScreenResources* screenRes = XRRGetScreenResources(display, RootWindow(display, i));
28
29 for(INT32 j = 0; j < screenRes->noutput; j++)
30 {
31 XRROutputInfo* outputInfo = XRRGetOutputInfo(display, screenRes, screenRes->outputs[j]);
32
33 if(outputInfo == nullptr || outputInfo->crtc == 0 || outputInfo->connection == RR_Disconnected)
34 {
35 XRRFreeOutputInfo(outputInfo);
36 continue;
37 }
38
39 XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(display, screenRes, outputInfo->crtc);
40 if(crtcInfo == nullptr)
41 {
42 XRRFreeCrtcInfo(crtcInfo);
43 XRRFreeOutputInfo(outputInfo);
44 continue;
45 }
46
47 VideoOutputInfo* output = bs_new<LinuxVideoOutputInfo>(display, i, outputInfo, crtcInfo, screenRes,
48 screenRes->outputs[j], (UINT32)mOutputs.size());
49
50 // Make sure the primary output is the first in the output list
51 if(i == defaultScreen && screenRes->outputs[j] == primaryOutput)
52 mOutputs.insert(mOutputs.begin(), output);
53 else
54 mOutputs.push_back(output);
55
56 XRRFreeCrtcInfo(crtcInfo);
57 XRRFreeOutputInfo(outputInfo);
58 }
59
60 XRRFreeScreenResources(screenRes);
61 }
62
63 LinuxPlatform::unlockX();
64 }
65
66 LinuxVideoOutputInfo::LinuxVideoOutputInfo(::Display* x11Display, INT32 screen, XRROutputInfo* outputInfo,
67 XRRCrtcInfo* crtcInfo, XRRScreenResources* screenRes, RROutput outputID, UINT32 outputIdx)
68 : mOutputID(outputID), mScreen(screen)
69 {
70 RRMode currentMode = crtcInfo->mode;
71
72 // Parse output name
73 Atom EDID = XInternAtom(x11Display, "EDID", False);
74
75 INT32 numOutputProps;
76 Atom* outputProps = XRRListOutputProperties(x11Display, mOutputID, &numOutputProps);
77
78 for(INT32 k = 0; k < numOutputProps; k++)
79 {
80 if(outputProps[k] != EDID)
81 continue;
82
83 Atom actualType;
84 unsigned long numItems, bytesAfter;
85 INT32 actualFormat;
86 UINT8* data;
87
88 Status status = XRRGetOutputProperty(x11Display, mOutputID, outputProps[k], 0, 128, False,
89 False, AnyPropertyType, &actualType, &actualFormat, &numItems, &bytesAfter, &data);
90 if(status == Success)
91 {
92 // Decode EDID to get the name
93 for(UINT32 l = 0; l < 4; l++)
94 {
95 INT32 idx = 0x36 + l * 18;
96 if(data[idx] == 0 && data[idx + 1] == 0 && data[idx + 3] == 0xFC)
97 {
98 UINT8* nameSrc = &data[idx + 5];
99
100 char name[14];
101 for(UINT32 m = 0; m < 13; m++)
102 {
103 if(nameSrc[m] == 0x0a)
104 {
105 name[m] = '\0';
106 break;
107 }
108 else if(nameSrc[m] == 0x00)
109 name[m] = ' ';
110 else
111 name[m] = nameSrc[m];
112 }
113
114 name[13] = '\0';
115 mName = String(name);
116 }
117 }
118
119 continue;
120 }
121
122 XFree(data);
123 break;
124 }
125
126 XFree(outputProps);
127
128 // Use the output name if display name cannot be found
129 if(mName.empty())
130 mName = outputInfo->name;
131
132 // Enumerate all valid resolutions
133 for(INT32 k = 0; k < screenRes->nmode; k++)
134 {
135 const XRRModeInfo& modeInfo = screenRes->modes[k];
136
137 UINT32 width, height;
138
139 if(crtcInfo->rotation & (XRANDR_ROTATION_LEFT | XRANDR_ROTATION_RIGHT))
140 {
141 width = modeInfo.height;
142 height = modeInfo.width;
143 }
144 else
145 {
146 width = modeInfo.width;
147 height = modeInfo.height;
148 }
149
150 float refreshRate;
151 if(modeInfo.hTotal != 0 && modeInfo.vTotal != 0)
152 refreshRate = (float)(modeInfo.dotClock / (double)(modeInfo.hTotal * modeInfo.vTotal));
153 else
154 refreshRate = 0.0f;
155
156 LinuxVideoMode* videoMode = new (bs_alloc<LinuxVideoMode>())
157 LinuxVideoMode(width, height, refreshRate, outputIdx, modeInfo.id);
158 mVideoModes.push_back(videoMode);
159 }
160
161 // Save current desktop mode
162 for(INT32 k = 0; k < screenRes->nmode; k++)
163 {
164 if(screenRes->modes[k].id == currentMode)
165 {
166 mDesktopVideoMode = new (bs_alloc<LinuxVideoMode>())
167 LinuxVideoMode(mVideoModes[k]->width, mVideoModes[k]->height,
168 mVideoModes[k]->refreshRate, mVideoModes[k]->outputIdx, currentMode);
169 break;
170 }
171 }
172 }
173
174 LinuxVideoMode::LinuxVideoMode(UINT32 width, UINT32 height, float refreshRate, UINT32 outputIdx)
175 :VideoMode(width, height, refreshRate, outputIdx), mModeID((RRMode)-1)
176 { }
177
178 LinuxVideoMode::LinuxVideoMode(UINT32 width, UINT32 height, float refreshRate, UINT32 outputIdx, RRMode modeID)
179 :VideoMode(width, height, refreshRate, outputIdx), mModeID(modeID)
180 {
181 isCustom = false;
182 }
183}}
184