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