#include "xx.h" #include "xprivate.h" #include "cvt.h" #include #include /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// static ScreenInfo initScreenInfo(const OutputInfo *oi, const ModeMap &om) { ScreenInfo si; si.position = oi->position; si.name = oi->modelName; si.output = oi->outputName; si.isProjector = (oi->outputType == Projector::Yes); if (oi->mode != nullptr) { si.currentResolution = QSize(QSize(int(oi->mode->width), int(oi->mode->height))); } for (int i = 0; i < oi->output->nmode; ++i) { if (om.contains(oi->output->modes[i])) { auto m = om.value(oi->output->modes[i]); const QSize size(int(m->width), int(m->height)); if (!si.modes.contains(size)) { si.modes.append(size); } if (i == 0 && oi->output->npreferred > 0) { si.preferredResolution = size; } } } return si; } ScreenSetup * ScreenSetup::_instance = nullptr; static int errorHandlerX(Display*) { exit(1); } ScreenSetup::ScreenSetup() : a(new XPrivate()) { int event_base_return, error_base_return; if (!XRRQueryExtension(a->_display, &event_base_return, &error_base_return)) { qDebug() << "No XRANDR extension found"; exit(1); } updateScreenResources(); XRRSelectInput(a->_display, DefaultRootWindow(a->_display), RROutputChangeNotifyMask); //XSync(a->_display, False); XSetIOErrorHandler((XIOErrorHandler) errorHandlerX); _socketNotifier = new QSocketNotifier(qintptr(ConnectionNumber(a->_display)), QSocketNotifier::Read); connect(_socketNotifier, &QSocketNotifier::activated, [=](int) { XEvent ev; qDebug() << "Socket Event"; while (XPending(a->_display) > 0) { XNextEvent(a->_display, &ev); if (ev.type - event_base_return != RRNotify) { qDebug() << "Received unknown X event"; continue; } qDebug() << "Got Change Event"; XRROutputChangeNotifyEvent *oce = reinterpret_cast(&ev); XRRScreenResources *sr = XRRGetScreenResources(oce->display, oce->window); if (sr == nullptr) { emit outputConfigChanged(ConnectionEvent::Unknown); continue; } XRROutputInfo *oi = XRRGetOutputInfo(a->_display, sr, oce->output); if (oi == nullptr) { XRRFreeScreenResources(sr); emit outputConfigChanged(ConnectionEvent::Unknown); continue; } if (oi->connection == RR_Connected) { emit outputConfigChanged(ConnectionEvent::Connected); } else if (oi->connection == RR_Disconnected) { emit outputConfigChanged(ConnectionEvent::Disconnected); } else { emit outputConfigChanged(ConnectionEvent::Unknown); } XRRFreeOutputInfo(oi); XRRFreeScreenResources(sr); } }); } static double toVertRefresh(const XRRModeInfo *mode) { if (mode->hTotal > 0 && mode->vTotal > 0) return (double(mode->dotClock) / (double(mode->hTotal) * double(mode->vTotal))); return 0; } //___________________________________________________________________________ void ScreenSetup::updateScreenResources() { a->updateScreenResources(); } QMap ScreenSetup::getScreenPositions() const { QMap ret; for (auto oi : a->_outputMap) { ret.insert(oi->outputName, initScreenInfo(oi, a->_modeMap)); } return ret; } /** * Create common modes and add them to all outputs. * Make sure every output has some variant of the most commonly * used modes. */ void ScreenSetup::initModes() { // First copy typical resolutions to all outputs #define RES(x,y) (((y) << 16) | (x)) QSet wanted; wanted << RES(1280, 720) << RES(1280, 800) << RES(1920, 1080); for (XRRModeInfo *mode : a->_modeMap) { if (toVertRefresh(mode) < 58 || toVertRefresh(mode) > 61) continue; // Play it safe and consider only those for copying that are 60Hz wanted.remove(RES(mode->width, mode->height)); // Make sure all outputs got it for (OutputInfo *info : a->_outputMap) { if (a->getOutputModeForResolution(info->output, mode->width, mode->height)) continue; XRRAddOutputMode(a->_display, info->id, mode->id); } } #undef RES // Create those that no output supported for (auto res : wanted) { unsigned int x = res & 0xffff; unsigned int y = res >> 16; createMode(x, y, 60, QString::asprintf("%ux%u", x, y)); } if (!wanted.isEmpty()) { updateScreenResources(); } // Finally copy all those the projector supports to other outputs for (auto key : a->_outputMap.keys()) { OutputInfo *oi = a->_outputMap[key]; if (oi->outputType == Projector::Yes) { a->copyModesToAll(key, oi->output->nmode); } } updateScreenResources(); } static QSize getTotalSizeHorz(const QList &list) { QSize ret(0, 0); for (auto e : list) { ret.rwidth() += e.width(); ret.rheight() = qMax(ret.height(), e.height()); } return ret; } ScreenMode ScreenSetup::getCurrentMode() { bool notAtOrigin = false; for (auto oi : a->_outputMap) { if (oi->mode != nullptr) { if (oi->crtc->x != 0 || oi->crtc->y != 0) { notAtOrigin = true; } } } if (a->_outputMap.size() == 1) return ScreenMode::Single; if (a->_outputMap.size() > 2) return ScreenMode::Advanced; if (notAtOrigin) return ScreenMode::Dual; return ScreenMode::Clone; } ScreenMode ScreenSetup::setDefaultMode(bool dryRun) { if (a->_outputMap.size() == 1) // Only one output exists, do nothing return ScreenMode::Single; QMap screenMap; QMap projectorMap; for (auto o : a->_outputMap) { qDebug() << o->outputName << quint32(o->outputType); if (o->outputType == Projector::Yes) { projectorMap.insert(o->outputName, o); } else { screenMap.insert(o->outputName, o); } } auto projectors = projectorMap.values(); auto screens = screenMap.values(); qDebug() << projectors.size() << "projectors," << screens.size() << "screens."; QList outputSizes = a->getTotalSize(projectors, screens); if (outputSizes.isEmpty()) return ScreenMode::Advanced; // Dunno lol QSize screenSize = getTotalSizeHorz(outputSizes); if (!dryRun) { a->createCrtcBackup(); XGrabServer(a->_display); a->disconnectAllCrtcs(); // Set new screen size a->setScreenSize(screenSize); } qDebug() << "Virtual screen size:" << screenSize << "with" << outputSizes.size() << "different screens."; int offset = 0; for (int i = 0; i < outputSizes.size(); ++i) { const QSize &size = outputSizes.at(i); if (i < projectors.size()) { a->setOutputResolution(projectors.at(i), offset, 0, size, dryRun); } if (i < screens.size()) { a->setOutputResolution(screens.at(i), offset, 0, size, dryRun); } offset += size.width(); } if (!dryRun) { XUngrabServer(a->_display); XSync(a->_display, False); } updateScreenResources(); // Re-Read if (outputSizes.size() == 1) // One output size, at least 2 outputs in total -- clone mode return ScreenMode::Clone; if (outputSizes.size() == 2 && a->_outputMap.size() == 2) // Two outputs, two sizes -- extended return ScreenMode::Dual; return ScreenMode::Advanced; // Must be more than 2 outputs -> something more involved } //___________________________________________________________________________ bool ScreenSetup::createMode(unsigned int resX, unsigned int resY, float refresh, QString name) { QByteArray ba = name.toLocal8Bit(); mode *mode = vert_refresh(int(resX), int(resY), refresh, 0, 0, 0); if (mode == nullptr) return false; 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); for (XRRModeInfo *mode : a->_modeMap) { if (mode->width == m.width && mode->height == m.height && mode->dotClock == m.dotClock) return true; // Already exists, return true? } RRMode xid = XRRCreateMode(a->_display, DefaultRootWindow(a->_display), &m); qDebug() << "Return value of create was" << xid; // Immediately add to all screens for (OutputInfo *info : a->_outputMap) { XRRAddOutputMode(a->_display, info->id, xid); } return true; } //___________________________________________________________________________ ScreenSetup::~ScreenSetup() { delete a; } bool ScreenSetup::setCenteredClone() { a->createCrtcBackup(); XRRModeInfo *fallback = nullptr; for (auto m : a->_modeMap) { if (m->width == 1024 && m->height == 768) { fallback = m; break; } } XGrabServer(a->_display); a->disconnectAllCrtcs(); QSize screenSize; bool ok = false; for (auto oi : a->_outputMap) { auto mode = a->getPreferredMode(oi, fallback); if (mode != nullptr) { if (mode->width > screenSize.width()) { screenSize.setWidth(mode->width); } if (mode->height > screenSize.height()) { screenSize.setHeight(mode->height); } // TODO: CENTER const int x = (screenSize.width() - mode->width) / 2; const int y = (screenSize.height() - mode->height) / 2; ok = a->setOutputResolution(oi, 0, 0, QSize(int(mode->width), int(mode->height))) || ok; } } a->setScreenSize(screenSize); XUngrabServer(a->_display); XSync(a->_display, False); return ok; } bool ScreenSetup::setClone(const QSize &resolution) { a->createCrtcBackup(); XGrabServer(a->_display); a->disconnectAllCrtcs(); a->setScreenSize(resolution); bool ok = false; for (auto oi : a->_outputMap) { ok = a->setOutputResolution(oi, 0, 0, resolution) || ok; } XUngrabServer(a->_display); XSync(a->_display, False); return ok; } bool ScreenSetup::setCustom(const QList>> &list) { QList sizes; for (auto e : list) { if (e.second.isEmpty()) continue; sizes.append(e.first); } if (sizes.isEmpty()) return false; a->createCrtcBackup(); auto screenSize = getTotalSizeHorz(sizes); if (screenSize.isEmpty()) return false; XGrabServer(a->_display); a->disconnectAllCrtcs(); a->setScreenSize(screenSize); int x = 0; bool ok = false; for (auto e : list) { if (e.second.isEmpty()) continue; const QSize &res = e.first; for (auto outputName : e.second) { for (auto oi : a->_outputMap) { if (oi->outputName != outputName) continue; ok = a->setOutputResolution(oi, x, 0, res) || ok; } } x += res.width(); } XUngrabServer(a->_display); XSync(a->_display, False); return ok; } void ScreenSetup::revertChanges() { if (a->_crtcBackup.isEmpty()) return; qDebug() << "Starting revert"; XGrabServer(a->_display); a->disconnectAllCrtcs(); QSize screenSize; for (auto e : a->_crtcBackup) { if (e->mode == None || !a->_modeMap.contains(e->mode)) continue; screenSize = screenSize.expandedTo(QSize(e->x + int(a->_modeMap[e->mode]->width), e->y + int(a->_modeMap[e->mode]->height))); } a->setScreenSize(screenSize); for (CrtcMap::iterator it = a->_crtcBackup.begin(); it != a->_crtcBackup.end(); ++it) { auto e = it.value(); if (e->mode == None) continue; XRRSetCrtcConfig(a->_display, a->_screenResources, it.key(), CurrentTime, e->x, e->y, e->mode, e->rotation, e->outputs, e->noutput); } XUngrabServer(a->_display); XSync(a->_display, False); } static bool modeBiggerThan(const QSize &a, const QSize &b) { if (a.width() > b.width()) return true; return a.width() == b.width() && a.height() > b.height(); } ResolutionVector ScreenSetup::getCommonModes() const { QHash, QSet> matches; for (auto oi : a->_outputMap) { for (int i = 0; i < oi->output->nmode; ++i) { if (!a->_modeMap.contains(oi->output->modes[i])) continue; const auto mode = a->_modeMap[oi->output->modes[i]]; const QPair pair = qMakePair(mode->width, mode->height); matches[pair].insert(oi->id); } } ResolutionVector ret; for (auto it = matches.begin(); it != matches.end(); ++it) { if (it.value().size() == a->_outputMap.size()) { ret.append(QSize(int(it.key().first), int(it.key().second))); } } qSort(ret.begin(), ret.end(), modeBiggerThan); return ret; } /** * Return number of connected (no active) outputs according to last query */ int ScreenSetup::getOutputCount() const { return a->_outputMap.size(); } /** * Query currently connected number of outputs */ int ScreenSetup::queryCurrentOutputCount() const { auto sr = XRRGetScreenResourcesCurrent(a->_display, DefaultRootWindow(a->_display)); if (sr == nullptr) return 0; int count = 0; for (int i = 0; i < sr->noutput; ++i) { XRROutputInfo* info = XRRGetOutputInfo(a->_display, sr, sr->outputs[i]); if (info == nullptr) continue; if (info->connection != RR_Disconnected) { count++; } XRRFreeOutputInfo(info); } XRRFreeScreenResources(sr); return count; } const ResolutionVector &ScreenSetup::getVirtualResolutions() const { return a->_resolutions; }