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 | |
10 | namespace 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 | |