summaryrefslogblamecommitdiffstats
path: root/src/widget.cpp
blob: 491735a22aa0d69310de02891116e57a37d7d30c (plain) (tree)
1
2
3
4
5
6
7
8
9
                   


                 

                          


                            
                            

                  
                      
                       



                          
 







                                                                                                   



                                    







































                                                                                                            

                                                                                                     
 



              
                                 
                        
                            

                                         
                                   

                             
 
                           
                             
                                                                  

                                                                  
                                                                          

                               
                                                            

                                              

                                     
                                                

                                                           

                                              











                                                                       
                 





                                                 



                                     
                                                       
                                            
                                                       
                                                                                  









                                                                                                                                                            
                                                
                                                                     



                                                                                                          
                                                                               
                                             


                                                                              
                         
                        
                                                                            
                                                              

                                               
                                                                                       

                         


                                                        


                                       
                                       
                        
                                                                               
                 

                                                      
                                        
         



                                                                               
                                              




                                                                                 

                                           






                                                                                    
                                          
                                                                                          
                                                                                           
                                                                                                      



                                                                                                           


                                                                                                    
                                                              
                                                             
                                           


                                                                                                      

                           



                                                                                                           
                                                                                               

                                 
                                                      


                                           
         
                                                                          

 
                                                                                
                   
 
 
 


                                         
                           
                                    
                
 
 

                                         

                                                                 


                                             

 








































                                                                                                                                        
                                         
 
                                                                 
                                                                  





























                                                                                                                                                     




                                                                        
                                                               


                                                                  


                                                                                             
                             
                                               
                                                            





                                                                                                                             


                                               
         




                                                                                                           
               





                                                        



                                                                                      
                                       
                                                                





                                                                                                                                
                                                                              




                                                                                               






                                                                      
                                  






















                                                                    

                     









                                                         
                                                                                                               






                                                                                                                  
                                                                                                                     




                                                                                                  






                                                                                                                     

                 

                                                                             


                                                                        
                            



                                                                   






                                                                                     

                                                 

                                                                                                                              
                                          
                      


                                            
                                                                                                        
                                                     













































                                                                                                                    
         







                                                                                        
 
 

                             





















                                                                    
                                                                  





                                                                
                                     

                                        
 

                                 
            
                              
                                                  
                                       



                                                                 
                 
                         







                                   
 
                           
 
 

                                                                                
                          



                                                                           
 

                                                                      

                                                                                                          
                                                      
                                     



                                                             
 






                                                                                                                                                         

                                                                         
                                                         
                                     



                                                             
 





                                                                                                    
                                



                                                                       
                                                                  
                                  
                 



                                                                
                                                       
                                     



                                                             
















                                                                                                                                       
 
 
 

























                                                                            
                                                                                
#include "widget.h"
#include "main.h"
#include "xx.h"
#include "bus.h"
#include "ui_widget.h"
#include "timeoutdialog.h"

#include <QDebug>
#include <QtWidgets/QAction>
#include <QAbstractItemView>
#include <QScreen>
#include <QThread>
#include <QMessageBox>
#include <QResizeEvent>

/*
 * Helper and static stuff
 */

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));
	}
	void setScaling(float scale)
	{
		this->scale = scale;
	}
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)));
	});
}

static const QString STYLE_RELOAD_BUTTON_NORMAL("padding: 2px; margin: 0 0 1px 1px;");
static const QString STYLE_RELOAD_BUTTON_HOT(STYLE_RELOAD_BUTTON_NORMAL + "background-color: #f99;");

/*
 * Main widget
 */

