cleanup
[presencevnc] / src / vncview.cpp
index 98787b3..ccbae25 100644 (file)
@@ -52,7 +52,14 @@ critical(parent, caption, message)
 #define KMOD_Control_L         0x08
 #define KMOD_Shift_L   0x10
 
-VncView::VncView(QWidget *parent, const KUrl &url, RemoteView::Quality quality)
+//local cursor width/height in px, should be an odd number
+const int CURSOR_SIZE = 7;
+
+const int TAP_PRESS_TIME = 180;
+const int DOUBLE_TAP_UP_TIME = 500;
+
+
+VncView::VncView(QWidget *parent, const KUrl &url, RemoteView::Quality quality, int listen_port)
         : RemoteView(parent),
         m_initDone(false),
         m_buttonMask(0),
@@ -66,12 +73,14 @@ VncView::VncView(QWidget *parent, const KUrl &url, RemoteView::Quality quality)
         m_verticalFactor(1.0),
         m_forceLocalCursor(false),
        force_full_repaint(false),
-       quality(quality)
+       quality(quality),
+       listen_port(listen_port)
 {
     m_url = url;
     m_host = url.host();
     m_port = url.port();
 
+       //BlockingQueuedConnection can cause deadlocks when exiting, handled in startQuitting()
     connect(&vncThread, SIGNAL(imageUpdated(int, int, int, int)), this, SLOT(updateImage(int, int, int, int)), Qt::BlockingQueuedConnection);
     connect(&vncThread, SIGNAL(gotCut(const QString&)), this, SLOT(setCut(const QString&)), Qt::BlockingQueuedConnection);
     connect(&vncThread, SIGNAL(passwordRequest()), this, SLOT(requestPassword()), Qt::BlockingQueuedConnection);
@@ -130,48 +139,28 @@ QSize VncView::minimumSizeHint() const
     return size();
 }
 
-void VncView::scaleResize(int w, int h)
-{
-    RemoteView::scaleResize(w, h);
-    
-    kDebug(5011) << "scaleResize(): " <<w << h;
-    if (m_scale) {
-        m_verticalFactor = (qreal) h / m_frame.height();
-        m_horizontalFactor = (qreal) w / m_frame.width();
-
-        m_verticalFactor = m_horizontalFactor = qMin(m_verticalFactor, m_horizontalFactor);
-
-        const qreal newW = m_frame.width() * m_horizontalFactor;
-        const qreal newH = m_frame.height() * m_verticalFactor;
-       /*
-        setMaximumSize(newW, newH); //This is a hack to force Qt to center the view in the scroll area
-       //also causes the widget's size to flicker
-       */
-        resize(newW, newH);
-    } 
-}
-
-
 void VncView::startQuitting()
 {
     kDebug(5011) << "about to quit";
 
-    const bool connected = status() == RemoteView::Connected;
+    //const bool connected = status() == RemoteView::Connected;
 
     setStatus(Disconnecting);
 
     m_quitFlag = true;
 
-    if (connected) {
-        vncThread.stop();
-    }
-
-    vncThread.quit();
-
-    const bool quitSuccess = vncThread.wait(500);
-
-    kDebug(5011) << "startQuitting(): Quit VNC thread success:" << quitSuccess;
-
+       //if(connected) //remove if things work without it
+       vncThread.stop();
+
+    const bool quitSuccess = vncThread.wait(700);
+       if(!quitSuccess) {
+               //happens when vncThread wants to call a slot via BlockingQueuedConnection,
+               //needs an event loop in this thread so execution continues after 'emit'
+               QEventLoop loop;
+               if(!loop.processEvents())
+                       kDebug(5011) << "BUG: deadlocked, but no events to deliver?";
+               vncThread.wait(700);
+       }
     setStatus(Disconnected);
 }
 
