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