Widget::Widget(QWidget *parent) :
	QDialog(parent),
	_ui(new Ui::Widget),
	_popupCount(0),
	_iProjector(QIcon(":projector")),
	_iScreen(QIcon(":screen")),
	_addEventDbus(false),
	_addEventOther(false)
{
	_ui->setupUi(this);
	// Add refresh button
	_ui->btnReload->setStyleSheet(STYLE_RELOAD_BUTTON_NORMAL);
	_ui->tabWidget->setCornerWidget(_ui->btnReload);
	connect(_ui->btnReload, &QPushButton::clicked, [=](bool) {
		_ui->btnReload->setStyleSheet(STYLE_RELOAD_BUTTON_NORMAL);
		initControls();
	});
	setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
	QTimer *top = new QTimer(this);
	connect(top, &QTimer::timeout, [=]() {
		if (this->isHidden())
			return;
		// Move window to current screen
		if (qApp->mouseButtons() == Qt::NoButton) {
			updateWindowPlacement();
		}
		// Raise window if appropriate
		if (_popupCount == 0) {
			bool ok = true;
			auto combos = this->findChildren<QComboBox*>();
			for (auto combo : combos) {
				if (combo->view()->isVisible()) {
					ok = false;
					break;
				}
			}
			if (ok) {
				raise();
			}
		}
	});
	top->start(1000);
	addBoldListener(_ui->cboCloneResolution);
	addBoldListener(_ui->cboDualLeft);
	addBoldListener(_ui->cboDualRight);
	connectButtons();
	// Handle screens on Qt level
	// Timer
	QTimer *t = new QTimer(this);
	t->setSingleShot(true);
	// Timeout after screen setup changed -- handle
	connect(t, &QTimer::timeout, [=]() {
		// Query how many screens there are now
		int currentCount = ScreenSetup::inst()->queryCurrentOutputCount();
		qDebug() << "Timeout. Dbus:" << _addEventDbus << "X:" << _addEventOther << "old count:" << _lastScreenCount << "new count:" << currentCount;
		if (_addEventDbus && !_addEventOther) {
			// Only dbus reported a change -- wait some more in case we have link flap (don't make it worse than it already is)
			t->start(2000); // If no other DBus event fires within 2 secs, we'll continue below next time
		} else if (this->isHidden()) {
			// GUI is currently hidden
			if (currentCount == _lastScreenCount) {
				// Nothing seems to have changed. This might happen when another tool changes the screen config. Let's not interfere.
			} else if (currentCount > _lastScreenCount) { // Screen count increased - auto setup
				_lastScreenCount = currentCount;
				ScreenMode mode;
                auto ret = ScreenSetup::inst()->setDefaultMode(mode);
				if (!ret.ok() || !keepResolution()) {
					ret.revert();
				}
			} else { // Screen count decreased - pop up GUI and don't just deconfig the screen
				setWindowFlag(Qt::WindowStaysOnTopHint, false);
				this->show();
				setWindowFlag(Qt::WindowStaysOnTopHint, true);
				this->raise();
				this->showNormal();
			}
		} else {
			// GUI currently visible -- highlight refresh button
			if (currentCount > _lastScreenCount) {
				initControls();
			} else {
				_ui->btnReload->setStyleSheet(STYLE_RELOAD_BUTTON_HOT);
			}
		}
		// Reset flags (don't use return above!)
		_addEventDbus = false;
		_addEventOther = false;
	});
	auto popupGui = [=]() {
		if (this->isHidden()) {
			t->start(1500);
		} else {
			_ui->btnReload->setStyleSheet(STYLE_RELOAD_BUTTON_HOT);
		}
	};
	for (auto scrn : QGuiApplication::screens()) {
		_qtScreens.append(scrn);
	}
	connect(qApp, &QGuiApplication::screenAdded, [=](const QScreen *scrn) {
		qDebug() << "QT SEES SCREEN" << scrn->geometry();
		_qtScreens.append(scrn);
		if (CommandLine::backgroundMode()) {
			_addEventOther = true;
			popupGui();
		}
	});
	connect(qApp, &QGuiApplication::screenRemoved, [=](const QScreen *scrn) {
		qDebug() << "Qt lost screen" << scrn->geometry();
		_qtScreens.removeAll(scrn);
	});
	_ui->btnExit->setVisible(CommandLine::backgroundMode());
	if (CommandLine::backgroundMode()) {
		// Listener
		if (!Bus::inst()->registerListener()) {
			qDebug() << "WARNING: CANNOT CONNECT TO DBUS FOR LISTENING";
			// TODO: GUI feedback
		} else {
			// GUI popup logic
			connect(Bus::inst(), &Bus::serviceConnected, [=](bool isSession) {
				qDebug() << "\\o/ Received DBus connect notification \\o/";
				if (isSession) { // Session bus means user triggered, show immediately
					// Try to be very aggressive to get on top, since we don't want the
					// window to hang around behind the full screen VMplayer
					setWindowFlag(Qt::WindowStaysOnTopHint, false);
					this->hide();
					QTimer::singleShot(100, [=]() {
						this->showNormal();
						this->setWindowFlag(Qt::WindowStaysOnTopHint, true);
						this->raise();
						this->show();
					});
				} else { // Otherwise, systembus means udev event -- take normal route
					popupGui();
				}
			});
		}
		// Xlib
		connect(ScreenSetup::inst(), &ScreenSetup::outputConfigChanged, [=](ConnectionEvent type) {
			if (type == ConnectionEvent::Disconnected) {
				if (!this->isHidden()) {
					_ui->btnReload->setStyleSheet(STYLE_RELOAD_BUTTON_HOT);
				}
			} else {
				_addEventOther = true;
				popupGui();
			}
		});
	}
	_lastScreenCount = ScreenSetup::inst()->queryCurrentOutputCount();
}

