handle configurable zoom buttons
[presencevnc] / src / vncview.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2007-2008 Urs Wolfer <uwolfer @ kde.org>
4 **
5 ** This file is part of KDE.
6 **
7 ** This program is free software; you can redistribute it and/or modify
8 ** it under the terms of the GNU General Public License as published by
9 ** the Free Software Foundation; either version 2 of the License, or
10 ** (at your option) any later version.
11 **
12 ** This program is distributed in the hope that it will be useful,
13 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ** GNU General Public License for more details.
16 **
17 ** You should have received a copy of the GNU General Public License
18 ** along with this program; see the file COPYING. If not, write to
19 ** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 ** Boston, MA 02110-1301, USA.
21 **
22 ****************************************************************************/
23
24 #include "vncview.h"
25
26 #include <QMessageBox>
27 #include <QInputDialog>
28 #define KMessageBox QMessageBox
29 #define error(parent, message, caption) \
30 critical(parent, caption, message)
31
32 #include <QApplication>
33 #include <QImage>
34 #include <QPainter>
35 #include <QMouseEvent>
36 #include <QEvent>
37 #include <QSettings>
38 #include <QTime>
39 #include <QTimer>
40
41 // Definition of key modifier mask constants
42 #define KMOD_Alt_R      0x01
43 #define KMOD_Alt_L      0x02
44 #define KMOD_Meta_L     0x04
45 #define KMOD_Control_L  0x08
46 #define KMOD_Shift_L    0x10
47
48 VncView::VncView(QWidget *parent, const KUrl &url, RemoteView::Quality quality)
49         : RemoteView(parent),
50         m_initDone(false),
51         m_buttonMask(0),
52         cursor_x(0),
53         cursor_y(0),
54         m_repaint(false),
55         m_quitFlag(false),
56         m_firstPasswordTry(true),
57         m_authenticaionCanceled(false),
58         m_dontSendClipboard(false),
59         m_horizontalFactor(1.0),
60         m_verticalFactor(1.0),
61         m_forceLocalCursor(false),
62         force_full_repaint(false),
63         quality(quality)
64 {
65     m_url = url;
66     m_host = url.host();
67     m_port = url.port();
68
69     connect(&vncThread, SIGNAL(imageUpdated(int, int, int, int)), this, SLOT(updateImage(int, int, int, int)), Qt::BlockingQueuedConnection);
70     connect(&vncThread, SIGNAL(gotCut(const QString&)), this, SLOT(setCut(const QString&)), Qt::BlockingQueuedConnection);
71     connect(&vncThread, SIGNAL(passwordRequest()), this, SLOT(requestPassword()), Qt::BlockingQueuedConnection);
72     connect(&vncThread, SIGNAL(outputErrorMessage(QString)), this, SLOT(outputErrorMessage(QString)));
73
74     m_clipboard = QApplication::clipboard();
75     connect(m_clipboard, SIGNAL(selectionChanged()), this, SLOT(clipboardSelectionChanged()));
76     connect(m_clipboard, SIGNAL(dataChanged()), this, SLOT(clipboardDataChanged()));
77
78     reloadSettings();
79 }
80
81 VncView::~VncView()
82 {
83     unpressModifiers();
84
85     // Disconnect all signals so that we don't get any more callbacks from the client thread
86     disconnect(&vncThread, SIGNAL(imageUpdated(int, int, int, int)), this, SLOT(updateImage(int, int, int, int)));
87     disconnect(&vncThread, SIGNAL(gotCut(const QString&)), this, SLOT(setCut(const QString&)));
88     disconnect(&vncThread, SIGNAL(passwordRequest()), this, SLOT(requestPassword()));
89     disconnect(&vncThread, SIGNAL(outputErrorMessage(QString)), this, SLOT(outputErrorMessage(QString)));
90
91     startQuitting();
92 }
93
94 void VncView::forceFullRepaint()
95 {
96         force_full_repaint = true;
97         repaint();
98 }
99
100 bool VncView::eventFilter(QObject *obj, QEvent *event)
101 {
102     if (m_viewOnly) {
103         if (event->type() == QEvent::KeyPress ||
104                 event->type() == QEvent::KeyRelease ||
105                 event->type() == QEvent::MouseButtonDblClick ||
106                 event->type() == QEvent::MouseButtonPress ||
107                 event->type() == QEvent::MouseButtonRelease ||
108                 event->type() == QEvent::Wheel ||
109                 event->type() == QEvent::MouseMove)
110             return true;
111     }
112     return RemoteView::eventFilter(obj, event);
113 }
114
115 QSize VncView::framebufferSize()
116 {
117     return m_frame.size();
118 }
119
120 QSize VncView::sizeHint() const
121 {
122     return size();
123 }
124
125 QSize VncView::minimumSizeHint() const
126 {
127     return size();
128 }
129
130 void VncView::scaleResize(int w, int h)
131 {
132     RemoteView::scaleResize(w, h);
133     
134     kDebug(5011) << "scaleResize(): " <<w << h;
135     if (m_scale) {
136         m_verticalFactor = (qreal) h / m_frame.height();
137         m_horizontalFactor = (qreal) w / m_frame.width();
138
139         m_verticalFactor = m_horizontalFactor = qMin(m_verticalFactor, m_horizontalFactor);
140
141         const qreal newW = m_frame.width() * m_horizontalFactor;
142         const qreal newH = m_frame.height() * m_verticalFactor;
143         /*
144         setMaximumSize(newW, newH); //This is a hack to force Qt to center the view in the scroll area
145         //also causes the widget's size to flicker
146         */
147         resize(newW, newH);
148     } 
149 }
150
151 void VncView::updateConfiguration()
152 {
153     RemoteView::updateConfiguration();
154
155     // Update the scaling mode in case KeepAspectRatio changed
156     if(parentWidget())
157             scaleResize(parentWidget()->width(), parentWidget()->height());
158     else
159         scaleResize(width(), height());
160 }
161
162 void VncView::startQuitting()
163 {
164     kDebug(5011) << "about to quit";
165
166     const bool connected = status() == RemoteView::Connected;
167
168     setStatus(Disconnecting);
169
170     m_quitFlag = true;
171
172     if (connected) {
173         vncThread.stop();
174     }
175
176     vncThread.quit();
177
178     const bool quitSuccess = vncThread.wait(500);
179
180     kDebug(5011) << "Quit VNC thread success:" << quitSuccess;
181
182     setStatus(Disconnected);
183 }
184
185 bool VncView::isQuitting()
186 {
187     return m_quitFlag;
188 }
189
190 bool VncView::start()
191 {
192     vncThread.setHost(m_host);
193     vncThread.setPort(m_port);
194
195     vncThread.setQuality(quality);
196
197     // set local cursor on by default because low quality mostly means slow internet connection
198     if (quality == RemoteView::Low) {
199         showDotCursor(RemoteView::CursorOn);
200     }
201
202     setStatus(Connecting);
203
204     vncThread.start();
205     return true;
206 }
207
208 bool VncView::supportsScaling() const
209 {
210     return true;
211 }
212
213 bool VncView::supportsLocalCursor() const
214 {
215     return true;
216 }
217
218 void VncView::requestPassword()
219 {
220     kDebug(5011) << "request password";
221
222     if (m_authenticaionCanceled) {
223         startQuitting();
224         return;
225     }
226
227     setStatus(Authenticating);
228
229     if (!m_url.password().isNull()) {
230         vncThread.setPassword(m_url.password());
231         return;
232     }
233
234     bool ok;
235     QString password = QInputDialog::getText(this, //krazy:exclude=qclasses
236                                              tr("Password required"),
237                                              tr("Please enter the password for the remote desktop:"),
238                                              QLineEdit::Password, QString(), &ok);
239     m_firstPasswordTry = false;
240     if (ok)
241         vncThread.setPassword(password);
242     else
243         m_authenticaionCanceled = true;
244 }
245
246 void VncView::outputErrorMessage(const QString &message)
247 {
248     kDebug(5011) << message;
249
250     if (message == "INTERNAL:APPLE_VNC_COMPATIBILTY") {
251         setCursor(localDotCursor());
252         m_forceLocalCursor = true;
253         return;
254     }
255
256     startQuitting();
257
258     emit errorMessage(i18n("VNC failure"), message);
259 }
260
261 void VncView::updateImage(int x, int y, int w, int h)
262 {
263 //     kDebug(5011) << "got update" << width() << height();
264
265     m_x = x;
266     m_y = y;
267     m_w = w;
268     m_h = h;
269
270     if (m_horizontalFactor != 1.0 || m_verticalFactor != 1.0) {
271         // If the view is scaled, grow the update rectangle to avoid artifacts
272         m_x-=1;
273         m_y-=1;
274         m_w+=2;
275         m_h+=2;
276     }
277
278     m_frame = vncThread.image();
279
280     if (!m_initDone) {
281         setAttribute(Qt::WA_StaticContents);
282         setAttribute(Qt::WA_OpaquePaintEvent);
283         installEventFilter(this);
284
285         setCursor(((m_dotCursorState == CursorOn) || m_forceLocalCursor) ? localDotCursor() : Qt::BlankCursor);
286
287         setMouseTracking(true); // get mouse events even when there is no mousebutton pressed
288         setFocusPolicy(Qt::WheelFocus);
289         setStatus(Connected);
290 //         emit framebufferSizeChanged(m_frame.width(), m_frame.height());
291         emit connected();
292         
293         if (m_scale) {
294             if (parentWidget())
295                 scaleResize(parentWidget()->width(), parentWidget()->height());
296             else
297                 scaleResize(width(), height());
298         } 
299         
300         m_initDone = true;
301
302     }
303
304         static QSize old_frame_size = QSize();
305     if ((y == 0 && x == 0) && (m_frame.size() != old_frame_size)) {
306             old_frame_size = m_frame.size();
307         kDebug(5011) << "Updating framebuffer size";
308         if (m_scale) {
309             //setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
310             if (parentWidget())
311                 scaleResize(parentWidget()->width(), parentWidget()->height());
312         } else {
313             kDebug(5011) << "Resizing: " << m_frame.width() << m_frame.height();
314             resize(m_frame.width(), m_frame.height());
315             //setMaximumSize(m_frame.width(), m_frame.height()); //This is a hack to force Qt to center the view in the scroll area
316             //setMinimumSize(m_frame.width(), m_frame.height());
317         }
318         emit framebufferSizeChanged(m_frame.width(), m_frame.height());
319     }
320
321     m_repaint = true;
322     repaint(qRound(m_x * m_horizontalFactor), qRound(m_y * m_verticalFactor), qRound(m_w * m_horizontalFactor), qRound(m_h * m_verticalFactor));
323     m_repaint = false;
324 }
325
326 void VncView::setViewOnly(bool viewOnly)
327 {
328     RemoteView::setViewOnly(viewOnly);
329
330     m_dontSendClipboard = viewOnly;
331
332     if (viewOnly)
333         setCursor(Qt::ArrowCursor);
334     else
335         setCursor(m_dotCursorState == CursorOn ? localDotCursor() : Qt::BlankCursor);
336 }
337
338 void VncView::showDotCursor(DotCursorState state)
339 {
340     RemoteView::showDotCursor(state);
341
342     setCursor(state == CursorOn ? localDotCursor() : Qt::BlankCursor);
343 }
344
345 void VncView::enableScaling(bool scale)
346 {
347     RemoteView::enableScaling(scale);
348
349     if (scale) {
350         //setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
351         //setMinimumSize(1, 1);
352         if (parentWidget())
353             scaleResize(parentWidget()->width(), parentWidget()->height());
354             else
355                 scaleResize(width(), height());
356     } else {
357         m_verticalFactor = 1.0;
358         m_horizontalFactor = 1.0;
359
360         //setMaximumSize(m_frame.width(), m_frame.height()); //This is a hack to force Qt to center the view in the scroll area
361         //setMinimumSize(m_frame.width(), m_frame.height());
362         resize(m_frame.width(), m_frame.height());
363     }
364 }
365
366 void VncView::setCut(const QString &text)
367 {
368     m_dontSendClipboard = true;
369     m_clipboard->setText(text, QClipboard::Clipboard);
370     m_clipboard->setText(text, QClipboard::Selection);
371     m_dontSendClipboard = false;
372 }
373
374 void VncView::paintEvent(QPaintEvent *event)
375 {
376 //     kDebug(5011) << "paint event: x: " << m_x << ", y: " << m_y << ", w: " << m_w << ", h: " << m_h;
377     if (m_frame.isNull() || m_frame.format() == QImage::Format_Invalid) {
378         kDebug(5011) << "no valid image to paint";
379         RemoteView::paintEvent(event);
380         return;
381     }
382
383     event->accept();
384
385     QPainter painter(this);
386
387     if (m_repaint) {
388 //         kDebug(5011) << "normal repaint";
389         painter.drawImage(QRect(qRound(m_x*m_horizontalFactor), qRound(m_y*m_verticalFactor),
390                                 qRound(m_w*m_horizontalFactor), qRound(m_h*m_verticalFactor)), 
391                           m_frame.copy(m_x, m_y, m_w, m_h).scaled(qRound(m_w*m_horizontalFactor), 
392                                                                   qRound(m_h*m_verticalFactor),
393                                                                   Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
394     } else {
395          kDebug(5011) << "resize repaint";
396         QRect rect = event->rect();
397         if (!force_full_repaint and (rect.width() != width() || rect.height() != height())) {
398              kDebug(5011) << "Partial repaint";
399             const int sx = rect.x()/m_horizontalFactor;
400             const int sy = rect.y()/m_verticalFactor;
401             const int sw = rect.width()/m_horizontalFactor;
402             const int sh = rect.height()/m_verticalFactor;
403             painter.drawImage(rect, 
404                               m_frame.copy(sx, sy, sw, sh).scaled(rect.width(), rect.height(),
405                                                                   Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
406         } else {
407              kDebug(5011) << "Full repaint" << width() << height() << m_frame.width() << m_frame.height();
408             painter.drawImage(QRect(0, 0, width(), height()), 
409                               m_frame.scaled(m_frame.width() * m_horizontalFactor, m_frame.height() * m_verticalFactor,
410                                              Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
411             force_full_repaint = false;
412         }
413     }
414
415     RemoteView::paintEvent(event);
416 }
417
418 void VncView::resizeEvent(QResizeEvent *event)
419 {
420     RemoteView::resizeEvent(event);
421     scaleResize(event->size().width(), event->size().height());
422     forceFullRepaint();
423 }
424
425 bool VncView::event(QEvent *event)
426 {
427     switch (event->type()) {
428     case QEvent::KeyPress:
429     case QEvent::KeyRelease:
430 //         kDebug(5011) << "keyEvent";
431         keyEventHandler(static_cast<QKeyEvent*>(event));
432         return true;
433         break;
434     case QEvent::MouseButtonDblClick:
435     case QEvent::MouseButtonPress:
436     case QEvent::MouseButtonRelease:
437     case QEvent::MouseMove:
438 //         kDebug(5011) << "mouseEvent";
439         mouseEventHandler(static_cast<QMouseEvent*>(event));
440         return true;
441         break;
442     case QEvent::Wheel:
443 //         kDebug(5011) << "wheelEvent";
444         wheelEventHandler(static_cast<QWheelEvent*>(event));
445         return true;
446         break;
447     default:
448         return RemoteView::event(event);
449     }
450 }
451
452
453 void VncView::mouseEventHandler(QMouseEvent *e)
454 {
455         static bool tap_detected = false;
456         static bool double_tap_detected = false;
457         static bool tap_drag_detected = false;
458         static QTime press_time;
459         static QTime up_time; //used for double clicks/tap&drag, for time after first tap
460
461         const int TAP_PRESS_TIME = 200;
462         const int DOUBLE_TAP_UP_TIME = 500;
463
464         if(!e) { //flush held taps
465                 if(tap_detected) {
466                         m_buttonMask |= 0x01;
467                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
468                         m_buttonMask &= 0xfe;
469                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
470                         tap_detected = false;
471                 } else if(double_tap_detected and press_time.elapsed() > TAP_PRESS_TIME) { //got tap + another press -> tap & drag
472                         m_buttonMask |= 0x01;
473                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
474                         double_tap_detected = false;
475                         tap_drag_detected = true;
476                 }
477                         
478                 return;
479         }
480
481         if(e->x() < 0 or e->y() < 0) { //QScrollArea tends to send invalid events sometimes...
482                 e->ignore();
483                 return;
484         }
485
486         cursor_x = qRound(e->x()/m_horizontalFactor);
487         cursor_y = qRound(e->y()/m_verticalFactor);
488         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask); // plain move event
489
490         if(e->type() == QEvent::MouseButtonPress or e->type() == QEvent::MouseButtonDblClick) {
491                 press_time.start();
492                 if(tap_detected and up_time.elapsed() < DOUBLE_TAP_UP_TIME) {
493                         tap_detected = false;
494                         double_tap_detected = true;
495
496                         QTimer::singleShot(TAP_PRESS_TIME, this, SLOT(mouseEventHandler()));
497                 }
498         } else if(e->type() == QEvent::MouseButtonRelease) {
499                 if(tap_drag_detected) {
500                         m_buttonMask &= 0xfe;
501                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
502                         tap_drag_detected = false;
503                 } else if(double_tap_detected) { //double click
504                         double_tap_detected = false;
505
506                         m_buttonMask |= 0x01;
507                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
508                         m_buttonMask &= 0xfe;
509                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
510                         m_buttonMask |= 0x01;
511                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
512                         m_buttonMask &= 0xfe;
513                         vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
514                 } else if(press_time.elapsed() < TAP_PRESS_TIME) { //tap
515                         up_time.start();
516                         tap_detected = true;
517                         QTimer::singleShot(DOUBLE_TAP_UP_TIME, this, SLOT(mouseEventHandler()));
518                 }
519
520         }
521
522 /* for reference:
523     if (e->type() != QEvent::MouseMove) {
524         if ((e->type() == QEvent::MouseButtonPress)) {
525             if (e->button() & Qt::LeftButton)
526                 m_buttonMask |= 0x01;
527             if (e->button() & Qt::MidButton)
528                 m_buttonMask |= 0x02;
529             if (e->button() & Qt::RightButton)
530                 m_buttonMask |= 0x04;
531         } else if (e->type() == QEvent::MouseButtonRelease) {
532             if (e->button() & Qt::LeftButton)
533                 m_buttonMask &= 0xfe;
534             if (e->button() & Qt::MidButton)
535                 m_buttonMask &= 0xfd;
536             if (e->button() & Qt::RightButton)
537                 m_buttonMask &= 0xfb;
538         */
539 }
540
541 void VncView::wheelEventHandler(QWheelEvent *event)
542 {
543     int eb = 0;
544     if (event->delta() < 0)
545         eb |= 0x10;
546     else
547         eb |= 0x8;
548
549     const int x = qRound(event->x() / m_horizontalFactor);
550     const int y = qRound(event->y() / m_verticalFactor);
551
552         kDebug(5011) << "Wheelevent";
553     vncThread.mouseEvent(x, y, eb | m_buttonMask);
554     vncThread.mouseEvent(x, y, m_buttonMask);
555 }
556
557 void VncView::keyEventHandler(QKeyEvent *e)
558 {
559     // strip away autorepeating KeyRelease; see bug #206598
560     if (e->isAutoRepeat() && (e->type() == QEvent::KeyRelease))
561         return;
562
563 // parts of this code are based on http://italc.sourcearchive.com/documentation/1.0.9.1/vncview_8cpp-source.html
564     rfbKeySym k = e->nativeVirtualKey();
565
566     // we do not handle Key_Backtab separately as the Shift-modifier
567     // is already enabled
568     if (e->key() == Qt::Key_Backtab) {
569         k = XK_Tab;
570     }
571
572     const bool pressed = (e->type() == QEvent::KeyPress);
573
574     // handle modifiers
575     if (k == XK_Shift_L || k == XK_Control_L || k == XK_Meta_L || k == XK_Alt_L) {
576         if (pressed) {
577             m_mods[k] = true;
578         } else if (m_mods.contains(k)) {
579             m_mods.remove(k);
580         } else {
581             unpressModifiers();
582         }
583     }
584
585
586         int current_zoom = -1;
587         if(e->key() == Qt::Key_F8)
588                 current_zoom = left_zoom;
589         else if(e->key() == Qt::Key_F7)
590                 current_zoom = right_zoom;
591         else if (k)
592                 vncThread.keyEvent(k, pressed);
593         
594         if(current_zoom == -1)
595                 return;
596
597         //handle zoom buttons
598         if(current_zoom == 0) { //left click
599                 if(pressed)
600                         m_buttonMask |= 0x01;
601                 else
602                         m_buttonMask &= 0xfe;
603                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
604         } else if(current_zoom == 1) { //right click
605                 if(pressed)
606                         m_buttonMask |= 0x04;
607                 else
608                         m_buttonMask &= 0xfb;
609                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
610         } else if(current_zoom == 2) { //wheel up
611                 int eb = 0x10;
612                 vncThread.mouseEvent(cursor_x, cursor_y, eb | m_buttonMask);
613                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
614         } else if(current_zoom == 3) { //wheel down
615                 int eb = 0x8;
616                 vncThread.mouseEvent(cursor_x, cursor_y, eb | m_buttonMask);
617                 vncThread.mouseEvent(cursor_x, cursor_y, m_buttonMask);
618         } else if(current_zoom == 4) { //page up
619                 vncThread.keyEvent(0xff55, pressed);
620         } else if(current_zoom == 5) { //page down
621                 vncThread.keyEvent(0xff56, pressed);
622         }
623 }
624
625 void VncView::unpressModifiers()
626 {
627     const QList<unsigned int> keys = m_mods.keys();
628     QList<unsigned int>::const_iterator it = keys.constBegin();
629     while (it != keys.end()) {
630         vncThread.keyEvent(*it, false);
631         it++;
632     }
633     m_mods.clear();
634 }
635
636 void VncView::clipboardSelectionChanged()
637 {
638     kDebug(5011);
639
640     if (m_status != Connected)
641         return;
642
643     if (m_clipboard->ownsSelection() || m_dontSendClipboard)
644         return;
645
646     const QString text = m_clipboard->text(QClipboard::Selection);
647
648     vncThread.clientCut(text);
649 }
650
651 void VncView::clipboardDataChanged()
652 {
653     kDebug(5011);
654
655     if (m_status != Connected)
656         return;
657
658     if (m_clipboard->ownsClipboard() || m_dontSendClipboard)
659         return;
660
661     const QString text = m_clipboard->text(QClipboard::Clipboard);
662
663     vncThread.clientCut(text);
664 }
665
666 //fake key events
667 void VncView::sendKey(Qt::Key key)
668 {
669         int k = 0; //X11 keysym
670         switch(key) {
671         case Qt::Key_Escape:
672                 k = 0xff1b;
673                 break;
674         case Qt::Key_Tab:
675                 k = 0xff09;
676                 break;
677         case Qt::Key_PageUp:
678                 k = 0xff55;
679                 break;
680         case Qt::Key_PageDown:
681                 k = 0xff56;
682                 break;
683         case Qt::Key_Meta: //TODO: test this.
684                 k = XK_Super_L;
685                 break;
686         case Qt::Key_Alt:
687                 k = XK_Alt_L;
688                 break;
689         default:
690                 kDebug(5011) << "unhandled Qt::Key value " << key;
691                 return;
692         }
693
694         if (k == XK_Shift_L || k == XK_Control_L || k == XK_Meta_L || k == XK_Alt_L || k == XK_Super_L) {
695                 if (m_mods.contains(k)) { //release
696                         m_mods.remove(k);
697                         vncThread.keyEvent(k, false);
698                 } else { //press
699                         m_mods[k] = true;
700                         vncThread.keyEvent(k, true);
701                 }
702         } else { //normal key
703                 vncThread.keyEvent(k, true);
704                 vncThread.keyEvent(k, false);
705         }
706 }
707
708 void VncView::reloadSettings()
709 {
710         QSettings settings;
711         left_zoom = settings.value("left_zoom", 0).toInt();
712         right_zoom = settings.value("right_zoom", 1).toInt();
713 }
714
715 #include "moc_vncview.cpp"