summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2018-08-22 21:17:09 +0200
committerSimon Rettberg2018-08-22 21:17:09 +0200
commit7e423e5d01c28ce7a7844f90963934fb3b034918 (patch)
treea020b8d8df971e3be245e6fec1f9d5305cbbf2d6
parentNew Version, WIP (diff)
downloadbeamergui-7e423e5d01c28ce7a7844f90963934fb3b034918.tar.gz
beamergui-7e423e5d01c28ce7a7844f90963934fb3b034918.tar.xz
beamergui-7e423e5d01c28ce7a7844f90963934fb3b034918.zip
Applying changes does at least something, but is still buggy...
-rw-r--r--src/i18n/de.ts35
-rw-r--r--src/widget.cpp453
-rw-r--r--src/widget.h13
-rw-r--r--src/widget.ui5
-rw-r--r--src/x.cpp199
-rw-r--r--src/x.h39
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
@@ -2,6 +2,14 @@
<!DOCTYPE TS>
<TS version="2.1">
<context>
+ <name>QCoreApplication</name>
+ <message>
+ <location filename="../widget.cpp" line="122"/>
+ <source>%1x%2</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>TimeOutDialog</name>
<message>
<location filename="../timeoutdialog.cpp" line="16"/>
@@ -28,14 +36,14 @@
</message>
<message>
<location filename="../widget.ui" line="86"/>
- <location filename="../widget.ui" line="171"/>
- <location filename="../widget.ui" line="208"/>
+ <location filename="../widget.ui" line="174"/>
+ <location filename="../widget.ui" line="211"/>
<source>Apply</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../widget.ui" line="102"/>
- <location filename="../widget.cpp" line="40"/>
+ <location filename="../widget.cpp" line="92"/>
<source>Dual Screen</source>
<translation type="unfinished"></translation>
</message>
@@ -45,17 +53,32 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../widget.ui" line="181"/>
+ <location filename="../widget.ui" line="184"/>
<source>Advanced Setup</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../widget.cpp" line="145"/>
+ <location filename="../widget.cpp" line="218"/>
+ <source>(off)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../widget.cpp" line="243"/>
+ <source>Output</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../widget.cpp" line="244"/>
+ <source>Position</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../widget.cpp" line="324"/>
<source>Do you want to keep this resolution?</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../widget.cpp" line="146"/>
+ <location filename="../widget.cpp" line="325"/>
<source>Keep</source>
<translation type="unfinished"></translation>
</message>
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 <QDebug>
#include <QtWidgets/QAction>
+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<int>::of(&QComboBox::currentIndexChanged), [=](int index) {
+ combo->setFont(qvariant_cast<QFont>(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<QComboBox*>();
- for (auto combo : combos) {
- if (combo->view()->isVisible())
- return;
- }
- raise();
+ QTimer *top = new QTimer(this);
+ connect(top, &QTimer::timeout, [=]() {
+ auto combos = this->findChildren<QComboBox*>();
+ 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<QFont>(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<QComboBox*>(QObject::sender());
+ if (cbo != nullptr) {
+ cbo->setFont(qvariant_cast<QFont>(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<QComboBox*>({_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<QWidget*>();
- 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<QString, int> 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<int>::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<int>::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<QSize> 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<QVariant> 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<QSize, QList<QString>> left = { _ui->cboDualLeft->currentData().toSize(), { _ui->cboDualLeft->property("output").toString() } };
+ QPair<QSize, QList<QString>> 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<QPair<QSize, QList<QString>>> list;
+ for (auto e : _advancedScreens) {
+ list.append({ e->cboResolution->currentData().toSize(), QList<QString>() });
+ }
+ 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<AdvancedScreen*> _advancedScreens;
+ QVector<AdvancedOutput*> _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 @@
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
- <number>2</number>
+ <number>1</number>
</property>
<widget class="QWidget" name="tabClone">
<attribute name="title">
@@ -126,6 +126,9 @@ border: 2px solid black;
<property name="text">
<string>&lt;- Swap -&gt;</string>
</property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
</widget>
</item>
<item>
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<QString, int> ScreenSetup::getScreenPositions() const
+QMap<QString, ScreenInfo> ScreenSetup::getScreenPositions() const
{
- QHash<QString, int> ret;
+ QMap<QString, ScreenInfo> 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<QSize> &list)
void ScreenSetup::setOutputResolution(OutputInfo *oi, int x, int y, const QSize &size, bool dryRun)
{
- RRMode mode = getOutputModeForResolution(oi->output,
- static_cast<unsigned int>(size.width()), static_cast<unsigned int>(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<unsigned int>(resolution.width()), static_cast<unsigned int>(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<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();
+}
+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<QPair<QSize, QList<QString>>> &list)
+{
+ createCrtcBackup();
+ QList<QSize> 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<quint32, quint32> &a, const QPair<quint32, quint32> &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<QPair<quint32, quint32>> ScreenSetup::getCommonModes() const
+ResolutionVector ScreenSetup::getCommonModes() const
{
QHash<QPair<quint32, quint32>, QSet<RROutput>> matches;
for (auto oi : _outputMap) {
@@ -608,10 +749,10 @@ QList<QPair<quint32, quint32>> ScreenSetup::getCommonModes() const
matches[pair].insert(oi->id);
}
}
- QList<QPair<quint32, quint32>> 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<RRMode, XRRModeInfo*> ModeMap;
+typedef QHash<RRCrtc, XRRCrtcInfo*> CrtcMap;
+typedef QHash<RROutput, OutputInfo*> OutputMap;
+typedef QVector<QSize> 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<RRMode, XRRModeInfo*> ModeMap;
- typedef QHash<RRCrtc, XRRCrtcInfo*> CrtcMap;
- typedef QHash<RROutput, OutputInfo*> OutputMap;
- typedef QVector<QSize> ResolutionVector;
-
+class ScreenSetup
+{
+public:
void updateScreenResources();
- bool applyChanges();
void initModes();
XRRModeInfo* getPreferredMode(OutputInfo *oi) const;
QList<QSize> getTotalSize(const QList<OutputInfo*> &projectors, const QList<OutputInfo*> &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<QPair<quint32, quint32>> getCommonModes() const;
+ bool setClone(const QSize &resolution);
+ bool setCustom(const QList<QPair<QSize, QList<QString>>> &list);
+ ResolutionVector getCommonModes() const;
int getOutputCount() const { return _outputMap.size(); }
- QHash<QString, int> getScreenPositions() const;
+ QMap<QString, ScreenInfo> 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;
};