#include "xprivate.h" #include "cvt.h" #include #include #include // 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 tempMap; QMap 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 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; int maxClock; if (!getEdid(resptr->outputs[i], prop, &edidLen)) continue; if (edidLen < 128) continue; for (unsigned int j = 128; j < edidLen - 127; j+= 128) { maxClock = getMaxTmdsClockMhz(prop + j); if (maxClock != 0) break; } qDebug() << "Standard block DTD for output" << resptr->outputs[i] << "aka" << _outputMap[resptr->outputs[i]]->outputName << "MaxClock" << maxClock << "MHz"; for (unsigned int j = 54; j < 125; j+= 18) { addMissingModesFromDtd(resptr, resptr->outputs[i], prop + j, maxClock); } for (unsigned int j = 128; j < edidLen - 127; j += 128) { addMissingModesFromExtBlock(resptr, resptr->outputs[i], prop + j, maxClock); } } 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(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; } int XPrivate::getMaxTmdsClockMhz(unsigned char *data) { if (data[0] != 2) // Not type 2 (CTA extension block), skip return 0; if (data[1] < 1) // Need version 1+ return 0; int nonDtdEnd = data[2]; int len; for (int i = 4; i < nonDtdEnd; i += len) { unsigned char *db = data + i; len = (db[0] & 0x1f) + 1; if ((db[0] & 0xe0) != 0x60) // Not vendor specific continue; if (db[3] != 0 || db[2] != 0x0c || db[1] != 0x03) // HDMI OUI? Non! continue; return db[7] * 5; } return 0; } void XPrivate::addMissingModesFromExtBlock(XRRScreenResources *res, RROutput outputId, unsigned char *data, unsigned int maxClockMhz) { if (data[0] != 2) // Not type 2 (CTA extension block), 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 for output" << outputId; for (int i = dtdOffset; i < 128 - 17; i += 18) { addMissingModesFromDtd(res, outputId, data + i, maxClockMhz); } } void XPrivate::addMissingModesFromDtd(XRRScreenResources *res, RROutput outputId, unsigned char *data, unsigned int maxClockMhz) { if (data[0] == 0 && data[1] == 0) return; int hactive, vactive, pixclk, hsyncoff, hsyncwidth, hblank, vsyncoff, vsyncwidth, vblank, refresh; 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(hactive); m.height = static_cast(vactive); m.dotClock = static_cast(pixclk) * 10ul * 1000ul; m.hSyncStart= static_cast(hactive + hsyncoff); m.hSyncEnd = static_cast(hactive + hsyncoff + hsyncwidth); m.hTotal = static_cast(hactive + hblank); m.hSkew = 0; m.vSyncStart= static_cast(vactive + vsyncoff); m.vSyncEnd = static_cast(vactive + vsyncoff + vsyncwidth); m.vTotal = static_cast(vactive + vblank); m.id = 0; m.name = buf; refresh = static_cast(m.dotClock / (m.hTotal * m.vTotal)); m.nameLength = static_cast(snprintf(buf, sizeof(buf), "%dx%d_%d", hactive, vactive, refresh)); 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; } RRMode modeId = 0; // More special case B/S: So, X11 mysteriously ignores a couple of resolutions, sometimes. // Curiously, this happens with a Philips TV, but not an LG TV. EDID dump looks very similar // on both. More curiously, in Xorg.log, the first time the modelines for the Philips TV get dumped, // the 4k resolution is missing, even though it's in the EDID dump from right before those lines. // Then the xserver logs the modelines for the Philips TV a couple more times, and every time the 4k // resolution is in the list. Just not the first time. However, the resolutions never get added to // the TV. if (m.dotClock > maxClockMhz * 1000000) { for (int mode = 0; mode < res->nmode; ++mode) { if (res->modes[mode].width == m.width && res->modes[mode].height == m.height && res->modes[mode].dotClock <= maxClockMhz * 1000000) { modeId = res->modes[mode].id; break; } } if (modeId != 0) { qDebug() << "Not creating/adding" << m.width << "x" << m.height << "because pixel clock > TMDS and resolution already exists with different clock"; } else { refresh = 30; m.dotClock = refresh * m.hTotal * m.vTotal; if (m.dotClock > maxClockMhz * 1000000) { refresh = 24; m.dotClock = refresh * m.hTotal * m.vTotal; } qDebug() << "Capping to" << refresh << "Hz, " << (m.dotClock / 1000000) << "MHz"; } } // See if we should add this mode if (modeId == 0) { for (int mode = 0; mode < res->nmode; ++mode) { if (modeEqual(&res->modes[mode], &m)) { // Identical modeline already known to X modeId = res->modes[mode].id; break; } } } if (modeId == 0) { qDebug() << "Creating" << m.name << "for output" << outputId << m.width << "x" << m.height << "@" << (m.dotClock / 1000000); qDebug() << m.hSyncStart << m.hSyncEnd << m.hTotal; qDebug() << m.vSyncStart << m.vSyncEnd << m.vTotal; modeId = XRRCreateMode(_display, DefaultRootWindow(_display), &m); } if (modeId != 0) { qDebug() << "Adding mode for output" << outputId << m.width << "x" << m.height << "@" << (m.dotClock / 1000000); XRRAddOutputMode(_display, outputId, modeId); } else { qDebug() << "Failed to add" << buf << "to" << outputId << "- mode not found/created"; } } /** * 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 XPrivate::getOutputModeForResolution(const XRROutputInfo *output, unsigned int width, unsigned int height) const { XRRModeInfo *pref = nullptr; QList 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 ret; if (pref != nullptr) { ret.append(pref->id); } for (auto e : others) { ret.append(e->id); } return ret; } QList XPrivate::getOutputModeForResolution(const XRROutputInfo *output, const QSize &resolution) const { return getOutputModeForResolution(output, static_cast(resolution.width()), static_cast(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 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 XPrivate::getTotalSize(const QList &projectors, const QList &screens) const { const int max = qMax(screens.size(), projectors.size()); QList 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 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 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(mode->hr); m.height = static_cast(mode->vr); m.dotClock = static_cast(mode->pclk) * 1000ul * 1000ul; m.hSyncStart= static_cast(mode->hss); m.hSyncEnd = static_cast(mode->hse); m.hTotal = static_cast(mode->hfl); m.hSkew = 0; m.vSyncStart= static_cast(mode->vss); m.vSyncEnd = static_cast(mode->vse); m.vTotal = static_cast(mode->vfl); m.id = 0; m.name = ba.data(); m.nameLength= static_cast(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" } }