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