// Copyright 2013, University of Freiburg,
// Author: Manuel Schneider <ms1144>
#include <iostream>
#include <algorithm>
#include <QDebug>
#include <QtWidgets/QAction>
#include "widget.h"
#include "ui_widget.h"
#include "timeoutdialog.h"
#include "math.h"
//______________________________________________________________________________
Widget::Widget(QWidget *parent) :
QWidget(parent),
_ui(new Ui::Widget)
{
_ui->setupUi(this);
// Get initial data (to be freed)
_display = XOpenDisplay(NULL);
_screenResources = XRRGetScreenResourcesCurrent(
_display,
DefaultRootWindow(_display));
// Get the information about the X elements
updateScreenResources();
switch ( _connectedOutputList.size() ){
/*************************************************************************/
case 1:// In case of one connected output - xrandr --auto
qDebug() << "One connected output";
exit(0);
break;
case 2: // In case of two connected outputs
qDebug() << "Two connected outputs";
// Check if we are in clone mode
if (cloneMode()) {
qDebug() << "Dual output with cloned screen!";
// TODO make that mess nice and clean
double_t w0 = _outputMap[_connectedOutputList[0]]->mm_width;
double_t h0 = _outputMap[_connectedOutputList[0]]->mm_height;
double_t w1 = _outputMap[_connectedOutputList[1]]->mm_width;
double_t h1 = _outputMap[_connectedOutputList[1]]->mm_height;
// Get a human readable reference
if (w0 == 0 && h0 == 0) {
_beamer = _connectedOutputList[0];
_monitor = _connectedOutputList[1];
} else if (w1 == 0 && h1 == 0) {
_beamer = _connectedOutputList[1];
_monitor = _connectedOutputList[0];
} else {
double_t d0 = sqrt((pow(w0, 2) + pow(h0, 2)));
double_t d1 = sqrt((pow(w1, 2) + pow(h1, 2)));
if (d0 > d1) {
_beamer = _connectedOutputList[0];
_monitor = _connectedOutputList[1];
} else {
_beamer = _connectedOutputList[1];
_monitor = _connectedOutputList[0];
}
}
// Intersect them by the resolution sorted, dont care about O(n³)
QList<QPair<XRRModeInfo*, XRRModeInfo*> > commonModes;
// Iterate over the modes the monitor supports
for (int i = 0; i < _outputMap[_monitor]->nmode; ++i) {
XRRModeInfo* monitorMode = _modeMap[_outputMap[_monitor]->modes[i]];
// Skip interlace modes
if ( monitorMode->modeFlags & RR_Interlace )
continue;
// Iterate over the modes the beamer supports
for (int j = 0; j < _outputMap[_beamer]->nmode; ++j) {
XRRModeInfo* beamerMode = _modeMap[_outputMap[_beamer]->modes[j]];
// Skip interlace modes
if ( beamerMode->modeFlags & RR_Interlace )
continue;
// Only if the modes have the same size, list them in common modes
if ( monitorMode->height == beamerMode->height
&& monitorMode->width == beamerMode->width ) {
// Build a sorted list of common modes in descending order
QList<QPair<XRRModeInfo*, XRRModeInfo*> >::iterator k
= commonModes.begin();
for (;;++k) {
// If at the end, the mode to insert is the smallest, insert.
// This has to be first to avoid segfaults
if (k == commonModes.end()) {
commonModes.insert(k, qMakePair (monitorMode, beamerMode));
break;
}
// If the mode to insert is larger than k, insert.
if ( monitorMode->width > k->first->width) {
commonModes.insert(k, qMakePair (monitorMode, beamerMode));
break;
}
// If the width is the same ...
if ( monitorMode->width == k->first->width ) {
// ... and the height is the same, the mode already exists
if ( monitorMode->height == k->first->height ) {
break;
}
// ... and the height is larger, insert.
if ( monitorMode->height > k->first->height ) {
commonModes.insert(k, qMakePair (monitorMode, beamerMode));
break;
}
}
}
}
}
}
// Check if the beamer transmitted reliable data.
bool gotEDID = false;
int nprop;
Atom *props = XRRListOutputProperties(_display, _beamer, &nprop);
for (int i = 0; i < nprop; ++i) {
char *atom_name = XGetAtomName (_display, props[i]);
if ( strcmp (atom_name, "EDID") == 0) {
gotEDID = true;
XFree (atom_name);
break;
}
XFree (atom_name);
}
free(props);
// If the beamer transmits no reliable EDID data add modes
#ifdef QT_DEBUG
gotEDID = false;
#endif
if (gotEDID) {
qDebug() << "GOT EDID!";
// Extract the preferred mode of the beamer
RRMode preferredBeamerModeId = 0;
for (int i = 0; i < _outputMap[_beamer]->nmode; ++i) {
if (i < _outputMap[_beamer]->npreferred) {
preferredBeamerModeId = _outputMap[_beamer]->modes[i];
break;
}
}
// Compute the aspect ratio of the beamer
float aspectRatio = (float)_modeMap[preferredBeamerModeId]->width
/ _modeMap[preferredBeamerModeId]->height;
// Fill widget with data
for (QList<QPair<XRRModeInfo*, XRRModeInfo*> >::iterator i
= commonModes.begin(); i != commonModes.end(); ++i ) {
float modeAspectRatio = ((float)i->first->width / i->first->height);
if ( abs(modeAspectRatio - aspectRatio) < 0.05 ) // APPROX
_ui->comboBox->addItem(i->first->name,
QList<QVariant>()
<< QVariant((qulonglong)i->first->id)
<< QVariant((qulonglong)i->second->id));
}
} else {
qDebug() << "NO EDID!";
// Fill widget with data without AR match
// Fill widget with data
for ( QList<QPair<XRRModeInfo*, XRRModeInfo*> >::iterator i
= commonModes.begin(); i != commonModes.end(); ++i ) {
qDebug() << "Insert into QComboBox"
<< i->first->width << "x" << i->first->height
<< "(" << i->first->id << ")";
_ui->comboBox->addItem(i->first->name,
QList<QVariant>()
<< QVariant((qulonglong)i->first->id)
<< QVariant((qulonglong)i->second->id));
}
}
// Set the current resolution highlighted
QString n = _modeMap[_crtcMap[_outputMap[_monitor]->crtc]->mode]->name;
int index = _ui->comboBox->findText(n);
_ui->comboBox->setCurrentIndex(index);
// Remove borders and stuff
setWindowFlags(Qt::Widget | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint);
//setStyleSheet("background:transparent;");
//setAttribute(Qt::WA_TranslucentBackground);
// Resize widget to its content
resize(sizeHint());
show();
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(bringToTopTimer()));
timer->start(1000);
// Center dialog on screenbottom
const QRect desktopRect = QApplication::desktop()->screenGeometry();
this->move( desktopRect.width()/2-this->width()/2,
desktopRect.height()-this->height());
// Connect button signal to appropriate slot
connect(_ui->pushButton, SIGNAL(clicked()), this, SLOT(handleButton()));
}
/*********************************************************************/
// If NEITHER of the outputs is a beamer (likely dualscreen setup)
else {
// TODO(manuel): Future feature. Setup dualscreen
qDebug() << "Dual output with extended screen!";
exit(0);
}
break;
/*************************************************************************/
default:
// If there are more than 3 outputs
// its up to the user. Quit.
qDebug() << "More than two outputs. Quit.";
exit(0);
break;
}
/*************************************************************************/
}
void Widget::bringToTopTimer()
{
raise();
}
//______________________________________________________________________________
void Widget::updateScreenResources()
{
// Clear the modemap (nothing to be freed, stored in screenResources)
_modeMap.clear();
// Create the modemap
qDebug() << "_MODES_";
for (int i = 0; i < _screenResources->nmode; ++i) {
_modeMap.insert(
_screenResources->modes[i].id,
&_screenResources->modes[i]);
qDebug() << _screenResources->modes[i].id << "\t"
<< _screenResources->modes[i].width << "x"
<< _screenResources->modes[i].height;
}
// Clear the crtmap
for (CrtcMap::iterator it = _crtcMap.begin();
it != _crtcMap.end();) {
XRRFreeCrtcInfo(*it);
it = _crtcMap.erase(it);
}
// Create crtcMap
for (int i = 0; i < _screenResources->ncrtc; ++i) {
XRRCrtcInfo * info = XRRGetCrtcInfo(
_display,
_screenResources,
_screenResources->crtcs[i]);
_crtcMap.insert(
_screenResources->crtcs[i],
info);
}
// Clear the outputmap
for (OutputMap::iterator it = _outputMap.begin();
it != _outputMap.end();) {
XRRFreeOutputInfo(*it);
it = _outputMap.erase(it);
}
// Create outputmap and connectedOutputMap
for (int i = 0; i < _screenResources->noutput; ++i) {
XRROutputInfo* info = XRRGetOutputInfo(
_display,
_screenResources,
_screenResources->outputs[i]);
_outputMap.insert(
_screenResources->outputs[i],
info);
// Store the connected ones separate
if ( info->connection == RR_Connected )
_connectedOutputList.push_back(_screenResources->outputs[i]);
}
}
//______________________________________________________________________________
Widget::~Widget() {
delete _ui;
XCloseDisplay(_display);
XRRFreeScreenResources(_screenResources);
}
//______________________________________________________________________________
void Widget::handleButton(){
/*************************** Backup the crtcinfos ***************************/
qDebug() << "Backup the crtc infos";
CrtcMap backup;
for ( CrtcMap::iterator it = _crtcMap.begin();
it != _crtcMap.end(); ++it ) {
backup[it.key()] = new XRRCrtcInfo;
backup[it.key()]->x = it.value()->x;
backup[it.key()]->y = it.value()->y;
backup[it.key()]->mode = it.value()->mode;
backup[it.key()]->rotation = it.value()->rotation;
backup[it.key()]->noutput = it.value()->noutput;
backup[it.key()]->outputs = new RROutput[it.value()->noutput];
for (int i = 0; i < it.value()->noutput; ++i) {
backup[it.key()]->outputs[i] = it.value()->outputs[i];
}
}
/**************************** Apply the resolution **************************/
// 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) {
XRRSetCrtcConfig(_display, _screenResources, it.key(), CurrentTime,
0, 0, None, RR_Rotate_0, NULL, 0);
}
// 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
XRRSetCrtcConfig(_display,
_screenResources,
_outputMap[_monitor]->crtc,
CurrentTime,
0, 0, monitorMode->id,
RR_Rotate_0,
&_monitor, 1);
XRRSetCrtcConfig(_display,
_screenResources,
_outputMap[_beamer]->crtc,
CurrentTime,
0, 0, beamerMode->id,
RR_Rotate_0,
&_beamer, 1);
// Sync... whatever...
XSync (_display, False);
// 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 ****************************/
// Show a dialog asking if the res should be kept
TimeOutDialog *keepDialog = new TimeOutDialog(15, this);
keepDialog->setWindowTitle(" ");
keepDialog->setLabelText("Do you want to keep this resolution?");
keepDialog->setCancelButtonText("Keep");
keepDialog->move(monitorMode->width/2 - this->width()/2,
monitorMode->height/2 - this->height());
keepDialog->exec();
// If the dialog was not canceled revert the resolution
if ( ! keepDialog->wasCanceled()) {
/**************************** Apply the backup ****************************/
// 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, NULL, 0);
}
// 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));
}
}
// 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);
}
// Sync... whatever...
XSync (_display, False);
// center dialog on screenbottom
qDebug() << "Again center dialog on screenbottom";
this->move( ScreenSize.width()/2 - this->width()/2,
ScreenSize.height() - this->height());
// Set the mouse pointer in the middle of the screen
QCursor::setPos(monitorMode->width/2, monitorMode->height/2);
}
// Delete the backup
for ( CrtcMap::iterator it = backup.begin();
it != backup.end(); ++it ) {
delete[] it.value()->outputs;
delete it.value();
}
// Intenal settings changed. Update!
updateScreenResources();
}
bool Widget::cloneMode()
{
bool cloneMode = true;
for (CrtcMap::iterator it = _crtcMap.begin(); it != _crtcMap.end(); it++) {
XRRCrtcInfo* crtc = it.value();
// check if x starts on upper left corner
if (crtc->x != 0 || crtc->y != 0) {
cloneMode = false;
}
qDebug() << "width: " << crtc->width
<< "height: " << crtc->height
<< "x: " << crtc->x
<< "y: " << crtc->y
<< "mode: " << crtc->mode;
}
return cloneMode;
}
////////////////////////////////////////////////////////////////////////////////