#include "xprivate.h"
#include "cvt.h"
#include <QDebug>
#include <QRegularExpression>
// Put this here as we need it in the static error handler
static Display* __display;
static XErrorHandler old_handler;
/**
* Check list of known model names that falsely report a screen size or similar
*/
static QStringList initProjectorList()
{
QStringList list;
list << "AT-HDVS-RX"; // Switchbox
// TODO: Load from file
QFile file("/opt/openslx/beamergui/beamer.conf"); // TODO: Config option
if (file.open(QIODevice::ReadOnly)) {
QRegularExpression re("^([^=]+)=beamer$");
while (!file.atEnd()) {
QString line = file.readLine();
auto match = re.match(line);
if (match.hasMatch()) {
list << match.captured(1);
}
}
}
qDebug() << "Forced beamer list:" << list;
return list;
}
static bool isProjectorName(const QString &name)
{
static QStringList projectors = initProjectorList();
return projectors.contains(name);
}
static bool xRectLessThan(const OutputInfo* const &a, const OutputInfo* const &b)
{
return a->crtc->x < b->crtc->x;
}
static int modeEqual(const XRRModeInfo *a, const XRRModeInfo *b)
{
return (a->width == b->width &&
a->height == b->height &&
a->dotClock / 10 == b->dotClock / 10 &&
a->hSyncStart == b->hSyncStart &&
a->hSyncEnd == b->hSyncEnd &&
a->hTotal == b->hTotal &&
a->hSkew == b->hSkew &&
a->vSyncStart == b->vSyncStart &&
a->vSyncEnd == b->vSyncEnd &&
a->vTotal == b->vTotal &&
a->modeFlags == b->modeFlags);
}
static int errorHandler(Display *dpy, XErrorEvent *err)
{
if (dpy != __display) {
if (old_handler != nullptr) {
return old_handler(dpy, err);
}
} else {
char buf[1000];
XGetErrorText(dpy, err->error_code, buf, 1000);
qDebug() << "**X11 error**" << buf;
}
return 0;
}
/*
* Class members
*/
XPrivate::XPrivate()
: _screenResources(nullptr)
{
// Get initial data (to be freed)
__display = _display = XOpenDisplay(nullptr);
if (_display == nullptr) {
qFatal("Cannot open display");
::exit(1);
}
old_handler = XSetErrorHandler(errorHandler);
_EDID_ATOM = XInternAtom(_display, RR_PROPERTY_RANDR_EDID, False);
}
XPrivate::~XPrivate()
{
XCloseDisplay(_display);
}
void XPrivate::freeResources()
{
// Clear the modemap (nothing to be freed, stored in screenResources)
_modeMap.clear();
_resolutions.clear();
// Clear output info
for (OutputInfo *output : _outputMap.values()) {
delete output;
}
_outputMap.clear();
// Clear crtc info
for (XRRCrtcInfo *info : _crtcMap.values()) {
XRRFreeCrtcInfo(info);
}
_crtcMap.clear();
// Release resources
if (_screenResources != nullptr) {
XRRFreeScreenResources(_screenResources);
_screenResources = nullptr;
}
XRRFreeScreenResources(_screenResources);
}
void XPrivate::updateScreenResources()
{
freeResources();
_screenResources = XRRGetScreenResourcesCurrent(_display, DefaultRootWindow(_display));
// Create the modemap
qDebug() << "updateScreenResources: Modes";
for (int i = 0; i < _screenResources->nmode; ++i) {
_modeMap.insert(
_screenResources->modes[i].id,
&_screenResources->modes[i]);
/*
qDebug() << _screenResources->modes[i].id << "\t"
<< _screenResources->modes[i].width << "x"
<< _screenResources->modes[i].height;
*/
}
/*
int num = 0;
XRRMonitorInfo *mi = XRRGetMonitors(_display, DefaultRootWindow(_display), True, &num);
for (int i = 0; i < num; ++i) {
char *name = XGetAtomName(_display, mi [i].name);
if (name == nullptr)
continue;
qDebug() << "some monitor with name" << name << mi[i].primary;
XFree(name);
}
*/
// Create crtcMap
qDebug() << "updateScreenResources: CRTCs";
for (int i = 0; i < _screenResources->ncrtc; ++i) {
XRRCrtcInfo * info = XRRGetCrtcInfo(
_display,
_screenResources,
_screenResources->crtcs[i]);
if (info == nullptr) {
qDebug() << "Error getting CRTC info for crtc" << i;
continue;
}
qDebug() << _screenResources->crtcs[i] << "-- Outputs:" << info->noutput << "Possible:" << info->npossible;
_crtcMap.insert(_screenResources->crtcs[i], info);
}
// Create outputmap and connectedOutputMap
qDebug() << "updateScreenResources: Outputs";
QHash<RROutput, QString> tempMap;
QMap<Projector, int> typeCount;
_allOutputs.clear();
for (int i = 0; i < _screenResources->noutput; ++i) {
XRROutputInfo* info = XRRGetOutputInfo(
_display,
_screenResources,
_screenResources->outputs[i]);
if (info == nullptr) {
qDebug() << "Error getting info for output" << i;
continue;
}
const QString outputName = QString::fromLocal8Bit(info->name, info->nameLen);
_allOutputs.append(outputName);
tempMap.insert(_screenResources->outputs[i], outputName);
if (info->connection == RR_Disconnected) {
bool ign = !_crtcMap.contains(info->crtc);
if (!ign) {
ign = !_modeMap.contains(_crtcMap[info->crtc]->mode);
}
if (!ign) {
auto mode = _modeMap[_crtcMap[info->crtc]->mode];
ign = (mode->width == 0 || mode->height == 0);
}
if (ign) {
qDebug() << "Ignoring disconnected output" << outputName;
XRRFreeOutputInfo(info);
continue;
}
}
bool disconnected = false;
if (info->crtc == None && info->connection == RR_Connected) {
disconnected = true;
qDebug() << "Connected output" << outputName << "has no CRTC -- trying to find free one";
info->crtc = getFreeCrtc(info);
}
if (!_crtcMap.contains(info->crtc)) {
qDebug() << "Have output" << outputName << "with no known crtc";
XRRFreeOutputInfo(info);
continue;
}
XRRCrtcInfo *crtc = _crtcMap.value(info->crtc);
XRRModeInfo *mode = nullptr;
if (!disconnected) {
if (!_modeMap.contains(crtc->mode)) {
qDebug() << "Have output" << outputName << " with crtc with no known mode -- offline?";
} else {
mode = _modeMap.value(crtc->mode);
}
}
OutputInfo *oi = new OutputInfo(_screenResources->outputs[i], info, crtc, mode);
oi->outputName = outputName;
if (!this->readEdid(oi)) { // We have no EDID - take it as an indicator that there's a dumb output split/switch box
oi->outputType = Projector::Maybe;
} else if ((info->mm_height == 0 && info->mm_width == 0) || isProjectorName(oi->modelName)) {
oi->outputType = Projector::Yes; // Screens with size 0x0 are projectors by convention
} else if (info->mm_width > 500 && info->mm_height > 500) { // Big screen - probably should be handled like a projector
oi->outputType = Projector::Yes;
} else if (info->mm_width > 350 && info->mm_height > 350) { // Somewhere in-between
oi->outputType = Projector::Maybe;
}
typeCount[oi->outputType]++;
_outputMap.insert(_screenResources->outputs[i], oi);
qDebug() << "Connected" << outputName << "-- Clones:" << info->nclone << "Modes:" << info->nmode << "Crtcs:" << info->ncrtc << "Preferred:" << info->npreferred;
}
// Final checks for projector
if (typeCount[Projector::Yes] == 0) {
// No definitive projector
if (typeCount[Projector::Maybe] > 0 && typeCount[Projector::No] > 0) {
// Potential projector(s) and normal screen, promote
for (OutputInfo *info : _outputMap) {
if (info->outputType == Projector::Maybe) {
info->outputType = Projector::Yes;
break;
}
}
}
}
// Determine each screen's position
QList<OutputInfo*> screens = _outputMap.values();
// Nothing connected?
if (screens.isEmpty())
return;
qSort(screens.begin(), screens.end(), xRectLessThan);
int endX = -0xffff;
qDebug() << "updateScreenResources: From left to right";
for (OutputInfo* output : screens) {
if (output->mode == nullptr) {
qDebug() << "(Ignoring" << output->outputName << "since it's disconnected)";
continue;
}
if (output->crtc->x >= endX) {
QSize res(0, 0);
res = QSize(int(output->mode->width), int(output->mode->height));
_resolutions.append(res);
endX = -0xffff; // Reset
}
output->position = _resolutions.size() - 1;
qDebug() << "Screen (" << output->crtc->x << "," << output->crtc->y << ") @" << output->mode->width << "x" << output->mode->height << "as screen" << output->position;
if (output->crtc->x + int(output->crtc->width) > endX) {
endX = output->crtc->x + int(output->mode->width);
}
}
qDebug() << "updateScreenResources: Loaded.";
}
void XPrivate::addMissingEdidResolutions()
{
qDebug() << "addMissingEdidResolutions";
XRRScreenResources *resptr = XRRGetScreenResourcesCurrent(_display, DefaultRootWindow(_display));
if (resptr == nullptr)
return;
for (int i = 0; i < resptr->noutput; ++i) {
unsigned char prop[512];
unsigned long edidLen = 512;
if (!getEdid(resptr->outputs[i], prop, &edidLen))
continue;
if (edidLen < 128)
continue;
for (unsigned int j = 54; j < 125; j+= 18) {
addMissingModesFromDtd(resptr, resptr->outputs[i], prop + j);
}
for (unsigned int j = 128; j < edidLen - 127; j += 128) {
addMissingModesFromExtBlock(resptr, resptr->outputs[i], prop + j);
}
}
XRRFreeScreenResources(resptr);
}
bool XPrivate::getEdid(RROutput outputId, unsigned char *buffer, unsigned long *size)
{
int numProps = 0;
bool found = false;
qDebug() << "getEdid:" << outputId;
Atom* properties = XRRListOutputProperties(_display, outputId, &numProps);
for (int i = 0; i < numProps; ++i) {
if (properties[i] == _EDID_ATOM) {
found = true;
break;
}
}
XFree(properties);
if (!found)
return false;
unsigned long nitems, bytes_after;
unsigned char *prop = nullptr;
int actual_format;
Atom actual_type;
bool valid;
XRRGetOutputProperty(_display, outputId, _EDID_ATOM,
0, long(size), False, False,
AnyPropertyType,
&actual_type, &actual_format,
&nitems, &bytes_after, &prop);
valid = (actual_format == 8 && nitems >= 128 && prop != nullptr);
if (valid) {
memcpy(buffer, prop, qMin(*size, nitems));
*size = qMin(nitems, *size);
} else {
*size = 0;
}
if (prop != nullptr) {
XFree(prop);
}
return valid;
}
bool XPrivate::readEdid(OutputInfo* output)
{
unsigned char prop[512];
unsigned long edidLen = 512;
if (!getEdid(output->id, prop, &edidLen))
return false;
int idx;
for (unsigned char *byte = prop + 54; byte < prop + 126; byte += 18) {
if (byte[0] != 0 || byte[1] != 0 || byte[2] != 0)
continue; // Not a text block
if (byte[3] == 0xfc) { // Serial number
output->modelName = QString::fromLatin1(reinterpret_cast<const char*>(byte) + 5, 13); // It's actually CP-437 but meh
if ((idx = output->modelName.indexOf('\r')) != -1) {
output->modelName.truncate(idx);
}
output->modelName = output->modelName.trimmed();
qDebug() << "Display name:" << output->modelName;
}
}
return true;
}
void XPrivate::addMissingModesFromExtBlock(XRRScreenResources *res, RROutput outputId, unsigned char *data)
{
if (data[0] != 2) // Not type 2, skip
return;
if (data[2] == 0) // Empty block
return;
int dtdOffset = data[2];
if (dtdOffset < 4) // No DTD blocks
return;
if (dtdOffset > 128) {
dtdOffset = 128;
}
qDebug() << "addMissingModesFromExtBlock:" << outputId;
for (int i = dtdOffset; i < 128 - 17; i += 18) {
addMissingModesFromDtd(res, outputId, data + i);
}
}
void XPrivate::addMissingModesFromDtd(XRRScreenResources *res, RROutput outputId, unsigned char *data)
{
if (data[0] == 0 && data[1] == 0)
return;
int hactive, vactive, pixclk, hsyncoff, hsyncwidth, hblank, vsyncoff, vsyncwidth, vblank;
hactive = data[2] + ((data[4] & 0xf0) << 4);
hblank = data[3] + ((data[4] & 0x0f) << 8);
vactive = data[5] + ((data[7] & 0xf0) << 4);
vblank = data[6] + ((data[7] & 0x0f) << 8);
pixclk = (data[1] << 8) | (data[0]); // 10kHz
hsyncoff = data[8] | ((data[11] & 0xC0) << 2);
hsyncwidth = data[9] | ((data[11] & 0x30) << 4);
vsyncoff = ((data[10] & 0xf0) >> 4) | ((data[11] & 0x0C) << 2);
vsyncwidth = (data[10] & 0x0f) | ((data[11] & 0x03) << 4);
char buf[100];
XRRModeInfo m;
memset(&m, 0, sizeof(m));
m.width = static_cast<unsigned int>(hactive);
m.height = static_cast<unsigned int>(vactive);
m.dotClock = static_cast<unsigned long>(pixclk) * 10ul * 1000ul;
m.hSyncStart= static_cast<unsigned int>(hactive + hsyncoff);
m.hSyncEnd = static_cast<unsigned int>(hactive + hsyncoff + hsyncwidth);
m.hTotal = static_cast<unsigned int>(hactive + hblank);
m.hSkew = 0;
m.vSyncStart= static_cast<unsigned int>(vactive + vsyncoff);
m.vSyncEnd = static_cast<unsigned int>(vactive + vsyncoff + vsyncwidth);
m.vTotal = static_cast<unsigned int>(vactive + vblank);
m.id = 0;
m.name = buf;
m.nameLength = static_cast<unsigned int>(snprintf(buf, sizeof(buf), "%dx%d_%d", hactive, vactive, static_cast<int>(m.dotClock / (m.hTotal * m.vTotal))));
if (m.nameLength > sizeof(buf)) {
m.nameLength = sizeof(buf);
}
if ((data[17] & 0x18) == 0x18) {
// Digital Separate
if (data[17] & 0x04) {
m.modeFlags |= RR_VSyncPositive;
} else {
m.modeFlags |= RR_VSyncNegative;
}
if (data[17] & 0x02) {
m.modeFlags |= RR_HSyncPositive;
} else {
m.modeFlags |= RR_HSyncNegative;
}
} else if ((data[17] & 0x18) == 0x10) {
// Digital Composite
if (data[17] & 0x02) {
m.modeFlags |= RR_HSyncPositive;
} else {
m.modeFlags |= RR_HSyncNegative;
}
m.modeFlags |= RR_VSyncNegative; // Default? Clarify...
} else {
m.modeFlags |= RR_VSyncNegative | RR_HSyncNegative; // ...
}
if (data[17] & 0x80) {
m.modeFlags |= RR_Interlace;
}
// See if we should add this mode
RRMode modeId = 0;
for (int mode = 0; mode < res->nmode; ++mode) {
if (modeEqual(&res->modes[mode], &m)) {
modeId = res->modes[mode].id;
break;
}
}
if (modeId == 0) {
modeId = XRRCreateMode(_display, DefaultRootWindow(_display), &m);
}
if (modeId != 0) {
XRRAddOutputMode(_display, outputId, modeId);
}
}
/**
* Copy first "num" modes from output to all other outputs if they
* dont have a suitable mode yet.
*/
void XPrivate::copyModesToAll(RROutput sourceId, int num)
{
const XRROutputInfo *outputInfo = _outputMap[sourceId]->output;
for (int i = 0; i < num; ++i) {
if (!_modeMap.contains(outputInfo->modes[i])) {
qDebug() << "BUG: Mode" << outputInfo->modes[i] << "not found in copyModesToAll";
continue;
}
const XRRModeInfo *mode = _modeMap[outputInfo->modes[i]];
for (auto other : _outputMap.keys()) {
if (other == sourceId)
continue;
const XRROutputInfo *otherInfo = _outputMap[other]->output;
if (!getOutputModeForResolution(otherInfo, mode->width, mode->height).isEmpty())
continue;
XRRAddOutputMode(_display, other, outputInfo->modes[i]);
}
}
}
/**
* Get all known modes matching the given resolution.
* If multiple modes match the given resolution, the function will
* put the preferred resolution first, then all others sorted by
* dotClock, descending.
*/
QList<RRMode> XPrivate::getOutputModeForResolution(const XRROutputInfo *output, unsigned int width, unsigned int height) const
{
XRRModeInfo *pref = nullptr;
QList<XRRModeInfo*> others;
for (int i = 0; i < output->nmode; ++i) {
if (!_modeMap.contains(output->modes[i]))
continue;
XRRModeInfo *info = _modeMap[output->modes[i]];
if (info->width == width && info->height == height) {
if (i < output->npreferred && pref == nullptr) {
pref = info;
} else {
others.append(info);
}
}
}
// Sort others descending by dotClock
qSort(others.begin(), others.end(), [](const XRRModeInfo *a, const XRRModeInfo *b) -> bool {
if ((a->modeFlags & RR_DoubleScan) != (b->modeFlags & RR_DoubleScan))
return (a->modeFlags & RR_DoubleScan) == 0; // Prefer non-interlaced resolutions
return toVertRefresh(a) > toVertRefresh(b);
});
QList<RRMode> ret;
if (pref != nullptr) {
ret.append(pref->id);
}
for (auto e : others) {
ret.append(e->id);
}
return ret;
}
QList<RRMode> XPrivate::getOutputModeForResolution(const XRROutputInfo *output, const QSize &resolution) const
{
return getOutputModeForResolution(output, static_cast<unsigned int>(resolution.width()), static_cast<unsigned int>(resolution.height()));
}
RRCrtc XPrivate::getFreeCrtc(const XRROutputInfo* output) const
{
for (int i = 0; i < output->ncrtc; ++i) {
RRCrtc c = output->crtcs[i];
if (!_crtcMap.contains(c)) {
// Unknown CRTC!? Scan already known outputs
qDebug() << "BUG: Output has unknown possible CRTC";
for (auto oi : _outputMap) {
if (oi->output->crtc == c)
goto next;
}
} else {
// Known CRTC -- see if free
if (_crtcMap[c]->noutput > 0)
goto next;
}
return c;
next:;
}
return None;
}
XRRModeInfo* XPrivate::getPreferredMode(const OutputInfo *oi, XRRModeInfo *fallback) const
{
if (oi->output->nmode == 0) {
qDebug() << "getPreferredMode: Output" << oi->outputName << "has no modes!?";
return nullptr; // WTF!?
}
RRMode mode;
// H4xx0r - Ultrawide screens seem to incorrectly report a 16:9 resultion as their preferred one
// I can only guess this is a safety measure for old/bad OS or gfx cards
// So, determine the aspect ratio by looking at the physical size and if it's ultrawide, look for a
// more fitting resolution in the list of supported ones
// UPDATE: Further digging using edid-decode suggests that Xorg parses EDID data improperly, most notably
// not preferring the resolution given in the first DTD block in the base section (first 128b) but rather
// going for the first DTD in the CTA extension block. (This is just a guess since I didn't check the
// Xorg source code.)
if (oi->output->mm_height > 0) {
float ar = float(oi->output->mm_width) / float(oi->output->mm_height);
if (ar >= 1.999f) { // Consider 18:9+ as ultrawide
XRRModeInfo *best = nullptr;
for (int i = 0; i < oi->output->nmode; ++i) {
if (!_modeMap.contains(oi->output->modes[i]))
continue;
auto mode = _modeMap[oi->output->modes[i]];
if (mode->height == 0)
continue;
float resAr = float(mode->width) / float(mode->height);
if (qAbs(resAr - ar) > 0.02f) // Check if aspect ratios are same
continue;
if (best == nullptr || mode->width > best->width) {
best = mode; // If multiple resolutions match, use the largest one
}
}
if (best != nullptr) {
qDebug() << oi->outputName << "Overriding UltraWide";
return best; // Ultrawide it is!
}
}
}
// "Normal" way of determining optimal resolution
if (oi->output->npreferred > 0) {
mode = oi->output->modes[0];
qDebug() << "Picking preferred mode for" << oi->outputName;
} else {
// No preferred one, try some more or less clever fallback
qDebug() << "No preferred mode for" << oi->outputName;
mode = None;
int maxX = 1920;
int maxY = 1080;
if (fallback != nullptr) {
maxX = int(fallback->width);
maxY = int(fallback->height);
auto list = getOutputModeForResolution(oi->output, fallback->width, fallback->height);
if (!list.isEmpty()) {
mode = list.first();
}
}
static const std::vector<QSize> wanted = {QSize(1920, 1080), QSize(1280, 800), QSize(1280, 720), QSize(1152, 864), QSize(1024, 768)};
for (const QSize s : wanted) {
if (mode != None)
break;
if (s.width() <= maxX && s.height() <= maxY) {
auto list = getOutputModeForResolution(oi->output, s.width(), s.height());
if (!list.isEmpty()) {
mode = list.first();
}
}
}
if (mode == None) {
mode = oi->output->modes[0];
}
}
if (!_modeMap.contains(mode)) {
qDebug() << "Could not pick a preferred mode";
return nullptr;
}
qDebug() << "Final Preferred mode of" << oi->outputName << "is" << _modeMap[mode]->width << "x" << _modeMap[mode]->height;
return _modeMap[mode];
}
QList<QSize> XPrivate::getTotalSize(const QList<OutputInfo*> &projectors, const QList<OutputInfo*> &screens) const
{
const int max = qMax(screens.size(), projectors.size());
QList<QSize> modes;
for (int i = 0; i < max; ++i) {
XRRModeInfo *modeP = nullptr, *modeS = nullptr;
if (i < projectors.size()) {
modeP = getPreferredMode(projectors.at(i));
if (modeP != nullptr) {
qDebug() << "Projector wants" << modeP->width << "x" << modeP->height;
}
}
if (i < screens.size()) {
if (modeP != nullptr) {
QList<RRMode> list = getOutputModeForResolution(screens.at(i)->output, modeP->width, modeP->height);
if (!list.empty()) {
// Screen supports same resolution as projector
modeS = _modeMap[list.first()];
qDebug() << "Screen supports same";
} else {
// Screen does not support same resolution
// Usually that should not happen as we add the projector's mode(s) to the
// other outputs, but that doesn't work on nvidia cards, so try to use another
// suitable resolution there
int x = 0, y = 0;
if (modeP->width == 1280 && modeP->height == 800) {
x = 1280;
y = 720;
} else if (modeP->width == 1920 && modeP->height == 1200) {
x = 1920;
y = 1080;
}
if (x != 0) {
auto s = getOutputModeForResolution(screens.at(i)->output, x, y);
auto p = getOutputModeForResolution(projectors.at(i)->output, x, y);
if (!s.empty() && !p.empty()) {
modeP = _modeMap[p.first()];
modeS = _modeMap[s.first()];
qDebug() << "Falling back to" << modeP->width << "x" << modeP->height << " because of screen incompatibility";
}
}
}
}
if (modeS == nullptr) {
modeS = getPreferredMode(screens.at(i), modeP);
if (modeS != nullptr) {
qDebug() << "Screen wants" << modeS->width << "x" << modeS->height;
} else {
qDebug() << "Could not find any suitable resolution for screen";
}
}
}
XRRModeInfo *mode = modeP ? modeP : modeS;
if (mode != nullptr) {
QSize size(int(mode->width), int(mode->height));
modes.append(size);
qDebug() << "Next screen:" << size;
} else {
qDebug() << "Empty screen :(";
}
}
return modes;
}
XRRModeInfo* XPrivate::setOutputResolution(QStringList &args, OutputInfo *oi, int x, int y, const QSize &size)
{
QList<RRMode> modes = getOutputModeForResolution(oi->output, size);
if (modes.isEmpty()) {
qDebug() << "Cannot set" << oi->outputName << "to" << size << " since it's not supported";
if (oi->output->nmode == 0)
return nullptr;
qDebug() << "falling back to its default mode";
modes.append(oi->output->modes[0]);
}
// modes list is known to be sorted by preferred resolution/rate, then sorted by highest to lowest rate
// so we just pick the first one for now, as the GUI doesn't offer picking the rate
XRRModeInfo *best = _modeMap[modes.first()];
args << "--output" << oi->outputName << "--mode" << best->name << "--rate" << QString::number(toVertRefresh(best), 'f', 2);
args << "--pos" << QString::asprintf("%dx%d", x, y);
if (x == 0 && y == 0 && !args.contains(QLatin1String("--primary"))) {
args.append("--primary");
}
return best;
}
/**
* !! oi pointer might be invalid after calling this !!
* @return true if updateScreenResources() should be called later
*/
bool XPrivate::addResolutionToOutput(OutputInfo *oi, const QSize &res)
{
if (!getOutputModeForResolution(oi->output, res).empty())
return false; // Nothing to do
XRRModeInfo *bestMode = nullptr;
for (auto *mode : _modeMap) {
if (int(mode->width) != res.width() || int(mode->height) != res.height())
continue;
if (toVertRefresh(mode) < 55 || toVertRefresh(mode) > 70)
continue; // Play it safe
if (bestMode == nullptr || abs(toVertRefresh(bestMode) - 60) > abs(toVertRefresh(mode) - 60)) {
bestMode = mode; // As close to 60 as possible
}
}
if (bestMode != nullptr) {
qDebug() << "Adding existing mode" << res.width() << 'x' << res.height() << "to" << oi->outputName;
XRRAddOutputMode(_display, oi->id, bestMode->id);
return true;
} else {
// From scratch
qDebug() << "Creating requested mode" << res.width() << 'x' << res.height() << "from scratch";
QByteArray ba = QString::asprintf("%dx%d_BG", res.width(), res.height()).toLocal8Bit();
mode *mode = vert_refresh(res.width(), res.height(), 60, 0, 0, 0);
if (mode == nullptr)
return false; // Failed
XRRModeInfo m;
memset(&m, 0, sizeof(m));
m.width = static_cast<unsigned int>(mode->hr);
m.height = static_cast<unsigned int>(mode->vr);
m.dotClock = static_cast<unsigned long>(mode->pclk) * 1000ul * 1000ul;
m.hSyncStart= static_cast<unsigned int>(mode->hss);
m.hSyncEnd = static_cast<unsigned int>(mode->hse);
m.hTotal = static_cast<unsigned int>(mode->hfl);
m.hSkew = 0;
m.vSyncStart= static_cast<unsigned int>(mode->vss);
m.vSyncEnd = static_cast<unsigned int>(mode->vse);
m.vTotal = static_cast<unsigned int>(mode->vfl);
m.id = 0;
m.name = ba.data();
m.nameLength= static_cast<unsigned int>(ba.length());
m.modeFlags = RR_VSyncPositive | RR_HSyncNegative;
free(mode);
RRMode xid = XRRCreateMode(_display, DefaultRootWindow(_display), &m);
if (xid <= 0) {
qDebug() << "Creating mode failed";
return false;
}
qDebug() << "Adding created mode to" << oi->outputName;
XRRAddOutputMode(_display, oi->id, xid);
// Stuff changed, update now in case other screens want the same resolution
updateScreenResources();
return false; // We just updated ourselves, don't signal "update required"
}
}