From 7e423e5d01c28ce7a7844f90963934fb3b034918 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 22 Aug 2018 21:17:09 +0200 Subject: Applying changes does at least something, but is still buggy... --- src/i18n/de.ts | 35 ++++- src/widget.cpp | 453 +++++++++++++++++++++++++++++++++++++++------------------ src/widget.h | 13 +- src/widget.ui | 5 +- src/x.cpp | 199 +++++++++++++++++++++---- src/x.h | 39 +++-- 6 files changed, 553 insertions(+), 191 deletions(-) diff --git a/src/i18n/de.ts b/src/i18n/de.ts index ef4f6fc..d93a667 100644 --- a/src/i18n/de.ts +++ b/src/i18n/de.ts @@ -1,6 +1,14 @@ + + QCoreApplication + + + %1x%2 + + + TimeOutDialog @@ -28,14 +36,14 @@ - - + + Apply - + Dual Screen @@ -45,17 +53,32 @@ - + Advanced Setup - + + (off) + + + + + Output + + + + + Position + + + + Do you want to keep this resolution? - + Keep diff --git a/src/widget.cpp b/src/widget.cpp index 751c69d..52ae889 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -7,6 +7,54 @@ #include #include +class ScreenWidget : public QWidget +{ +public: + ScreenWidget(float scale, QWidget *parent = nullptr) : QWidget(parent), scale(scale) + { + QWidget::setStyleSheet(".QWidget { border-radius: 3px;border: 2px solid black; }"); + QWidget::setLayout(new QVBoxLayout(this)); + } +protected: + float scale; + void resizeEvent(QResizeEvent *event) override + { + event->accept(); + float diff = (float(event->size().width()) / float(event->size().height())) - scale; + if(diff > .01f) { + QWidget::resize(int(float(event->size().height()) * scale), event->size().height()); + } else if (diff < -.01f) { + QWidget::resize(event->size().width(), int(float(event->size().width()) / scale)); + } + } +}; + +class AdvancedScreen +{ +public: + ScreenWidget *screen; + QComboBox *cboResolution; + QSize desiredResolution; +}; + +class AdvancedOutput +{ +public: + AdvancedOutput(const ScreenInfo& si) : info(si) {} + ScreenInfo info; + QLabel *assignmentLabel; + QLabel *rowLabel; + QComboBox *cboPosition; +}; +Q_DECLARE_METATYPE(AdvancedOutput*) + +static void addBoldListener(QComboBox *combo) +{ + combo->connect(combo, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) { + combo->setFont(qvariant_cast(combo->itemData(index, Qt::FontRole))); + }); +} + //______________________________________________________________________________ Widget::Widget(QWidget *parent) : QWidget(parent), @@ -14,16 +62,20 @@ Widget::Widget(QWidget *parent) : { _ui->setupUi(this); setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); -} - -void Widget::bringToTopTimer() -{ - auto combos = this->findChildren(); - for (auto combo : combos) { - if (combo->view()->isVisible()) - return; - } - raise(); + QTimer *top = new QTimer(this); + connect(top, &QTimer::timeout, [=]() { + auto combos = this->findChildren(); + for (auto combo : combos) { + if (combo->view()->isVisible()) + return; + } + raise(); + }); + top->start(1000); + addBoldListener(_ui->cboCloneResolution); + addBoldListener(_ui->cboDualLeft); + addBoldListener(_ui->cboDualRight); + connectButtons(); } //______________________________________________________________________________ @@ -35,7 +87,7 @@ void Widget::showEvent(QShowEvent *event) { QWidget::showEvent(event); ScreenMode mode = ScreenSetup::inst()->getCurrentMode(); - if (ScreenSetup::inst()->getOutputCount() == 2 || mode == ScreenMode::Dual) { + if (ScreenSetup::inst()->getOutputCount() >= 2 || mode == ScreenMode::Dual) { if (_ui->tabWidget->widget(1) != _ui->tabDual) { _ui->tabWidget->insertTab(1, _ui->tabDual, tr("Dual Screen")); } @@ -59,85 +111,212 @@ void Widget::showEvent(QShowEvent *event) initControls(); } +static void fillCombo(QComboBox *combo, const ResolutionVector &resolutions, const QSize &preselected, const QSize &preferred = QSize()) +{ + combo->clear(); + QFont font = combo->font(); + font.setBold(false); + combo->setFont(font); + bool foundPreselected = false; + for (auto res : resolutions) { + combo->addItem(QCoreApplication::tr("%1x%2").arg(res.width()).arg(res.height()), QVariant(res)); + if (res == preferred) { + QFont boldFont(font); + boldFont.setBold(true); + combo->setItemData(combo->count() - 1, boldFont, Qt::FontRole); + } else { + combo->setItemData(combo->count() - 1, font, Qt::FontRole); + } + if (res == preselected) { + combo->setCurrentIndex(combo->count() - 1); + foundPreselected = true; + } else if (!foundPreselected && res == preferred) { + combo->setCurrentIndex(combo->count() - 1); + } + } + combo->setFont(qvariant_cast(combo->currentData(Qt::FontRole))); +} + +static bool dualHeadLess(const ScreenInfo &a, const ScreenInfo &b) +{ + if (a.position != -1 && a.position < b.position) + return true; + return a.position == b.position && a.output < b.output; +} + +void Widget::comboBold(int index) +{ + QComboBox *cbo = qobject_cast(QObject::sender()); + if (cbo != nullptr) { + cbo->setFont(qvariant_cast(cbo->itemData(index, Qt::FontRole))); + } +} + void Widget::initControls() { + if (_ui->btnDualSwap->isChecked()) { + _ui->btnDualSwap->toggle(); + } + auto resolutions = ScreenSetup::inst()->getVirtualResolutions(); + auto screenMap = ScreenSetup::inst()->getScreenPositions(); auto modes = ScreenSetup::inst()->getCommonModes(); - for (auto mode : modes) { - _ui->cboCloneResolution->addItem(QString::asprintf("%ux%u", mode.first, mode.second)); + auto screenList = screenMap.values(); + qSort(screenList.begin(), screenList.end(), dualHeadLess); + // Clone + QSize preferredClone; + for (auto screen : screenList) { + if (!screen.preferredResolution.isEmpty()) { + preferredClone = screen.preferredResolution; + if (screen.isProjector) + break; + } } + fillCombo(_ui->cboCloneResolution, modes, screenList[0].currentResolution, preferredClone); + // Dual + if (screenList.size() >= 2) { + auto lists = QList({_ui->cboDualLeft, _ui->cboDualRight}); + int j = 0; + for (int i = 0; i < screenList.size() && j < 2; ++i) { + fillCombo(lists[j], screenList[i].modes, screenList[i].currentResolution, screenList[i].preferredResolution); + lists[j]->setProperty("output", screenList[i].output); + QLabel *sl = ( j == 0 ? _ui->lblDualLeft : _ui->lblDualRight ); + sl->setText(screenList[i].output + "\n" + screenList[i].name); + if (screenList[i].currentResolution.isEmpty()) + continue; + ++j; + } + } + + // // Clear advanced controls - auto widgets = _ui->advancedCombos->findChildren(); - for (auto w : widgets) { - _ui->advancedCombos->removeWidget(w); - w->deleteLater(); + for (auto e : _advancedScreens) { + delete e; + } + for (auto e : _advancedOutput) { + if (e->assignmentLabel->layout() == nullptr) { + e->assignmentLabel->deleteLater(); + } + delete e; + } + _advancedScreens.clear(); + _advancedOutput.clear(); + QLayoutItem *w; + while ((w = _ui->advancedCombos->takeAt(0)) != nullptr) { + if (w->widget() != nullptr) { + w->widget()->deleteLater(); + } + delete w; + } + while ((w = _ui->advancedContainer->takeAt(0)) != nullptr) { + if (w->widget() != nullptr) { + w->widget()->deleteLater(); + } + delete w; } // Create new - QHash positions = ScreenSetup::inst()->getScreenPositions(); - ScreenSetup::ResolutionVector resolutions = ScreenSetup::inst()->getVirtualResolutions(); - int row = 0; - for (auto it = positions.begin(); it != positions.end(); ++it) { - _ui->advancedCombos->addWidget(new QLabel(it.key()), row, 0, 1, -1); - _ui->advancedCombos->addWidget(new QComboBox(), row, 2); + // Screens + QStringList positionList(tr("(off)")); + for (int i = 0; i < screenMap.size(); ++i) { + QSize res; + if (i < resolutions.size()) { + res = resolutions[i]; + } else { + res = QSize(16, 9); + } + AdvancedScreen *a = new AdvancedScreen(); + a->screen = new ScreenWidget(float(res.width()) / float(res.height())); + a->desiredResolution = res; + a->cboResolution = new QComboBox(); + _ui->advancedContainer->addWidget(a->screen); + _advancedScreens.append(a); + a->screen->layout()->addWidget(a->cboResolution); + a->screen->layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); + positionList.append(QString::number(_advancedScreens.size())); + connect(a->cboResolution, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) { + if (!_ignoreResolutionChange) { + a->desiredResolution = a->cboResolution->itemData(index).toSize(); + } + }); + addBoldListener(a->cboResolution); + } + // Header + _ui->advancedCombos->addWidget(new QLabel(tr("Output")), 0, 0); + _ui->advancedCombos->addWidget(new QLabel(tr("Position")), 0, 2); + // List + int row = 1; + for (auto it = screenMap.begin(); it != screenMap.end(); ++it) { + AdvancedOutput *a = new AdvancedOutput(it.value()); + a->assignmentLabel = new QLabel(it.key(), this); + a->assignmentLabel->hide(); + a->rowLabel = new QLabel(it.key()); + _ui->advancedCombos->addWidget(a->rowLabel, row, 0); + _ui->advancedCombos->addWidget(new QLabel(a->info.name), row, 1); + QComboBox *cbo = new QComboBox(); + a->cboPosition = cbo; + cbo->setProperty("output", QVariant::fromValue(a)); + _ui->advancedCombos->addWidget(cbo, row, 2); + _ui->advancedCombos->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum), row, 3); + _advancedOutput.append(a); row++; + // Logic + cbo->addItems(positionList); + // TODO Signal + connect(cbo, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) { + a->info.position = index - 1; + if (a->assignmentLabel->layout() != nullptr) { + a->assignmentLabel->layout()->removeWidget(cbo); + } + if (index > 0) { + _advancedScreens[index - 1]->screen->layout()->addWidget(a->assignmentLabel); + a->assignmentLabel->show(); + } else { + a->assignmentLabel->hide(); + } + // Refill boxes + _ignoreResolutionChange = true; + for (int i = 0; i < _advancedScreens.size(); ++i) { + QSize preferred, fallbackPreferred; + // Iterate over all virtual screens + AdvancedScreen *screen = _advancedScreens[i]; + bool first = true; + ResolutionVector common; + for (auto output : _advancedOutput) { + if (output->info.position != i) + continue; // Output not mapped to this virtual screen + if (first) { + common = output->info.modes; + first = false; + } else { + QMutableVectorIterator it(common); + while (it.hasNext()) { + if (!output->info.modes.contains(it.next())) { + it.remove(); + } + } + } + if (!output->info.preferredResolution.isEmpty()) { + QSize &p = output->info.isProjector ? preferred : fallbackPreferred; + if (p.isEmpty() || !common.contains(p)) { + p = output->info.preferredResolution; + } + } + } + // Set list + // TODO: Highlight preferred mode + if (preferred.isEmpty()) { + preferred = fallbackPreferred; + } + qDebug() << "Preferred:" << preferred; + fillCombo(screen->cboResolution, common, screen->desiredResolution, preferred); + } + _ignoreResolutionChange = false; + }); + cbo->setCurrentIndex(a->info.position + 1); } } -//______________________________________________________________________________ -void Widget::handleButton() { - // Apply - - /* - - // Get the modes which has to be applied from QComboBox - QList modes = - _ui->comboBox->itemData( _ui->comboBox->currentIndex()).toList(); - XRRModeInfo* monitorMode = _modeMap[modes[0].toULongLong()]; - XRRModeInfo* beamerMode = _modeMap[modes[1].toULongLong()]; - - // First disconnect all crts to avoid conflicts - for(CrtcMap::iterator it = _crtcMap.begin(); it != _crtcMap.end(); ++it) { - Status st = XRRSetCrtcConfig(_display, _screenResources, it.key(), CurrentTime, - 0, 0, None, RR_Rotate_0, nullptr, 0); - qDebug() << "Disconnecting" << it.key() << ":" << st; - } - - // Set screensize - XRRSetScreenSize(_display, DefaultRootWindow(_display), - monitorMode->width, monitorMode->height, - 25.4 * monitorMode->width / 96, // standard dpi that X uses - 25.4 * monitorMode->height / 96); // standard dpi that X uses - - // Apply the modes - Status stMon = XRRSetCrtcConfig(_display, - _screenResources, - _outputMap[_monitor]->crtc, - CurrentTime, - 0, 0, monitorMode->id, - RR_Rotate_0, - &_monitor, 1); - Status stBem = XRRSetCrtcConfig(_display, - _screenResources, - _outputMap[_beamer]->crtc, - CurrentTime, - 0, 0, beamerMode->id, - RR_Rotate_0, - &_beamer, 1); - - // Sync... whatever... - XSync (_display, False); - qDebug() << "Monitor set:" << stMon << ", beamer set: " << stBem; - - // Center widget on screenbottom - this->move( monitorMode->width/2 - this->width()/2, - monitorMode->height - this->height() ); - - // Set the mouse pointer in the middle of the screen - QCursor::setPos(monitorMode->width/2, monitorMode->height/2); - - */ - - /*************************** ASK for confirmtion ****************************/ - +bool Widget::keepResolution() +{ // Show a dialog asking if the res should be kept TimeOutDialog keepDialog(15, this); keepDialog.setWindowModality(Qt::ApplicationModal); @@ -150,77 +329,67 @@ void Widget::handleButton() { */ keepDialog.show(); - while (keepDialog.isActive()) { + do { QCoreApplication::processEvents(); - } - - /* - // If the dialog was not canceled revert the resolution - if ( !keepDialog.wasCanceled()) { - qDebug() << "User did not cancel timeout, reverting!"; - - // First disconnect all crts to avoid conflicts - for(CrtcMap::iterator it = _crtcMap.begin(); it != _crtcMap.end(); ++it) { - XRRSetCrtcConfig(_display, _screenResources, it.key(), CurrentTime, - 0, 0, None, RR_Rotate_0, nullptr, 0); - } + } while (keepDialog.isActive()); - // Then calc backed up screensize - QSize ScreenSize(0,0); - for ( CrtcMap::iterator it = backup.begin(); - it != backup.end(); ++it ) { - // Dangerzone. Only access existing modes! - if ( it.value()->mode != None ) { - ScreenSize.setWidth( - std::max((uint)ScreenSize.width(), - it.value()->x+_modeMap[it.value()->mode]->width)); - ScreenSize.setHeight( - std::max((uint)ScreenSize.height(), - it.value()->y+_modeMap[it.value()->mode]->height)); - } - } + return keepDialog.wasCanceled(); +} - // Set screensize - XRRSetScreenSize(_display, DefaultRootWindow(_display), - ScreenSize.width(), ScreenSize.height(), - 25.4 * ScreenSize.width() / 96, // dpi used by X - 25.4 * ScreenSize.height() / 96); // dpi used by X - - // Apply the backup - for ( CrtcMap::iterator it = backup.begin(); - it != backup.end(); ++it ) { - XRRSetCrtcConfig(_display, - _screenResources, - it.key(), - CurrentTime, - it.value()->x, - it.value()->y, - it.value()->mode, - it.value()->rotation, - it.value()->outputs, - it.value()->noutput); - } +//______________________________________________________________________________ +void Widget::connectButtons() { - // Sync... whatever... - XSync (_display, False); + // Swap dualscreen + connect(_ui->btnDualSwap, &QPushButton::clicked, [=](bool) { + _ui->dualContainer->addItem(_ui->dualContainer->takeAt(1)); + _ui->dualContainer->addItem(_ui->dualContainer->takeAt(0)); + }); - // center dialog on screenbottom - qDebug() << "Again center dialog on screenbottom"; - this->move( ScreenSize.width()/2 - this->width()/2, - ScreenSize.height() - this->height()); + // Apply CLONE + connect(_ui->btnCloneApply, &QPushButton::clicked, [=](bool) { + if (!ScreenSetup::inst()->setClone(_ui->cboCloneResolution->currentData().toSize()) || !keepResolution()) { + qDebug() << "reverting clone"; + ScreenSetup::inst()->revertChanges(); + } + ScreenSetup::inst()->updateScreenResources(); + initControls(); + }); - // Set the mouse pointer in the middle of the screen - QCursor::setPos(monitorMode->width/2, monitorMode->height/2); - } + // Apply DUALHEAD + connect(_ui->btnDualApply, &QPushButton::clicked, [=](bool) { + QPair> left = { _ui->cboDualLeft->currentData().toSize(), { _ui->cboDualLeft->property("output").toString() } }; + QPair> right = { _ui->cboDualRight->currentData().toSize(), { _ui->cboDualRight->property("output").toString() } }; + if (_ui->btnDualSwap->isChecked()) { + qSwap(left, right); + } + if (!ScreenSetup::inst()->setCustom({left, right}) || !keepResolution()) { + qDebug() << "reverting dualhead"; + ScreenSetup::inst()->revertChanges(); + } + ScreenSetup::inst()->updateScreenResources(); + initControls(); + }); - // Delete the backup - for ( CrtcMap::iterator it = backup.begin(); - it != backup.end(); ++it ) { - delete[] it.value()->outputs; - delete it.value(); - } + // Apply custom + connect(_ui->btnAdvancedApply, &QPushButton::clicked, [=](bool) { + QList>> list; + for (auto e : _advancedScreens) { + list.append({ e->cboResolution->currentData().toSize(), QList() }); + } + for (auto e : _advancedOutput) { + int index = e->cboPosition->currentIndex() - 1; + if (index < 0 || index >= list.size()) + continue; + list[index].second.append(e->cboPosition->property("output").toString()); + } + if (!ScreenSetup::inst()->setCustom(list) || !keepResolution()) { + qDebug() << "reverting custom"; + ScreenSetup::inst()->revertChanges(); + } + ScreenSetup::inst()->updateScreenResources(); + initControls(); + }); - */ } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/widget.h b/src/widget.h index 05e27a6..96e6f1a 100644 --- a/src/widget.h +++ b/src/widget.h @@ -8,6 +8,9 @@ namespace Ui { class Widget; } +class AdvancedScreen; +class AdvancedOutput; + class Widget : public QWidget { Q_OBJECT @@ -17,16 +20,20 @@ public: ~Widget(); protected: - virtual void showEvent(QShowEvent *event); + virtual void showEvent(QShowEvent *event) override; private slots: - void handleButton(); - void bringToTopTimer(); + void comboBold(int index); private: Ui::Widget *_ui; + bool _ignoreResolutionChange; + QVector _advancedScreens; + QVector _advancedOutput; void initControls(); + void connectButtons(); + bool keepResolution(); }; #endif // WIDGET_H diff --git a/src/widget.ui b/src/widget.ui index 2287936..5836a42 100644 --- a/src/widget.ui +++ b/src/widget.ui @@ -22,7 +22,7 @@ - 2 + 1 @@ -126,6 +126,9 @@ border: 2px solid black; <- Swap -> + + true + diff --git a/src/x.cpp b/src/x.cpp index 74020e0..df257bb 100644 --- a/src/x.cpp +++ b/src/x.cpp @@ -29,6 +29,25 @@ struct OutputInfo { Projector isProjector; }; +ScreenInfo::ScreenInfo(const OutputInfo *oi, const ModeMap &om) + : position(oi->position), name(oi->modelName), output(oi->outputName), + isProjector(oi->isProjector == Projector::Yes) +{ + if (oi->mode != nullptr) { + 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)); + modes.append(size); + if (i == 0 && oi->output->npreferred > 0) { + preferredResolution = size; + } + } + } +} + ScreenSetup * ScreenSetup::_instance = nullptr; ScreenSetup::ScreenSetup() : _screenResources(nullptr) @@ -47,6 +66,7 @@ ScreenSetup::ScreenSetup() : _screenResources(nullptr) void ScreenSetup::freeResources() { + freeCrtcBackup(); // Clear the modemap (nothing to be freed, stored in screenResources) _modeMap.clear(); _resolutions.clear(); @@ -163,18 +183,21 @@ void ScreenSetup::updateScreenResources() XRRFreeOutputInfo(info); continue; } + if (info->crtc == None) { + 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 (!_modeMap.contains(crtc->mode)) { - qDebug() << "Have output" << outputName << " with crtc with no known mode"; - XRRFreeOutputInfo(info); - continue; + qDebug() << "Have output" << outputName << " with crtc with no known mode -- offline?"; + } else { + mode = _modeMap.value(crtc->mode); } - XRRModeInfo *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 @@ -237,11 +260,11 @@ void ScreenSetup::updateScreenResources() qDebug() << "Loaded."; } -QHash ScreenSetup::getScreenPositions() const +QMap ScreenSetup::getScreenPositions() const { - QHash ret; + QMap ret; for (auto oi : _outputMap) { - ret.insert(oi->outputName, oi->position); + ret.insert(oi->outputName, ScreenInfo(oi, _modeMap)); } return ret; } @@ -394,8 +417,7 @@ static QSize getTotalSizeHorz(const QList &list) void ScreenSetup::setOutputResolution(OutputInfo *oi, int x, int y, const QSize &size, bool dryRun) { - RRMode mode = getOutputModeForResolution(oi->output, - static_cast(size.width()), static_cast(size.height())); + RRMode mode = getOutputModeForResolution(oi->output, size); if (mode == None) { if (oi->output->nmode == 0) return; @@ -449,16 +471,9 @@ ScreenMode ScreenSetup::setDefaultMode(bool dryRun) return ScreenMode::Advanced; // Dunno lol QSize screenSize = getTotalSizeHorz(outputSizes); if (!dryRun) { - // Disconnect everything - for (auto crtc : _crtcMap.keys()) { - XRRSetCrtcConfig(_display, _screenResources, crtc, CurrentTime, - 0, 0, None, RR_Rotate_0, nullptr, 0); - } + disconnectAllCrtcs(); // Set new screen size - XRRSetScreenSize(_display, DefaultRootWindow(_display), - screenSize.width(), screenSize.height(), - int(25.4 * screenSize.width() / 96.0), // standard dpi that X uses - int(25.4 * screenSize.height() / 96.0)); // standard dpi that X uses + setScreenSize(screenSize); } qDebug() << "Virtual size:" << screenSize << "with" << outputSizes.size() << "different screens."; @@ -533,6 +548,25 @@ RRMode ScreenSetup::getOutputModeForResolution(const XRROutputInfo *output, unsi return retval == nullptr ? None : retval->id; } +RRMode ScreenSetup::getOutputModeForResolution(const XRROutputInfo *output, const QSize &resolution) const +{ + return getOutputModeForResolution(output, static_cast(resolution.width()), static_cast(resolution.height())); +} + +RRCrtc ScreenSetup::getFreeCrtc(const XRROutputInfo* output) const +{ + for (int i = 0; i < output->ncrtc; ++i) { + RRCrtc c = output->crtcs[i]; + for (auto oi : _outputMap) { + if (oi->output->crtc == c) + goto next; + } + return c; +next:; + } + return None; +} + //___________________________________________________________________________ bool ScreenSetup::createMode(unsigned int resX, unsigned int resY, float refresh, QString name) { @@ -577,26 +611,133 @@ ScreenSetup::~ScreenSetup() XCloseDisplay(_display); } -bool ScreenSetup::applyChanges() +void ScreenSetup::setScreenSize(const QSize &size) { - return true; + 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 ScreenSetup::revertChanges() +void ScreenSetup::disconnectAllCrtcs() +{ + // Disconnect everything + for (auto crtc : _crtcMap.keys()) { + XRRSetCrtcConfig(_display, _screenResources, crtc, CurrentTime, + 0, 0, None, RR_Rotate_0, nullptr, 0); + } +} + +void ScreenSetup::freeCrtcBackup() +{ + for (auto entry : _crtcBackup) { + free(entry->outputs); + free(entry); + } + _crtcBackup.clear(); + qDebug() << "CRTC freed"; +} + +void ScreenSetup::createCrtcBackup() { + freeCrtcBackup(); + for (CrtcMap::iterator it = _crtcMap.begin(); it != _crtcMap.end(); ++it) { + const auto src = it.value(); + XRRCrtcInfo *copy = static_cast(calloc(1, sizeof(XRRCrtcInfo))); + copy->outputs = static_cast(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(); +} +bool ScreenSetup::setClone(const QSize &resolution) +{ + createCrtcBackup(); + disconnectAllCrtcs(); + setScreenSize(resolution); + bool ok = false; + for (auto oi : _outputMap) { + RRMode mode = getOutputModeForResolution(oi->output, resolution); + if (mode != None) { + XRRSetCrtcConfig(_display, _screenResources, oi->output->crtc, CurrentTime, 0, 0, mode, RR_Rotate_0, &oi->id, 1); + ok = true; + } + } + XSync(_display, False); + return ok; +} + +bool ScreenSetup::setCustom(const QList>> &list) +{ + createCrtcBackup(); + QList sizes; + for (auto e : list) { + sizes.append(e.first); + } + auto screenSize = getTotalSizeHorz(sizes); + if (screenSize.isEmpty()) + return false; + disconnectAllCrtcs(); + setScreenSize(screenSize); + int x = 0; + bool ok = false; + for (auto e : list) { + const QSize &res = e.first; + for (auto outputName : e.second) { + for (auto oi : _outputMap) { + if (oi->outputName != outputName) + continue; + RRMode mode = getOutputModeForResolution(oi->output, res); + if (mode == None) + continue; + XRRSetCrtcConfig(_display, _screenResources, oi->output->crtc, CurrentTime, x, 0, mode, RR_Rotate_0, &oi->id, 1); + ok = true; + } + } + x += res.width(); + } + XSync(_display, False); + return ok; +} + +void ScreenSetup::revertChanges() +{ + if (_crtcBackup.isEmpty()) + return; + qDebug() << "Starting revert"; + disconnectAllCrtcs(); + QSize screenSize; + for (auto e : _crtcBackup) { + if (e->mode == None || !_modeMap.contains(e->mode)) + continue; + screenSize = screenSize.expandedTo(QSize(e->x + int(_modeMap[e->mode]->width), e->y + int(_modeMap[e->mode]->height))); + } + for (CrtcMap::iterator it = _crtcBackup.begin(); it != _crtcBackup.end(); ++it) { + auto e = it.value(); + if (e->mode == None) + continue; + XRRSetCrtcConfig(_display, _screenResources, it.key(), CurrentTime, + e->x, e->y, e->mode, e->rotation, e->outputs, e->noutput); + } + XSync (_display, False); } -static bool modeBiggerThan(const QPair &a, const QPair &b) +static bool modeBiggerThan(const QSize &a, const QSize &b) { - if (a.first > b.first) + if (a.width() > b.width()) return true; - if (a.first == b.first) - return a.second > b.second; - return false; + return a.width() == b.width() && a.height() > b.height(); } -QList> ScreenSetup::getCommonModes() const +ResolutionVector ScreenSetup::getCommonModes() const { QHash, QSet> matches; for (auto oi : _outputMap) { @@ -608,10 +749,10 @@ QList> ScreenSetup::getCommonModes() const matches[pair].insert(oi->id); } } - QList> ret; + ResolutionVector ret; for (auto it = matches.begin(); it != matches.end(); ++it) { if (it.value().size() == _outputMap.size()) { - ret.append(it.key()); + ret.append(QSize(int(it.key().first), int(it.key().second))); } } qSort(ret.begin(), ret.end(), modeBiggerThan); diff --git a/src/x.h b/src/x.h index 50008c4..8a37b62 100644 --- a/src/x.h +++ b/src/x.h @@ -13,6 +13,10 @@ struct OutputInfo; +typedef QHash ModeMap; +typedef QHash CrtcMap; +typedef QHash OutputMap; +typedef QVector ResolutionVector; /////////////////////////////////////////////////////////////////////////// @@ -24,17 +28,23 @@ enum class ScreenMode Advanced, }; -class ScreenSetup +class ScreenInfo { public: + ScreenInfo(const OutputInfo *oi, const ModeMap &om); + int position; + QString name; + QString output; + QSize currentResolution; + QSize preferredResolution; + bool isProjector; + ResolutionVector modes; +}; - typedef QHash ModeMap; - typedef QHash CrtcMap; - typedef QHash OutputMap; - typedef QVector ResolutionVector; - +class ScreenSetup +{ +public: void updateScreenResources(); - bool applyChanges(); void initModes(); XRRModeInfo* getPreferredMode(OutputInfo *oi) const; QList getTotalSize(const QList &projectors, const QList &screens) const; @@ -42,12 +52,13 @@ public: ScreenMode getCurrentMode(); ScreenMode setDefaultMode(bool dryRun = false); void copyModesToAll(RROutput id, int num); - RRMode getOutputModeForResolution(const XRROutputInfo *output, unsigned int width, unsigned int height) const; bool createMode(unsigned int resX, unsigned int resY, float refresh, QString name); void revertChanges(); - QList> getCommonModes() const; + bool setClone(const QSize &resolution); + bool setCustom(const QList>> &list); + ResolutionVector getCommonModes() const; int getOutputCount() const { return _outputMap.size(); } - QHash getScreenPositions() const; + QMap getScreenPositions() const; const ResolutionVector &getVirtualResolutions() const { return _resolutions; } // Singleton @@ -62,6 +73,13 @@ private: void freeResources(); bool readEdid(OutputInfo* output); + void createCrtcBackup(); + void freeCrtcBackup(); + void disconnectAllCrtcs(); + void setScreenSize(const QSize &size); + RRMode getOutputModeForResolution(const XRROutputInfo *output, unsigned int width, unsigned int height) const; + RRMode getOutputModeForResolution(const XRROutputInfo *output, const QSize &resolution) const; + RRCrtc getFreeCrtc(const XRROutputInfo* output) const; static ScreenSetup * _instance; Display* _display; @@ -69,6 +87,7 @@ private: XRRScreenResources* _screenResources; ModeMap _modeMap; CrtcMap _crtcMap; + CrtcMap _crtcBackup; OutputMap _outputMap; ResolutionVector _resolutions; }; -- cgit v1.2.3-55-g7522