//______________________________________________________________________________
Widget::~Widget() {

}

void Widget::showEvent(QShowEvent *event)
{
	QWidget::showEvent(event);
	initControls(true);
	updateWindowPlacement(true);
	raise();
}

void Widget::hideEvent(QHideEvent *event)
{
	QWidget::hideEvent(event);
	_lastScreenCount = ScreenSetup::inst()->getOutputCount();
	if (!CommandLine::backgroundMode()) {
		qApp->quit();
	}
}

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(bool jumpToTab)
{
	_lastScreenCount = ScreenSetup::inst()->getOutputCount();
	_ui->btnReload->setStyleSheet(STYLE_RELOAD_BUTTON_NORMAL);
	//
	ScreenMode currentOpMode = ScreenSetup::inst()->getCurrentMode();
	_ui->tabWidget->setTabEnabled(1, CommandLine::testMode() || ScreenSetup::inst()->getOutputCount() == 2 || currentOpMode == ScreenMode::Dual);
	QWidget *current = nullptr;
	switch (currentOpMode) {
	case ScreenMode::Single:
	case ScreenMode::Clone:
		current = _ui->tabClone;
		break;
	case ScreenMode::Dual:
		current = _ui->tabDual;
		break;
	case ScreenMode::Advanced:
		current = _ui->tabAdvanced;
		break;
	}
	if (current != nullptr) {
		if (jumpToTab) {
			_ui->tabWidget->setCurrentWidget(current);
		}
		for (int i = 0; i < _ui->tabWidget->count(); ++i) {
			auto w = _ui->tabWidget->widget(i);
			if (w == current) {
				_ui->tabWidget->setTabIcon(i, QIcon(":check"));
			} else {
				_ui->tabWidget->setTabIcon(i, QIcon());
			}
		}
	}
	//
	if (_ui->btnDualSwap->isChecked()) {
		_ui->btnDualSwap->toggle();
	}
	auto resolutions = ScreenSetup::inst()->getVirtualResolutions();
	auto screenMap = ScreenSetup::inst()->getScreenPositions();
	auto modes = ScreenSetup::inst()->getCommonModes(true);
	auto screenList = screenMap.values();
	qSort(screenList.begin(), screenList.end(), dualHeadLess);
	// Clone
	// Determine preferred resolution for cloning: If no screen is detected as projector,
	// use the smallest prefered resolution across all screens
	// Otherwise, use the prefered resolution of the first projector we encounter
	QSize preferredClone;
	for (const auto &screen : screenList) {
		if (!screen.preferredResolution.isEmpty()) {
			if (screen.isProjector // Projector always overrides
					|| preferredClone.isEmpty() // Have no preferred yet
					|| screen.preferredResolution.width() < preferredClone.width() // For normal screens,
					|| screen.preferredResolution.height() < preferredClone.height()) { // smallest wins
				preferredClone = screen.preferredResolution;
			}
			if (screen.isProjector)
				break;
		}
	}
	if (screenList.empty()) {
		fillCombo(_ui->cboCloneResolution, modes, QSize(), preferredClone);
	} else {
		fillCombo(_ui->cboCloneResolution, modes, screenList[0].currentResolution, preferredClone);
	}
	// Dual
	_ui->dualContainer->takeAt(0);
	_ui->dualContainer->takeAt(1);
	_ui->dualContainer->takeAt(2);
	_ui->dualContainer->addLayout(_ui->dualLeft);
	_ui->dualContainer->addWidget(_ui->btnDualSwap);
	_ui->dualContainer->addLayout(_ui->dualRight);
	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) {
			QSize selected;
			if (currentOpMode == ScreenMode::Dual) {
				// When we're not in dualhead mode, pre-select the preferred solution, so in case the user wants
				// to switch to dualhead, they just need to switch to the "dual" tab and hit apply to get
				// each screen configured to its preferred resolution.
				selected = screenList[i].currentResolution;
			}
			fillCombo(lists[j], screenList[i].modes, selected, screenList[i].preferredResolution);
			lists[j]->setProperty("output", screenList[i].output);
			QWidget *sl = ( j == 0 ? _ui->wDualLeft : _ui->wDualRight );
			const QIcon &icon = screenList[i].isProjector ? _iProjector : _iScreen;
			auto labels = sl->findChildren<QLabel*>();
			labels[0]->setPixmap(icon.pixmap(QSize(32, 32)));
			labels[1]->setText(screenList[i].output + "\n" + screenList[i].name);
			if (screenList[i].currentResolution.isEmpty())
				continue;
			++j;
		}
	}

	//
	// Clear advanced controls
	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
	// 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(res.isEmpty() ? 1.33f : 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), [a, this](int index) {
			if (!_ignoreResolutionChange) {
				a->desiredResolution = a->cboResolution->itemData(index).toSize();
			}
		});
		addBoldListener(a->cboResolution);
		connect(a->cboResolution, QOverload<int>::of(&QComboBox::currentIndexChanged), [a, this](int index) {
			if (index < 0)
				return;
			QSize res = a->cboResolution->itemData(index).toSize();
			a->screen->setScaling(res.isEmpty() ? 1.33f : float(res.width()) / float(res.height()));
			_ui->advancedContainer->update();
		});
	}
	// Header
	_ui->advancedCombos->addWidget(new QLabel(tr("Output")), 0, 0, 1, 2);
	_ui->advancedCombos->addWidget(new QLabel(tr("Position")), 0, 3);
	// List
	int row = 1;
	for (auto it = screenMap.begin(); it != screenMap.end(); ++it) {
		int col = 0;
		AdvancedOutput *a = new AdvancedOutput(it.value());
		a->assignmentLabel = new QLabel(it.key(), this);
		a->assignmentLabel->hide();
		a->rowLabel = new QLabel(it.key());
		auto ico = new QLabel();
		const QIcon &icon = (a->info.isProjector ? _iProjector : _iScreen);
		auto h = a->assignmentLabel->fontMetrics().height();
		ico->setPixmap(icon.pixmap(QSize(h + 3, h)));
		_ui->advancedCombos->addWidget(ico, row, col++);
		_ui->advancedCombos->addWidget(a->rowLabel, row, col++);
		_ui->advancedCombos->addWidget(new QLabel(a->info.name), row, col++);
		QComboBox *cbo = new QComboBox();
		a->cboPosition = cbo;
		_ui->advancedCombos->addWidget(cbo, row, col++);
		_ui->advancedCombos->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum), row, col++);
		_advancedOutput.append(a);
		row++;
		// Logic
		cbo->addItems(positionList);
		// TODO Signal
		connect(cbo, QOverload<int>::of(&QComboBox::currentIndexChanged), [a, this](int index) {
			a->info.position = index - 1;
			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);
	}
	// Apply Spacing
	QLayoutItem *control;
	int i = 0;
	while ((control = _ui->advancedCombos->itemAt(i++)) != nullptr) {
		if (control->widget() != nullptr) {
			control->widget()->setStyleSheet(".QLabel {padding: 1px 7px;}");
		}
	}
}

