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


                                          

                    
 
                 
                            
 


                          
                 
 
                                                                                
                                                
                    
                       
 
                     
 

                                   


                                                    



                                             





                                                   


                                                                               
                                         
              
            
                                               
 


                                          
                                        
 
                                                      
 
                                             




                                                                     
 
                                         
                                 

                                             
                                        

                                             
                

                                                        







                                               
         
 

                                                                          
 

                                                               
 

                                                                              
                                 







                                                                              
                                   






                                                                              
                                                                        

                                                                   






                                                                             
                        




                                                                             

                        





                                                                            





                                                                               


                          
               


             
 















                                                                         
                                                                  
               

                        
                      


                                  
                                                     
                                           





                                                                    
 

                                                                           
                                                               

                                  

                                                                     
                                                                                
                                                                      



                                                                             
           
                
 
                                 
 

                                                   

                                                                      

                                                                  
                                                   



                                                                           
           

         




                                                                               
                                   
                                                                                                                         

                                                     


                                       



                                                                         





                                                                            

                                                                                
       


                                                                             
                                                         
                                                        






                                                                               
                                                 





                                                                               

                              
                                                 


          
                                                                                





                                                                       
                        



                                                     
                                                     

                                                       








































                                                                   
 
 

                                                                                
                   


                                           
 
 

                                                                                
                            


                                                                                
                                       
 













                                                                  
 




                                                                                
                                                              
                                                              
 

                                                                            
                                                                                       
                                                       
                                                             
   
 
                   
                                                         


                                                                                

                    

                                                    

                                              
                                         
                               
                                 
                                           


                                             
                                        
                               
                                                
 

                          
                                                                   
 
                                  
                                                      
                                                                 
 
                                                      


                                                                                

                                                   





                                                                          
                                                           




                                            
 
                                                         

                                                                
 

                                                                                
                                                   


                                                                              





                                                



                                                
                                                                         

                                                 
                                                                          
       



                                                           


                                                                       
 
                       













                                                



                                    
                                                       

                                                        


                                                                 
   






                                              


                                      
 
 




















                                                                             
                                                                                
// 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(bool testMode, 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();

  if (testMode) {
	  while (_connectedOutputList.size() > 2) {
		  _connectedOutputList.pop_front();
	  }
  }

  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 (testMode || 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()
{
  if (_ui->comboBox->view()->isVisible()) return;
  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) {
	Status st = XRRSetCrtcConfig(_display, _screenResources, it.key(), CurrentTime,
                     0, 0, None, RR_Rotate_0, NULL, 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 ****************************/

  // Show a dialog asking if the res should be kept
  TimeOutDialog keepDialog(15, this);
  keepDialog.setWindowModality(Qt::ApplicationModal);
  keepDialog.setWindowTitle(" ");
  keepDialog.setLabelText(trUtf8("Do you want to keep this resolution?"));
  keepDialog.setCancelButtonText(trUtf8("Keep"));
  keepDialog.move(monitorMode->width/2  - this->width()/2,
                   monitorMode->height/2 - this->height());
  keepDialog.show();

  while (keepDialog.isActive()) {
	  QCoreApplication::processEvents();
  }

  // If the dialog was not canceled revert the resolution
  if ( !keepDialog.wasCanceled()) {
	  qDebug() << "User did not cancel timeout, reverting!";

    /**************************** 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;
}
////////////////////////////////////////////////////////////////////////////////