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