bool Widget::keepResolution()
{
	_popupCount += 1;
	// Qt needs some time to notice the screen setup has changed
	QCoreApplication::processEvents();
	QThread::msleep(10);
	QCoreApplication::processEvents();
	QList<TimeOutDialog*> list;
	this->setDisabled(true);
	for (auto screen : _qtScreens) {
		QRect geo = screen->geometry();
		bool skip = false;
		// See if it overlaps with an existing screen
		for (auto other : _qtScreens) {
			if (other == screen)
				break;
			if (other->geometry().intersects(geo)) {
				skip = true;
				break;
			}
		}
		if (skip)
			continue;
		// Show a dialog asking if the res should be kept
		TimeOutDialog *keepDialog = new TimeOutDialog(15);
		QSize s = (geo.size() - keepDialog->size()) / 2;
		QPoint tl = geo.topLeft();
		tl.rx() += s.width();
		tl.ry() += s.height();
		keepDialog->move(tl);
		keepDialog->show();
		keepDialog->move(tl);
		list.append(keepDialog);
	}

	bool wasCanceled = false;
	bool active;
	do {
		active = true;
		QCoreApplication::processEvents();
		for (auto win : list) {
			if (!win->isActive()) {
				active = false;
				wasCanceled = win->wasCanceled();
			}
		}
	} while (active);

	for (auto win : list) {
		win->deleteLater();
	}

	this->setDisabled(false);

	_popupCount -= 1;

	return wasCanceled;
}

