summaryrefslogtreecommitdiffstats
path: root/src/xprivate.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/xprivate.cpp')
-rw-r--r--src/xprivate.cpp452
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;
+}