diff options
Diffstat (limited to 'src/xprivate.cpp')
-rw-r--r-- | src/xprivate.cpp | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/src/xprivate.cpp b/src/xprivate.cpp new file mode 100644 index 0000000..120fb66 --- /dev/null +++ b/src/xprivate.cpp @@ -0,0 +1,452 @@ +#include "xprivate.h" + +#include <QDebug> + +/** + * 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 + 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; +} + +/* + * Class members + */ + +XPrivate::XPrivate() + : _screenResources(nullptr) +{ + // Get initial data (to be freed) + _display = XOpenDisplay(nullptr); + if (_display == nullptr) { + qFatal("Cannot open display"); + ::exit(1); + } + _EDID_ATOM = XInternAtom(_display, RR_PROPERTY_RANDR_EDID, False); +} + +XPrivate::~XPrivate() +{ + XCloseDisplay(_display); +} + +void XPrivate::freeResources() +{ + freeCrtcBackup(); + // 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() << "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() << "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() << "Outputs"; + QHash<RROutput, QString> tempMap; + QMap<Projector, int> typeCount; + 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); + tempMap.insert(_screenResources->outputs[i], outputName); + if (info->connection == RR_Disconnected) { + qDebug() << "Ignoring disconnected output" << outputName; + XRRFreeOutputInfo(info); + continue; + } + bool disconnected = false; + if (info->crtc == None) { + 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() << "From left to right"; + for (OutputInfo* output : screens) { + if (output->mode == nullptr) { + qDebug() << "(Ignoring" << output->outputName << "since it's disconnected)"; + } + if (output->crtc->x >= endX) { + QSize res(0, 0); + if (_modeMap.contains(output->crtc->mode)) { + auto mode = _modeMap.value(output->crtc->mode); + res = QSize(int(mode->width), int(mode->height)); + } + _resolutions.append(res); + endX = -0xffff; // Reset + } + output->position = _resolutions.size() - 1; + qDebug() << "Screen (" << output->crtc->x << "," << output->crtc->y << ") @" << output->crtc->width << "x" << output->crtc->height << "as screen" << output->position; + if (output->crtc->x + int(output->crtc->width) > endX) { + endX = output->crtc->x + int(output->crtc->width); + } + } + qDebug() << "Loaded."; +} + +bool XPrivate::readEdid(OutputInfo* output) +{ + int numProps = 0; + bool found = false; + Atom* properties = XRRListOutputProperties(_display, output->id, &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; + int actual_format; + Atom actual_type; + bool valid; + + XRRGetOutputProperty(_display, output->id, _EDID_ATOM, + 0, 128, False, False, + AnyPropertyType, + &actual_type, &actual_format, + &nitems, &bytes_after, &prop); + valid = (actual_format == 8 && nitems >= 128); + + if (valid) { + 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; + } + } + } + XFree(prop); + return valid; +} + +void XPrivate::setScreenSize(const QSize &size) +{ + XRRSetScreenSize(_display, DefaultRootWindow(_display), + size.width(), size.height(), + int(25.4 * size.width() / 96.0), // standard dpi that X uses + int(25.4 * size.height() / 96.0)); // standard dpi that X uses +} + +void XPrivate::disconnectAllCrtcs() +{ + // Disconnect everything + for (auto crtc : _crtcMap.keys()) { + XRRSetCrtcConfig(_display, _screenResources, crtc, CurrentTime, + 0, 0, None, RR_Rotate_0, nullptr, 0); + } +} + +void XPrivate::freeCrtcBackup() +{ + for (auto entry : _crtcBackup) { + free(entry->outputs); + free(entry); + } + _crtcBackup.clear(); + qDebug() << "CRTC freed"; +} + +void XPrivate::createCrtcBackup() +{ + freeCrtcBackup(); + for (CrtcMap::iterator it = _crtcMap.begin(); it != _crtcMap.end(); ++it) { + const auto src = it.value(); + XRRCrtcInfo *copy = static_cast<XRRCrtcInfo*>(calloc(1, sizeof(XRRCrtcInfo))); + copy->outputs = static_cast<RROutput*>(calloc(size_t(src->noutput), sizeof(RROutput))); + copy->x = src->x; + copy->y = src->y; + copy->mode = src->mode; + copy->rotation = src->rotation; + copy->noutput = src->noutput; + for (int i = 0; i < src->noutput; ++i) { + copy->outputs[i] = src->outputs[i]; + } + _crtcBackup[it.key()] = copy; + } + qDebug() << "Created CRTC backup with entries:" << _crtcBackup.size(); +} + +/** + * 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) != None) + continue; + XRRAddOutputMode(_display, other, outputInfo->modes[i]); + } + } +} + +/** + * Get the RRMode for the specified output matching the given resolution. + * If multiple modes match the given resolution, the function will + * return the first matching preferred mode. If no preferred + * mode matches, the non-preferred mode with the highest refresh rate + * will be returned. + * Otherwise, None will be returned. + */ +RRMode XPrivate::getOutputModeForResolution(const XRROutputInfo *output, unsigned int width, unsigned int height) const +{ + XRRModeInfo *retval = nullptr; + 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) + return output->modes[i]; + if (retval == nullptr || retval->dotClock < info->dotClock) { + retval = info; + } + } + } + return retval == nullptr ? None : retval->id; +} + +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(OutputInfo *oi) const +{ + if (oi->output->nmode == 0) { + qDebug() << "getPreferredMode: Output" << oi->outputName << "has no modes!?"; + return nullptr; // WTF!? + } + RRMode mode; + if (oi->output->npreferred > 0) { + mode = oi->output->modes[0]; + } else { + mode = getOutputModeForResolution(oi->output, 1920, 1080); + if (mode == None) { + mode = getOutputModeForResolution(oi->output, 1280, 720); + } + if (mode == None) { + mode = getOutputModeForResolution(oi->output, 1280, 800); + } + if (mode == None) { + mode = oi->output->modes[0]; + } + } + if (!_modeMap.contains(mode)) + return nullptr; + 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 *mode = nullptr; + if (i < projectors.size()) { + mode = getPreferredMode(projectors.at(i)); + } + if (mode == nullptr && i < screens.size()) { + mode = getPreferredMode(screens.at(i)); + } + if (mode != nullptr) { + modes.append(QSize(int(mode->width), int(mode->height))); + } + } + return modes; +} + +bool XPrivate::setOutputResolution(OutputInfo *oi, int x, int y, const QSize &size, bool dryRun) +{ + RRMode mode = getOutputModeForResolution(oi->output, size); + if (mode == None) { + qDebug() << "Cannot set" << oi->outputName << "to" << size << " since it's not supported"; + if (oi->output->nmode == 0) + return false; + qDebug() << "falling back to its default"; + mode = oi->output->modes[0]; + } + if (!dryRun) { + XRRSetCrtcConfig(_display, + _screenResources, + oi->output->crtc, + CurrentTime, + x, y, mode, + RR_Rotate_0, + &oi->id, 1); + } + qDebug() << "Set" << oi->outputName << "to" << _modeMap[mode]->width << "x" << _modeMap[mode]->height << "-- offset" << x << "/" << y; + return true; +} |