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