@@ -184,7 +173,7 @@ bool VncView::start()
 {
     vncThread.setHost(m_host);
     vncThread.setPort(m_port);
-
+       vncThread.setListenPort(listen_port); //if port is != 0, thread will listen for connections
     vncThread.setQuality(quality);
 
     // set local cursor on by default because low quality mostly means slow internet connection
@@ -248,7 +237,8 @@ void VncView::requestPassword()
        QHBoxLayout layout1;
        QVBoxLayout layout2;
        layout2.addWidget(&passwordbox);
-       layout2.addWidget(&save_password);
+       if(!m_host.isEmpty()) //don't save incomming connections
+               layout2.addWidget(&save_password);
        layout1.addLayout(&layout2);
        layout1.addWidget(&ok_button);
        dialog.setLayout(&layout1);
@@ -256,7 +246,7 @@ void VncView::requestPassword()
        if(dialog.exec()) { //dialog accepted
                password = passwordbox.text();
 
-               if(save_password.isChecked()) {
+               if(!m_host.isEmpty() and save_password.isChecked()) {
                        kDebug(5011) << "Saving password for host '" << m_host << "'";
 
                        settings.setValue(QString("%1/password").arg(m_host), password);
@@ -271,8 +261,6 @@ void VncView::requestPassword()
 
 void VncView::outputErrorMessage(const QString &message)
 {
-    kDebug(5011) << message;
-
     if (message == "INTERNAL:APPLE_VNC_COMPATIBILTY") {
         setCursor(localDotCursor());
         m_forceLocalCursor = true;
@@ -289,21 +277,8 @@ void VncView::updateImage(int x, int y, int w, int h)
        if(!QApplication::focusWidget()) { //no focus, we're probably minimized
                return;
        }
-     //kDebug(5011) << "got update" << width() << height();
 
-     /*
-     static unsigned int frames = 0;
-     static unsigned int updates = 0;
-     static QTime time = QTime::currentTime();
-     updates++;
-     if(updates % 100 == 0)
-            kDebug(5011) << "u/s: " << updates/double(time.elapsed()) * 1000.0;
-if(x == 0 and y == 0) {
-       frames++;
-     if(frames % 100 == 0)
-            kDebug(5011) << "f/s: " << frames/double(time.elapsed()) * 1000.0;
-}
-*/
+     //kDebug(5011) << "got update" << width() << height();
 
     m_x = x;
     m_y = y;
@@ -312,8 +287,8 @@ if(x == 0 and y == 0) {
 
     if (m_horizontalFactor != 1.0 || m_verticalFactor != 1.0) {
         // If the view is scaled, grow the update rectangle to avoid artifacts
-        int x_extrapixels = 1.0/m_horizontalFactor+1;
-        int y_extrapixels = 1.0/m_verticalFactor+1;
+        int x_extrapixels = 1.0/m_horizontalFactor + 1;
+        int y_extrapixels = 1.0/m_verticalFactor + 1;
 
         m_x-=x_extrapixels;
         m_y-=y_extrapixels;
@@ -336,12 +311,7 @@ if(x == 0 and y == 0) {
 //         emit framebufferSizeChanged(m_frame.width(), m_frame.height());
         emit connected();
         
-        if (m_scale) {
-            if (parentWidget())
-                scaleResize(parentWidget()->width(), parentWidget()->height());
-           else
-                scaleResize(width(), height());
-        } 
+               resize(width(), height());
         
         m_initDone = true;
 
@@ -351,16 +321,7 @@ if(x == 0 and y == 0) {
     if ((y == 0 && x == 0) && (m_frame.size() != old_frame_size)) {
            old_frame_size = m_frame.size();
         kDebug(5011) << "Updating framebuffer size";
-        if (m_scale) {
-            //setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
-            if (parentWidget())
-                scaleResize(parentWidget()->width(), parentWidget()->height());
-        } else {
-            kDebug(5011) << "Resizing: " << m_frame.width() << m_frame.height();
-            resize(m_frame.width(), m_frame.height());
-            //setMaximumSize(m_frame.width(), m_frame.height()); //This is a hack to force Qt to center the view in the scroll area
-            //setMinimumSize(m_frame.width(), m_frame.height());
-        }
+               setZoomLevel();
         emit framebufferSizeChanged(m_frame.width(), m_frame.height());
     }
 
@@ -388,25 +349,36 @@ void VncView::showDotCursor(DotCursorState state)
     setCursor(state == CursorOn ? localDotCursor() : Qt::BlankCursor);
 }
 
-void VncView::enableScaling(bool scale)
+//level should be in [0, 100]
+void VncView::setZoomLevel(int level)
 {
-    RemoteView::enableScaling(scale);
+       Q_ASSERT(parentWidget() != 0);
 
-    if (scale) {
-        //setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
-        //setMinimumSize(1, 1);
-        if (parentWidget())
-            scaleResize(parentWidget()->width(), parentWidget()->height());
-           else
-               scaleResize(width(), height());
-    } else {
-        m_verticalFactor = 1.0;
-        m_horizontalFactor = 1.0;
+       if(level == -1) { //handle resize
+               resize(m_frame.width()*m_horizontalFactor, m_frame.height()*m_verticalFactor);
+               return;
+       }
 
-        //setMaximumSize(m_frame.width(), m_frame.height()); //This is a hack to force Qt to center the view in the scroll area
-        //setMinimumSize(m_frame.width(), m_frame.height());
-        resize(m_frame.width(), m_frame.height());
-    }
+       double magnification;
+       if(level == 100) {
+               magnification = 2.0;
+       } else if(level >= 90) {
+               magnification = 1.0;
+       } else {
+               const double min_horiz_magnification = double(parentWidget()->width())/m_frame.width();
+               const double min_vert_magnification = double(parentWidget()->height())/m_frame.height();
+               const double fit_screen_magnification = qMin(min_horiz_magnification, min_vert_magnification);
+
+               //level=90 => magnification=1.0, level=0 => magnification=fit_screen_magnification
+               magnification = (level)/90.0*(1.0 - fit_screen_magnification) + fit_screen_magnification;
+       }
+
+       if(magnification < 0                    //remote display smaller than local?
+       or magnification != magnification)      //nan
+               magnification = 1.0;
+       
+       m_verticalFactor = m_horizontalFactor = magnification;
+       resize(m_frame.width()*magnification, m_frame.height()*magnification);
 }
 
 void VncView::setCut(const QString &text)
@@ -430,13 +402,17 @@ void VncView::paintEvent(QPaintEvent *event)
 
     QPainter painter(this);
 
+       Qt::TransformationMode transformation_mode = Qt::SmoothTransformation;
+       if( m_horizontalFactor >= 1.0 )
+               transformation_mode = Qt::FastTransformation;
+
     if (m_repaint and !force_full_repaint) {
 //         kDebug(5011) << "normal repaint";
         painter.drawImage(QRect(qRound(m_x*m_horizontalFactor), qRound(m_y*m_verticalFactor),
                                 qRound(m_w*m_horizontalFactor), qRound(m_h*m_verticalFactor)), 
                           m_frame.copy(m_x, m_y, m_w, m_h).scaled(qRound(m_w*m_horizontalFactor), 
                                                                   qRound(m_h*m_verticalFactor),
-                                                                  Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+                                                                  Qt::IgnoreAspectRatio, transformation_mode));
     } else {
          //kDebug(5011) << "resize repaint";
         QRect rect = event->rect();
@@ -448,13 +424,13 @@ void VncView::paintEvent(QPaintEvent *event)
             const int sh = rect.height()/m_verticalFactor;
             painter.drawImage(rect, 
                               m_frame.copy(sx, sy, sw, sh).scaled(rect.width(), rect.height(),
-                                                                  Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+                                                                  Qt::IgnoreAspectRatio, transformation_mode));
         } else {
              kDebug(5011) << "Full repaint" << width() << height() << m_frame.width() << m_frame.height();
             painter.drawImage(QRect(0, 0, width(), height()), 
                               m_frame.scaled(m_frame.width() * m_horizontalFactor, m_frame.height() * m_verticalFactor,
-                                             Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
-           force_full_repaint = false;
+                                             Qt::IgnoreAspectRatio, transformation_mode));
+                       force_full_repaint = false;
         }
     }
 
@@ -463,7 +439,8 @@ void VncView::paintEvent(QPaintEvent *event)
 #if QT_VERSION >= 0x040500
                painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
 #endif
-               painter.drawRect(cursor_x*m_horizontalFactor - 2, cursor_y*m_verticalFactor - 2, 5, 5);
+               //rectangle size includes 1px pen width
+               painter.drawRect(cursor_x*m_horizontalFactor - CURSOR_SIZE/2, cursor_y*m_verticalFactor - CURSOR_SIZE/2, CURSOR_SIZE-1, CURSOR_SIZE-1);
        }
 
     RemoteView::paintEvent(event);
@@ -472,8 +449,7 @@ void VncView::paintEvent(QPaintEvent *event)
 void VncView::resizeEvent(QResizeEvent *event)
 {
     RemoteView::resizeEvent(event);
-    scaleResize(event->size().width(), event->size().height());
-    forceFullRepaint();
+    update();
 }
 
 bool VncView::event(QEvent *event)
@@ -515,9 +491,6 @@ void VncView::mouseEventHandler(QMouseEvent *e)
        static QTime press_time;
        static QTime up_time; //used for double clicks/tap&drag, for time after first tap
 
-       const int TAP_PRESS_TIME = 180;
-       const int DOUBLE_TAP_UP_TIME = 500;
-
        if(!e) { //flush held taps
                if(tap_detected) {
                        m_buttonMask |= 0x01;
@@ -596,10 +569,10 @@ void VncView::mouseEventHandler(QMouseEvent *e)
        static int old_cursor_y = cursor_y;
        if(((m_dotCursorState == CursorOn) || m_forceLocalCursor)
        and (cursor_x != old_cursor_x or cursor_y != old_cursor_y)) {
-               //clear last position (plus a few extra pixels)
-               repaint(old_cursor_x*m_horizontalFactor - 3, old_cursor_y*m_verticalFactor - 3, 7, 7);
+               //clear last position
+               repaint(old_cursor_x*m_horizontalFactor - CURSOR_SIZE/2, old_cursor_y*m_verticalFactor - CURSOR_SIZE/2, CURSOR_SIZE, CURSOR_SIZE);
                //and refresh new one
-               repaint(cursor_x*m_horizontalFactor - 3, cursor_y*m_verticalFactor - 3, 7, 7);
+               repaint(cursor_x*m_horizontalFactor - CURSOR_SIZE/2, cursor_y*m_verticalFactor - CURSOR_SIZE/2, CURSOR_SIZE, CURSOR_SIZE);
 
                old_cursor_x = cursor_x; old_cursor_y = cursor_y;
        }
@@ -797,6 +770,15 @@ void VncView::sendKey(Qt::Key key)
        case Qt::Key_F12:
                k = 0xffbe + int(key - Qt::Key_F1);
                break;
+       case Qt::Key_Pause:
+               k = 0xff13;
+               break;
+       case Qt::Key_Print:
+               k = 0xff61;
+               break;
+       case Qt::Key_Menu:
+               k = 0xff67;
+               break;
        case Qt::Key_Meta:
        case Qt::MetaModifier:
                k = XK_Super_L;
@@ -834,10 +816,12 @@ void VncView::sendKeySequence(QKeySequence keys)
 
        //to get at individual key presses, we split 'keys' into its components
        QList<int> key_list;
-       for(int i = 0; ; i++) {
-               QString k = keys.toString().section('+', i, i);
+       int pos = 0;
+       while(true) {
+               QString k = keys.toString().section('+', pos, pos);
                if(k.isEmpty())
                        break;
+
                //kDebug(5011) << "found key: " << k;
                if(k == "Alt") {
                        key_list.append(Qt::Key_Alt);
@@ -848,6 +832,8 @@ void VncView::sendKeySequence(QKeySequence keys)
                } else {
                        key_list.append(QKeySequence(k)[0]);
                }
+               
+               pos++;
        }
        
        for(int i = 0; i < key_list.count(); i++)
@@ -868,6 +854,8 @@ void VncView::reloadSettings()
        bool always_show_local_cursor = settings.value("always_show_local_cursor", false).toBool();
        if(always_show_local_cursor)
                showDotCursor(CursorOn);
+
+       enableScaling(true);
 }
 
 //convert commitString into keyevents