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