summaryrefslogblamecommitdiffstats
path: root/src/xprivate.cpp
blob: 3aea9b5997731aab1642eb587c28ade4f5d4fba6 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
                     
                

                 
                             
                
 



                                                          







                                                                               











                                                                                        













                                                                                 



                                                                
                                                         





                                                 
                                        

 

                                                       








                                                       


             







                                     
                                                 



                                          
                                                 









                                                                      




























                                                                                           
                                               



                                                                  
                          


                                                                       
                                                           














                                                                                           
                                               













                                                                                                                       
                                                 

                                     
                        









                                                                                         
                                           

                                                                     








                                                                         


                                                                             
                 

                                      
                                                                         




















































                                                                                                                                                                            
                                                            
















                                                                                                                                                                                      
                                                 

 

                                          






                                                                                                     
                     



                                                         




                                                                


                            

                                                                                                                                
                                                    
                                                                                   

                                                                 
                                                                                        


                                   





                                                                                     
                                       


































                                                                                                                       

                                           

                                            
 

                                                         
 














                                                                                                                                                     
 




















                                                                                                                                     
 
                                                                   









                                           
                                                                         
                                                        
                                                                             


         
                                                                                                                                


                                         
                                                                                                          






















                                                                                 
                                                                       



                                                        
                        
                                                        
                 



                                                        
                 





                                                        
                 






                                                                          
                          

                                                                                                                     
                                                 

                                                                                                                                                             
                                                             


                                      
                                  

                                                                                                                                

                                     
                                                                   




                                                                                                         
                 
         






                                                                                                                    
                                         






                                                                        

                 
                          
                                                                                                                                                     

                                                                   


                                                                                  
                                                                                                                                         
                                                             

                                                                                                     
         

 
















                                                                                                         
                                                                                                        






                                                                                
                                                     
                                                                  

                                                                
   
                                                                                                                              
 

                                    
                                                 







                                                                 
                 


                                             
                                                                                                    

                                                                                                







                                                       
         
                   

 
                                                                                                              

























                                                                                                                                                 
                                                                                          





                                                                                             



                                                                                                           



                                                                                                                         
















                                                                                          

                                                                                             
                                                    
                                 


                                                         

                                            
                                                                                           
                
                                                                      
                                                                              





                                                             



                                                                                                      





                                                                                                                                                             



                                                                                              




                                                            
         




                                                                                                                                          







                                                                                                                  






                                                                                          
                                                 




























                                                                                                                                                                              
                                 






                                                                                                                   

                                 
                                                                  






                                                                                



                     
                                                                                                              
 
                                                                       
                          

                                                                                                  
                                          
                                                       
                                           
     



                                                                                                                               
                                                        

                                                                         
     
                     
 












                                                                                         
                                                                         












































                                                                                                                   
#include "xprivate.h"
#include "cvt.h"

#include <QDebug>
#include <QRegularExpression>
#include <QFile>

// 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 / 100 == b->dotClock / 100 &&
		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);
}

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;
        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;
        }
        if (maxClock <= 0) {
            maxClock = 165;
        }
        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<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;
}

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<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 gets set after we potentially limit the Hz down below
	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;
	// Kernel might have wrongly identified a DP++ adapter as type 1, limiting TMDS to 165MHz, we ignore this and
	// hope for the best.
	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";
		}
	}
	// Now set name
	m.name = buf;
	refresh = static_cast<int>(m.dotClock / (m.hTotal * m.vTotal));
	m.nameLength = static_cast<unsigned int>(snprintf(buf, sizeof(buf), "%dx%d_%d", hactive, vactive, refresh));
	if (m.nameLength > sizeof(buf)) {
		m.nameLength = sizeof(buf);
	}
	// 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) << "MHz";
		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) << "MHz";
		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<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"
	}
}