//______________________________________________________________________________
void Widget::connectButtons() {
	// Swap dualscreen
	connect(_ui->btnDualSwap, &QPushButton::clicked, [=](bool) {
		_ui->dualContainer->addItem(_ui->dualContainer->takeAt(1));
		_ui->dualContainer->addItem(_ui->dualContainer->takeAt(0));
	});

	// Apply CLONE
	connect(_ui->btnCloneApply, &QPushButton::clicked, [=](bool) {
		auto ret = ScreenSetup::inst()->setClone(_ui->cboCloneResolution->currentData().toSize());
		if (!ret.ok() || !keepResolution()) {
			qDebug() << "reverting clone";
			ret.revert();
		}
		ScreenSetup::inst()->updateScreenResources();
		initControls();
	});

	// 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);
		}
		auto ret = ScreenSetup::inst()->setCustom({left, right});
		if (!ret.ok() || !keepResolution()) {
			qDebug() << "reverting dualhead";
			ret.revert();
		}
		ScreenSetup::inst()->updateScreenResources();
		initControls();
	});

	// 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>() });
		}
		int numConf = 0;
		for (auto e : _advancedOutput) {
			int index = e->cboPosition->currentIndex() - 1;
			if (index < 0 || index >= list.size())
				continue;
			list[index].second.append(e->info.output);
			numConf++;
		}
		if (numConf == 0)
			return;
		auto ret = ScreenSetup::inst()->setCustom(list);
		if (!ret.ok() || !keepResolution()) {
			qDebug() << "reverting custom";
			ret.revert();
		}
		ScreenSetup::inst()->updateScreenResources();
		initControls();
	});
	// Close
	connect(_ui->btnClose, &QPushButton::clicked, [=](bool) {
		if (CommandLine::backgroundMode()) {
			this->hide();
		} else {
			qApp->exit(0);
		}
	});
	// Exit
	connect(_ui->btnExit, &QPushButton::clicked, [=](bool) {
		if (QMessageBox::question(this, tr("Confirm"),
								  tr("This terminates the GUI.\n"
									 "It will not pop up again if further screens are connected.\n"
									 "Are you sure?")) == QMessageBox::Yes) {
			qApp->exit(0);
		}
	});

}

void Widget::updateWindowPlacement(bool force)
{
	QRect win = this->geometry();
	QPoint cursor = QCursor::pos();
	const QScreen *winScreen = nullptr, *mouseScreen = nullptr;
	//qDebug() << "Mouse at" << cursor << "   window at" << win;
	for (auto screen : _qtScreens) {
		QRect geo = screen->geometry();
		if (geo.contains(win)) {
			winScreen = screen;
			//qDebug() << "Window on screen" << geo;
		}
		if (geo.contains(cursor)) {
			mouseScreen = screen;
			//qDebug() << "Mouse on screen" << geo;
		}
	}
	if (mouseScreen != nullptr && (force || mouseScreen != winScreen)) {
		QPoint offset = mouseScreen->geometry().topLeft();
		QSize spacing = (mouseScreen->size() - this->size()) / 2;
		offset.rx() += spacing.width();
		offset.ry() += spacing.height();
		this->move(offset);
	}
}

////////////////////////////////////////////////////////////////////////////////